SFML 2: Integrando Box2D

SFML 2: Integrando Box2D
Sin comentarios Facebook Twitter Flipboard E-mail

En GenbetaDev hemos hablado bastante de SFML 2 aprendimos a usar los conceptos más básicos, pero una de las cosas que más me pregunta la gente es como integrar la biblioteca de Box2D con SFML 2. Aquí vamos a repasar los conceptos de como hacer trabajar estas dos grandes bibliotecas de manera conjunta.

Antes de nada cabe aclarar que es cada cosa y cual es su finalidad. SFML es una biblioteca multimedia para el manejo de gráficos, audio y red además de otras utilidades que facilitan la programación de videojuegos. Por otra parte Box2D es una biblioteca de física 2D que controla movimientos, fuerzas y demás. Aclarar que Box2D es una biblioteca sólo de física no dibuja nada en pantalla, para eso debemos usar otras bibliotecas que con los datos generados con Box2D dibujemos cosas en pantalla.

Vamos a crear un ejemplo en el que tengamos un suelo y con el botón izquierdo del ratón podremos crear cajas en nuestro mundo a las que les afecta la física.

Conceptos básicos

Lo primero es saber como trabaja Box2D, a diferencia de SFML no trabaja en píxeles como unidad sino que trabaja en el Sistema Internacional, es decir: metros, segundos y kilogramos. Es importante tener esto en cuenta porque a la hora de dibujar debemos convertir los metros en píxeles definiendo una escale prefijada.

Box2D tiene un objeto b2World que es el que controla todo, cualquier cuerpo que exista debe estar inscrito en el mundo para que Box2D haga los cálculos pertinentes.

Manos a la obra

Bueno una vez aclarados los conceptos básicos vamos a ver como sería el código. Lo primero que necesitamos es preparar una ventana SFML y crear el objeto b2World de Box2D.

#include 
#include 
#define SCALE 30.0f // Escala de conversión de Metros a Píxeles
int main()
{
    /** Preparamos la ventana */
    sf::RenderWindow Window(sf::VideoMode(800, 600, 32), "Test Box2D Genbeta Dev");
    window.setVerticalSyncEnabled(true);
    /** Definimos el mundo */
    b2Vec2 Gravity(0.f, 9.8f);
    b2World World(Gravity);
    return 0;
}

Primero creamos una ventana de SFML como es habitual y activamos la sincronización vertical. A continuación definimos un vector 2D que es un tipo de dato que trae Box2D para representar a la gravedad. Por últimos creamos el objeto world asignando la gravedad.

A continuación vamos a crear las texturas tanto del suelo como de la caja, puedes usar estas imágenes como texturas o crearlas tu mismo.

sf::Texture ground_t;
sf::Texture box_t;
ground_t.loadFromFile("data/ground.png");
box_t.loadFromFile("data/box.png");

Aquí nada de Box2D, simplemente creamos dos texturas de SFML 2 como ya hemos visto en tutoriales anteriores. A continuación vamos a crear dos funciones que nos permitan generar un terreno y las cajas, este es su prototipo:

void CreateGround(b2World& World, float X, float Y);
void CreateBox(b2World& World, int MouseX, int MouseY);

Como vemos ambas reciben el parámetro World que es nuestro objeto mundo único y luego el creador de terrenos recibe las coordenadas en las que se debe crear y las cajas reciben la posición X e Y del ratón que serán las usadas para crear una caja nueva.

Vamos ahora a ver la implementación de ambas funciones, en primer lugar el generador de terreno.


