Collision¶
Collisioniga alustamine¶
Selles peatükis loome kokkupõrkesüsteemi (collision) kasutades LibGDX Box2D füüsikamootorit. Eesmärk on panna mängija ja kaardi objektid üksteisega füüsikaliselt reageerima.
Box2D aitab hallata:
Kokkupõrkeid
Liikumist
Füüsikat (nt gravitatsioon)
Erinevaid objektide tüüpe
Alternatiiv oleks kirjutada collision-süsteem nullist, kuid see on märksa keerulisem ja ajamahukam.
Collision süsteemi eeldused
Eeldame, et projektis on juba olemas:
Tiledisloodud kaartCollision object layer
Kaart laaditakse LibGDX-is
TmxMapLoader mapLoader = new TmxMapLoader();
TiledMap map = mapLoader.load("maps/level1.tmx"); // Laeb Tiled kaardi failist
Box2D lisamine projekti
Box2D tuleb lisada projekti Gradle dependency kaudu.
core/build.gradle - siia lisame Box2D API, mis on vajalik kogu mängu loogika jaoks.
dependencies {
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
}
desktop/build.gradle – siia lisame Box2D native library (natives-desktop), et see töötaks lauaarvutites.
project(":desktop") {
apply plugin: "java-library"
dependencies {
implementation project(":core")
implementation "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop"
}
}
Box2D maailma loomine¶
Box2D kasutamiseks tuleb esmalt initsialiseerida füüsikamootor.
Box2D.init();
Seejärel loome füüsikamaailma (World).
private final World world;
private final Box2DDebugRenderer b2dr;
this.world = new World(new Vector2(0, 0), true);
this.b2dr = new Box2DDebugRenderer();
World klass määratleb Box2D maailma, kus toimub füüsika simuleerimine.
World muutujas on Vector2 gravitatsiooni määramiseks ning doSleep, mis säästab arvutusvõimsust staatiliste objektide puhul.
Top-down mängu jaoks on gravitatsioon (0, 0). Platformeri jaoks kasuta näiteks new Vector2(0, -9.8f)
Kui doSleep on true, siis Box2D ei arvuta füüsikat kehadelt, mis ei liigu.
Box2DDebugRenderer renderdab kehasid ja objekte. (See on testimiseks mõeldud.)
Box2D keha tüübid¶
Box2D-s on kolm peamist kehatüüpi:
StaticBody
Ei liigu
Ei reageeri jõududele
Kasutatakse seinte, põrandate ja platvormide jaoks
DynamicBody
Reageerib füüsikale
Mõjutavad gravitatsioon, jõud ja kokkupõrked
Kasutatakse mängijate, kastide ja muude liikuvate objektide jaoks
KinematicBody
Liigub käsitsi määratud kiirusega
Ei reageeri füüsikale
Kasutatakse näiteks liikuvate platvormide jaoks
Body ja Fixture¶
Box2D objektid koosnevad kahest osast.
Body
Füüsiline keha maailmas
Määrab asukoha ja liikumise
Fixture
Määrab keha kuju
Määrab kokkupõrke omadused (nt friction, restitution, density)
Collision objektide loomine¶
Tiledis saab collision objektid lisada object layerile.
Need objektid saab Box2D kehadeks teisendada.
Mängu kehade ja objektide lisamiseks loo uus klass, näiteks Box2DWorldGenerator.
BodyDef bdef = new BodyDef();
PolygonShape shape = new PolygonShape();
FixtureDef fdef = new FixtureDef();
Body body;
for (MapObject object : map.getLayers().get(2).getObjects().getByType(RectangleMapObject.class)) {
Rectangle rectangle = ((RectangleMapObject) object).getRectangle();
bdef.type = BodyDef.BodyType.StaticBody;
bdef.position.set(
(rectangle.getX() + rectangle.getWidth() / 2) / Demo.PPM,
(rectangle.getY() + rectangle.getHeight() / 2) / Demo.PPM
);
body = world.createBody(bdef);
shape.setAsBox(
rectangle.getWidth() / 2 / Demo.PPM,
rectangle.getHeight() / 2 / Demo.PPM
);
fdef.shape = shape;
body.createFixture(fdef);
}
Oluline: pärast shape kasutamist tuleks see vabastada.
shape.dispose();
BodyDefi kasutatakse Box2D-s keha omaduste määratlemiseks.
See sisaldab atribuute nagu keha tüüp (staatiline, kineetiline, dünaamiline), asend, nurk, lineaarne kiirus ja muud keha algsed omadused.
PolygonShape on Box2D klass, mida kasutatakse keha külge kinnitatud objekti kuju määratlemiseks.
FixtureDefi kasutatakse objekti omaduste (nt kuju, tiheduse, hõõrdumise jne) määratlemiseks. See “kinnitatakse” keha külge.
Omadus |
BodyDef (Keha määratlus) |
FixtureDef (Kokkupõrgete määratlus) |
|---|---|---|
Mida määrab? |
Keha üldised füüsikalised omadused |
Kokkupõrgete ja füüsika omadused |
Olulised parameetrid |
|
|
Kas see lisatakse kehale? |
Kasutatakse |
Lisatakse kehale |
for tsükli abil loome kehasid objektidest, mis on varem Tiled abil kaardile lisatud.
(Hiljem saad lisada teise sarnase tsükli ka teiste kujundite jaoks, näiteks ümmarguste kehade loomiseks.)
bdef.type = BodyDef.BodyType.StaticBody: määrab keha tüübiks StaticBody, mis ei liigu ega reageeri jõududele.
Seejärel määrame keha asukoha ristküliku keskpunkti ja shape saab kuju, mis on pool ristküliku laiusest ja pikkusest. Määrame objekti kujuks shape’i.
Lisaks peame render() meetodisse lisama read:
this.b2dr.setDrawBodies(true);
this.b2dr.render(world, camera.combined);
this.b2dr.setDrawBodies(true);:
b2dr.render(world, camera.combined)joonistab Box2D debug vaate ekraanileb2dr.setDrawBodies(true)lubab kehade joonistamise debug vaatesKui väärtus on false, siis kehasid ei joonistata
Mängu jooksutamisel võiksid nüüd ruudukujuliste objektide ümber olla sellised jooned:
Mängija keha loomine¶
Eeldame, et on olemas eraldi mängija klass. Mängija jaoks loome dünaamilise keha.
public Player(World world) {
this.world = world;
definePlayer();
}
Mängija klassi võiksid nüüd teha järgnevad muudatused:
private void definePlayer() {
BodyDef bdef = new BodyDef();
bdef.position.set(32 / Demo.PPM, 32 / Demo.PPM);
bdef.type = BodyDef.BodyType.DynamicBody;
b2body = world.createBody(bdef);
CircleShape shape = new CircleShape();
shape.setRadius(10 / Demo.PPM);
FixtureDef fdef = new FixtureDef();
fdef.shape = shape;
b2body.createFixture(fdef);
shape.dispose();
}
PPM (Pixels Per Meter)¶
PPM ehk pixels per meter on konstant, mille võiksid defineerida oma mängu
põhiklassis või eraldi konstantidele pühendatud klassis. (väärtus võiks olla nt 100).
Kuna Box2D kasutab mõõtühikuna meetreid, tuleb mängu objektid skaleerida väiksemaks.
Vastasel juhul muutuvad kehad Box2D jaoks liiga suureks ja simulatsioon võib muutuda ebastabiilseks.
Seetõttu kasutatakse skaleerimiseks konstantset väärtust:
public static final float PPM = 100f;
Näiteks oleks meie b2body raadius ilma PPMita 10 meetrit.
Samuti oleks vaja läbi jagada PPM'ga eelnevalt defineeritud väärtused:
this.viewport = new FitViewport(
Gdx.graphics.getWidth() / Demo.PPM,
Gdx.graphics.getHeight() / Demo.PPM,
camera
);
this.renderer = new OrthogonalTiledMapRenderer(map, 1 / Demo.PPM);
Mängija liigutamine¶
Box2D keha saab liigutada mitmel viisil.
applyLinearImpulse(impulse, point, wake)
Lisab kehale kiire tõuke
Sobib hüpeteks või kiireks liikumiseks
impulse – määrab jõu vektori (kiirus ja suund), mida rakendatakse kehasse.
point – punkt, kus jõud rakendatakse (siin on kasutatud keha keskmist punkti playerBody.getWorldCenter()).
wake – määrab, kas keha peaks pärast jõu rakendamist olema ärkvel (kas keha hakkab liikumiseks arvutama järgmise sammu või mitte).
playerBody.applyLinearImpulse(
new Vector2(0.5f, 0),
playerBody.getWorldCenter(),
true
);
setLinearVelocity(vx, vy)
Määrab kehale otse kiiruse
Sobib täpse kontrolliga liikumiseks (nt top-down mängudes)
vx – keha horisontaalne kiirus (X-telg)
vy – keha vertikaalne kiirus (Y-telg)
playerBody.setLinearVelocity(2f, playerBody.getLinearVelocity().y);
Nüüd peame lisama update() meetodisse:
this.player.b2body.setLinearVelocity(Vector2.Zero);
Vastasel juhul hõljub mängija keha mööda maailma ringi ning ta reageerib kasutaja sisendile vaevumärgatavalt. Seda kasutatakse peamiselt top-down mängudes, kus mängija ei kasuta gravitatsiooni. Platformerites ei tohiks seda kasutada, sest see nullib ka gravitatsiooni mõju.
Füüsikamaailma uuendamine¶
Box2D maailma tuleb igal kaadril uuendada.
world.step(1/60f, 6, 2);
world.step() uuendab Box2D füüsikasimulatsiooni ühe ajasammu võrra.
Tüüpilise mängu jaoks sobivad need väärtused hästi, kuid vajadusel saab neid kohandada.
Parameetrid:
1/60f — simulatsiooni ajasamm
6 — kiiruse iteratsioonid
2 — positsiooni iteratsioonid
Nüüd mängu jooksutades peaksid kaardil nägema ringi, millega saad ringi liikuda. Ring peaks objektidega kokku põrkama.
Kui collision ei tööta¶
Kui kokkupõrked ei tööta, kontrolli järgmisi asju:
Kas
world.step(...)kutsutakse igal kaadrilKas
debug renderernäitab kehasidKas
PPMskaleerimine on järjepidevKas keha tüüp on õige (StaticBody vs DynamicBody)
Kas
Tiledison õige layerKas
fixtureloodi õigestiKas
shape.dispose()kutsuti alles pärast fixture loomist