Collision

Collisioniga alustamine

1. Eeldame, et meil on valmis:

  • Mängukaart

  • Kaardi collision layer

  • Kaart on laetud

TmxMapLoader mapLoader = new TmxMapLoader();
TiledMap map = mapLoader.load("maps/level1.tmx");  // Laeb Tiled kaardi failist

2. Siit saab jätkata vähemalt kahel erineval viisil:

Esimeseks võimaluseks, ning mõistlikuks otsuseks oleks kasutada LibGDX-is Box2D füüsikaliidest, mille abil saab hallata:

  • Collisioneid

  • Mängija liikumist

  • Gravitatsiooni (nt platformerites) jne.

Teiseks võimaluseks oleks kirjutada kõik nullist ise, mis on oluliselt keerulisem ja aeganõudvam. (seda ma siin ei käsitle)

3. Alustame Box2D lisamisega

Box2D lisamine build.gradle failidesse

  • build.gradle juurkaustas (see kontrollib kõiki mooduleid) – seda ei pea tavaliselt muutma.

  • core/build.gradle – siia lisame Box2D API, mis on vajalik kogu mängu loogika jaoks.

dependencies {
    implementation group: 'com.esotericsoftware', name: 'kryonet', version: '2.22.0-RC1'
    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")  // Desktop kasutab Core moodulit
        api "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop"
    }
}

Selle jaoks tuleb build.gradle faili vajalik dependency lisada

implementation "com.badlogicgames.gdx:gdx-box2d:1.10.0"

(vajadusel GPT aitab)

4. Järgmiseks täiendame veel oma GameScreen klassi konstruktorit:

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 Vector 2 gravitatsiooni määramiseks ning doSleep mis säästab arvutusvõimsust staatiliste objektide puhul.

Box2D füüsikamootoris on kolm peamist kehatüüpi, mis käituvad erinevalt:

Staatiline (StaticBody): ei liigu ega reageeri jõududele

  • Kasutatakse seinte, põrandate, platvormide jms jaoks.

  • Ei mõjuta dünaamilisi kehasid otseselt, aga neile võib vastu põrgata.

Dünaamiline (DynamicBody) – füüsikaseadustele alluv keha

  • Reageerib gravitatsioonile, impulssidele, jõududele ja kokkupõrgetele.

  • Kasutatakse tegelaste, kastide, pallide jms jaoks.

Kineetiline (KinematicBody) – juhitav, kuid ei reageeri füüsikale

  • Liigutatakse käsitsi setLinearVelocity või setTransform abil.

  • Ei mõjuta dünaamilisi kehasid (aga neile võib vastu põrgata).

Mida doSleep teeb? Kui doSleep on tõsi, siis Box2D ei arvuta füüsikat kehadelt, mis ei liigu.

Selle maailma sees on kehad (body) ja objektid (fixture). Fixture kinnitatakse kehale, andes sellele kuju, võimaldades teiste kehadega kokku põrgata jne.

  • Box2DDebugRenderer renderdab kehasid ja objekte. (See on testimiseks mõeldud.)

5. Mängu kehade ja objektide lisamiseks loo uus klass, näiteks ** ``Box2DWorldGenerator``, **, ja anna sinna sisse GameScreen klass.

Mina teen seda oma GameScreenis

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);
}
  • BodyDefi kasutatakse Box2D-s keha omaduste määratlemiseks.

    See sisaldab atribuute nagu keha tüüp (staatiline, kineetiline, dünaamiline), asend, kaal, kiirus jne.

  • 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.

BodyDef ja FixtureDef erinevused

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

type, position, linearVelocity, angle

shape, density, restitution, friction

Kas see lisatakse kehale?

Kasutatakse world.createBody(bdef); kaudu keha loomiseks

Lisatakse kehale body.createFixture(fdef); kaudu

For loopi 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.

6. Lisaks peame render() meetodisse lisama read:

this.b2dr.render(world, camera.combined);
this.b2dr.setDrawBodies(true);

this.b2dr.setDrawBodies(true);:

  1. Renderdab meie objektidele ümber jooned, et saaksime paremini vajadusel debug-ida.

  2. Kui sulgudesse panna false, saame jooned peita.

Mängu jooksutamisel võiksid nüüd ruudukujuliste objektide ümber olla sellised jooned:

1

Arvatavasti on sul olemas juba mingi klass mängija jaoks.

7. Mängija klassi võiksid nüüd teha järgnevad muudatused:

  • Uued muutujad b2body (klass Body).

  • World Konstruktor võiks olla midagi sellist (lisaks olemasolevale loogikale):

public Player(World world) {
    this.world = world;
    definePlayer();
}

8. Uus meetod definePlayer().

Teeme midagi sarnast, mis enne objektide genereerimisega:

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);
 FixtureDef fdef = new FixtureDef();
 CircleShape shape = new CircleShape();
 shape.setRadius(10 / Demo.PPM);

 fdef.shape = shape;
 b2body.createFixture(fdef);
}

Näed, et igal pool on arvulised väärtused jagatud muutujaga PPM.

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 engine kasutab meetreid, siis peame skaleerima oma mängu kasutama väiksemaid suurusi, vastasel juhul liiguvad objektid väga aeglaselt.

Näiteks oleks meie b2body raadius ilma PPMita 10 meetrit.

9. Nüüd navigeeri uuesti GameScreen klassi ja jaga seal ka läbi PPMiga järgnevad 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);

this.bdef.position.set((rectangle.getX() + rectangle.getWidth() / 2) /
Demo.PPM, (rectangle.getY() + rectangle.getHeight() / 2) / Demo.PPM);

this.shape.setAsBox(rectangle.getWidth() / 2 / Demo.PPM,
this.rectangle.getHeight() / 2 / Demo.PPM);

Kui sul seda veel pole, siis võiksid luua mängijale liikumisloogika.

Kui sul see juba on, siis võiksid seda muuta nii, et liigutad b2body, rakendades sellele applyLinearImpulse meetodit.

playerBody.applyLinearImpulse(impulse, playerBody.getWorldCenter(), true);

applyLinearImpulse(impulse, point, wake) – rakendab liikumisjõu kehale (b2body): - 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).

10. Nüüd peame lisama update() meetodisse:

this.player.b2body.setLinearVelocity(Vector2.Zero);

sest vastasel juhul hõljub mängija keha mööda maailma ringi ning ta reageerib kasutaja sisendile vaevumärgatavalt.

11. Samuti lisame rea:

this.world.step(1/60f, 6, 2);

See määrab kui kiiresti Su mängus igasuguseid kokkupõrkeid jms handlitakse. Selle väärtusi pole otseselt muuta vaja.

Nüüd jooksuta jälle oma mängu ning:

  • Peaksid kaardil nägema ringi, millega saad ringi liikuda.

  • Ring peaks collide'ima objektidega, mille enne genereerisime.

2