Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
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 9 - Přidání parallax efektu a životů hráče ve SpriteKit

V předchozí lekci, Další částicové efekty ve SpriteKit, jsme dokončili snad jednu z nejobsáhlejších částí naší iOS hry ve Swift - fyziku a kolize.

K dokončení Galaxy Invaders již zbývají jen drobnosti.

Střely zničených nepřátel

V první řadě vyřešíme problém laserové střely, kterou nepřítel vystřelí krátce před svým zničením. Taková střela se může objevit tam, kde již nepřítel není, což působí, jako by se tam najednou objevila sama. Střela totiž čeká neviditelná, díky čemuž nepřátelé nestřílí ve stejných intervalech.

Ideálně potřebujeme mechanismus, který střelu zruší v případě, že již má dojít k jejímu zviditelnění (skutečnému vystřelení) a daný nepřítel je v tu chvíli již zničený.

Uložení aktuální střely

Začneme přidáním vlastnosti currentShot třídě Enemy:

weak var currentShot: SKNode?

Ta v sobě bude dočasně držet aktuální střelu. Pro jistotu je weak, aby případně nebránila správné dealokaci.

Při vytvoření střely v metodě createLaserFire() ji nastavíme:

currentShot = laser

Úprava laserMovement

Ve třídě Enemy již zbývá jen úprava proměnné laserMovement, vracející sekvenci s pohybem nepřátelské střely. Tuto sekvenci potřebujeme předat instanci nepřítele, pro kterou střelu vytváříme, takže ji přeměníme na statickou funkci:

static func laserMovement(for enemy: Enemy) -> SKAction

A přidáme jednu SKAction. Upravené tělo funkce vypadá následovně:

let randomWait = SKAction.wait(forDuration: Double.random(in: 0...2))
let setCurrentShotNil = SKAction.run { [weak enemy] in
    enemy?.currentShot = nil
}
let fadeIn = SKAction.fadeIn(withDuration: 0.2)
let move = SKAction.moveBy(x: 0, y: -1500, duration: 2.5)
let remove = SKAction.removeFromParent()
return SKAction.sequence([randomWait, setCurrentShotNil, fadeIn, move, remove])

Přidali jsme akci setCurrentShotNil, kde díky SKAction.run můžeme provést jakýkoliv kus kódu. Zde nastavíme currentShot na nil. Tuto akci spustíme v sekvenci hned po náhodném čekání.

Pokud má zničený nepřítel nastavenou proměnnou currentShot, tak to tedy znamená, že střela stále neviditelně čeká a ještě nebyla na nil nastavena.

Zavolání metody

Teď musíme upravit, jak se metoda volá. Přejdeme tedy do GameScene a nejprve upravíme metodu enemyFireTimerTick(), konkrétně tento řádek:

laser.run(Enemy.laserMovement(for: child))

Potom stačí poslední úprava ve funkci missileHit(). Třeba hned za explozi přidáme novou if let konstrukci:

if let enemy = enemy as? Enemy {
    enemy.currentShot?.removeAllActions()
    enemy.currentShot?.removeFromParent()
}

Pokud střela nepřítele stále neviditelně čeká, odstraníme z ní všechny akce a celkově ji odebereme. Pokud již čekání proběhlo, tak dřívější akce setCurrentShotNil nastavila proměnnou na nil a nemůže se nám stát, že bychom omylem odebrali střelu, která již letí směrem k lodi hráče.

Bylo to sice komplikovanější, ale odstranili jsme problém a ukázali si další možnosti SKAction.

Parallax efekt

Tento efekt najdete ve spoustě 2D her, protože je jednoduchý a krásně vytváří efekt hloubky. Nejde přitom o nic komplexního. Základní varianta používá dvě nebo více nekonečných pozadí, která jsou průhledná a překrývají se. Pozadí se stále dokola pohybují, ale každé jinou rychlostí. Je to podobné, jako když jedete např. vlakem a díváte se ven z okénka. Sloupy kolem trati se míhají velmi rychle, vzdálenější stromy pomaleji a kopce v dálce se posouvají již jen velmi pomalu. Právě tímto vzniká efekt hloubky pozadí za hrou:

Efekt paralaxního pozadí v 2D hrách - SpriteKit - Tvorba iOS her ve Swift

My tedy vlastně potřebujeme zajistit dvě nekonečně scrollující pozadí, každé s jinou rychlostí. To základní s vesmírem, které zabírá celou herní obrazovku, již máme. Bude ho tedy stačit pouze rozpohybovat. Druhé si můžete vytvořit nebo použít mé pozadí s meteory níže.

První pozadí - Vesmír

Nekonečné scrollování může znít jako výzva, jedná se ale pouze o dvě textury, které se pohybují za sebou a vrací zpět na začátek. To je celé. Uvidíte, že pomocí SKAction to bude hotové raz dva.

Konfigurační metoda

Začneme s deklarací metody, která pozadí připraví:

func configureParallax(for imageName: String, duration: TimeInterval, zPosition: CGFloat) {
}

