6. Űrlovag játék

 

 

6.1. A játék célja

Az Űrlovag (Laser Defender) egy másik népszerű játék, a Space Invider reinkarnációja lesz, némi módosítással: nem fogunk használni védőpajzsokat. A játékos a hajóját jobbra-balra mozgathatja, és közben felfelé lőhet az ellenséges hajók irányába. Az ellenséges hajók is lőhetnek a játékosra, a játékosnak igyekeznie kell ezeket a lövéseket kikerülni.

A játék egyetlen végtelenített szintből fog állni: az ellenségek újjászületnek, amint lelőjük őket. Minden lelőtt ellenséges hajójért pontot kapunk, így a játék célja a minél magasabb pontszám elérése, mielőtt az ellenség megsemmisítené a játékost.

 

A játék fontosabb megvalósítandó jellemzői a következők lesznek:

  • a játékos hajóját lehessen jobra-balra mozgatni a játéktérben

  • a játékos hajója csupán egyféle fegyverrel lesz felszerelve

  • az ellenséges hajók alakzatban fognak mozogni

  • a hajók mozgását egy egyszerű animáció fogja kísérni

  • a lelőtt ellenséges hajók helyét újak veszik át, amelyek beállnak az alakzatba

  • A játéktér háttere is legyen animált, hogy a repülés benyomását keltse

  • A játék használjon hangeffektusokat a lövésekhez és, találatokhoz, illetve a megsemmisülések bekövetkeztekor, plusz legyen egy halk háttérzene

  • A játékos által elért pontszámot a játék számolja, minden ellenséges hajót ért találat 150 pontot ér.

Továbbfejlesztési lehetőségek

  • Fokozatosan erősödő, egyre nehezebben legyőzhető ellenfelek

  • Többféle fegyver, esetleg megszerezhető/válsztható fegyverek

  • Törmelékek szimulálása az ellenséges hajókat ért találat után

  • Sérthetetlenség, nagyobb tűzerő egy rövid időre

  • Jutalmak a megsemmisített ellenséges hajók után (vitalitás növekedés, plusz élet, extra fegyver).

A játék elkészítéséhez szükségünk lesz pár asset-re, ezeket tömörített formában innét tudjuk letölteni.

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

Ahogy a korábbi játékoknál is tettük, itt is azzal kezdünk, hogy kialakítjuk a játék menürendszerét és megvalósítjuk a képernyők közötti navigációt. 

1. Hozzunk létre egy új, LaserDefender nevű 2D-s Unity projektet és importáljuk a letöltött asset Bundles mappájában található Menus csomagot (ami az előző játékból származik).

2. A már rendelkezésre álló Start és Win scene-ek mellé szükség lesz egy harmadik, Game scene-re, hozzuk ezt létre és adjuk hozzá mindhárom scene-t a projekt Build Settings-éhez. Érjük el, hogy a Start scene Start gombjára a Game scene nyíljon meg.

6.3. A játékos hajója

1. A játékunkban fel fogunk használni egy szabadon letölthető, hasonló játékokhoz készített asset készletet, amit innét tölthetünk le közvetlenül, de megtalálható a fent említett asset csomagban is. Ebből húzzuk be az asset panelra például a playerShip1_blue.png 

képfájlt és győződjünk meg róla, hogy sprite-ként lett importálva.

2. Húzzuk a hajó sprite-ját a Game scene-be nézetbe, nevezzük át Player-re és adjunk hozzá egy PlayerController szkriptet az alábbi tartalommal.

        

 

 

 

 

 

 

 

 

 

  • A szkriptben a publikus speed változó a játékos hajójának mozgási sebességét fogja tárolni, a padding  pedig a margót, amennyivel a jobb, illetve bal képernyőszél előtt meg kell állnia a hajónak. A tényleges, xmin és xmax korlátokat a kamera ViewportToWorldPoint()metódusával számítjuk ki. Figyeljük meg, hogy ennek köszönhetően tetszőleges képernyőarány esetén is a megfelelő helyen lesz lekorlátozva a hajónk mozgása.

  • Az Update() metódusban lehetővé tesszük, hogy a hajót iránythassuk a jobbra és balra nyilakkal. Az Input.GetKey() függvény true-t add vissza, ha a paraméterként megadott billentyű lett lenyomva. A hajó pozícióját az x tengely mentén változtatjuk a sebesség változó értékével arányosan. Ezt az értéket meg kell szoroznunk a Time.deltaTime értékkel, hogy kiegyenlítsük a különböző hardverek teljesítménykülönbségét, azaz ugyanolyan gyorsak legyenek a mozgások a gyors és hardvereken. A gyakorlatban a Time.deltaTime a legutólsó frame renderelési idejét takarja. Bővebb magyarázat a deltaTime-ról az alábbi videóban látható:

img023 - 6.1 - 1.png
task_green.png
 
task_green.png
task_green.png
task_green.png
 

 

  • A Mathf.Clamp(x, a, b)függvény a paraméterül kapott x értéket adja vissza, ha x a és b közé esik, illetve a-t, ha x < a és b-t, ha x > b.

6.4. Az ellenséges hajók

1. Az asset csomagunkból húzzuk be az asset panelra az enemyBlack2.png képfájlt (vagy egy hasonlót) és győződjünk meg róla, hogy sprite-ként lett importálva. Húzzuk be a Hierarchy nézetbe és nevezzük át Enemy-re, majd húzzuk vissza a Prefabs mappába, hogy prefabbá váljon.

2. Az asset-jeinket ajánlott úgy rendszereznünk, hogy biztosított legyen az átláthatóság és az egyes elemek gyors megtalálása. Ilyen elrendezés lehet például, ha létrehozunk egy Entities mappát az Assets mappa alatt, majd az Entities mappán belül a Player és Enemy mappákat és minden olyan prefabot és egyéb assetet belehúzunk ezekbe, amelyek az adott entitáshoz tartoznak.

3. Az ellenséges hajókat reprezentáló GameObject-eket nem feltétlenül célszerű tervezési időben kézzel elkészíteni, szerencsésebb a játék indulásakor kódból létrehozni azokat a prefabunk új példányaiként. Ha már felvettük az Enemy GameObject-et a Hierarchy nézetbe, akkor töröljük azt onnan, majd hozzunk létre egy EnemyFormation nevű üres GameObject-et és hozzá egy EnemyCreator nevű szkript komponenst. Ebben vegyünk fel egy publikus enemyPrefab nevű, GameObject típusú változót és az Inspector nézetben kössük össze az Enemy prefabunkal. A példány létrehozását a Start() metódusban fogjuk végezni az Instantiate() metódus meghívásával: 

Instantiate(enemyPrefab, new Vector3(0, 0, 0), Quaternion.identity);

Az első paraméter a prefab, amelyből egy új példányt kell készíteni, a következő paraméter a pont, ahol az új példányt el kell helyezni és a harmadik paraméter a térbeli elforgatást határozza meg. A Quaternion.identity kifejezést használjuk, ha nem akarunk elforgatást.

A visszaadott objektumot eltárolhatjuk egy enemy nevű, GameObject típusú változóba és beállíthatjuk a szülőobjektumként az EnemyFormation-t annak érdekében, hogy az újonnan létrehozott ellenséges hajó a csoportot reprezentáló objektum alatt jelenjen meg.  módosított kód így fog kinézni:

GameObject enemy = Instantiate(enemyPrefab, new Vector3(0, 0, 0), 
    Quaternion.identity) as GameObject;
enemy.transform.parent = transform;

4. Az ellenséges hajókból egynél többre lesz szükségünk, hogy alakzatban támadhassanak. Az alakzatban a hajók pozíciókat fognak elfoglalni, ezeket a pozíciókat valahogyan ki kell jelölnünk még a tervezés ideje alatt. A legegyszerűbb, ha üres GameObject-eket használunk erre a célra, amelyeket az EnemyFormation fogja majd össze. Hozzunk tehát létre az EnemyFormation alatt egy üres GameObject-et Position névvel. Azt tapasztaljuk, hogy mivel üres GameObject-ről van szól, a Scene nézetben ezek a pozíciók nem lesznek megjelölve. A Unity fejlesztési időben úgynevezett gizmókkal tudja jelölni a GameObject-ek pozícióját. A gizmók a játék futtatása közben nem jelennek meg, a céljuk pusztán a játékfejlesztő segítése. Ezeket a gizmókat nekünk kell megrajzolnunk kódból az 

OnDrawGizmos() metódusban. Adjunk tehát a Position GameObject-hez egy azonos nevű szkriptet, majd abban töröljük az alapértelmezett Start() és Update() metódusokat és vegyünk fel egy OnDrawGizmos() metódust. Ennek a kódja egy sorból fog állni:

Gizmos.DrawWireSphere(transform.position, 0.5f);

Eredményként a Scene nézetben egy kör fogja jelölni a pozíciónk helyét. 

5. A Position GameObject-ből készítsünk prefabot és a Position prefabbal és szkripttel együtt húzzuk be azt egy újonnan az Entities mappa alatt létrehozott, EnemyFormation nevű mappába. Az így elkészített prefabból húzzunk be tetszőleges számút a Scene nézetbe és alakítsuk ki az ellenséges hajók támadó alakzatát, például az alábbiak szerint:

task_green.png
task_green.png
task_green.png
task_green.png
task_green.png
 
img024 - 6.4 - 1.png

 

Már csak annyi a dolgunk, hogy ezekbe a pozíciókba a játék indulásakor egy-egy hajót helyezzünk el. Ezt úgy érhetjük el, hogy az EnemyCreator nevű szkript Start() metódusát így írjuk át: 

Ha mindent jól csináltunk, akkor a játék indulásakor az alábbi formáció jelenik meg:

img025 - 6.4 - 2.png

 

6. A következő lépés az, hogy az ellenséges hajók támadását szimuláljuk. Ehhez a hajók formációját jobbra-balra kell mozgatnunk a játéktéren belül. Hogy ne csússzunk ki a játéktérből, ismernünk kell a formáció teljes méretét, ezt az EnemyFormation gizmójának megrajzolásával ábrázolhatjuk. Az EnemyCreator szkriptben vegyünk fel egy OnDrawGizmos() metódust az alábbi forrással:

Gizmos.DrawWireCube(transform.position, new Vector3(width, height));

A width és height változók értékét állítsuk be az Inspector nézetben úgy, hogy a kapott téglalap lefedje a teljes formációt. 

7. A következő lépés a formáció mozgásának megvalósítása. A teljes alakzatnak először jobbra kell mozognia, majd amikor elérjük a képernyő szélét, a mozgás irányának meg kell változnia az ellenkezőjére egészen addig, amíg a képernyő bal szélét el nem éri a formáció, majd az egész ismétlődik újra. Az EnemyCreator szkriptben vegyünk fel egy movingRight nevű változót, melynek az értékét true-ra fogjuk állítani, amikor jobbra mozog az alakzat és false-ra ellenkező esetben. Szükségünk lesz még egy publikus speed változóra, amely a mozgás sebességét fogja tárolni, ennek az alapértelmezett értékét állítsuk be 5-re. Végül szükség lesz a két szélső pozíció x koordinátájára, amelyeknél nem mehetünk tovább az adott irányba (xmin és xmax).

public float speed = 5f;
private bool movingRight = true;

private float xmax;
private float xmin;

Az xmin és xmax értékét a ViewportToWorldPoint() metódus segíségével tudjuk megkapni, amely a kamera látóterébe eső pontok számára a látótér koordináta-rendszerében megadott koordinátákból kiszámolja azok játéktérbeli koordinátáit. A mi esetünkben a látótér bal alsó (0, 0) és jobb alsó (1, 0) pontjainak a játéktérbeli x koordinátáit akarjuk kiszámolni. Ezt az EnemyCreator szkript Start() metódusához hozzáadott alábbi szkript végzi el:

Vector3 left = Camera.main.ViewportToWorldPoint(new Vector3(0, 0, 0));
Vector3 right = Camera.main.ViewportToWorldPoint(new Vector3(1, 0, 0));
xmax = right.x;
xmin = left.x;

Most hogy ismerjük a határokat, az Update() metódusban megvalósíthatjuk a formáció mozgatását a határok figyelembe vételével. Az EnemyCreator szkript így fog kinézni:

task_green.png
task_green.png

Látható, hogy a formáció mozgatásának megvalósítása nagyon hasonló ahhoz, ahogy a játékos hajójának a mozgatását oldottuk meg. Ha kipróbáljuk a játékunkat, akkor az ellenséges hajók szépen alakzatban fognak mozogni és a játékos hajóját is megfelelően tudjuk majd mozgatni.

6.5. Lövés funkció implementálása

