Lekce 11 - Nekonečné vlny nepřátel a jejich animace ve SpriteKit
V předchozí lekci, Poškození hráče, menu hry a restart ve SpriteKit, jsme si ukázali mimo jiné jak implementovat menu hry a přidali restart, pokud hráče nepřátelé zničí. Hráč ale pořád může dříve zničit vlnu nepřátel a potom již nerušeně pokračovat ve hře, protože se dále nic nestane.
K dokončení hry nám zbývá upravit, jak fungují vlny nepřátel, aby se
po zničení objevila nová. Připravíme si novou metodu
createEnemies()
, která se bude starat o vytvoření všech
nepřátel a také animaci jejich příletu na scénu.
Náhodné formace nepřátel
Aktuálně používáme metodu createEnemyWave()
, která
vytvoří vlnu nepřátel podle zadaného počtu. My bychom ovšem potřebovali,
aby se vlny od sebe nelišily jen počtem nepřátel, ale také formací, v
které letí.
createEnemyWave()
Nejprve upravíme createEnemyWave()
, aby brala v potaz řady
nepřátel. Přidáme tedy parametr row
typu Int
:
func createEnemyWave(enemyCount: Int, row: Int)
Po úpravě také smažeme volání metody v
didMove()
, ať na nás Xcode nekřičí, že nesedí parametry.
A uvnitř metody nad cyklem vypočítáme pozici y:
let yPosition: CGFloat = CGFloat(row) * 120
Tu potom nastavíme nepřátelům v těle cyklu:
newEnemy.position = CGPoint(x: xPosition, y: yPosition)
Na závěr přidáme náhodný výběr typu nepřátel upravením volání
Enemy.create()
na začátku metody:
let enemyTemplate = Enemy.create(variant: Int.random(in: 1...3))
createEnemies()
A teď se můžeme pustit do metody createEnemies()
:
func createEnemies() { let rows = Int.random(in: 2...3) for row in 1...rows { createEnemyWave(enemyCount: Int.random(in: 3...5), row: row) } }
Odměnou za docela složitou metodu createEnemyWave()
je její
jednoduché použití, když chceme podle náhody připravit dvě nebo tři
řady nepřátel a v každé mít v rozmezí od 3
do 5
nepřátelských lodí.
setupEnemyAnchor()
Ještě musíme upravit metodu setupEnemyAnchor()
, konkrétně
pozici, aby bylo místo na tři řady nepřátel:
enemyAnchor.position = CGPoint(x: 0, y: size.height - 550)
Můžeme vyzkoušet:
Každé zavolání createEnemies()
vytvoří náhodnou
nepřátelskou formaci
Generování nových nepřátel
Teď stačí přidat animaci příletu a generovat nové nepřátele, když hráč všechny zlikviduje.
isGameInProgress
Musíme hlavně vyřešit, aby nepřátelé nestříleli, pokud zrovna
animujeme jejich přílet. Mohli bychom si vytvořit další bool
proměnnou. Přehlednější a jednodušší bude ale upravit současnou
proměnnou isGameOver
, kterou přejmenujeme na obecnější
isGameInProgress
.
Ze začátku bude nastavena rovněž na false
a vždy ji
nastavíme na true
, když nepřátelé přilétnou. Na
false
ji opět nastavíme v případě, když hráč ztratí
všechny životy nebo zlikviduje všechny nepřátele. Začneme tedy s
přejmenováním:
var isGameInProgress = false
lives
Upravíme didSet
blok proměnné lives
:
if lives < 0 { isGameInProgress = false controller?.showGameOver() return }
didBegin()
Další úpravu je třeba provést na začátku metody
didBegin()
:
guard isGameInProgress else { return }
enemyFireTimerTick()
A konečně v metodě enemyFireTimerTick()
, kde na začátek
přidáme to samé:
guard isGameInProgress else { return }
To samé bychom mohli udělat v metodě playerFireTimerTick()
,
ale díky kontrole v didBegin()
jsou stejně rakety hráče
neškodné. Opět je to na vás.
enemyCount
Abychom mohli zjistit, jestli už byli zničeni všichni nepřátelé,
přidáme si vlastnost enemyCount
, která se jednoduše dotáže na
potomky enemyAnchor
:
var enemyCount: Int { return enemyAnchor.children.count }
missileHit()
Zbývá přidat kontrolu na konec metody missileHit()
. Zde ale
pozor. Nemůžeme se na konci jednoduše zeptat na enemyCount
,
protože nepřátele ničíme nejprve fadeOut
akcí, která
nějaký čas trvá. Na enemyCount
bychom se tedy zeptali dříve,
než dojde ke zničení nepřítele.
Zjistil jsem, že na tomto řádku:
let fadeOut = SKAction.fadeOut(withDuration: 0.2)
Jsem měl překlep a použil jsem akci SKAction.fadeIn()
s
opačným efektem, tak si to prosím opravte a omlouvám se za komplikace
Zpět k našemu problému. Vytvoříme si obecnou SKAction
,
která zkontroluje počet nepřátel:
let createNewEnemiesIfNeeded = SKAction.run { if self.enemyCount == 0 { self.isGameInProgress = false self.createEnemies() } }
A pak ji jen přidáme na konec existující sekvence:
let sequence = SKAction.sequence([fadeOut, SKAction.removeFromParent(), createNewEnemiesIfNeeded])
A máme nekonečné nepřátele. Zbývá jejich animace.
Animace vlny nepřátel
Upravíme metodu createEnemies()
, aby se nepřátelé prostě
neobjevili, ale místo toho přilétli. Na její začátek tedy přidáme
posunutí enemyAnchor
mimo viditelnou scénu a odstranění všech
akcí, protože pro nepřátele konfigurujeme nekonečný pohyb do stran. Takto
by nám akorát rozhazoval naše chystané animace.
enemyAnchor.position = CGPoint(x: 0, y: size.height + 700) enemyAnchor.removeAllActions()
Ještě nezapomeňte z metody didMove()
odstranit volání
startEnemyMovement()
. Stejně tak můžeme pročistit
setupEnemyAnchor()
a odstranit nastavení pozice:
enemyAnchor.position = CGPoint(x: 0, y: size.height - 550) // není již třeba
Posunutí pozice enemyAnchor
výše by nám mělo poskytnout
dost prostoru na vytvoření nepřátel, aniž by byli vidět.
Pod existujícím for
cyklem si připravíme animace a další
SKAction
:
let shrink = SKAction.scale(to: 0.7, duration: 0) let moveIntoView = SKAction.moveTo(y: size.height - 550, duration: 3) let resetSize = SKAction.scale(to: 1, duration: 3) let group = SKAction.group([moveIntoView, resetSize]) let startGame = SKAction.run { self.isGameInProgress = true self.startEnemyMovement() }
SKAction
je hodně, ale nejedná se o nic komplikovaného Nejdříve zmenšíme vytvořené
nepřátele a potom je zároveň pomocí SKAction.group
posuneme do
viditelné scény a zvětšíme na původní velikost.
Potom již jen přes SKAction.run
spustíme hru, včetně pohybu
nepřátel.
Můžeme vyzkoušet:
A tím je naše vesmírná střílečka Galaxy Invaders ve SpriteKit hotová. Děkuji za zájem!
V tutoriálu jsem se snažil ukázat co možná nejvíce technik a možností, které nám SpriteKit ke tvorbě 2D her pro iOS poskytuje. Ukázali jsme si základní práci s texturami a herními objekty, docela dost se věnovali částicovým efektům, zapracovali jsme kolize pomocí fyziky a mnoho dalšího.
Možná vylepšení
Do hry je toho možné samozřejmě ještě kupu přidat. A jestli se hrám plánujete věnovat, rozhodně bych doporučil zkusit si Galaxy Invaders co nejvíce rozšířit. Přeci jen experimentováním a úpravami se můžete mnoho naučit.
Například můžete přidat další zvukové efekty nebo třeba hudbu na
pozadí pomocí SKAudioNode
, kterou stačí jen vytvořit a přidat
do scény. O přehrávání audia se postará sama a funguje v nekonečné
smyčce.
Vlny nepřátel aktuálně generujeme, lepší by bylo nadefinovat je a nabídnout hráči, aby postupoval v úrovních. Podle dané úrovně můžete třeba i měnit, jak často nepřátelé střílí. Stejně tak by nebylo těžké přidat více typů pohybů nepřátel a třeba mezi nimi náhodně vybírat nebo zkusit nabídnout hráči souboj s jedním velkým nepřítelem, kterého nezničí jeden zásah, takže u něj bude třeba ukládat poškození a třeba i změnit jeho bojový arzenál.
Dále můžete přidat třeba power-upy, které hráč pomocí kolize sebere. Například rychlejší frekvenci střelby, doplnění životů nebo sebrání štítu, který balíček od Kenney.nl obsahuje.
A určitě sami vymyslíte spoustu dalších možností, jak hru vylepšit.
Nezapomeňte, že své výsledné výtvory můžete nahrát na ITnetwork a třeba ostatní inspirovat nebo jim ukázat, jak další rozšíření implementovat.
Pokud si nebudete vědět s něčím rady, určitě se nebojte napsat do komentářů, na zdejší fórum (klidně mě můžete v příspěvku označit, ať o něm vím) nebo do soukromých zpráv. Rád pomohu, pokud to bude v mých silách.
V následujícím kvízu, Kvíz - Tvorba iOS her ve SpriteKit, si vyzkoušíme nabyté zkušenosti z kurzu.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 17x (1.73 MB)
Aplikace je včetně zdrojových kódů v jazyce Swift