Tiled
Tiled on kasutajasõbralik programm 2D kaartide loomiseks. See võimaldab hõlpsasti määrata / muuta kaartide suurusi, tõmmata alla kõikvõimalikke kujunduselemente ning mis kõige peamine - selle abil saab väga lihtsasti hallata collisioneid.
Esimese sammuna mine leheküljele https://www.mapeditor.org/ ning tõmba endale Tiled alla.
Kui oled programmi käima tõmmanud, siis vali New Map… ning seejärel saad valida kaardi ja tile’ide suuruseid. Enamik internetist leitavaid tilesete on küll mõeldud 32x32 px suurustele tile’idele, aga nii tile’ide kui kaardi suurust on hiljem võimalik väga lihtsasti muuta.
Järgmiseks peaks avanema tühi ruudustik ja kaardil on üksainus layer. Kõigepealt peame lisama oma projektile tileseti, mis näeb välja midagi sellist:
Tileset-id koosnevad tile’idest ning ongi sisuliselt need, millest me oma kaardi visuaalse poole ehitame.
Mõned lingid, kust saab tilesete alla laadida:
https://rpg.hamsterrepublic.com/ohrrpgce/Free_Tilemaps
Samuti on võimalik kujundada ise oma tilesete programmidega nagu Blender, Inkscape või GIMP, aga see on üpriski aeganõudev tegevus ning internetis on väga lai valik erinevaid tasuta saadaval olevaid sete.
Kui oled midagi välja valinud ning alla laadinud, siis mine File-> New -> New Tileset ning lisa sinna oma tileset. Sulle võiks vastu vaadata midagi sellist:
Nagu näed, siis minu tilesetis on olemas nii tausta jaoks mõeldud ruudud kui ka ruudud, millest saab ehitada erinevaid objekte (lombid, trepid, kastid jms). Tilesete võid enda kaardile lisada mitu ning neid saab vabalt omavahel kombineerida.
Tiled ei erista neid ruute aga omavahel kuidagi ning seetõttu peame lisama oma kaardile juurde uue layeri. Kui ehitaksime tausta valmis ja hakkaksime sama kihi peale objekte laduma, siis joonistataks taustal olevad ruudud lihtsalt üle.
Paremal tileset’ide menüü kohal on koht layerite haldamiseks. Vali sealt uus tile layer:
Esimesele layerile panemegi taustaks mõeldud tile’id, näiteks muru, vett, mulda jms. Kui soovid suuremaid alasid katta mingit ühte sorti tile’iga, siis selleks on väga kasulik bucket fill tööriist, mis täidab kõik kaardil veel täitmata alad:
Seejärel vajuta äsjaloodud layeri peale ning saad taustale hakata lisama objekte. Objektide üksteise otsa ladumiseks korda seda sammu nii mitu korda kui vaja.
Kui oled ära märgistanud kõik soovitud objektid, siis salvesta kaart enda projekti assets kausta. Samuti lisa sinna kõik tilesetid, mida kaardi loomisel kasutasid. Kaart peaks sulle salvestuma .tmx failina.
Arvatavasti on Sul juba olemas mingisugune GameScreen (võiks implementida Screen interface’i) vms klass, mis haldab Sinu mängus toimuvat. Klassi konstruktorisse on vaja nüüd lisada järgnevad väljad:
- Kaamera - saab liigutada, pöörata, zoomida, fikseerida mängijale jne; positsioneerime selle enda kaardi keskele
- ViewPort - aitab skaleerida mängu ekraani suuruse järgi. Neid on mitmeid, mina kasutan FitViewporti. On päris tõenäoline, et pead muutma worldWidth ja -Height väärtusi, olenevalt oma kaardi suurusest.
- MapLoader - laeb kaardi mängu
- MapRenderer - renderdab kaardi
Update meetodisse lisa camera.update() ning renderer.setView(camera). Kui tahad kaardil ka ringi liikuda, aga ringi liikumise loogikat Sul veel pole, siis tee mingi lihtne input handling meetod, mis reageerib näiteks ekraanipuudutusele.
Näiteks võid teha midagi sellist, et liikuda kaameraga mööda x ja y telge:
Screen interface’i implementeerimisel tuleb kaasa käputäis erinevaid @Override annotatsiooniga meetodeid. Otsi üles render() meetod ja lisa sinna järgnevad väljad, kui sul neist mõnda veel ei ole:
Kui proovid oma mängu tööle panna, siis peaks Sulle kuvatama sinu kaart. Suure tõenäosusega pead muutma viewpordis worldWidth ja -Height väärtusi nagu minul juhtus:
Collisionid
Lisame enda kaardile veel ühe layeri, seekord aga collisionite jaoks:
Ülevalt menüüst saad valida sobiva kujundi ning selle abil hakata tõmbama jooni ümber kõikide objektide, millega sa tahad, et maailmas olevad mängijad, vaenlased jms kokku põrkaksid. Vastasel juhul saab nendest lihtsalt üle kõndida nagu tavalisest taustaks mõeldud ruutudest.
Pro tip: Vaikimisi saavad mängijad, vastased, kaamera jms vabalt kaardilt välja liikuda. Selle vältimiseks on kõige lihtsam viis teha piisavalt suur kaart ning piirata selle sees mängimiseks mõeldud ala object layeri objektidega.
LibGdx-is on vägagi mõistlik kasutada Box2D füüsikaliidest, mille abil saab hallata collisioneid, mängija liikumist, gravitatsiooni (nt platformerites) jne.
Selle jaoks tuleb build.gradle faili vajalik dependency lisada. (Sõber GPT aitab)
Järgmiseks täiendame veel oma GameScreen klassi konstruktorit:
World defineeribki meile Box2D maailma, milles eksisteerib füüsika. Selle maailma sees on kehad (body) ja objektid (fixture). Fixture kinnitatakse keha külge, et anda talle kuju, võime teiste kehadega kokku põrgata jne.
Box2DDebugRenderer renderdab kehasid ja objekte. World muutujas on:
- Vector 2 - gravitatsiooni määramiseks
- doSleep - säästetakse aega nende objektide pealt, mis on staatilised
Nüüd lisamegi mängu kehad ja objektid. Selle jaoks tee uus klass, näiteks Box2DWorldGenerator vms, kuhu anna kaasa GameScreen klass. Mina teen seda oma GameScreeni konstruktoris:
- 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.
For loopi abil hakkame looma kehasid objektidest, mis me varem Tiled abil enda kaardile lõime. Nagu näed, siis mina saan enda objektid teisest layerist - loendamist alustatakse 0st ja minu kaardis on kokku 3 layerit, millest objektide layer on viimane.
Iga teises layeris oleva RectangleMapObjecti jaoks otsib see ristküliku, mis määrab objekti piirid. Hiljem saad lisada teise samasuguse loopi ka teiste kujundite jaoks, näiteks ringikujuliste kehade loomiseks.
bdef.type = BodyDef.BodyType.StaticBody määrab kehatüübiks StaticBody, mis ei liigu ega reageeri jõududele.
Seejärel määrame keha asukoha ristküliku keskele ja shape saab enda kujuks pool ristküliku laiusest ja pikkusest. Määrame objekti kujuks shape’i.
Lõpuks loome keha, kasutades enne valmis tehtud fdefi.
Lisaks peame render() meetodisse lisama read:
Renderdab meie objektidele ümber jooned, et saaksime paremini vajadusel debug-ida Kui sulgudesse panna false, saame jooned peita
Mängu jooksutamisel võiksid nüüd ruudukujuliste objektide ümber olla sellised jooned:
Arvatavasti on sul olemas juba mingi klass mängija jaoks. Sinna võiksid nüüd teha järgnevad muudatused: Uued muutujad b2body (klass Body) ja world Konstruktor võiks olla midagi sellist (lisaks olemasolevale loogikale):
- Uus meetod definePlayer() Teeme midagi sarnast, mis enne objektide genereerimisega:
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äärtus võiks olla nt 100). Kuna Box2D engine kasutab meetreid, siis peame skaleerima oma mängu kasutama väiksemaid suurusi. Näiteks oleks meie b2body raadius ilma PPMita 10 meetrit.
Nüüd navigeeri uuesti GameScreen klassi ja jaga seal ka läbi PPMiga järgnevad väärtused:
viewport = new FitViewport(Gdx.graphics.getWidth() / Demo.PPM, Gdx.graphics.getHeight()/ Demo.PPM, camera);
renderer = new OrthogonalTiledMapRenderer(map, 1 / Demo.PPM);
bdef.position.set((rectangle.getX() + rectangle.getWidth() / 2) / Demo.PPM, (rectangle.getY() + rectangle.getHeight() / 2) / Demo.PPM);
shape.setAsBox(rectangle.getWidth() / 2 / Demo.PPM, 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.
Nüüd peame lisama update() meetodisse player.b2body.setLinearVelocity(Vector2.Zero); sest vastasel juhul hõljub mängija keha mööda maailma ringi ning ta reageerib kasutaja sisendile vaevumärgatavalt.
Samuti lisame rea world.step(1/60f, 6, 2);. See põhimõtteliselt määrab kui kiiresti Su mängus igasuguseid kokkupõrkeid jms handlitakse. Selle väärtusi pole otseselt muuta vaja.
Viimaseks lisame kaks rida, mis fikseerivad kaamera mängija peale.
Nüüd jooksuta jälle oma mängu ning peaksid kaardil nägema ringi, millega saad ringi liikuda. Kaamera peaks olema fikseeritud ringile ning ring peaks collidema objektidega, mille enne genereerisime.