1. Itt az ideje, hogy a játékos hajóját ártalmassá tegyük az ellenséges hajók számára. A játékos hajója a Space billentyű lenyomásakor egy lézer lövedéket fog kilőni az ellenségek felé. A prefab elkészítéséhez húzzuk be az asset panelra a laserBlue03.png képfájlt (vagy egy hasonlót) és győződjünk meg róla, hogy sprite-ként lett importálva. Húzzuk be a sprite-ot a Scene nézetbe, adjunk hozzá egy BoxCollider2D és egy RigidBody2D komponenst, ha szükséges, igazítsuk a BoxCollider méretét megfelelőre, jelöljük be az IsTrigger-t, állítsuk be a Gravity Scale tulajdonságot 0-ra, majd a létrejött GameObject-et a Hierarchy nézetből húzzuk vissza a Player assetjei közé, ezzel létrehozva a PlayerLaser prefab-ot. Az eredeti GameObject-et immár törölhetjük a Hierarchy nézetből, mivel nem lesz rá szükségünk.

2. A lövés úgy fog megvalósulni, hogy a Space lenyomásakor a PlayerLaser prefabból a PlayerController kódjában létrehozunk egy új példányt és adunk neki egy kezdősebességet. Első lépésben vegyünk fel a PlayerController szkriptben egy publikus laserPrefab nevű, GameObject típusú változót, amely a létrehozandó GameObject prefab-ját fogja megadni, majd az Inspector nézetben ebbe húzzuk bele a PlayerLaser prefab-ot. Szükségünk lesz még egy laserSpeed változóra, amely a lézerünk sebesságát fogja meghatározni. Ezután a kívánt új lézer példányt az Update() metódushoz adott következő kóddal lehet létre hozni:

 

if (Input.GetKeyDown(KeyCode.Space)) {
   GameObject laser = Instantiate(laserPrefab, transform.position, 
      Quaternion.identity) as GameObject;
   laser.GetComponent<Rigidbody2D>().velocity = 

     new Vector3(0, laserSpeed, 0);
}

Észrevehetjük, hogy ez a kód nagyon hasoló ahhoz, amivel az ellenséges hajókat hoztuk létre. A kamera háttérszínét állítsuk be feketére, hogy a létrehozott lézer objektumok jobban láthatók legyenek. A lézernyalábok immár szépen elindulnak felfelé, az ellenséges formáció irányába. Figyeljük meg, hogy a fenti kódban a GetKeyDown() metódust használtunk a GetKey() helyett, mivel az utóbbi folyamatosan meghívásra kerül, amíg a billentyű le van nyomva és ez folyamatos lövést eredményez, ami nem feltétlenül az elvárt működés.

3. Ha azt szeretnénk, hogy a lövés ne legyen folyamatos, de lenyomott billentyű esetén mégis ismétlődjön mondjuk 4 lövés/másodperces sebességgel, akkor a lövés kódját kiemelhetjük egy külön Fire() metódusba, amelyet ütemezett módon 0.25 másodperces gyakorisággal megh1vunk a Space billentyű lenyomásakor az InvokeRepeating() metódus segítségével, majd a billentyű felengedésekor töröljük az ütemezést a CancelInvove() metódussal.

task_green.png
 
task_green.png
task_green.png

Célszerű lehet az ismétlési rátát nem konkrét számként megadni, hanem felvenni egy publikus firingRate változót, így annak értékét az Inspector nézetből is be tudjuk majd állítani.

4. Játék közben a Hierarchy nézetben azt figyelhetjük meg, hogy a létrehozott PlayerLaser objektumok nem semmisülnek meg és a számuk gyorsan nő. A célunk az, hogy azok a lövedékek, amelyek elhagyják a játékteret, megsemmisüljenek. A játéktér elhagyását egy játéktér fölé pozicionált üres objektummal való ütközésként fogjuk felismerni. Hozzunk tehát létre egy Shredder nevű üres GameObject-et, adjunk hozzá egy BoxCollider2D komponenst, jelöljük be a Trigger tulajdonságát és pozícionáljuk a játéktér fölé úgy, hogy a játéktér teljes szélességét átfogja. Vegyünk fel a Shredder-hez egy azonos nevű szkriptet a következő egyszerű kóddal:

task_green.png

Ezzel elértük, hogy a PlayerLaser objektumok megsemmisüljenek, amint elérték a játéktér tetejét.