Tuto metodu pak jen zavoláme pro jednotlivá pozadí, abychom nemuseli programovat každé zvlášť. A jako první vytvoříme první objekt pozadí:

let firstNode = SKSpriteNode(imageNamed: imageName)
firstNode.zPosition = zPosition
firstNode.anchorPoint = .zero

Nastavujeme také anchorPoint pro snadnější výpočty. Potom již stačí pomocí kopírování vytvořit identické pozadí a přidat obě do scény. Obě pozadí, která na sebe navazují, budeme posouvat dolů a jakmile jedno vyjede z obrazovky, přemístíme jej nad to druhé. Tímto způsobem se budou stále vyměňovat a ve viditelné části obrazovky to bude vypadat, že je pozadí nekonečné.

let secondNode = firstNode.copy() as! SKSpriteNode
secondNode.position.y += secondNode.size.height - 1

addChild(firstNode)
addChild(secondNode)

Druhou texturu posuneme nahoru o její výšku a odečteme pixel, abychom náhodou neměli mezeru v našem efektu.

Posledním krokem je definice nekonečné sekvence SKAction a jejich spuštění pro obě textury pozadí:

let move = SKAction.moveBy(x: 0, y: -firstNode.size.height, duration: duration)
let reset = SKAction.moveBy(x: 0, y: firstNode.size.height, duration: 0)
let loop = SKAction.sequence([move, reset])
let forever = SKAction.repeatForever(loop)

firstNode.run(forever)
secondNode.run(forever)

didMove()

Nyní zbývá v didMove() zavolat nastavení parallax efektu a smazat starší createBackground():

configureParallax(for: "background", duration: 25, zPosition: -5)

A vyzkoušíme:

SpriteKit - Tvorba iOS her ve Swift

Abyste mohli názorně vidět, co se děje, nahradil jsem dočasně naše pozadí dvojicí různobarevného a zrychlil:

Paralaxní pozadí ve SpriteKit - SpriteKit - Tvorba iOS her ve Swift

Druhé pozadí - Meteory

Pro parallax samozřejmě potřebujeme alespoň dvě pozadí. Jelikož celou přípravu máme hotovou, bude přidání meteorů otázka pouze dalšího zavolání metody configureParallax():

configureParallax(for: "meteors", duration: 15, zPosition: -4)

Použitý obrázek je ke stažení níže:

SpriteKit - Tvorba iOS her ve Swift

A výsledek:

SpriteKit - Tvorba iOS her ve Swift

Životy hráče

Náš hráč je stále nesmrtelný. Tuto superschopnost mu nyní odebereme a bude se muset spolehnout na tradiční tři životy.

Aby hráč věděl, kolik životů mu zbývá, vypíšeme si je jako text. Životy bychom samozřejmě mohli reprezentovat třeba obrázky srdcí nebo miniaturou lodi hráče, takto si ale ukážeme práci s SKLabelNode, která slouží právě k zobrazování textu.

Definice labelu a životů

Nejdříve si v GameScene definujeme proměnnou pro SKLabelNode a rovnou také pro počet životů:

let livesLabel = SKLabelNode(fontNamed: "Verdana-Bold")
var lives: Int = 3 {
    didSet {
        livesLabel.text = "LIVES: \(lives)"
    }
}

Pomocí didSet() automaticky nastavíme text na aktuální počet životů.

Přidání do scény

Dále si připravíme metodu, která livesLabel nastaví a přidá do scény:

func setupLivesLabel() {
    livesLabel.verticalAlignmentMode = .top
    livesLabel.position = CGPoint(x: frame.midX, y: frame.maxY - 70)
    addChild(livesLabel)
    lives = 3
}

Nastavení verticalAlignmentMode určuje, od jakého okraje se počítá pozice na ose Y. Existuje ještě horizontalAlignmentMode, které to samé dělá pro osu X. Ve výchozím stavu jsou nastaveny na hodnotu .center. Takže náš label nastavíme k horní hraně a na horizontální střed.

Explicitně je tu ještě nastavení proměnné lives, aby došlo k aktivaci didSet() a korektně se nastavil text.

Zavolání metody

Zbývá metodu zavolat uvnitř didMove():

setupLivesLabel()

Poslední, co v této lekci provedeme, je snížení životů o jeden, pokud je hráč zasažen laserem. To uděláme v metodě laserHitPlayer():

lives -= 1

Korektnější by samozřejmě bylo mít životy ve třídě Player, ale pak bychom museli složitěji aktualizovat label. Pro naše potřeby současné řešení stačí.

SpriteKit - Tvorba iOS her ve Swift

V další lekci, Poškození hráče, menu hry a restart ve SpriteKit, přidáme poškození, konec hry a ukážeme si, jak vytvořit herní menu.


 

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

 

Předchozí článek
Další částicové efekty ve SpriteKit
Všechny články v sekci
SpriteKit - Tvorba iOS her ve Swift
Přeskočit článek
(nedoporučujeme)
Poškození hráče, menu hry a restart 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