Физические безумства

    Добрый день, уважаемый читатель! Я, начинающий Java-программист, долгое время не мог освоиться с библиотекой Box2D. Во-первых, из-за того, что она написана для C++, и по ней нет документации, а синтаксис Си я не знаю. Во-вторых, из-за того, что подробные уроки по этой библиотеке есть только в качестве расширения libGDX. Спустя пару недель упорной борьбы, я наконец смог понять, как надо работать с этой библиотекой, и в этой статье я расскажу об этом (и покажу это).

    Работаю в Eclipse, статья будет связана с этой средой разработки. Для начала скачайте сборщик libGDX и сделайте стандартную сборку. Нам понадобится Desktop’ное приложение с расширением Box2D. Потом заходите в Eclipse, нажимаете File → Import → Gradle → Gradle Project и указываете путь к вашей сборке.

    Вот изображение с демонстрацией того, как выглядит моя. Я добавил в папку Core пакет Utils с классом Constants, в котором содержится только одна константа – к-во пикселей в метре. Это для того, чтобы мир не был гигантским.

    image


    Вот код для класса DesktopLauncher из пакета com.mygdx.game.desktop:

    Вставьте этот код в класс и забудьте про него
    package com.mygdx.game.desktop;
    
    import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
    import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
    import com.mygdx.game.MyGdxGame;
    
    public class DesktopLauncher {
    	public static void main(String[] arg) {
    		LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
    		// ширина окна
    		config.width = 720;
    		// высота окна
    		config.height = 480;
    		config.backgroundFPS = 60;
    		config.foregroundFPS = 60;
    		new LwjglApplication(new MyGdxGame(), config);
    	}
    }
    


    Кода будет много, поэтому я заключу его в спойлеры. Изменяется только класс MyGdxGame из пакета com.mygdx.game.

    Итак, попробуем сделать довольно простую вещь в нашем приложении. Путь мячик врезается в стену из дощечек, и они разлетаются. Что-то подобное:

    image


    Идея 1. Боулинг
    package com.mygdx.game;
    
    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.Input.Keys;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.OrthographicCamera;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.math.Vector2;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.physics.box2d.Body;
    import com.badlogic.gdx.physics.box2d.BodyDef;
    import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
    import com.badlogic.gdx.physics.box2d.CircleShape;
    import com.badlogic.gdx.physics.box2d.PolygonShape;
    import com.badlogic.gdx.physics.box2d.World;
    import utils.Constants;
    
    public class MyGdxGame extends ApplicationAdapter {
    	SpriteBatch batch;
    	Texture img;
    	private OrthographicCamera camera;
    	private boolean DEBUG = false;
    	private World world;
    	private Body ball;
    	private Body floor;
    	private Body wall;
    	private Body verticals;
    	private Body horizontals;
    	private Box2DDebugRenderer b2dr;
    
    	// здесь происходит создание мира и его наполнение
    	public void create() {
    		float w = Gdx.graphics.getWidth();
    		float h = Gdx.graphics.getHeight();
    		camera = new OrthographicCamera();
    		camera.setToOrtho(false, w / 2, h / 2);
    		world = new World(new Vector2(0, -9.8f), false);
    		// мир со стандартной силой тяжести
    		b2dr = new Box2DDebugRenderer();
    		// здесь создается мячик
    		ball = createPlayer();
    		// здесь создается "пол", чтобы все не проваливалось
    		floor = createfloor();
    		// здесь создаются дощечки
    		verticals = createverticals(80, 80);
    		verticals = createverticals(100, 80);
    		verticals = createverticals(120, 80);
    		verticals = createverticals(90, 111);
    		verticals = createverticals(110, 111);
    		verticals = createverticals(100, 141);
    		horizontals = createhorizontals(85, 95);
    		horizontals = createhorizontals(115, 95);
    		horizontals = createhorizontals(100, 126);
    		// здесь создаются стены-ограничители
    		wall = createwall(430, 170);
    		wall = createwall(-430, 170);
    	}
    
    	public void render() {
    		update(Gdx.graphics.getDeltaTime());
    		// просто черный экран
    		Gdx.gl.glClearColor(0, 0, 0, 1);
    		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    		b2dr.render(world, camera.combined.scl(Constants.PPM));
    	}
    
    	public void resize(int width, int height) {
    		camera.setToOrtho(false, width / 2, height / 2);
    	}
    
    	public void dispose() {
    		world.dispose();
    		b2dr.dispose();
    	}
    
    	// обновление мира и камеры
    	public void update(float delta) {
    		world.step(1 / 60f, 6, 2);
    		inputUpdate(delta);
    		cameraUpdate(delta);
    
    	}
    
    	// обработка нажатия клавиши пробел
    	public void inputUpdate(float delta) {
    		int HF = 0;
    		if (Gdx.input.isKeyPressed(Keys.SPACE)) {
    			HF = 5;
    		}
    		ball.setLinearVelocity(HF * 5, ball.getLinearVelocity().y);
    	}
    
    	// камера повсюду следует за мячиком
    	public void cameraUpdate(float delta) {
    		Vector3 position = camera.position;
    		position.x = ball.getPosition().x * Constants.PPM;
    		position.y = ball.getPosition().y * Constants.PPM;
    		camera.position.set(position);
    		camera.update();
    	}
    
    	// обратите внимание, что не заданы такие параметры, как коэффициент трения,
    	// масса, коэффициент упругости.
    	// мячик и его параметры
    	public Body createPlayer() {
    		Body pBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(20 / Constants.PPM, 90 / Constants.PPM);
    		def.fixedRotation = false;
    		pBody = world.createBody(def);
    		CircleShape shape = new CircleShape();
    		shape.setRadius(10 / Constants.PPM);
    		pBody.createFixture(shape, 1.0f);
    		def.bullet = true;
    		shape.dispose();
    		return pBody;
    	}
    
    	// пол и его параметры
    	public Body createfloor() {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		// тип тела
    		def.type = BodyDef.BodyType.StaticBody;
    		// координата
    		def.position.set(0, 0);
    		// будет вращаться
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		// его размеры
    		shape.setAsBox(480 / Constants.PPM, 70 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    
    	// стены и их параметры
    	public Body createwall(int xo, int yo) {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(xo / Constants.PPM, yo / Constants.PPM);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(50 / Constants.PPM, 100 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    
    	// вертикальные дощечки
    	public Body createverticals(int xo, int xy) {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(xo / Constants.PPM, xy / Constants.PPM);
    		def.fixedRotation = false;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(5 / Constants.PPM, 10 / Constants.PPM);
    		fBody.createFixture(shape, 1.0f);
    		def.bullet = true;
    		shape.dispose();
    		return fBody;
    	}
    
    	// горизонтальные дощечки
    	public Body createhorizontals(int xo, int xy) {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(xo / Constants.PPM, xy / Constants.PPM);
    		def.fixedRotation = false;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(13 / Constants.PPM, 5 / Constants.PPM);
    		fBody.createFixture(shape, 1.0f);
    		def.bullet = true;
    		shape.dispose();
    		return fBody;
    	}
    }
    


    Замечательно получилось, не так ли? Изменив несколько параметров, вы можете добиться именно того, что нужно вам! И не надо писать так много строк кода.

    Но теперь хочется увидеть упругий удар. И пусть объектов на сцене будет побольше. Получим такой результат:

    image

    А вот и код:
    package com.mygdx.game;
    
    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.physics.box2d.FixtureDef;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.OrthographicCamera;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.math.Vector2;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.physics.box2d.Body;
    import com.badlogic.gdx.physics.box2d.BodyDef;
    import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
    import com.badlogic.gdx.physics.box2d.CircleShape;
    import com.badlogic.gdx.physics.box2d.PolygonShape;
    import com.badlogic.gdx.physics.box2d.World;
    import utils.Constants;
    
    public class MyGdxGame extends ApplicationAdapter {
    	SpriteBatch batch;
    	Texture img;
    	private OrthographicCamera camera;
    	private boolean DEBUG = false;
    	private World world;
    	private Body ball;
    	private Body floor;
    	private Body wall;
    	private Body verticals;
    	private Body horizontals;
    	private Box2DDebugRenderer b2dr;
    
    	public void create() {
    		float w = Gdx.graphics.getWidth();
    		float h = Gdx.graphics.getHeight();
    		camera = new OrthographicCamera();
    		camera.setToOrtho(false, w / 2, h / 2);
    		world = new World(new Vector2(0, -9.8f), false);
    		b2dr = new Box2DDebugRenderer();
    		// наш мячик
    		ball = createPlayer();
    		// границы мира
    		floor = createfloor();
    		wall = createwall(100, 170);
    		wall = createwall(-60, 170);
    
    		// создаем мячики снизу. Через цикл пробовал, не работает
    		verticals = createverticals(5, 50);
    		verticals = createverticals(10, 50);
    		verticals = createverticals(15, 50);
    		verticals = createverticals(20, 50);
    		verticals = createverticals(25, 50);
    		verticals = createverticals(30, 50);
    		verticals = createverticals(35, 50);
    
    		verticals = createverticals(5, 55);
    		verticals = createverticals(10, 55);
    		verticals = createverticals(15, 55);
    		verticals = createverticals(20, 55);
    		verticals = createverticals(25, 55);
    		verticals = createverticals(30, 55);
    		verticals = createverticals(35, 55);
    
    		verticals = createverticals(5, 60);
    		verticals = createverticals(10, 60);
    		verticals = createverticals(15, 60);
    		verticals = createverticals(20, 60);
    		verticals = createverticals(25, 60);
    		verticals = createverticals(30, 60);
    		verticals = createverticals(35, 60);
    
    		verticals = createverticals(5, 70);
    		verticals = createverticals(10, 70);
    		verticals = createverticals(15, 70);
    		verticals = createverticals(20, 70);
    		verticals = createverticals(25, 70);
    		verticals = createverticals(30, 70);
    		verticals = createverticals(35, 70);
    
    		verticals = createverticals(5, 80);
    		verticals = createverticals(10, 80);
    		verticals = createverticals(15, 80);
    		verticals = createverticals(20, 80);
    		verticals = createverticals(25, 80);
    		verticals = createverticals(30, 80);
    		verticals = createverticals(35, 80);
    	}
    
    	public void render() {
    		update(Gdx.graphics.getDeltaTime());
    		Gdx.gl.glClearColor(0, 0, 0, 1);
    		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    		b2dr.render(world, camera.combined.scl(Constants.PPM));
    	}
    
    	public void resize(int width, int height) {
    		camera.setToOrtho(false, width / 2, height / 2);
    	}
    
    	public void dispose() {
    		world.dispose();
    		b2dr.dispose();
    	}
    
    	public void update(float delta) {
    		world.step(1 / 60f, 6, 2);
    		inputUpdate(delta);
    		cameraUpdate(delta);
    
    	}
    
    	// чтобы мяч вращался
    	public void inputUpdate(float delta) {
    		ball.setAngularVelocity(3.0f);
    	}
    
    	public void cameraUpdate(float delta) {
    		Vector3 position = camera.position;
    		position.x = ball.getPosition().x * Constants.PPM;
    		position.y = ball.getPosition().y * Constants.PPM;
    		camera.position.set(position);
    		camera.update();
    	}
    
    	// создаем мячик
    	public Body createPlayer() {
    		Body pBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(20 / Constants.PPM, 800 / Constants.PPM);
    		def.fixedRotation = false;
    		pBody = world.createBody(def);
    		CircleShape shape = new CircleShape();
    		shape.setRadius(10 / Constants.PPM);
    		pBody.createFixture(shape, 1.0f);
    		def.bullet = true;
    		shape.dispose();
    		return pBody;
    	}
    
    	// создаем пол
    	public Body createfloor() {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(0, 0);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(480 / Constants.PPM, 70 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    
    	// создаем стены
    	public Body createwall(int xo, int yo) {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(xo / Constants.PPM, yo / Constants.PPM);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(50 / Constants.PPM, 100 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    
    	// обратите здесь внимание на параметры
    	public Body createverticals(int xo, int yo) {
    		Body pBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(xo / Constants.PPM, yo / Constants.PPM);
    		def.fixedRotation = false;
    		pBody = world.createBody(def);
    		CircleShape shape = new CircleShape();
    		shape.setRadius(2 / Constants.PPM);
    		// здесь заключена вся магия
    		FixtureDef fd = new FixtureDef();
    		// упругость
    		fd.restitution = 1.0f;
    		// плотность
    		fd.density = 5.0f;
    		// трение
    		fd.friction = 0.01f;
    		fd.shape = shape;
    		pBody.createFixture(fd);
    		def.bullet = true;
    		shape.dispose();
    		return pBody;
    	}
    }
    


    Создать тележку в Box2D непросто. Нужно связывать тела, чтобы они двигались, как единое целое. Следующая гифка демонстрирует только суть.

    image


    Как это получилось?
    package com.mygdx.game;
    
    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.Input;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.OrthographicCamera;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.math.Vector2;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.physics.box2d.Body;
    import com.badlogic.gdx.physics.box2d.BodyDef;
    import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
    import com.badlogic.gdx.physics.box2d.CircleShape;
    import com.badlogic.gdx.physics.box2d.PolygonShape;
    import com.badlogic.gdx.physics.box2d.World;
    import utils.Constants;
    
    public class MyGdxGame extends ApplicationAdapter {
    	SpriteBatch batch;
    	Texture img;
    	private OrthographicCamera camera;
    	private boolean DEBUG = false;
    	private World world;
    	private Body ball;
    	private Body ball1;
    	private Body floor;
    	private Body wall;
    	private Body verticals;
    	private Body horizontals;
    	private Box2DDebugRenderer b2dr;
    
    	public void create() {
    		float w = Gdx.graphics.getWidth();
    		float h = Gdx.graphics.getHeight();
    		camera = new OrthographicCamera();
    		camera.setToOrtho(false, w / 2, h / 2);
    		world = new World(new Vector2(0, -9.8f), false);
    		b2dr = new Box2DDebugRenderer();
    		// создаем колеса
    		ball = createPlayer(20 / Constants.PPM, 70 / Constants.PPM);
    		ball1 = createPlayer(50 / Constants.PPM, 70 / Constants.PPM);
    		floor = createfloor();
    		wall = createwall(430, 170);
    		wall = createwall(-430, 170);
    		verticals = createverticals();
    
    	}
    
    	public void render() {
    		update(Gdx.graphics.getDeltaTime());
    		Gdx.gl.glClearColor(0, 0, 0, 1);
    		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    		b2dr.render(world, camera.combined.scl(Constants.PPM));
    	}
    
    	public void resize(int width, int height) {
    		camera.setToOrtho(false, width / 2, height / 2);
    	}
    
    	public void dispose() {
    		world.dispose();
    		b2dr.dispose();
    	}
    
    	public void update(float delta) {
    		world.step(1 / 60f, 6, 2);
    		inputUpdate(delta);
    		cameraUpdate(delta);
    
    	}
    
    	public void inputUpdate(float delta) {
    		// задаем вращение колесам. А верхняя планка пусть двигается вместе с ними
    		ball.setLinearVelocity(3.0f, ball.getLinearVelocity().y);
    		ball1.setLinearVelocity(3.0f, ball.getLinearVelocity().y);
    		verticals.setLinearVelocity(3.0f, verticals.getLinearVelocity().y);
    	}
    
    	public void cameraUpdate(float delta) {
    		Vector3 position = camera.position;
    		position.x = ball.getPosition().x * Constants.PPM;
    		position.y = ball.getPosition().y * Constants.PPM;
    		camera.position.set(position);
    		camera.update();
    	}
    
    	// два колеса
    	public Body createPlayer(float xo, float yo) {
    		Body pBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(xo, yo);
    		def.fixedRotation = false;
    		pBody = world.createBody(def);
    		CircleShape shape = new CircleShape();
    		shape.setRadius(10 / Constants.PPM);
    		pBody.createFixture(shape, 1.0f);
    		def.bullet = true;
    		shape.dispose();
    		return pBody;
    	}
    
    	public Body createfloor() {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(0, 0);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(480 / Constants.PPM, 70 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    
    	public Body createwall(int xo, int yo) {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(xo / Constants.PPM, yo / Constants.PPM);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(50 / Constants.PPM, 100 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    
    	// вертикальная планка
    	public Body createverticals() {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(30 / Constants.PPM, 90 / Constants.PPM);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(30 / Constants.PPM, 3 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    }
    


    Если вы присмотритесь к коду, то заметите, что это не настоящая тележка, а, скорее, палочка на колесиках. Вперед, энтузиасты! На Youtube есть видео, где в Box2D сделали четырехтактный ДВС. Разве мы хуже? Жду в комментариях Ваши успехи!

    image


    Больше столкновений!
    package com.mygdx.game;
    
    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.OrthographicCamera;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.g2d.SpriteBatch;
    import com.badlogic.gdx.math.Vector2;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.physics.box2d.Body;
    import com.badlogic.gdx.physics.box2d.BodyDef;
    import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
    import com.badlogic.gdx.physics.box2d.CircleShape;
    import com.badlogic.gdx.physics.box2d.FixtureDef;
    import com.badlogic.gdx.physics.box2d.PolygonShape;
    import com.badlogic.gdx.physics.box2d.World;
    import utils.Constants;
    
    public class MyGdxGame extends ApplicationAdapter {
    	SpriteBatch batch;
    	Texture img;
    	private OrthographicCamera camera;
    	private boolean DEBUG = false;
    	private World world;
    	private Body ball;
    	private Body ball1;
    	private Body ball2;
    	private Body ball3;
    	private Body ball4;
    	private Body ball5;
    	private Body floor;
    	private Body wall;
    	private Body verticals;
    	private Body horizontals;
    	private Box2DDebugRenderer b2dr;
    
    	public void create() {
    		float w = Gdx.graphics.getWidth();
    		float h = Gdx.graphics.getHeight();
    		camera = new OrthographicCamera();
    		camera.setToOrtho(false, w / 2, h / 2);
    		world = new World(new Vector2(0, -9.8f), false);
    		b2dr = new Box2DDebugRenderer();
    		// создадим шесть больших упругих мячиков
    		ball = createPlayer(20 / Constants.PPM, 150 / Constants.PPM);
    		ball1 = createPlayer(35 / Constants.PPM, 200 / Constants.PPM);
    		ball2 = createPlayer(35 / Constants.PPM, 300 / Constants.PPM);
    		ball3 = createPlayer(20 / Constants.PPM, 400 / Constants.PPM);
    		ball4 = createPlayer(35 / Constants.PPM, 200 / Constants.PPM);
    		ball5 = createPlayer(45 / Constants.PPM, 500 / Constants.PPM);
    		floor = createfloor();
    		wall = createwall(100, 170);
    		wall = createwall(-60, 170);
    	}
    
    	public void render() {
    		update(Gdx.graphics.getDeltaTime());
    		Gdx.gl.glClearColor(0, 0, 0, 1);
    		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    		b2dr.render(world, camera.combined.scl(Constants.PPM));
    	}
    
    	public void resize(int width, int height) {
    		camera.setToOrtho(false, width / 2, height / 2);
    	}
    
    	public void dispose() {
    		world.dispose();
    		b2dr.dispose();
    	}
    
    	public void update(float delta) {
    		world.step(1 / 60f, 6, 2);
    		cameraUpdate(delta);
    
    	}
    
    	public void cameraUpdate(float delta) {
    		Vector3 position = camera.position;
    		position.x = ball.getPosition().x * Constants.PPM;
    		position.y = ball.getPosition().y * Constants.PPM;
    		camera.position.set(position);
    		camera.update();
    	}
    
    	public Body createPlayer(float xo, float yo) {
    		Body pBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(xo, yo);
    		def.fixedRotation = false;
    		pBody = world.createBody(def);
    		CircleShape shape = new CircleShape();
    		shape.setRadius(10 / Constants.PPM);
    		pBody.createFixture(shape, 0.0001f);
    		def.bullet = true;
    		// параметры, благодаря которым они такие упругие
    		FixtureDef fd = new FixtureDef();
    		fd.restitution = 1.0f;
    		fd.density = 5.0f;
    		fd.friction = 0.01f;
    		fd.shape = shape;
    		pBody.createFixture(fd);
    		shape.dispose();
    		return pBody;
    	}
    
    	public Body createfloor() {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(0, 0);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(480 / Constants.PPM, 70 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    
    	public Body createwall(int xo, int yo) {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(xo / Constants.PPM, yo / Constants.PPM);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(50 / Constants.PPM, 100 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    }
    


    И последняя гифка на сегодня. Наклонная плоскость знакома нам с уроков физики. В данном коде представлено, как можно получить более сложную форму (теоретически произвольную), чтобы открылись безграничные возможности для игровой физики.

    image

    Наклонная плоскость
    public class MyGdxGame extends ApplicationAdapter {
    	SpriteBatch batch;
    	Texture img;
    	private OrthographicCamera camera;
    	private boolean DEBUG = false;
    	private World world;
    	private Body ball;
    	private Body floor;
    	private Body wall;
    	private Body plos;
    	private Body verticals;
    	private Body horizontals;
    	private Box2DDebugRenderer b2dr;
    
    	public void create() {
    		float w = Gdx.graphics.getWidth();
    		float h = Gdx.graphics.getHeight();
    		camera = new OrthographicCamera();
    		camera.setToOrtho(false, w / 2, h / 2);
    		world = new World(new Vector2(0, -9.8f), false);
    		b2dr = new Box2DDebugRenderer();
    		ball = createPlayer();
    		floor = createfloor();
    		wall = createwall(430, 170);
    		wall = createwall(-430, 170);
    		plos = createplos();
    	}
    
    	public void render() {
    		update(Gdx.graphics.getDeltaTime());
    		Gdx.gl.glClearColor(0, 0, 0, 1);
    		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    		b2dr.render(world, camera.combined.scl(Constants.PPM));
    	}
    
    	public void resize(int width, int height) {
    		camera.setToOrtho(false, width / 2, height / 2);
    	}
    
    	public void dispose() {
    		world.dispose();
    		b2dr.dispose();
    	}
    
    	public void update(float delta) {
    		world.step(1 / 60f, 6, 2);
    		cameraUpdate(delta);
    
    	}
    	
    	public void cameraUpdate(float delta) {
    		Vector3 position = camera.position;
    		position.x = ball.getPosition().x * Constants.PPM;
    		position.y = ball.getPosition().y * Constants.PPM;
    		camera.position.set(position);
    		camera.update();
    	}
    
    	public Body createPlayer() {
    		Body pBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(30 / Constants.PPM, 190 / Constants.PPM);
    		def.fixedRotation = false;
    		pBody = world.createBody(def);
    		CircleShape shape = new CircleShape();
    		shape.setRadius(10 / Constants.PPM);
    		pBody.createFixture(shape, 1.0f);		
    		def.bullet = true;
    		shape.dispose();
    		return pBody;
    	}
    
    	public Body createfloor() {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(0, 0);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(480 / Constants.PPM, 70 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    
    	public Body createwall(int xo, int yo) {
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(xo / Constants.PPM, yo / Constants.PPM);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		PolygonShape shape = new PolygonShape();
    		shape.setAsBox(50 / Constants.PPM, 100 / Constants.PPM);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    	
    	public Body createplos() {
    		Vector2[] vertices = new Vector2[3];
    	    vertices[0] = new Vector2(0f  , -0.6f  );
    	    vertices[1] = new Vector2(1f , -0.6f  );
    	    vertices[2] = new Vector2(1f , 1f);
    	    PolygonShape shape = new PolygonShape();
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(20 / Constants.PPM, 90 / Constants.PPM);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		shape.set(vertices);
    		fBody.createFixture(shape, 0.001f);
    		shape.dispose();
    		return fBody;
    	}
    }
    


    Надеюсь, благодаря этому коду Вам удастся разобраться с основами Box2D, и на свет появятся исключительно превосходные приложения! Спасибо, что прочли до конца! Постараюсь ответить на все вопросы в комментариях!

    Комментарии 3

      +1

      Впечатляет!
      В современных играх физика, к сожалению, сильно отстает от графики.
      Было бы интересно увидеть больше сложных структур, похожих на ДВС.

        +2
        Здравствуйте! Я написал еще одну статью по теме! Мне самому очень хочется сделать что-то выдающееся, возьму крутой проект. Реализую в скором времени, опубликую! Спасибо за Ваш интерес!
        0
        Пробовал Box2D на Corona SDK (язык там Lua) — движок имеет очень удобный и простой API, тем не менее работает шикарно. Особенно впечатлила обработка импульсов.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое