Perpetuum Mobile

в 16:28, , рубрики: box2d, eclipse, java, libgdx, вечный двигатель, машина, физика, физический движок

Добрый день, уважаемый пользователь 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. Жду Ваших вопросов в комментариях! Да здравствует игровая физика!

Автор: Васильев Алексей

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js