5. A következő lépésben azt kell megoldanunk, hogy ha találat ér egy ellenséges hajót, akkor annak csökkenjen az élete annyival, amekkora a hatékonysága az adott lövedéktípusnak (ez előrevetíti annak a lehetőségét, hogy többféle lövedék típust használjunk a játékunkban, melyek különböző mértékű sérülést okoznak az ellenséges hajóknak). Adjunk az Enemy prefabhoz hozzá egy BoxCollider2D és egy RigidBody2D komponenst, állítsuk be a Body Type tulajdonság értékét Kinematic-ra és jelöljük be a Freeze Rotation opciót. Ezután vegyünk fel a PlayerLaser prefabhoz egy azonos nevű szkriptet az alábbi forrással:

task_green.png

A kódban megadjuk, hogy mekkora sérülést okozhat a lövés az ellenséges hajónak, illetve létrehozunk egy publikus Hit() metódust, amit ha meghívnak, akkor a lövedék objektum megsemmisíti önmagát.

Az Enemy prefab-hoz szintén vegyünk fel egy azonos nevű szkriptet az alábbi kóddal:

Itt az ellenséges hajó életerejét 150-re állítjuk, ami azt jelenti, hogy legalább kétszer el kell találnunk a hajót ahhoz, hogy megsemmisítsük. Az OnTriggerEnter2D() metódus akkor fut le, ha egy lövedék az ellenséges hajóba ütközik. Ekkor a hajó élete annyival csökken, amekkora a lézer károkozó képessége. Ha ennek következtében az élet nulla alá csökken, akkor az ellenséges hajó megsemmisül a Destroy() metódus meghívásának köszönhetően.

6.6. Ellenséges tűzerő

Ebben a fejezetben azzal fokozzuk a játékélményt, hogy az ellenséges hajók képesek legyenek visszalőni ránk. 

1. Másoljuk le PlayerLaser prefab-ot EnemyLaser néven. Ha szeretnénk, lecserélhetjük az új prefab Sprite tulajdonságát egy zöld vagy piros színű lézer képére. Az Enemy szkriptben vegyünk fel egy enemyLaser nevű, GameObject típusú publikus változót és az Inspector nézetben húzzuk ebbe bele az EnemyLaser prefab-ot. Szintén az Enemy szkripthez adjuk hozzá az alábbi kódot:

task_green.png
 

Itt a Fire() metódus valósítja meg az ellenséges lövéseket: ahogy a játékos tüzelésénél is, itt is létre hozzuk a lézer egy példányát (kissé távolabb az ellenséges hajótól, hogy elkerüljük az "öngyilkosságot"), majd adunk neki egy a laserSpeed változó értéke által megadott kezdősebességet. A shotsPerSecond változó azt tartalmazza, hogy másodpercenként hányszor kell egy ellenséges hajónak lőnie. Ezzel tudjuk a játék nehézségét belőni.

2. Adjunk hozzá a Player objektumhoz egy PoligonCollider2D komponenst, ez biztosítja majd, hogy az ellenséges találatokat érzékelni tudjuk. Ez a komponens pontosabb üztközésdetektálást biztosít, mint a sima BoxCollider2D, cserébe viszont kissé költségesebb (értsd: lassabb) a futása, de mivel csak egy játékosunk van, ez elfogadható. Jelöljük be az IsTrigger opciót. Adjunk továbbá a Player objektumunkhoz egy RigidBody2D komponenst, állítsuk a típusát Kinematic-ra és jelöljük be a Freeze Rotation opciót. Másoljuk be az Enemy szkript OnTriggerEnter2D() metódusát a PlayerController szkriptbe és vegyük fel a hiányzó publikus float típusú, health nevű változót 250-es kezdőértékkel. Ha mindent jól csináltunk, akkor a játék elindítása után három ellenséges találat eredményeként a játékos hajója megsemmisül.

Végül ahhoz, hogy az ellenség lézer objektumai megsemmisüljenek, amikor elérik a képernyő alját, egyszerűen másoljuk le a Shredder GameObject-et és a másolatot pozicionáljuk a játéktér alá.

3. Gyakran probléma, hogy hogyan tartsuk kézben, hogy mi mivel ütközhet. Fentebb az 1. lépésben az ellenséges lézer egy példányát a hajótól kissé távolabb kellett létrehoznunk, hogy ne semmisítse meg saját magát. Azt is tapasztalhatjuk, hogy a lövedékek ütközhetnek egymással. Jobb lenne, ha meg tudnánk azt adni általános módon, hogy milyen ütközések lehetségesek, és melyek nem. Ezt a rétegek (layer) segítségével tudjuk megtenni. 

