task_green.png
task_green.png
task_green.png
task_green.png
task_green.png

5. Falbontó játék

 

5.1. A játék célja

A falbontó (Block Breaker vagy Breakout) játékötletet már nagyon sokan megvalósították, egy időben nagyon népszerű játék volt, a legismertebb neve talán az Arkanoid volt, jelenleg is rengeteg megvalósítása elérhető. A játék célja a képernyő alján elhelyezkedő "ütővel" a labdát úgy irányítani, hogy az a téglákhoz pattanva eltüntesse azokat. Ütőnket csak vízszintesen tudjuk mozgatni. Ha a labda leesik mellettünk, elvész egy életünk. Mivel ez nem egy túl bonyolult játék, tökéletes tanulási célra, így most egy ilyen játékot fogunk készíteni.

5.2. Navigáció megvalósítása

 

Jó gyakorlat, ha a játékfejlesztési projektet azzal kezdjük, hogy kialakítjuk a játék menürendszerét és a navigációt, így a fejlesztés során szinte mindig egy többé-kevésbé működő játékunk lesz. A számkitalálós játékban már megvalósítottunk nyitó- és játékképernyőket, illetve a közöttük ltörténő navigálást, ezeket újrahasznosíthatjuk a Falbontó játékban is.

1. Nyissuk meg a NumberGuessing projektünket Unity-ben, majd az Assets » Export Package menüpont alatt exportáljuk ki egy menu nevű csomagba a Start és Win képernyőket, illetve a LevelManager szkriptet.

2. Hozzunk létre egy új Breakout nevű 2D-s Unity projektet, importáljuk az előző lépésben kiexportált menu csomagot és az alapértelmezettként létrehozott SampleScene-t nevezzük át Level_01-re. Jegyezük meg a projektek közötti másolás ezen módját.

3. A játékunkhoz szükségünk lesz pár asset-re, ezeket töltsük le innét tömörített formában, csomagoljuk ki a zip állományt és importáljuk be a benne található hang és képfájlokat a projektünkbe. Rendezzük az assetjeinket a következő négy mappába: Scenes, Scripts, Sounds, Sprites.

4. A Start képernyőn lévő szöveg betütípusa eléggé unalmas, cseréljük le egy jobbra. Betütípust letölthetünk a korábban megismert fonts.google.com oldalról vagy a dafont.com-ról. A példában én az utóbbi oldalon található BMBlock fontot fogom használni. Természetesen célszerű a szöveget is a játékunkhoz igazítani.

5. Oldjuk meg, hogy:

  • a Start gomb nyissa meg a Level_01 scene-t, a Play Again pedig a Start scene-t.

  • a Quit gomb hívja meg a QuitRequest() eljárást

  • a Level_01 scene-hez adjunk hozzá egy ideiglenes Game Over gombot, amely a Win képernyőre viszi a felhasználót. Ehhez másoljuk be a LevelManager objektumot a Win vagy Start scene-ről a Level_01 scene-re.​

Ha ezzel készen vagyunk, akkor a játékunk navigációja​ gyakorlatilag működőképes.

5.3. Háttérzene hozzáadása

A jó háttérzene meghatározza az egész játék hangulatát. Gyakran a játékban van egy végtelenített alapzene, amin felül a különböző hangeffektusok szólalnak meg. Ebben a fejezetben a háttérzene hozzáadásával fogunk foglalkozni.

1. Válasszuk ki a hátterzenének szánt hangfájlt. Letölthetünk egyet a freesond.org-ról, más hasonló profilú oldalról, vagy adott esetben ha van hozzá kedvünk, türelmünk és tehetségünk, komponálhatunk egy saját számot is. A példában az innét letölthető, de a korábban letöltött asset-ek között is megtalálható hangállományt fogjuk használni. Ha még erre nem került volna sor, húzzuk a kiválasztott hangállományt a Sounds mappába. Törekedjünk arra, hogy kisméretű, lehetőleg tömörített hangfájlt használjunk.

2. Hozznuk létre egy Music Player nevű üres GameObject-et, reseteljük a pozícióadatokat, majd húzzuk az Inspector nézetébe a hangállományt. Ennek eredményeként egy AudioSource komponens adódik hozzá az objektumunkhoz. Pipáljuk ki a Play on Awake és Loop jelölőnégyzeteket, a hangerőt pedig vegyük vissza 25%-ra. Ha elindítjuk a projektünket, akkor a hátterzene is elindul. Az egyetlen probléma, hogy amikor más scene kerül betöltésre, a zene elhallgat, viszont mi azt szeretnénk, ha a lejátszása nem szakadna meg. Ezt egy rövid szkripttel tudjuk megoldani.

