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 18x (1.73 MB)
Aplikace je včetně zdrojových kódů v jazyce Swift