void CreateGround(b2World& World, float X, float Y)
{
    b2BodyDef BodyDef;
    BodyDef.position = b2Vec2(X/SCALE, Y/SCALE);
    BodyDef.type = b2_staticBody;
    b2Body* Body = World.CreateBody(&BodyDef);
    b2PolygonShape Shape;
    Shape.SetAsBox((800.f/2)/SCALE, (16.f/2)/SCALE
    b2FixtureDef FixtureDef;
    FixtureDef.density = 0.f;
    FixtureDef.shape = &Shape;
    Body->CreateFixture(&FixtureDef);
}

Ahora vamos a explicarlo paso a paso, lo primero que hacemos es crear un b2BodyDef esto es por así decirlo la definición de un cuerpo el tipo y su posición que es lo que editamos a continuación. Darse cuenta como usamos la constante SCALE para pasar las coordenadas en píxeles a coordenadas en metros.

A continuación definimos el cuerpo que es de tipo estático, Box2D acepta tres tipos de cuerpos:

  • Estáticos - Objetos que no se mueven, y no les afecta a los demás, pero a los demás si les afecta él. Ejemplo una pared o el suelo.

  • Dinámicos - Son objetos físicos que se mueven afectan a otros objetos y otros objetos los afectan a ellos. Ejemplo las cajas de nuestro juego.

  • Cinemáticos - Son objetos que se mueven, pero que no les afectan otros objetos. Por ejemplo un ascensor.

Una vez definido el cuerpo pasamos a definir la forma, esto es el aspecto de nuestra figura, la densidad o la fricción que pueda generar dicho objeto.

Ahora veamos las cajas que son parecidas, pero son objetos dinámicos.

void CreateBox(b2World& World, int MouseX, int MouseY)
{
    b2BodyDef BodyDef;
    BodyDef.position = b2Vec2(MouseX/SCALE, MouseY/SCALE);
    BodyDef.type = b2_dynamicBody;
    b2Body* Body = World.CreateBody(&BodyDef);
    b2PolygonShape Shape;
    Shape.SetAsBox((32.f/2)/SCALE, (32.f/2)/SCALE);
    b2FixtureDef FixtureDef;
    FixtureDef.density = 1.f;
    FixtureDef.friction = 0.7f;
    FixtureDef.shape = &Shape;
    Body->CreateFixture(&FixtureDef);
}

Por último vamos a darle iteracción dentro de SFML 2, dejo el código de la función main y lo comentamos.

int main()
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "Pruebas Box2D");
    window.setVerticalSyncEnabled(true);
    b2Vec2 Gravity(0.f, 9.8f);
    b2World World(Gravity);
    CreateGround(World, 400.f, 500.f);
    sf::Texture ground_t;
    sf::Texture box_t;
    ground_t.loadFromFile("data/ground.png");
    box_t.loadFromFile("data/box.png");
    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }
        if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
        {
            int MouseX = sf::Mouse::getPosition(window).x;
            int MouseY = sf::Mouse::getPosition(window).y;
            CreateBox(World, MouseX, MouseY);
        }
        World.Step(1/60.f, 8, 3);
        window.clear(sf::Color(180, 200, 255));
        for (b2Body* BodyIterator = World.GetBodyList(); BodyIterator != 0; BodyIterator = BodyIterator->GetNext())
        {
            if (BodyIterator->GetType() == b2_dynamicBody)
            {
                sf::Sprite Sprite;
                Sprite.setTexture(box_t);
                Sprite.setOrigin(16.f, 16.f);
                Sprite.setPosition(SCALE * BodyIterator->GetPosition().x, SCALE * BodyIterator->GetPosition().y);
                Sprite.setRotation(BodyIterator->GetAngle() * 180/b2_pi);
                window.draw(Sprite);
            }
            else
            {
                sf::Sprite GroundSprite;
                GroundSprite.setTexture(ground_t);
                GroundSprite.setOrigin(400.f, 8.f);
                GroundSprite.setPosition(BodyIterator->GetPosition().x * SCALE, BodyIterator->GetPosition().y * SCALE);
                GroundSprite.setRotation(180/b2_pi * BodyIterator->GetAngle());
                window.draw(GroundSprite);
            }
        }

        window.display();
    }

    return 0;
}

Lo que hacemos es primero definir el suelo, a continuación como vemos no definimos ningún Sprite de SFML sino que estos los vamos generando cada vez en función de los parámetros que nos devuelve Box2D para cada objeto a los que accedemos mediante un bucle.

Esto es solo una introducción rápida, veremos más en profundiad Box2D en próximos artículos.

Comentarios cerrados
Inicio