Lekce 10 - Poškození hráče, menu hry a restart ve SpriteKit
V předchozí lekci, Přidání parallax efektu a životů hráče ve SpriteKit, jsme implementovali životy hráče.
Dnes se budeme věnovat poškození lodi, hernímu menu a restartu hry.
Poškození hráče
Prvně si ukážeme, jak simulovat poškození.
Textury
Balíček textur od Kenney.nl obsahuje také varianty poškození pro
jednotlivé lodě hráče. Jedná se vlastně o obrázky, kterými překryjeme
hlavní texturu a tím bude poškození vidět. Další možností by bylo mít
kompletní obrázky poškozené lodi a měnit vlastnost texture
u
SKSpriteNode
.
Překryvné obrázky poškození lodě vypadají takto a na lodi budou indikovat kolik ji ještě zbývá životů:
applyDamage()
Ve třídě Player
si přidáme metodu
applyDamage()
. Ta bude jako parametr očekávat zbývající
životy, takže v našem případě buď 2
, 1
nebo
0
. Ještě před implementací si připravíme instanci
SKSpriteNode
pro první poškození:
private let damageNode = SKSpriteNode(imageNamed: "playerShip2_damage1")
A metodu implementujeme:
switch livesLeft { case 3: break case 2: damageNode.zPosition = 5 addChild(damageNode) case 1: damageNode.texture = SKTexture(imageNamed: "playerShip2_damage2") case 0: damageNode.texture = SKTexture(imageNamed: "playerShip2_damage3") default: assertionFailure("Invalid parameter") }
Při prvním zásahu přidáme poškození do scény, aby překrylo loď hráče a při dalších jen upravíme texturu poškození.
Větev default
jsme vyřešili s pomocí šikovné funkce
assertionFailure()
, abychom byli upozorněni v případě, že
odněkud pošleme neplatný parametr. case 3
je zde z důvodu, že
metodu budeme volat přes didSet
v GameScene
a na
začátku nastavujeme lives
na 3
, aby se propsal text
také do SKLabelNode
.
Společně s assert()
se jedná o skvělé
pomocníky při vývoji, můžete se pomocí nich totiž ujišťovat, že je
program v takovém stavu, v jakém očekáváte. assert()
totiž
jako první parametr očekává podmínku. V release buildu tyto funkce vůbec
nejsou, takže uživatelům aplikace/hra nespadne, pokud se stane něco
neočekávaného.
Zavolání metody
Upravíme tedy proměnnou lives
a při změně nastavíme
poškození hráči:
var lives: Int = 3 { didSet { livesLabel.text = "LIVES: \(lives)" player.applyDamage(livesLeft: lives) } }
A hru můžeme vyzkoušet:
Menu
Určitě budete souhlasit, že vrhnout hráče po zapnutí hry hned do boje není úplně ideální. Chtělo by to nějaké menu.
Menu bychom mohli zkusit udělat přes SpriteKit, ovšem nabízí se
jednodušší možnost. Sice jsme pracovali v posledních dílech výhradně v
GameScene
, ale na pozadí je UIViewController
, v
našem případě pojmenovaný GameViewController
, a tradiční
UIKit aplikace s Main.storyboard
.
Nový View Controller
Hlavní menu tedy vytvoříme jako obyčejnou UIKit obrazovku. Tato část se
netýká SpriteKit, takže ji projdeme v rychlosti. Práce s UIKit je
případně detailně popsána v kurzu Vyvíjíme iOS
aplikace ve Swift. Do Main.storyboard
přetáhneme nový
View Controller
a nastavíme ho jako initial (zaškrtnutím "Is
Initial View Controller", poznáte to také podle šipky ukazující na tuto
obrazovku).
Bude nám stačit pozadí (klasický UIImageView
), další
obrázek pro logo, které jsem připravil v Affinity Photo a konečně
tlačítko (UIButton
), ze kterého přetáhneme segue (za držení
klávesy Control, viz ukázka níže) rovnou na druhou obrazovku. Zde
jsem zvolil "Present Modally" a "Full Screen". Vlastně ani nepotřebujeme
třídu pro tuto obrazovku.
UIImageView
pro pozadí jsem pomocí AutoLayout nastavil
0
od všech hran (přímo k Superview, takže je nutné odškrtnout
margin a ignorovat Safe Area). Nezapomeňte nastavit Content Mode na "Aspect
Fill", aby obrázek zaplnil celé okno. UIImageView
s logem hry
stačí vycentrovat a nastavit mu třeba 50
bodů od horní hrany.
Tlačítko opět vycentrujeme a nastavíme mu vertikální odsazení od loga.
Tím je layout hotový.
Výsledek vypadá takto:
SpriteKit menu by fungovalo tak, že bychom např. vytvořili
novou scénu, která by měla tyto samé prvky (pomocí
SKSpriteNode
) a po detekci výběru play bychom pomocí SpriteKit
přepnuli na jinou scénu. Výhodou tohoto postupu by bylo, že bychom
například mohli dosáhnout efektu, že logo "odlétne" pryč a místo něj
přilétne první vlna nepřátel.
Vrácení hráče zpět do menu
Nyní potřebujeme způsob, jak hráče vrátit zpět do menu. Pro zjednodušení zobrazíme dialog jen když dojde k jeho zabití nepřáteli. Ve skutečné hře bychom měli pro tyto potřeby ještě další tlačítko.
Zobrazení dialogu můžeme vyřešit přes
UIAlertViewController
, ale ten nevytvoříme z herní scény.
Potřebujeme tedy vyřešit komunikaci mezi scénou a
GameViewController
. Nejjednodušší bude držet si referenci ve
scéně pro snadný přístup. Zde ale pozor, protože reference musí být
weak
. View controller si totiž již drží silnou referenci herní
scény, takže by jinak došlo k retain cycle.
weak var controller: GameViewController?
GameViewController
Přejdeme do GameViewController
a upravíme
viewDidLoad()
. Již nám nestačí získat jakoukoliv herní
scénu, takže přidáme přetypování na konec if let
a
nastavení reference:
if let scene = SKScene(fileNamed: "GameScene") as? GameScene { // Set the scale mode to scale to fit the window scene.scaleMode = .aspectFill scene.controller = self ..
A přidáme metodu pro zobrazení konce hry:
func showGameOver() { let ac = UIAlertController(title: "Game over!", message: nil, preferredStyle: .alert) ac.addAction(UIAlertAction(title: "Retry", style: .default, handler: { (_) in // doplníme })) ac.addAction(UIAlertAction(title: "Menu", style: .cancel, handler: { [weak self] (_) in self?.dismiss(animated: true, completion: nil) })) present(ac, animated: true) }
GameScene
Zbytek vyřešíme v GameScene
. Přidáme si proměnnou pro
hlídání, jestli náhodou hra neskončila:
var isGameOver = false
Pokud ano, tak deaktivujeme kolize, respektive reagování na ně hned na
začátku metody didBegin()
:
guard !isGameOver else { return }
A zbývá přidat logiku na začátek bloku didSet
proměnné
lives
:
if lives < 0 { isGameOver = true controller?.showGameOver() return }
Jakmile hráč ztratí všechny životy, zobrazíme náš dialog, který zatím funguje pouze pro vrácení se do menu.
Restart hry
Zbývá nám dořešit situaci, kdy uživatel zvolí "Retry" volbu v dialogu. V takovém případě chceme hru resetovat.
Mohlo by vás napadnout prostě vrátit zpět počet životů, znovu vytvořit nepřátele, odstranit poškození a tak podobně. To je ale zbytečně moc práce a riskujete, že na něco zapomenete. Mnohem lepší je prostě zobrazit novou instanci scény, SpriteKit nám k tomu poskytne i hezké animace.
Takže se vrátíme zpět do GameViewController
a vytvoříme
zde metodu restartGame()
. Dále si přidáme ještě metodu
createGameScene()
, ať neduplikujeme kód z
viewDidLoad()
.
createGameScene()
Kód metody je následující:
func createGameScene() -> GameScene? { if let scene = SKScene(fileNamed: "GameScene") as? GameScene { // Set the scale mode to scale to fit the window scene.scaleMode = .aspectFill scene.controller = self return scene } return nil }
Většina metody pochází z viewDidLoad()
, kam během chvilky
doplníme nové vytvoření scény za pomoci nově vytvořené metody
createGameScene()
.
Logika je na jednom místě a můžeme ji později rozšířit a nebát se,
že třeba restart hry bude fungovat jinak než její prvotní zapnutí.
Samozřejmě ještě upravíme vnitřek viewDidLoad()
:
if let scene = createGameScene() { // Present the scene view.presentScene(scene) }
restartGame()
A můžeme se vrátit k restartGame()
a vytvořit novou scénu s
animovaným přechodem:
func restartGame() { if let scene = createGameScene() { let transition = SKTransition.flipHorizontal(withDuration: 1) skView.presentScene(scene, transition: transition) } }
SKTransition
nabízí spoustu efektů, takže se rozhodně
nebojte experimentovat. Protože potřebujeme scénu prezentovat z
SKView
a ne z UIView
(který je ve skutečnosti
SKView
), připravíme si vlastnost pro snadné získání:
var skView: SKView { return view as! SKView }
Samozřejmě nesmíme zapomenout restartGame()
zavolat z
dialogu, na místě, kde jsem nechal komentář // doplníme
.
restartGame()
Můžete vyzkoušet, že volba "Retry" funguje korektně
Zbývá nám již jen poslední lekce, Nekonečné vlny nepřátel a jejich animace ve SpriteKit, kde upravíme vlny nepřátel a řekneme si, jak by se hra dala ještě vylepšit.
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 6x (1.07 MB)
Aplikace je včetně zdrojových kódů v jazyce Swift