Hoy vamos a ver cómo implementar la física en LibGDX. Esto técnicamente no es parte de LibGDX en sí, sino que se implementa como una extensión. El motor de física utilizado en LibGDX es el popular Sistema de física Box2D una biblioteca que ha sido adaptada a prácticamente todas las plataformas y lenguajes jamás inventados, o eso parece. Vamos a cubrir cómo implementar la física Box2D en su juego 2D LibGDX. Este es un tema complejo, por lo que requerirá varias partes.
Si nunca antes ha usado un Physics Engine, deberíamos comenzar con una descripción general básica de lo que hacen y cómo funcionan. Esencialmente, un motor de física toma la información de la escena que usted proporciona, luego calcula el movimiento «realista» usando cálculos físicos. Es algo parecido a esto:
- describe todas las entidades físicas en su mundo al motor de física, incluidos los volúmenes límite, la masa, la velocidad, etc.
- le dices al motor que se actualice, ya sea por cuadro o en algún otro intervalo
- el motor de física calcula cómo ha cambiado el mundo, qué colisionó con qué, cuánto afecta la gravedad a cada elemento, velocidad y posición actuales, etc.
- tomas los resultados de la simulación física y actualizas tu mundo en consecuencia.
No se preocupe, veremos exactamente cómo en un momento.
Primero necesitamos hablar por un momento sobre la creación de su proyecto. Dado que Box2D ahora se implementa como una extensión (un componente LibGDX opcional), debe agregarlo manualmente o cuando crea su proyecto inicial. La adición de una biblioteca a un proyecto existente depende del IDE, por lo que voy a considerar agregarla durante la creación del proyecto… y no solo porque es realmente fácil de esa manera.
Cuando crea su proyecto LibGDX utilizando el Generador de proyectos, simplemente especifica qué extensiones desea incluir y Gradle hace el resto. En este caso, simplemente marque la casilla junto a Box2d al generar su proyecto como de costumbre:
… y listo. Usted puede estar preguntando, hey, ¿qué pasa con Box2dlights? No, actualmente no lo necesitas. Caja2dluces es un proyecto para simular luces y sombras basado en el motor de física Box2d. Puede notar en esa lista otra entidad llamada Bala. Bullet es otro motor físico, aunque más comúnmente orientado a juegos en 3D, posiblemente más sobre eso en una fecha posterior. Solo tenga en cuenta que si está trabajando en 3D, Box2d no es de mucha utilidad para usted, pero hay alternativas.
Bien, ahora que tenemos un proyecto correctamente configurado, echemos un vistazo a una simulación física muy básica. Simplemente vamos a tomar el gráfico LibGDX predeterminado y aplicarle gravedad, la simulación más simple que puede hacer que realmente hace algo. ¡Tiempo de código!
package com.gamefromscratch; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.*; public class Physics1 extends ApplicationAdapter { SpriteBatch batch; Sprite sprite; Texture img; World world; Body body; @Override public void create() { batch = new SpriteBatch(); // We will use the default LibGdx logo for this example, but we need a sprite since it's going to move img = new Texture("badlogic.jpg"); sprite = new Sprite(img); // Center the sprite in the top/middle of the screen sprite.setPosition(Gdx.graphics.getWidth() / 2 - sprite.getWidth() / 2, Gdx.graphics.getHeight() / 2); // Create a physics world, the heart of the simulation. The Vector passed in is gravity world = new World(new Vector2(0, -98f), true); // Now create a BodyDefinition. This defines the physics objects type and position in the simulation BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyDef.BodyType.DynamicBody; // We are going to use 1 to 1 dimensions. Meaning 1 in physics engine is 1 pixel // Set our body to the same position as our sprite bodyDef.position.set(sprite.getX(), sprite.getY()); // Create a body in the world using our definition body = world.createBody(bodyDef); // Now define the dimensions of the physics shape PolygonShape shape = new PolygonShape(); // We are a box, so this makes sense, no? // Basically set the physics polygon to a box with the same dimensions as our sprite shape.setAsBox(sprite.getWidth()/2, sprite.getHeight()/2); // FixtureDef is a confusing expression for physical properties // Basically this is where you, in addition to defining the shape of the body // you also define it's properties like density, restitution and others we will see shortly // If you are wondering, density and area are used to calculate over all mass FixtureDef fixtureDef = new FixtureDef(); fixtureDef.shape = shape; fixtureDef.density = 1f; Fixture fixture = body.createFixture(fixtureDef); // Shape is the only disposable of the lot, so get rid of it shape.dispose(); } @Override public void render() { // Advance the world, by the amount of time that has elapsed since the last frame // Generally in a real game, dont do this in the render loop, as you are tying the physics // update rate to the frame rate, and vice versa world.step(Gdx.graphics.getDeltaTime(), 6, 2); // Now update the spritee position accordingly to it's now updated Physics body sprite.setPosition(body.getPosition().x, body.getPosition().y); // You know the rest... Gdx.gl.glClearColor(1, 1, 1, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(sprite, sprite.getX(), sprite.getY()); batch.end(); } @Override public void dispose() { // Hey, I actually did some clean up in a code sample! img.dispose(); world.dispose(); } }
El programa en ejecución:

Lo que está sucediendo aquí se define principalmente en los comentarios, pero daré una descripción general más simple en inglés. Básicamente, cuando usas un motor de física, creas una representación física para cada objeto correspondiente en tu juego. En este caso, creamos un objeto de física (Cuerpo) que acompañaba a nuestro sprite. Es importante darse cuenta de que no existe una relación real entre estos dos objetos. Hay un par de componentes que entran en un cuerpo físico, BodyDef que define qué tipo de cuerpo es (más sobre esto más adelante, por ahora, DynamicBody significa un cuerpo actualizado y capaz de moverse) y FixtureDef, que define la forma. y propiedades físicas del Cuerpo. Por supuesto, también está el Mundo, que es la simulación física real.
Entonces, básicamente creamos un Cuerpo que es la representación física de nuestro Sprite en la simulación física. Luego, en render() llamamos al increíblemente importante método step(). El paso es lo que hace avanzar la simulación de la física… básicamente piense en él como el botón de reproducción. Luego, el motor de física calcula todas las diversas matemáticas que han cambiado desde la última llamada al paso. El primer valor que pasamos es la cantidad de tiempo que ha transcurrido desde la última actualización. Los siguientes dos valores controlan la cantidad de precisión en los cálculos de contacto/articulación para velocidad y posición. Básicamente, cuanto más altos sean los valores, más precisa será la simulación física, pero también más uso intensivo de la CPU. ¿Por qué 6 y 2? porque eso es lo que recomienda el sitio LibGDX y eso funciona para mí. Al final del día, estos son valores que puede ajustar a su juego individual. La otra conclusión crítica aquí es que actualizamos la posición de los sprites para que coincida con la posición del cuerpo recién actualizado. Una vez más, en este ejemplo, no existe un vínculo real entre un cuerpo físico y un sprite, por lo que debe hacerlo usted mismo.
Ahí lo tienes, la simulación de física más simple del mundo. Hay algunos temas rápidos para discutir antes de continuar. Primero, unidades.
Este es un concepto importante y, a veces, complicado para comprender los sistemas de física. ¿Qué significa 1? ¿Uno que? La respuesta es, sea lo que sea lo que quieras que sea, ¡sé constante al respecto! En este caso particular usé píxeles. Por lo tanto, 1 unidad en el motor de física representa 1 píxel en la pantalla. Entonces, cuando dije que la gravedad es (0,-98), eso significa que la gravedad se aplica a una velocidad de -98 píxeles a lo largo del eje y por segundo. Con la misma frecuencia, 1 en el motor de física podría ser metros, pies, kilómetros, etc., luego usa una proporción personalizada para traducir hacia y desde las coordenadas de la pantalla. Sin embargo, a la mayoría de los sistemas de física, incluido Box2d, realmente no les gusta que mezcles tus escalas. Por ejemplo, si tiene una simulación de universo donde 1 == 100 millas, entonces quiere calcular el movimiento de una hormiga a 0.0000001 x 100 millas por hora, romperá la simulación, con fuerza. Encuentra una escala que funcione bien con la mayoría de tu juego y apégate a ella. Los valores extremadamente grandes y extremadamente pequeños dentro de esa simulación causarán problemas.
Finalmente, una pequeña advertencia sobre cómo implementé esta demostración y, con suerte, algo que cubriré adecuadamente en una fecha posterior. En este caso, actualicé el sistema de física en el bucle de renderizado. Esta es una posibilidad, pero generalmente un desperdicio. Es bastante común ejecutar su simulación de física a una velocidad fija (30 Hz y 60 Hz son dos de los más comunes, pero también existe la posibilidad de que sea más baja si el procesamiento está restringido) y su bucle de renderizado lo más rápido posible.
En la siguiente parte le daremos a nuestro objeto algo con lo que chocar, estad atentos.