3. Vegyünk fel egy új MusicPlayer nevű szkriptet az azonos nevű GameObject-hez. A Start() metódusba egyetlen egy sort írjunk:

GameObject.DontDestroyOnLoad(gameObject);

Ez a sor gondoskodik arról, hogy a lejátszásért felelős objektum ne semmisüljön meg, amikor elhagyjuk az őt tartalmazó scene-t. Ha játékmódba váltunk, azt tapasztaljuk, hogy a hátterzene lejátszása már nem szakad meg akkor, amikor új scene töltődik be.

Van viszont egy új problémánk: ha visszajutunk a Start képernyőre, akkor a háttérzene egy második példánya is elindul. Ez azért van, mert a MusicPlayer osztály egy 2. példánya is létre jön, az első megszűnését meg ugye megakadályoztuk a szkriptünkkel. Meg kellene oldanunk, hogy a MusicPlayer 2. példánya ne jöjjön létre, ha már van belőle egy. Ezt statikus változók és a segítségükkel megvalósított Singleton tervezési minta használatával érhetjük el.

A statikus mezőkről és metódusokról bővebb információ itt található. A Singleton tervezési mintáról, annak előnyeiről és hátrányairól pedig itt olvashatunk.

4. A MusicPlayer szkriptet módosítsuk az alábbiak szerint:

task_green.png
task_green.png
task_green.png
img022 - 5.1 - 1.png
links.png
task_green.png

A fenti kód biztosítja, hogy a MusicPlayer osztályból maximum egy példány létezhessen. Figyeljük meg, hogy a fenti kód Start() metódus helyett az Awake()-et használja, ami még a Start() előtt fut le. A kód működését nem fontos pontosan megérteni, elég, ha tudjuk, hogy mi lesz az eredménye.

A módosítás után a zenénk folamatosan és egy példányban szól majd.

5.4. Játéktér kialakítása

A játékunk kidolgozásakor figyelembe kell vennünk, hogy az milyen eszközön fog futni és annak a képernyője milyen felbontásokat támogat. A felbontás mellett az egyik fontos jellemző a képernyő oldalainak aránya. Például az iPad felbontása 2048x1536 képpont ami 2048/1536 = 1,333-as, vagy másképpen felírva 4:3-as oldalarányt jelent. Több felbontás is hasonló oldalarányt produkál, pl. a 800x600 vagy a 1024x768. Egy másik elterjedt oldalarány a 16:9-es, például a FullHD 1920x1080-as, illetve a sima HD 1280x720-as felbontása ilyen oldalarányt takar. A játékaink kifejlesztésekor érdemes e két oldalarány valamelyikét, esetleg mindkettőt támogatnunk, attól függően, hogy milyen eszközöket célzunk meg. Az azonos oldalarányú felbontások közötti váltás nem okoz általában gondot, pl. egy 800x600-as játék helyesen fog megjelenni egy 1600x1200-as eszközön is. A példa projektünk 4:3-as oldalarányt fog használni,

1. A játékunknak lesz egyháttérképe, ez egy 800x600-as méretű kép, Level_01 bg néven találjuk meg a Sprites mappában. Állítsuk be az Inspector nézetben a Pixels per Unit tulajdoságát 50-re. Ennek köszönhetően egy Unity egység 50 pixelnek fog megfelelni. Húzzuk be a képet a Scene nézetbe, nevezzük át Background-ra a Hierarchy nézetben.

2. Hogy a háttér ne legyen nagyon élénk, a Color tulajdonságban állítsuk be az Alpha csatorna értékét 100 körülire. A Sprite Editor-ban állítsuk be a kép referenciapontját (Pivot Point) a bal alsó sarokra (Bottom Left). A Background Z koordinátája legyen 1.

3. Győződjünk meg, hogy a kameránk Projection tulajdonságának értéke Orthographic. 2D-s játékoknál használjuk elsősorban ezt a beállítást, ennek az az eredménye, hogy a látótérbe eső tárgyak megjelenítési mérete nem fog függeni a tárgy kamerától való távolságától (ellentétben a perspetívikus kamerával), A kamera pozíciójaként állítsuk be a (8, 6,  -10) értéket, a Size tulajdonság értékét pedig állítsuk 6-ra.

4. A Game nézetben állítsuk be a 4:3-as oldalarányt. Ekkor a kamera látómezőjét 