Minden GameObject számára az Inspector nézet jobb felső sarkában beállíthatjuk, hogy melyik rétegen helyezkedik el. A rétegeket a Unity elsősorban arra használja, hogy a játéktér csak bizonyos részét használja fel a kamera képének kiszámításához, de az ütközések menedzseléséhez is használatosak. 8 db beépített réteget kezel a Unity, de ezen felül 24 felhasználói réteget is létre hozhatunk.

Hozzuk létre a következő rétegeket és helyezzük el rajtuk a megfelelő prefabokat: Enemy, EnemyLaser, Player, PlayerLaser. Az Edit >> Project Settings >> Physics 2D menűpont alatt beállíthatjuk a réteg-ütközés mátrixot (Layer Collisison Matrix) az alábbiak szerint:

task_green.png
task_green.png
img026 - 6.6 - 1.png

6.7. A lelőtt ellenséges hajók pótlása

A játékuk addig tart, amíg a hajónk élete el nem fogy. Ha a játékos lelövi az ellenséges formáció összes hajóját, akkor azokat új hajókkal kell pótolnunk.

1. Első lépésben fel kell ismernünk azt a helyzetet, amikor minden ellenséges hajó le lett lőve. Ezt abból vehetjük észre, hogy az EnemyFormation egyetlen Position típusú gyereke sem tartalmaz Enemy típusú (vagy bármilyen) gyermeket. Az EnemyCreator szkriptben hozzunk létre egy új AllMembersDead() nevű metódust az alábbi egyszerű kóddal:

task_green.png
 

2. Ha ez az esemény bekövetkezik, akkor ismét létre kell hoznunk az ellenséges formáció minden pozíciójában egy ellenséges hajót. Az ezt végző kód már rendelkezésre áll a Start() függvényben, most másoljuk be azt egy saját CreateEnemies() függvénybe és a Start() függvényben használjuk ezt az ellenséges hajók létrehozására.

task_green.png
task_green.png

3. Végül az Update() függvény végéhez adjuk hozzá azt az egyszerű kódot, amely detektálja, ha minden hajó megsemmisült és újra létrehozza azokat:

4. Jelenleg az utolsó ellenséges hajó lelövésekor az összes hajót egyszerre hozzuk létre. Ez nem túl látványos, szerencsésebb lenne, ha az egyes hajók egyesével, némi időközzel regenerálódnának. Ahhoz, hogy tudjuk, hová kell létrehozni a következő hajót, hozzunk létre az AllMembersDead() metódus logikájához hasonló, NextFreePosition() metódust az alábbi forráskóddal:

task_green.png

Vegyünk fel egy publikus creationDelay float típusú változót 0.5f kezdőértékkel és a CreateEnemies() kódját cseréljük le a következőre:

6.8. Pozíció animálása

Az ellenséges hajók pótlását látványosabbá tehetjük azzal, ha nem egyszerűen megjelennek, hanem egy animáció kíséretében "berepülnek" a helyükre.

1. Ahhoz, hogy animálni tudjuk az ellenséges hajót, szükségünk lesz egyre a játéktérben, de jelenleg nincs ott egy sem, mert futásidőben hozzuk őket létre. Húzzunk tehát egy Enemy prefab-ot a játéktérbe és a Hierarchy nézetben húzzuk egy pozíció alá azért, hogy a koordinátái a pozícióhoz képest relatívak legyenek és reseteljük a pozícióját.

2. Szükségünk lesz továbbá az Animation ablakra, amiben az animációkkal dolgozhatunk, ezt jelenítsük meg a Console tab mellett a lokális Add Tab menüből, vagy a Window főmenüből kiválasztva azt. Kattintsunk a Create gombra ahhoz, hogy az Enemy GameObject-hez hozzáadjunk egy Animator komponenst. A Unity rákérdez, hogy milyen néven mentse le az animáció fájlt, legyen ez Appear.anim. A megjelenő ablak kissé bonyolultnak tűnhet, de a feladatunkat egyszerűen el tudjuk majd végezni. A projekt nézetben az Enemy mappában megjelenik két új típusú objektum is, az Appear Animation és az Enemy Controller. Ha ez utóbbira duplán kattintunk, akkor megjelenik egy további új, eddig nem látott Animator nézet. Az Appear animáció Loop Time tulajdonságát állítsuk FALSE-ra a pipa törlésével az Inspector nézetben. Mindezen változtatás-okat alkalmazzuk a prefab-ra.

