Zabawy z LibGDX BOX2D ciąg dalszy. Pobawiłem się trochę BodyDef i Joint. Na prostym przykładzie można się fajnie pobawić fizyką :-). Poprzez zmianę parametrów takich jak density, friction, restitution czy gravity możemy w grze modelować zachowanie poszczególnych aktorów/elementów tym samym uzyskiwać efekt podobny do tego ze świata realnego.
Efekt działania poniższego kodu można zobaczyć Tutaj
Lewy prawy klawisz pozwala na przesuwanie skrzynek i równoważenie ciężaru.
Co ciekawe LibGDX różnie sobie radzi z kodem tzn. ten sam kod działający na desktop’ie poprawnie wymagał pewnych korekt przy kompilacji na html.
Można stworzyć grę wykorzystując Rectangle posługujemy się tu jednak prostymi elementami i każdy obiekt w grze jest prostokątem o określonych wymiarach i pozycji. LibGDX zapewnia proste metody dzięki, którym można programować kolizje. Tak powstał Ninja Fighter. Nie mniej dopiero wykorzystanie PolygonShape z silnika Box2D pozwala na pełną magię. PolygonShape w tandemie z FixtureDef pozwala nie tylko na zabawy z kolizjami ale na definiowanie zachowania poszczególnych elementów podczas ruchu i kolizji. Kolejnym udogodnieniem w pracy z PolygonShape jest możliwość definiowania różnych kształtów co przy większym dopracowaniu szczegółów będzie nieodzowne. Trudno bowiem wyobrazić sobie dobrą graficznie grę opartą o poruszające się prostokąty :-).
Poprzez definiowanie grawitacji, gęstości obiektów, sprężystości uzyskuje się ciekawe efekty. Warto w poniższym kodzie pobawić się tymi parametrami.
Warto też wspomnieć, a właściwie od tego trzeba byłoby zacząć, że BodyDef pozwala zdefiniować rodzaj obiektu, aktora. Wyróżnia przy tym 3 typy Dynamic, Static i KineticBody.
Wszędzie tam gdzie potrzebujemy stałe obiekty nie reagujące na fizykę np. ziemia, ściana etc. użyjemy StaticBody.
DynamicBody to będzie nasz główny aktor np. piłka, samochód etc. wszystko co będzie w ruchu, będzie spadać, uderzać etc.
KinematicBody to coś pośredniego tzn. nie reaguje na siły ale ma możliwość ruchu np. ruchome platformy, mosty etc.
Jak widać w poniższym kodzie testowałem różne warianty do czego zachęcam. Nie mniej w finalnej wersji ustawiłem mocowanie na StaticBody pomimo iż pierwotnie ten obiekt testowałem jako KinematicBody dla zabawy.
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.math.Vector2; 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.Fixture; 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.DistanceJointDef; public class MyGdxGame extends ApplicationAdapter { World world; Body bodyPad, bodyPadLeg, weightR, weightL; BodyDef kinematicBodyDef, kinematicBodyDefMocowanie; static final float WORLD_TO_BOX = 0.01f; Box2DDebugRenderer box2DDebugRenderer; OrthographicCamera camera; float posX; float posY; float powerX, powerY; float maxRight; float maxTop; @Override public void create() { world = new World(new Vector2(0, -10), true); camera = new OrthographicCamera(); posX = ConvertToBox(300); posY = ConvertToBox(200); powerY = 0; maxRight = ConvertToBox(Gdx.graphics.getWidth()); maxTop = ConvertToBox(Gdx.graphics.getHeight()); camera.setToOrtho(false, maxRight, maxTop); box2DDebugRenderer = new Box2DDebugRenderer(); //Body static np. ground BodyDef groundBodyDef = new BodyDef(); groundBodyDef.position.set(new Vector2(0, 0)); Body groundBody = world.createBody(groundBodyDef); PolygonShape groundBox = new PolygonShape(); groundBox.setAsBox(maxRight, 0.5f); groundBody.createFixture(groundBox, 0.0f); groundBox.dispose(); //Body dynamic ball BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyDef.BodyType.DynamicBody; bodyDef.position.set(posX-ConvertToBox(10), ConvertToBox(250)); bodyDef.fixedRotation = false; Body body = world.createBody(bodyDef); CircleShape circle = new CircleShape(); circle.setRadius(ConvertToBox(20f)); FixtureDef fixtureDef = new FixtureDef(); fixtureDef.shape = circle; fixtureDef.density = 0.9f; fixtureDef.friction = 0.9f; fixtureDef.restitution = 0.5f; Fixture fixture = body.createFixture(fixtureDef); circle.dispose(); //Body dynamic pochylnia kinematicBodyDef = new BodyDef(); kinematicBodyDef.type = BodyDef.BodyType.DynamicBody; kinematicBodyDef.gravityScale = 0.0f; kinematicBodyDef.fixedRotation = false; kinematicBodyDef.position.set(posX, posY); bodyPad = world.createBody(kinematicBodyDef); PolygonShape box = new PolygonShape(); box.setAsBox(ConvertToBox(200), ConvertToBox(5)); FixtureDef fixtureDefKin = new FixtureDef(); fixtureDefKin.shape = box; fixtureDefKin.density = ConvertToBox(10.5f); fixtureDefKin.friction = ConvertToBox(1f); fixtureDefKin.restitution = ConvertToBox(10f); Fixture fixturek = bodyPad.createFixture(fixtureDefKin); box.dispose(); //Body mocowanie static kinematicBodyDefMocowanie = new BodyDef(); kinematicBodyDefMocowanie.type = BodyDef.BodyType.StaticBody; kinematicBodyDefMocowanie.gravityScale = 0.5f; kinematicBodyDefMocowanie.fixedRotation = false; kinematicBodyDefMocowanie.position.set(posX, posY); bodyPadLeg = world.createBody(kinematicBodyDefMocowanie); CircleShape mocowanie = new CircleShape(); mocowanie.setRadius(ConvertToBox(1f)); FixtureDef fixtureDefKinLeg = new FixtureDef(); mocowanie.dispose(); //join DistanceJointDef distanceJointDef = new DistanceJointDef(); distanceJointDef.bodyA = bodyPad; distanceJointDef.bodyB = bodyPadLeg; distanceJointDef.length = ConvertToBox(1); world.createJoint(distanceJointDef); //Weight BodyDef weightBodyDef; weightBodyDef = new BodyDef(); weightBodyDef.type = BodyDef.BodyType.DynamicBody; weightBodyDef.gravityScale = 0.8f; weightBodyDef.fixedRotation = true; weightBodyDef.position.set(posX+ConvertToBox(160), posY+ConvertToBox(50)); weightR = world.createBody(weightBodyDef); PolygonShape weightPolygon = new PolygonShape(); weightPolygon.setAsBox(ConvertToBox(10), ConvertToBox(10)); FixtureDef fixtureWeight = new FixtureDef(); fixtureWeight.shape = weightPolygon; fixtureWeight.density = 10.5f; fixtureWeight.friction = 0.4f; fixtureWeight.restitution = 0.3f; Fixture fixturekWeightt = weightR.createFixture(fixtureWeight); weightPolygon.dispose(); //Weight BodyDef weightBodyDefLeft; weightBodyDefLeft = new BodyDef(); weightBodyDefLeft.type = BodyDef.BodyType.DynamicBody; weightBodyDefLeft.gravityScale = 0.8f; weightBodyDefLeft.fixedRotation = true; weightBodyDefLeft.position.set(posX - ConvertToBox(160), posY + ConvertToBox(50)); weightL = world.createBody(weightBodyDefLeft); PolygonShape weightPolygonL = new PolygonShape(); weightPolygonL.setAsBox(ConvertToBox(10), ConvertToBox(10)); FixtureDef fixtureWeightL = new FixtureDef(); fixtureWeightL.shape = weightPolygonL; fixtureWeightL.density = 10.5f; fixtureWeightL.friction = 0.4f; fixtureWeightL.restitution = 0.3f; Fixture fixturekWeighttL = weightL.createFixture(fixtureWeightL); weightPolygonL.dispose(); } float ConvertToBox(float x) { return x * WORLD_TO_BOX; } @Override public void render() { Gdx.gl.glClearColor(0, 0, 0, 0); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); box2DDebugRenderer.render(world, camera.combined); world.step(1 / 45f, 6, 2); bodyPad.setLinearVelocity(powerX, powerY); input(); } public void input() { if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) { weightL.setLinearVelocity(ConvertToBox(10),0); weightR.setLinearVelocity(ConvertToBox(10), 0); } if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) { weightL.setLinearVelocity(-ConvertToBox(10),0); weightR.setLinearVelocity(-ConvertToBox(10), 0); } } }
Na koniec warto wspomnieć o DistanceJoint w skrócie pozwala na łączenie obiektów. W powyższym przykładzie pochylnia jest połączona ze statycznym ciałem czyli mocowaniem. Warto pobawić się parametrem
distanceJointDef.length = ConvertToBox(1);
Co bardziej zobrazuje o co chodzi. Nie mniej bez Joint’ów trudno byłoby stworzyć np. samochód :-).
LibGDX BOX2D to potężny tandem i dopiero uchylam sobie do niego drzwi ale zabawa jest przednia. Nie mniej kod powyższy po wygenerowaniu waży 30MB, przypuszczam, że coś takiego w językach web’owych byłoby lżejsze 🙂