tökéletesen ki kell töltenie a háttérképnek. A Game Over gomb némileg kilóg jobbra a játéktérből, mozgassuk azt kissé balra, hogy teljesen a játéktérbe kerüljön.

5.5. A labda hozzáadása a játéktérhez

A játékban szükségünk van egy pattogó labdára, ezt fogjuk most a játékterünkhöz adni. 

1. A Sprites mappában megtaláljuk a Ball nevű képet, ő lesz a labdánk. Állítsuk be a  Pixels per Unit tulajdoságát 50-re, majd húzzuk a játéktér közepáre és adjunk hozzá egy RigidBody2D komponenst.  Ha most elindítjuk a játékot, a labda engedelmeskedik a gravitációnak és leesik. Ez az esemény a játék elvesztését is jelenti, ezt valahogyan figyelnünk és érzékelnünk kell, hogy ilyenkor például újraindíthassuk a szintet, amivel feleslegesség válik az ideiglenes Game Over gomb. Ezen esemény figyeléséhez az ú.n. collider-ek segítségét fogjuk igénybe venni. A collider-ek láthatatlanok, de meghatároznak egy formát, amely nem feltétlenül azonos annak az objektumnak a formájval, amelyikhez tartozik. Ezen forma lesz figyelembe véve, amikor az ütközések fizikáját számolja a játékmotor.

A colliderekről további információkat itt olvashatunk. A colliderek alapos ismerete elengedhetetlen a Unity-ben történő játékfejlesztéshez.

2. Adjunk a Ball GameObject-hez egy Circle Collider 2D komponenst. Szükségünk lesz valamire, amivel "ütközni" fog a labdánk a játéktér alján. Hozznuk létre egy üres BottomCollider nevű GameObject-et, rendeljünk hozzá egy Box Collider 2D komponenst, állítsuk be a pozícióját (8, -0.5, 0)-ra, a méretét pedig (16, 1, 1)-re, hogy a játéktér teljes alját lefedje. A labdánk ezzel a BottomCollider komponensel fog ütközni. Jelöljük be az Is Trigger jelölőnégyzetet, hogy csak az eseményt detektálhassuk és ne álljon meg tégylegesen a labda a BottomCollider felületén.

3. Töröljük az EventSystem és Canvas GameObject-eket, már nem lesz rájuk szükség. Vegyünk fel egy BottomCollider szkriptet az azonos nevű komponenshez az alábbi egyszerű forráskóddal: 

task_green.png
task_green.png
task_green.png
task_green.png
task_green.png
links.png
task_green.png
task_green.png
 
 

Ezután ha leesik a labdánk, automatikusan betöltődik a Win scene.

5.6. A mozgó platform

A következő teendőnk a képernyő alján mozgó "ütő" (paddle) leprogramozása.

1. Állítsuk be a full brick nevű sprite referencia pontját középre, majd húzzuk a Scene nézet alsó részének közepére, a labdánk alá (a pontos pozíciója legyen (8, 0.5, 0)) és nevezzük át a Hierarchy nézetben Paddle-re. Ha elindítjuk a játékot, a labda továbbra is leesik, de "átzuhan" az ütőn. Az elvárt viselkedés az lenne, hogy visszapattanjon tőle. Ahhoz, hogy érvényesüljenek a fizika törvényei ütközéskor, mint mindig, szükségünk lesz egy collider-re.

2. Adjunk a Paddle objektumhoz egy Box Collier 2D-t. Így a labda már ottmarad az ütő felületén, de nem pattan vissza róla. Szükségünk lesz még egy Rigidbody 2D komponensre is, adjuk azt is hozzá a Paddle objektumhoz. Végül hogy az ütő maga ne zuhanjon le a mélybe, állítsuk be a Body Type tulajdonságát Kinematic-re. Már csak azt kell megoldanunk, hogy a labda visszapattanjon az ütőről. Ezt a problémát fizikai anyagok használatával oldjuk meg. 

3. Hozzunk létre egy új Physics Material 2D-t. Az anyagok az objektumok ütközése során a surlódást és a rugalmasságot szabályozzák. A Friction-t állítsuk be 0-ra, a Bounciness tulajdonságot pedig állítsuk be 1-re. A labda anyagát állítsuk be az újonnan létrehozottra és állítsuk be a Collision Detection tulajdonság értékét Continuous-ra. Ennek eredményeként a labda tökéletesen fog pattani, ami azt jelenti, hogy pontosan olyan magasra pattan fel, amilyen magasságból leesett. Ha azt szeretnénk, hogy csak 50% magasságig pattanjon vissza a labda, akkor a Bounciness értékének Sqrt(0.5) = 0.707-et kell beállítanunk., 

