Perpetuum Mobile

    Добрый день, уважаемый пользователь habr.com! Это уже третья статья по теме. Работаю дни напролет, не могу оторваться от изумительной библиотеки Box2D.

    Если вы не читали первую и вторую статьи, обязательно посмотрите, будет весело! Работаю в Eclipse, пишу на Java. Почему я назвал так свою статью? Читайте дальше – и уже очень скоро все станет понятно! Спойлер: мы с Вами сделаем свой вечный двигатель (в т. ч. для машинки), а, возможно, создадим и саму машинку!

    image

    Рисунок 1. Вечный двигатель.

    Итак, сегодня мы попробуем получить что-по подобное:

    image

    Рисунок 2. Машинка с двигателем.

    Да, это не опечатка! Сегодня мы сделаем машинку с настоящим двигателем, она будет неотличима от реальной! Это вам не «тележка» из первой статьи.

    По подключению libGDX смотрите первую статью.

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

    image

    Рисунок 3. Моя сборка.

    Вот код для класса 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. Все есть в комментариях к коду.

    Создаем мир и машинку.
    
    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.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 com.badlogic.gdx.physics.box2d.joints.RevoluteJointDef;
    
    import utils.Constants;
    
    public class MyGdxGame extends ApplicationAdapter {
    	private OrthographicCamera camera;
    	private boolean DEBUG = false;
    	private World world;
    	private Box2DDebugRenderer b2dr;
    	// кузов
    	private Body box;
    	// заднее колесо
    	private Body backweel;
    	// переднее колесо
    	private Body frontweel;
    	private Body floor;
    	private Body triangle;
    	// скорость мотора
    	private float m = -20;
    
    	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();
    		// создаем землю
    		floor = createFloor();
    		// создаем препятствия
    		triangle = createTriangle(20f, 40f, 30f, -2f, -2f, 5f);
    		triangle = createTriangle(50f, 60f, 80f, -2f, 10f, -2f);
    		triangle = createTriangle(100f, 160f, 200f, -2f, 20f, -2f);
    		triangle = createTriangle(280f, 290f, 300f, -2f, 100f, -2f);
    		// создаем кузов
    		box = createBox();
    		// создаем колеса
    		backweel = createWeel(-1f, 2f);
    		frontweel = createWeel(4.5f, 2f);
    		// здесь создаются связи между кузовом и колесами
    		connected();
    	}
    
    	// связи между кузовом и колесами
    	public void connected() {
    		revJoint(box, backweel, -1f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    		revJoint(box, frontweel, 4.5f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    	}
    
    	// здесь самое интересное! Читайте внимательнее!
    	public void revJoint(Body body1, Body body2, float xo, float yo) {
    		// настройка связи
    		RevoluteJointDef rjd = new RevoluteJointDef();
    		// здесь настраивается мотор
    		rjd.enableMotor = true;
    		// скорость мотора. Если обратите внимание на дальнейший код, станет ясно, что
    		// это константа. Мотор никогда не остановится. Вот он - наш вечный двигатель!
    		rjd.motorSpeed = m;
    		// лошадиные силы
    		rjd.maxMotorTorque = 30;
    		rjd.collideConnected = false;
    		rjd.bodyA = body1;
    		rjd.bodyB = body2;
    		rjd.localAnchorA.set(xo, yo);
    		world.createJoint(rjd);
    	}
    
    	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);
    		inputUpdate(delta);
    	}
    
    	// по нажатию на пробел машина получает импульс, который толкает ее вперед
    	public void inputUpdate(float delta) {
    		if (Gdx.input.isKeyPressed(Keys.SPACE)) {
    			box.setLinearVelocity(5, 5);
    		}
    	}
    
    	// камера следует за кузовом
    	public void cameraUpdate(float delta) {
    		Vector3 position = camera.position;
    		position.x = box.getPosition().x * Constants.PPM;
    		position.y = box.getPosition().y * Constants.PPM;
    		camera.position.set(position);
    		camera.update();
    	}
    
    	// создаем кузов сложной формы
    	public Body createBox() {
    		Vector2[] verticles = new Vector2[8];
    		verticles[0] = new Vector2(2f * 10 / utils.Constants.PPM, 2f * 10 / utils.Constants.PPM);
    		verticles[1] = new Vector2(2f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    		verticles[2] = new Vector2(-2f * 10 / utils.Constants.PPM, 2f * 10 / utils.Constants.PPM);
    		verticles[3] = new Vector2(-2f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    		verticles[4] = new Vector2(6f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    		verticles[5] = new Vector2(6f * 10 / utils.Constants.PPM, 0f * 10 / utils.Constants.PPM);
    		verticles[6] = new Vector2(-1f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    		verticles[7] = new Vector2(4.5f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    		PolygonShape shape = new PolygonShape();
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(0, 0);
    		def.fixedRotation = false;
    		fBody = world.createBody(def);
    		shape.set(verticles);
    		// его физические параметры
    		FixtureDef fd = new FixtureDef();
    		// форма, как полигон из точек
    		fd.shape = shape;
    		// коэффициент трения
    		fd.friction = 0.5f;
    		// коэффициент плотности
    		fd.density = 3f;
    		// коэффициент упругости
    		fd.restitution = 0.5f;
    		fBody.createFixture(fd);
    
    		fBody.createFixture(fd);
    		shape.dispose();
    		return fBody;
    	}
    
    	// создаем колеса
    	public Body createWeel(float xo, float yo) {
    		CircleShape shape = new CircleShape();
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.DynamicBody;
    		def.position.set(xo * 10 / utils.Constants.PPM, yo * 10 / utils.Constants.PPM);
    		def.fixedRotation = false;
    		fBody = world.createBody(def);
    		shape.setRadius(2.0f * 10 / utils.Constants.PPM);
    		// его физические параметры
    		FixtureDef fd = new FixtureDef();
    		fd.shape = shape;
    		fd.friction = 0.7f;
    		fd.density = 3f;
    		fd.restitution = 0.5f;
    		fBody.createFixture(fd);
    
    		shape.dispose();
    		return fBody;
    	}
    
    	// создаем пол
    	public Body createFloor() {
    		Vector2[] verticles = new Vector2[4];
    		verticles[0] = new Vector2(-100f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    		verticles[1] = new Vector2(300f * 10 / utils.Constants.PPM, -2f * 10 / utils.Constants.PPM);
    		verticles[2] = new Vector2(300f * 10 / utils.Constants.PPM, -5f * 10 / utils.Constants.PPM);
    		verticles[3] = new Vector2(-100f * 10 / utils.Constants.PPM, -5f * 10 / utils.Constants.PPM);
    		PolygonShape shape = new PolygonShape();
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(0, 0);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		shape.set(verticles);
    
    		FixtureDef fd = new FixtureDef();
    		fd.shape = shape;
    		fd.friction = 0.5f;
    		fd.density = 3f;
    		fd.restitution = 0.5f;
    
    		fBody.createFixture(fd);
    
    		shape.dispose();
    		return fBody;
    	}
    
    	// да здравствует полоса препятствий!
    	public Body createTriangle(float xo, float x1, float x2, float yo, float y1, float y2) {
    		Vector2[] verticles = new Vector2[3];
    		verticles[0] = new Vector2(xo * 10 / utils.Constants.PPM, yo * 10 / utils.Constants.PPM);
    		verticles[1] = new Vector2(x1 * 10 / utils.Constants.PPM, y1 * 10 / utils.Constants.PPM);
    		verticles[2] = new Vector2(x2 * 10 / utils.Constants.PPM, y2 * 10 / utils.Constants.PPM);
    		PolygonShape shape = new PolygonShape();
    		Body fBody;
    		BodyDef def = new BodyDef();
    		def.type = BodyDef.BodyType.StaticBody;
    		def.position.set(0, 0);
    		def.fixedRotation = true;
    		fBody = world.createBody(def);
    		shape.set(verticles);
    		FixtureDef fd = new FixtureDef();
    		fd.shape = shape;
    		fd.friction = 0.5f;
    		fd.density = 10f;
    		fd.restitution = 0f;
    		fBody.createFixture(fd);
    		shape.dispose();
    		return fBody;
    	}
    }
    



    image
    Рисунок 4. Что же получим при компиляции?

    Мы создаем нашей машине мотор, который крутит ее колеса. Но он работает вечно, т. к. потерь энергии нет. Таким образом мы создаем свой вечный двигатель!

    А вам интересно, что же находится в конце уровня? Отправляйте скриншоты в комментарии, как пройдёте. Спасибо, что прочли статью до конца! Надеюсь, у вас получится создать свою машину в Box2D!

    P.S. Жду Ваших вопросов в комментариях! Да здравствует игровая физика!

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

      +5
      Колёса вроде как одинаковые по диаметру, а разница во вращении заметна невооруженным глазом, как так получилось? Пробуксовывает? :)
        –8
        Здравствуйте! Ваш комментарий сделал мой день, спасибо! Да, застревает в игровой грязи. Только лебедкой и вытаскивать!
        +8

        Так а что автор хотел сказать своей статьёй?

          0
          Как что? То что нашёл для себя восхитительную игрушку. И теперь стремится поделиться своей радостью со всем миром. Я его прекрасно понимаю. Сам когда впервые в жизни увидел графический дисплей, несколько дней убил на такое полезное занятие, как построение различных кривых в полярных координатах. За уши было не оттащить. Так что не будьте строги, все мы дети :))))
        0
        Нда… Классную игрушку Вы себе нашли! :)))))
        Ещё идея (про шарик на длинной пружинке я уже писал в комменте к прошлой статье). Займитесь электричеством. Про гравитацию и задачу многих тел в ньютоновской механике наверно знают все и сто раз видели это в разных демках. Электричество хоть и действует по тому же закону обратных квадратов, однако из-за разных знаков заряда привносит в эту игру забавный элемент. Создайте несколько электрических диполей. Диполь это жесткий стержень, один конец которого заряжен положительно, другой отрицательно и заряды равны по величине. Впрочем не обязательно равны. И стержень не обязательно жесткий, он может быть и пружинкой. И поглядите как они будут взаимодействовать. Достаточно интересное зрелище.
          0
          Я прочел Ваш комментарий под прошлой статьей. Освою механику твердых тел, далее возьмусь за эти интересные идеи! Спасибо! Очень ценю Вашу поддержку!
          0
          Ну и ещё про гравитацию. Как нас учит марксизм-ленинизм, гравитация действует по прямой, соединяющей центры масс. И сила пропорциональна произведению масс, и обратно пропорциональна квадрату расстояния. Вопрос. А что будет если сила зависит от расстояния не обратноквадратично, а по какому-то другому закону, хотя и мало отличающемуся от обратных квадратов? Например как 1/r^1.99? Если не знаете ответа, попытайтесь это выяснить. Гарантирую будет прикольно. Создайте звезду и запустите вокруг неё одну планету по обычной кеплеровской траектории.
            0
            Мне кажется, я понимаю, о чем вы. Как известно, «Angry Birds» используют Box2D для реализации своих уровней. И там было как раз что-то такое, о чем вы говорите.
            0
            И да, ещё небольшой совет. Играйтесь с этим делом лучше не на яве, а на джаваскрипте. Для него Box2D тоже есть. Понимаю что писать на джаваскрипте не фонтан, но можете использовать typescript. Зато сможете выкладывать не гифки, а давать ссылки на живые, работающие примеры. Например вот такие www.iforce2d.net/embox2d/testbed.html. По-моему такая возможность вполне стоит того, чтобы сменить платформу.
              0
              К сожалению, я не программирую на JS. Все чаще думаю о том, что надо бы освоить. Может быть, выйдет цикл статей о Box2D для JS.

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

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