3. Az Animation nézetben ha az Add Property gombra kattintunk, akkor kiválaszthatjuk, hogy az objektum melyik tulajdonságát akarjuk az idő függvényében megváltoztatni, azaz animálni. Gyakorlatilag az Inspector nézetben megjelenő összes tulajdonság animálható. Az Animation nézet jobb oldalán megjelenik az idővonal (timeline). Az idővonalon kulcsképkockákat (key frame) helyezhetünk el és ezekben megadhatjuk az animált tulajdonság abban a pillanatban érvényes értékét. A kulcsképkockák közé eső minden pillanatra a Unity kiszámolja helyettünk az animált tulajdonság értékét. A játékunkban az objektum pozícióját, elfordulását és a méretét is megváltoztathatjuk, hogy a hajó "beúszását" szimuláljuk. Figyeljük meg, hogy az animáció lejátszásakor az animált tulajdonságok háttérszíne eltérő lesz az Inspector nézetben. Animáció közben az idővonalon egy fehér függőleges vonal jelöli a pillanatnyi helyzetet.

Az animációt úgy tervezzük meg, hogy a hajók ne kerüljenek a Shredder objektumokkal átfedésbe, mivel ha ez bekövetkezne, azok megsemmisítenék az ellenséges hajó új példányát. Ha készen vagyunk az animációval, alkalmazzuk a változtatásokat a prefab-ra és töröljük a pluszban felvett példányt.

6.9. Repülés a csillagtérben

A játékélmény fokozásának érdekében valósítsunk meg egy olyan háttérhatást, amely a csillagtérbeli haladás érzetét kelti. Ehhez a Unity sokoldalú eszközét, a Particle System-et hívjuk segítségül. 

1. A Hierarchy nézetben a jobb egérkattintásra megjelenő lokális menüből válasszuk ki az Effects >> Particle System pontot és a megjelenő objektumot nevezzük el Starfield1-nek. Ez fogja szimulálni a csillagok mozgását. Állítsuk be a Shape tulajdonság értékét Box-ra, a méretét (Scale) pedig akkorára, hogy vízszintesen kitöltse a játékteret és húzzuk a játéktér fölé. A Start Speed értékét állítsuk 1-re, a Rotation.X-et 90-re, a Max. Particle Size-t pedig 0.006-ra.. Annak érdekében, hogy a "csillagok" az űrhajók mögé kerüljenek, állítsuk be a Transform.Z értékét 5-re. Végül pipáljuk ki a Prewarm opciót, hogy ne üres képernyővel induljunk, illetve állítsuk be a nekünk tetsző értéket az Emission >> Rate over Time tulajdonságban.

2. A játékunk egész jól néz ki a csillagtér hozzáadása után, de még mindig kissé "lapos", nincs meg a térérzet. Hogy ezen javítsunk, bevezetünk egy úgynevezett paralax hatást, amely szerint a távolabbi objektumok mozgása lassabnak tűnik. Másoljuk le a Starfield1 objektumot Starfield2 néven és módosítsuk a Start Speed értékét 0.5-re, illetve a szín alfacsatornájának értékét csökkentsük, hogy a távolabbi objektumok fakóbbnak tűnjenek. Ha túl hamar tűnnének el a csillagok, növeljük meg a Start Lifetime és Duration tulajdonságok értékeit. Ha mindent jól csináltunk, az alábbi ábrához hasonlóan néz ki most a játékunk. Érdemes tovább kísérletezni a Particle System beállításaival, például a Shape-et lecserélhetjük Cone-ra, vagy más alakzatra, az eredmény igen látványos is lehet.

 
task_green.png
task_green.png
task_green.png
 
task_green.png
task_green.png
img027 - 6.9 - 1.png

6.1o. Pontszám megjelenítése

A következő feladatunk a pontszámok követése és megjelenítése. 

1. Hozzunk létre a Hierarchy nézetben egy új UI >> Text objektumot Score néven és pozicionáljuk a Canvas jobb felső sarkába. Töltsük le a Batman Forever fontot, húzzuk be a Fonts mappánkba és állítsuk be a Score betütípusának. A betűszínt is megváltoztathatjuk, ha kedvünk úgy tartja.

