IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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ů:

SpriteKit - Tvorba iOS her ve Swift
SpriteKit - Tvorba iOS her ve Swift
SpriteKit - Tvorba iOS her ve Swift

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:

SpriteKit - Tvorba iOS her ve Swift

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).

SpriteKit - Tvorba iOS her ve Swift

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.

Vytvoření Segue z tlačítka na druhou obrazovku - SpriteKit - Tvorba iOS her ve Swift

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:

Herní menu pro SpriteKit iOS hru ve Swift - SpriteKit - Tvorba iOS her ve Swift

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.

SpriteKit - Tvorba iOS her ve Swift

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

 

Předchozí článek
Přidání parallax efektu a životů hráče ve SpriteKit
Všechny články v sekci
SpriteKit - Tvorba iOS her ve Swift
Přeskočit článek
(nedoporučujeme)
Nekonečné vlny nepřátel a jejich animace ve SpriteKit
Článek pro vás napsal Filip Němeček
Avatar
Uživatelské hodnocení:
1 hlasů
Autor se věnuje vývoji iOS aplikací (občas macOS)
Aktivity