5.7. Irányítás egérrel

Az ütőt szeretnénk egérrel mozgatni jobbra-balra. Ebben a fejezetben azt nézzük meg, hogy ezt hogyan tudjuk megvalósítani.

1. Győződjünk meg arról, hogy az ütőként használt full brick sprite (és a left brick és right brick sprite-ok) Pixel per Unit tulajdonságának értéke 128. Ennek eredményeként az ütő pontosan egy Unity egység széles lesz és a két szélső állás X koordinátája 0.5 és 15.5 lesz.

2. Hozzunk létre a Paddle objektum számára egy hasonnevű szkriptet az alábbi forrással:

task_green.png
task_green.png
task_green.png
task_green.png
 
 
task_green.png

A kód átváltja az egérkurzor pixelben megadott pozícióját (Input.mousePosition.x

Unity egységbe, a Mathf.Clamp() függvény használatával biztosítja, hogy az a két szélső állás (0.5 és 15.5) közé essen és az ütő x koordinátáját beállítja a kapott értékre. Szeretnénk, ha a játék indulásakor a labda az ütőn helyezkedne el és az ütővel együtt mozogna mindaddig, amíg el nem indítjuk a labdát, mondjuk egy egérklikkel.

3. Állítsuk be a labda kezdeti pozícióját (8, 0.87, 0)-ra úgy, hogy az ütőn pontosan középen legyen. A labda feladata lesz, hogy megtartsa ezt a pozíciót az egérgomb lenyomásáig. Ennek érdekében adjunk hozzá a Ball objektumhoz egy hasonnevű szkript komponenst az alábbi forrással és az Inspector nézetben húzzuk be a Paddle publikus változóba a Paddle objektumunkat.

task_green.png

A kód a játék indulásakor megjegyzi a labda ütőhöz viszonyított pozícióját és megtartja az első egérklikkig (Input.GetMouseButtonDown(0)).  Egérkattintáskor beállítunk egy kezdeti sebességvektort a labdának a velocity tulajdonság felülírásával.

5.8. Visszapattanás az oldalfalakról és a játéktér tetejéről

A labdánk és az ütőnk már nagyjából az elvárásaink szerint múködik, viszont a labda jobrra vagy balra "kiszökhet" a játéktérből. Oldjuk meg, hogy a falakról és, nagyobb lendület esetén, a játéktér tetejéről is visszapattanjon az labda.

1. Készítsünk egy Play Space nevű üres gyújtő GameObject-et, ez fogja majd össze azokat az objektumokat, amelyek a játékterünket képezik. Húzzuk be alá rögtön a Background és BottomCollider objektumokat.

2. Hozzunk létre egy újabb üres GameObject-et Left Wall néven és adjunk hozzá egy Box Collider 2D komponenst, majd állítsuk be a Size (1, 12) és Offset (-0.5, 6) tulajdonságokat. Készítsünk másolatot erről az objektumról, nevezzük át Right Wall-ra és állítsuk be a Position (16, 0, 0) és Offset (0.5, 6) tulajdonságokat. Készítsünk egy újabb másolatot és nevezzük el azt Top Wall-nak. Állítsuk be a Position (0, 12, 0), az Offset (8, 0.5) és a Size (16, 1) tulajdonságokat. Ezzel körbezártuk a játékterünket, a labda nem szökhet ki és a falakról az elvártak szerint pattan vissza.

3. Jelenleg a labdánkra hat a gravitáció, ezért az nem egyenesvonalú egyenletes mozgást végez. Ez különösen éles szögben repülő labda esetén figyelhető meg, ilyenkor nem éri el a játéktér tetejét,. hanem visszahull. Ezt megakadályozhatjuk, ha a Ball objektum Gravity Scale tulajdonságát nullára állítjuk, vagy teljesen kikapcsoljuk a gravitációt az Edit » Project Settings » Physics 2D menüpont alatt.

5.9. Téglák hozzáadása a játéktérhez

Alakul a játékunk, a labda viselkedése már nagyjából megfelelő, viszont még nincsenek leszedendő téglák. Ebben a fejezetben ezeket fogjuk hozzáadni a játékunkhoz. A téglák hasonlítanak az ütőnkhöz, így abból kiindulva fogjuk a téglákat elkészíteni.

1. Készítsünk egy másolatot a Hierarchy nézetben a Paddle objektumról, nevezzük el 1-hit-nek. Az 1-es arra utal, hogy egyszer kell a labdának eltalálnia a táglát ahhoz, hogy eltűnjön a játéktérből. Húzzuk be a téglát az assets-ek közé, majd tegyük be egy újonnan létrehozott Prefabs mappába. A prefab-okkal már találkoztuk korábban, lénygében egy megadott kinézettel és viselkedéssel felruházott mintaobketumot jelent, amiből több példány is létre hozható a játékban. Figyeljük meg, hogy a Hierarchy nézetben az objektum neve kékké válik, ezzel jelezve, hogy ez egy prefab példánya. Töröljük az 1-hit-ről a szkriptet, mivel az az ütő viselkedését implementálja, téglaként nem lesz rá szükségünk. Igazából a RigidBody komponens is törölhető, mivel a téglák nem fognak mozogni. Kattintsunk az Apply gombra, hogy a törlés a prefab-on is érvényesüljön. A prefab-ot húzzuk párszor a játéktérbe, ezzel létre hozva a téglákat, amelyek tulajdonképpen a prefabunk példányai. Hozzunk létre egy új Bricks nevű gyűjtőobjektumot és a Hierarchy nézetben húzzuk alá az összes téglát. Ez segíti az objektumok kezelhetőségét és növeli az átláthatóságot.

2. A tégláknak el kell tünniük, ha a labda velük ütközik. Egyeseknek elég egy ütközés, másoknak 2 vagy 3 is kellhet. Ezt szkriptből fogjuk kezelni, ennek érdekében hozzunk létre a prefabunk számára egy Bricks nevű szkriptet az alábbi forráskóddal:

task_green.png
task_green.png
task_green.png
task_green.png
task_green.png
 
 

A kód felvesz egy publikus maxHits változót, amely azt szabályozza, hogy hány ütközés kell a tégla eltűnéséhez. Ezt az Inspector nézetben kell beállítanunk. Ha az ütközések száma meghaladja ezt a hátérértéket, akkor az objektumot megsemmisítjük a Destroy() utasítással. 

3. Készítsünk két újabb prefab-ot, amelyek csak a sz1nükben és abban különböznek az elsőtől, hogy a maxHits értékük 2, illetve 3. 

4. A téglák pontos pozicionálása kihívást jelenthet. Egy képzeletbeli rács segíthet ebben. Az Edit » Snap Settings menüpont alatt állítsuk be a következő értékeket: Move X: 0.5, Move Y: 0.35. Jelöljük ki az osszes téglát és kattintsunk a Snap All Axes gombra. A téglák egy képzeletbeli 0.5 x 0.35-ös rács pontjaira igazondak. Ezután ha egy objektum mozgatása közben lenyomva tartjuk a Ctrl billlentyűt, akkor a mozgás nem folyamatos lesz, hanem rácspontról rácspontra ugró. Ezzel gyorsan kialakíthatjuk az elképzelt játékterünket, például így:

task_green.png
task_green.png
img021 - 5.10 - 1.png

Letesztelve a játékot meggyőződhetünk arról, hogy az megfelelően működik. A következő kihívást egy új szint kialakítása jelenti. 

5.10. Új szint hozzáadása a játékhoz

Eddig sok energiát fektettünk abba, hogy a kidolgozzuk a játékobjektumokat és megírjuk a viselkedésüket szabályzó szkripteket. Jó lenne a befektetett munkát újrahasznosítani, amikor a játék további szintjeit dolgozzuk ki. Egy új szint hozzáadása a játékhoz a lehető legegyszerűbb kell, hogy legyen. Igazából egy új szint csak a téglák elhelyezésében és számában fog különbözni a már meglévőtől. A Unity-ben az újrahasznosítás kulcselemei a prefab-ok, ezért logikus amit csak lehet prefab-bá alakítani. Ha ezt jól csináljuk, akkor később ha valami változtatnunk kell, akkor elég lesz azt egy helyen (a prefab-on) megtennünk és a változások automatikusan propagálódnak majd az összes származtatott objektumhoz, legyen az része bármelyik játékszintnek.

1. A Main Camera háttérszínét állítsuk feketére, hogy megszűnjön a kékes elszíneződés a játék módban, majd húzzuk be a Hierarchy nézetből a prefab-ok közé. Tegyük ugyanezt a LevelManager objektummal, a Play Space csoporttal, valamint a Ball és Paddle objektumokkal. Az egyetlen nem prefab-bá alakított csoport a Bricks alat található téglák maradtak, de ez így van rendjén, hiszen ezek egyediek lesznek szintenként.

2. Hozzunk létre egy új, üres scene-t, mentsük el Level_02 néven, majd húzzuk a Scenes mappába. Nyissuk meg az új scene-t, töröljük az alapból létrehozott kamerát, mivel lesz egy prefab kameránk, majd húzzuk a Hierarchy nézetbe az összes prefab-unkat. A Background komponens Sprite tulajdonságát módosítsuk Level 02 bg-ra. Ha szükséges, módosítsuk a Pixel Per Unit értékét 50-re és a hivatkozási pontot (Pivot) állítsuk be Bottom Left-re. Hozzunk létre egy Bricks üres GameObject-et, ami majd a téglákat fogja összefogni, hasonlóan az 1. szinthez, majd húzzuk bele a prefab téglákat. Rendezzük el őket a játéktérben tetszésünk szerint, akár további példányokat is hozzáadhatunk a Scene-hez. Az Inspector nézetben kössük össze a Ball-t a Paddle-al és a BottomCollider-t a LevelManager-el. Ha ezzel készen vagyunk, próbáljuk ki a játékunkat: annak teljeskörűen működnie kell. Látható tehát, hogy mennyire leegyszerűsödik egy szint hozzáadása a játékhoz prefab-ok segítségével!

3. Az előző lépésben az Inspector nézetben kézzel kellett összekötnünk a Ball-t a Paddle-al és a BottomCollider-t a LevelManager-el. Ezt programkódból is megtehetjük, ennek érdekében módosítsuk a Ball osztály szkriptjében a paddle láthatóságát public-ról private-re és a Start() metódusához adjuk hozzá a következő sort:

paddle = FindObjectOfType<Paddle>();

Hasonlóképpen, a BottomCollider szkriptben tegyük priváttá a levelManager változót és adjuk hozzá az alábbi Start() metódust:

void Start(){
   levelManager = FindObjectOfType<LevelManager>();
}

Ha ezeket megtettük, akkor új szint létrehozásakor még ezeket a "drótozásokat" sem kell megtennünk kézzel az Inspector nézetben.

4. Jelenleg ha nem kapjuk el az ütővel a labdát, akkor a Win scene-t töltjük be, de igazából ilyenkor nem nyerünk, hanem veszítünk, ezért szükségünk lesz egy scene-re, amely arról tájékoztat, hogy elvesztettük a játékot és lehetőséget biztosít annak újrakezdésére. Ehhez készítsünk egy másolatot a Win scene-ről és állítsuk át értelemszerűen a szövegeket. A BottomCollider szkript OnTriggerEnter2D() metódusát módosítsuk úgy, hogy a Lose scene kerüljön betöltésre a Win helyett. Végül adjuk hozzá a Lose scene-t (és ha még nem tettük meg, a Level_02 scene-t is) a Build Settings alatt található Scenes in Build listához.

5. Vegyünk fel a LevelManager osztályhoz egy új LoadNextLevel() metódust, amit majd akkor hívunk meg, ha a játéktérben nem maradt tégla, azaz teljesítettük a szintet és be kell töltenünk a következőt:

public void LoadNextLevel() {
   SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}

Egyelőre még nem tudjuk felismerni azt a helyzetet, amikor minden téglát leszedtünk, ezért ideiglenesen tételezzük fel, hogy amint eltaláunk egy téglát és az megsemmisül, a szintet teljesítettük és be kell töltenünk a következőt. Ez nyilván abban a szkriptben fog megtörténnii, ahol a labda és a tégla ütközése van lekezelve, azaz a Brick szkript OnCollisionEnter2D() metódusában, innét meghívhatjuk az új metódusunkat. Ehhez a Brick szkriptben fel kell vennünk egy levelManager privát változót

private LevelManager levelManager;

és a Start() metódusban a fentebb ismertetett módszerrel meg kell azt keresnünk:

levelManager = FindObjectOfType<LevelManager>();

Végül az OnCollisionEnter2D() -ben a Destroy() után meghívhatjuk az új metódusunkat: 

levelManager.LoadNextLevel();

5.11. Spritesheet-ek használata

Idővel a programjainkban egyre több kisebb-nagyobb sprite-ot, képet fogunk használni és ez egy szint felett érdemben kihathat a játék sebességére. Ennek a problémának a kezelésére találták ki a spritesheet-eket, amelyek lényegében több kisebb kép egy nagy képbe való összeillesztését jelenti.

A spritesheet-ek lényege érthető formában az alábbi videóban van összefoglalva:

task_green.png
task_green.png
task_green.png
task_green.png
task_green.png
 
 

 

A játékunkban a zöld és piros téglákat kétszer, illetve háromszor kell eltalálnunk ahhoz, hogy eltűnjenek. Jó lenne, ha valahogyan láthatnák azt, ha egy ilyen téglát már korábban eltaláltunk, ebből tudhatnánk, hogy már csak egyszer vagy kétszer kell azt ismételten eltalánunk ahhoz, hogy eltűnjön. Ennek egy módja lehet az, hogy a téglát másképpen, kvázi "töröttnek" rajzoljuk meg. Ehhez újabb spriteokat kell elkészítenünk és az assetekhez hozzáadnunk. Alternatív megoldásként használhatnuk spritesheet-eket is. Mi egy olyan spritesheet-et fogunk használni, amely tartalmazza az eredeti ép tégla, plusz egy kevésbé és egy jobban sérült változat képét. 

1. Töltsük le ezt a spritesheet-et és húzzuk be a projektünk Sprites asset mappájába. Állítsuk át a Pixels Per Unit tulajdonságot 128-ra, a Sprite Mode-t pedig Multiple-ra.

2. Nyissuk meg a Sprite Editor-t és daraboljuk fel a képet 5 darabra és nevezzük el a darabokat sorban így: 0 hit, left, right, 1 hit, 2 hit. Ügyeljünk arra, hogy az egész téglák mérete 128x41, a féltéglák mérete pedig 63x41 legyen. A pivot pontokat hagyjuk középen. 

3. A Paddle, 1-hit, 2-hit és 3-hit prefab-ok Sprite tulajdonságának válaszuk ki a 0 hit sprite-ot. Ezután a full_brick, left_brick és right_brick sprite-ok akár törölhetőek is az asset-ek közül, mivel helyettük a Bricks spritesheet-en található sprite-okat fogjuk használni.

4. A Brick objektumnak tudnia kell, hogy hogy nézzen ki, ha egyszer vagy kétszer eltalálják ezért el kell tárolnunk az objektum szintjén az 1 és 2 ütésnek megfelelő sprite-okat egy public Sprite[] hitSprites tömbben. Ezek után az Inspector nézerben beállíthatjuk a 2 hit és 3 hit prefabok számára a tömb méretét 1-re és 2-re és behúzhatjuk a megfelelő sprite-okat a tömbbe. Mostmár futásidőben a kód ha ütközést észlel, de még nem kell megsemmisitenie a téglát, akkor beállíthatja annak a megfelelő sprite-ot a timesHit változó értékének függvényében.. A Brick szkript teljes forrása így fog kinézni:

task_green.png
task_green.png
task_green.png
task_green.png

Két további kisebb változást is tartalmaz a fenti kód:

- A maxHits értékét a hitSprites tömb méretéből vesszük, azt már nem kell megadni az Inspector nézetben

- Mielőtt betöltenénk a tömbből egy sprite-tot, megnézzük, hogy az nem üres-e.

5.11.1. feladat

 

Gondoljuk végig, hogy mi kellene ahhoz, hogy egy olyan új téglatípust adhassunk a játékhoz, amely nem semmisül meg akárhányszor is eltalálják. Értelemszerűen egy szint akkor is teljesíthető lesz, ha maradnak még ilyen téglák a játékban. 

 

5.12. A szint teljesítésének felismerése

A játékunk még mindig nem ismeri fel, ha gy szintet sikeresen teljesítettünk, ezt fogjuk most megoldani, Egy szint akkor lesz sikeresen teljesítve, ha az összes téglát sikerült eltűntetnünk. Hogy ezt a helyzetet felismerhessük, minden pillanatban tudnunk kell, hogy hány tégla maradt a játéktérben.

1. Szükségünk lesz tehát a Brick szkriptben egy statikus brickCount számlálóra, ami egyel növekszik, ha a szint betöltése után egy tégla létrejön (tehát a Start() metódusban) és csökken egyel, amikor egy tégla megsemmisül (az OnCollisionEnter2D() metódusban). Ilyenkor rögtön megvizsgáljuk, hogy a brickCount értéke nulla-e és ha igen, megkérjük a LevelManager-t, hogy töltse be a következő szintet a levelManager.LoadNextLevel(); meghívásával.

2. A labda néha kerülhet olyan helyzetbe, hogy végtelenül pattog ugyanazon az útvonalod, így megreked a játék. Ezt elkerülendő a visszapattanásba vigyünk egy kis véletlenszerű elemet az alábbi kóddal, amit a Ball szkript OnCollisionEnter2D() metódusához kell adnunk:

Vector2 corr = new Vector2(Random.Range(-0.2f, 0.2f), Random.Range(-0.2f, 0.2f));

GetComponent<Rigidbody2D>().velocity += corr;

5.13. Hangok használata

A játékunkhoz már hozzáadtunk egy háttérzenét, most a labda pattanását is tegyük hallhatóvá. 

1. Keressünk egy alkalmas hangfájlt (példaként használhatjuk ezt), töltsük le és másoljuk be a Sounds asset-ek közé. Adjunk a Ball prefab-hoz egy Audio Source komponenst, az Audio Clip tulajdonságát állítsuk be a letöltött hangállományra és szedjük ki a pipát a Play On Awake mellől. 

2. A Ball szkripthez vegyünk fel egy új OnCollisionEnter2D() metódust, ebben játszuk le a hangállományt a következő paranccsal: 

GetComponent<AudioSource>().Play();

Egy if parancssal megakadályozhatjuk, hogy a hang rögtön a szint betöltésekor is lejátszásra kerüljön.

3. Ha szeretnénk, ha a tégla törése is hanghatással járna, akkor a fenti módszer nem fog működni, mert a tégla megsemmisül az ütközés pillanatában, így nem tudja lejátszani a hangeffektust. Ehelyett alkalmazzunk egy másik módszert. Válasszuk ki a hangállományt és adjuk az asset-ekhez. A Brick szkriptben vegyünk fel egy változót és az Inspector nézetben rendeljük hozzá a kívánt hangállományt:

public AudioClip crack;

Végül ütközéskor (az OnCollisionEnter2D() metódusban) hívjuk meg az AudioSource PlayClipAtPoint metódusát at alábbi módon:

AudioSource.PlayClipAtPoint(crack, transform.position);

Ha mindent jól csináltunk, ütközéskor a labda és a tégla hangja is hallható lesz.

 

5.14. Automatikus játék

A játékban nagyon könnyen megvalósíthatunk egy automatikus módot, ahol a gép mozgatja az ütőt és ezzel tulajdonképpen vágigjátsza a játék szintjeit. Ehhez módosítsuk a Paddle szkriptet az alábbiak szerint:

task_green.png
task_green.png
task_green.png
task_green.png
task_green.png
 
 
 

Ezután ha a Paddle objektum Auto Play tulajdonságát bekapcsoljuk az Inspector nézetben (vagy programból), az ütő magától fog mozogni.

5.15. Füst generálása a Particle System segítségével

Érdekes lehet egy olyan hatás hozzáadása a programunkhoz, amely némi "füstöt" generál akkor, amikor egy tégla eltűnik. Ehhez a Particle System-et hívjuk segítségül.

1. Hozzunk létre egy üres Smoke nevű GameObject-et és adjuk hozzá a Particle System komponenst. A Renderer » Material tulajdonságot állítsuk be Default - Particle-ra, a következő tulajdonságokat pedig tetszés szerint: Start Lifetime, Start Speed, Start Size és Emmission » Rate over Time. Csináljunk a Smoke objektumból prefab-ot, reseteljük a pozícióját, majd töröljük az eredeti objektumot.

2. A Brick szkriptben vegyünk fel egy publikus smoke változót, majd a téglák prefabjaihoz rendeljük hozzá a smoke prefab-unkat:

public GameObject smoke;

Ugyanebben a szkriptben a Destroy() meghívása elé szúrjuk be az alábbi sorokat:

 GameObject smokePuff = Instantiate(smoke, transform.position, Quaternion.identity) as GameObject;
ParticleSystem.MainModule psmm = smokePuff.GetComponent<ParticleSystem>().main;
psmm.startColor = GetComponent<SpriteRenderer>().color;

Az utolsó sor beállítja a füst színét a  tégla színére. A szkript működését ezúttal sem kell pontosan értenünk, elég, ha bemásoljuk a fenti sorokat a megfelelő helyre.

A projektünket közzé tehetjük a weben, ha készítünk egy WEbGL build-et és az eredményt zip-be tömörítve feltöltjük pl. a sharemygame.com-ra.

A projekt tömörített teljes forrása letölthető innét.

task_green.png
task_green.png