80 % bodů zdarma na online výuku díky naší Letní akci!
Pouze tento týden sleva až 80 % na e-learning týkající se PHP

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()
}
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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

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:

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

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:

A výsledek:

Ž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čí.

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.


 

Stáhnout

Staženo 3x (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
Tvorba iOS her ve Swift
Článek pro vás napsal Filip Němeček
Avatar
Jak se ti líbí článek?
1 hlasů
Autor se věnuje vývoji iOS aplikací (občas macOS) či těch webových ve frameworku Django. Twitter: @nemecek_f | GitHub nemecek-filip - mrkněte na veřejné projekty
Aktivity (3)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!