2. A Score objektumhoz vegyünk fel egy azonos nevű szkriptet az alábbi kóddal:

 
task_green.png
task_green.png

3. Az Enemy szkripthez adjunk hozzá az alábbi kódot:

task_green.png

Végül szintén az Enemy szkriptben a Destroy() meghívása után frissítsük a pontszámot az alábbi metódushívással:

6.11. Hangeffektusok hozzáadása a játékhoz

Az utolsó simítások egyikeként adjunk hozzá hangokat a játékuknhoz. Három eseményhez fogunk hangot társtani:

  • a játékos lövése

  • az ellenséges hajó lövése

  • az ellenséges hajó megsemmisülése.

1. A letöltött assetek között találunk egy SFX mappát, abból az .ogg kiterjesztésű fájlokat ​húzzuk be a projekt Sounds mappájába.

2. Vegyünk fel a PlayerController szkriptben egy AudioClip típusú, fireSound nevű puiblikus változót, Inspector-ban húzzuk bele azt a hangfájlt, amelyet akkor szeretnénk lejátszani, amikor a hajónk az ellenségre lő, majd az alábbi módon hívjuk meg a Fire() metódusból a hangfájl lejátszását:

task_green.png
task_green.png
 

3. Hasonló módon az ellenség lövéséhez és a megsemmisüléséhez is vegyünk fel egy-egy AudioClip változót az Enemy szkriptben és hívjuk meg a hangok lejátszását végző eljárást a Fire() és OnTriggerEnter2D() metódusok megfelelő pontjain:

task_green.png

3. Hasonló módon az ellenség lövéséhez és a megsemmisüléséhez is vegyünk fel egy-egy AudioClip változót az Enemy szkriptben és hívjuk meg a hangok lejátszását végző eljárást a Fire() és OnTriggerEnter2D() metódusok megfelelő pontjain:

6.12. A játék menüjének finomhangolása

1. A Start scene-en cseréljük le a Title és Subtitle szövegét, színét, és karakterkészletét a megfelelőre. Szintén cseréljük le a gombok Highlighted Color tulajdonságát.

2. A Win scene-ben szintén végezzük el a szükséges változtatásokat, és adjunk hozzá két Text objektumot, amiben az elért pontszámot jelenítjük meg:

task_green.png
task_green.png
 
img028 - 6.11 - 1.png

3. A Game scene-ben húzzuk a LevelManager prefab-ot a Hierarchy nézetbe, majd a PlayerController szkript OnTriggerEnter2D() metódusához adjuk hozzá az alábbi kódot, amely a Win scene-re vált akkor, amikor elvesztettük az életünket.

task_green.png

4. Már csak azt kell megoldanunk, hogy a Win scene-re váltáskor megjelenjen az elért pontszám. Ezzez a Score szkriptben a score változóhoz és a ResetScore() metódushoz adjuk hozzá a static kulcsszót és a ResetScore() metódusból töröljük a myText értékét frissítő második sort.

Ezután a Win scene Score objektumához vegyünk fel egy új, DisplayScrore szkriptet az alábbi kóddal:

task_green.png

5. Ha szeretnénk, hogy a Start és a Win scene-eken is érvényesüljön a csillagközi haladás hatása, akkor egyszerűen másoljuk át a Starfield1 és Startfield2 objektumokat ezekre a scene-ekre.

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

Az előző játékból megmaradt a háttérzenénk, de az nem igazán illik ehhez a játékhoz, ezért lecseréljük azt, de ezúttal minden scene-nek saját zenéje lesz.

1. Töröljük a MusicPlayer AudioClip tulajdonságát. Hozzunk létre egy Music mappát a Sounds mappa alatt és húzzuk bele ebbe a mappába a letöltött assetek azonos nevű mappájában található négy hangfájlt.  

2. Cseréljük le a MusicPlayer szkript forrását az alábbival:

task_green.png
task_green.png
task_green.png
 

Ebben felveszünk egy-egy AudioClip típusú változót a scene-ekhez tartozó zeneállományok eltárolásához, majd az OnLevelWasLoaded() eseményben (sajnos ez már elavult esemény), amely egy szint betöltésekor következik be, elindítjuk a megfelelő zene lejátszását. 

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.