Lekce 2 - Pozadí, ovládání hráče a částicové efekty ve SpriteKit
V předchozí lekci, Úvod do tvorby iOS her s frameworkem SpriteKit, jsme si představili SpriteKit včetně struktury projektu a nachystali textury pro vesmírnou střílečku.
Nyní se pustíme do budování naší hry.
Nakopírování textur
Jako první krok je třeba nakopírovat naše obrázky do
Assets.xcassets
v projektu a ideálně zvolit nějaké výstižné
a jednoduché názvy. My si zvolíme background
pro pozadí a
player
pro loď hráče.
Pozicování ve SpriteKit
Začneme s vytvořením pozadí. Než si pozadí přidáme do naší scény, potřebujeme si vysvětlit, jak funguje souřadnicový systém SpriteKit. Ve výchozím stavu totiž nezačíná v levém horním rohu jako UIKit.
Když si otevřete GameScene.sks
, tak napravo v Attributes
inspektoru můžete vidět informace o naší scéně. Nás zajímá položka
"Anchor". Je to vlastně taková virtuální kotva ve scéně, podle které se
budou umisťovat další prvky.
Ve výchozím stavu je nastavena na X: 0.5
a Y:
0.5
. To znamená střed scény. V praxi se používá buď toto
nastavení nebo X: 0.0
a Y: 0.0
, což znamená levý
dolní roh.
Rozhodnutí jaký Anchor zvolit je dost podstatné a nejde udělat
jednoznačné doporučení. Záleží totiž na tom, jak je vaše hra
koncipovaná. Pokud bychom tvořili hru, ve které bude obrazovka celá herní
aréna a hráč se nebude pohybovat mimo, tak je nejlogičtější ponechat
právě 0.5
;0.5
a vše nastavovat vzhledem ke středu.
Naše hra bude mít ve spodní části loď hráče, iluzi letu vesmírem a
nepřátele v horní části obrazovky. Z pohledu této hry dává smysl
použít Anchor 0.0
;0,0
.
Budu tedy používat toto pozicování. Pokud se vám lépe uvažuje a pracuje s jiným, tak ho samozřejmě používejte. Jen budete muset při každém pozicování přijít na souřadnice pro váš Anchor, aby byl výsledek stejný.
Protože je pozicování důležité, připravil jsem ještě ilustrační obrázek různého nastavení Anchor:
Pozadí
Přesuneme se do GameScene.swift
. Jak již víme z první lekce,
máme zde k dispozici metodu didMove()
, která je zavolána vždy
na začátku a můžeme v ní nastavit všechno ve scéně.
Podobně jako s viewDidLoad()
doporučuji
vytvářet pro nastavení jednotlivých věcí ve scéně oddělené metody a ty
poté volat v didMove()
, aby se její tělo četlo jako seznam
příkazů. Díky tomu mnohem snadněji zjistíte, co má vlastně metoda na
starost. Vytvoříme si tedy novou metodu createBackground()
, ve
které pozadí vytvoříme. Vlastně skoro jakýkoliv objekt ve SpriteKit má za
potomka třídu SKNode
, která slouží jako takový základní
stavební blok. My budeme často používat SKSpriteNode
, jenž je
specializovaná na zobrazování 2D objektů.
Začneme tedy jejím vytvořením a použijeme náš obrázek jako texturu:
let background = SKSpriteNode(imageNamed: "background")
Nyní nastavíme vlastnost zPosition
, což je klasická
z
souřadnice určující, v jakém pořadí jsou na sobě objekty
"naskládané". Pozadí bude logicky nejníže, takže mu dáme např.
-5
.
background.zPosition = -5
Zbývá nastavit pozici a přidat objekt do scény. Pozici musíme nastavit z
důvodu, že jsme změnili výchozí Anchor ze středové
(0.5
;0.5
) na levý dolní roh, ale pozice jakýchkoliv
dalších SKNode
je stále výchozí, tedy
(0.5
;0.5
). Pozici tedy posuneme o polovinu šířky a
o polovinu výšky scény a pomocí addChild()
přidáme pozadí
jako objekt scény:
background.position = CGPoint(x: size.width / 2, y: size.height / 2) addChild(background)
Nyní stačí createBackground()
zavolat v
didMove()
a hru již můžete zapnout. Uvidíte pozadí.
Hráč
Máme pozadí a podobným stylem přidáme do hry vesmírnou loď hráče.
Opět si tedy vytvoříme metodu setupPlayer()
, ovšem samotný
objekt inicializujeme na úrovni třídy, abychom k němu mohli
přistupovat:
let player = SKSpriteNode(imageNamed: "player")
A nyní již v těle metody zatím přidáme tyto řádky, které hráče přidají dolů doprostřed:
player.position = CGPoint(x: size.width / 2, y: 120) addChild(player)
Ovládání
S lodí hráče budeme horizontálně hýbat pomocí tažení prstu. To je
intuitivní a na implementaci jednoduchá metoda. Začneme v metodě
touchesMoved()
, kterou jsme sice při našem čistění smazali,
ale není problém ji pomocí nápovědy dostat zpět:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { }
Metoda je zavolána vždy, když hráč pohne prstem po displeji a dostaneme sadu dotyků. Zde je nejjednodušší vzít ten první a zjistit jeho umístění ve scéně:
guard let first = touches.first else { return } let touchPosition = first.location(in: self)
Potom již stačí jen upravit pozici hráče takto:
player.position.x = touchPosition.x
Protože nesaháme na souřadnici y
, tak se bude hráč
pohybovat jen horizontálně. Můžete hru zapnout a vyzkoušet.
Vyjetí z obrazovky a teleport
Jsou tu dva menší problémy. Hráč může loď dostat zhruba z poloviny mimo obrazovku, což není zrovna hezké. Kromě toho může využít teleportu, když se prstem nedotkne přímo lodi.
Vyjetí z obrazovky
První problém vyřeší guard
před nastavením nové pozice
hráče, prostě se zeptáme, jestli je nová souřadnice x
větší než šířka lodi a zároveň menší než
šířka scény - šířka lodi
:
guard touchPosition.x > player.size.width && touchPosition.x < size.width - player.size.width else { return }
Opět můžete vyzkoušet
Teleport
A nyní zamezení teleportu. Nejjednodušší bude vytvoření
bool
proměnné, které nám řekne, jestli se hráč dotkl lodi.
Jinak mu pohyb nedovolíme. Vytvoříme tedy proměnnou:
var shouldMovePlayer = false
A přesuneme se do metody touchesBegan()
. Zde se opět zeptáme
na první dotek a pomocí dostupných metod zjistíme, jestli se hráč dotkl
lodi. Celé to bude vypadat takto:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let first = touches.first else { return } let tapped = nodes(at: first.location(in: self)) shouldMovePlayer = tapped.contains(player) }
Metoda nodes(at: )
je velmi užitečná. Ze zadané pozice ve
scéně nám vrátí všechny objekty (tedy SKNode
), které se na
daném místě nacházejí.
Potom už stačí upravit touchesMoved()
a přidat na začátek
další guard
, kde ověříme přidanou bool
proměnnou:
guard shouldMovePlayer else { return }
A samozřejmě nesmíme zapomenout na situaci, kdy hráč přestane jezdit
prstem po displeji. V metodě touchesEnded()
nastavíme proměnnou
opět na false
:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { shouldMovePlayer = false }
Můžete vyzkoušet. Teď už jde posouvat loď jen při přímém dotyku.
Částicové efekty
Na závěr této lekce si ukážeme, jak hru oživit částicemi. Ty nám později poslouží k vytvoření explozí. Zatím je využijeme na simulování "prachu" ve vesmíru, abychom získali dojem, že se hráč skutečně pohybuje.
Částicové efekty mohou znít jako obtížná část vývoje her, ale SpriteKit nám práci s nimi podstatně ulehčuje. Primárně díky vizuálnímu editoru a připraveným šablonám.
Hvězdný prach
Přidejte si tedy do projektu nový soubor a jako typ vyberte "SpriteKit
Particle File" a jako šablonu "Snow". Jako název jsem zvolil
Space Dust
.
Na začátek vypadá výchozí sníh docela dobře. Padá směrem, který chceme, takže nás čekají spíše menší úpravy.
Nejdříve upravíme hodnotu "Position range" na 750
pro
x
(což je šířka scény) a 0
pro y
. To
znamená, že se částice budou objevovat se souřadnicí x
někde
v rozmezí 0
až 750
.
Dále jsem nastavil "Speed" na hodnoty 20
pro "Start" a
0
pro "Range". Tedy konstantní rychlost. Částice jsem rovněž
zmenšil nastavením "Scale" na 0.05
a "Range" na stejnou
hodnotu.
Počet se ovládá pomocí nastavení "Emitter" úplně nahoře, kde jsem pro
"Birthrate" nastavil 20
. Jako poslední jsem nastavil "Lifetime" na
20
, aby částice nezmizely, dokud jsou vidět na obrazovce.
Zde samozřejmě zas platí, že hodnoty výše jsou spíše doporučené. Zkuste si s nastavením pohrát a vytvořit si částice vlastní
Přidání částic do scény
Nyní již stačí přidat částice do scény. Opět začneme s metodou
createParticles()
. Pro částice máme speciální
SKEmitterNode
, kterou vytvoříme a předáme ji náš soubor:
func createParticles() { if let spaceDust = SKEmitterNode(fileNamed: "SpaceDust") { spaceDust.zPosition = -1 spaceDust.position = CGPoint(x: size.width / 2, y: size.height) addChild(spaceDust) } }
Je to podobné, jako u pozadí a hráče. Vytvoříme objekt, nastavíme
zPosition
a position
tak, aby částice začínaly
uprostřed horní hrany. Potom už jen stačí přidat do scény.
Nyní můžete hru vyzkoušet, bude o poznání živější.
advanceSimulationTime()
Máme tu ale problém, protože po spuštění efekt nevypadá dobře, jelikož částicím zabere, než zaplní celou obrazovku.
Naštěstí má SKEmitterNode
skvělou metodu, která tohle
řeší. Jmenuje se advanceSimulationTime()
a dovolí nám vlastně
"přetočit" čas simulace. Nastavil jsem hodnotu na 15
, těsně
před addChild(spaceDust)
a po spuštění výsledek vypadá mnohem
lépe.
Výsledek je velmi působivý:
Pokračovat budeme příště, v lekci Nepřátelé a jejich pohyb ve SpriteKit.
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 26x (119.84 kB)
Aplikace je včetně zdrojových kódů v jazyce Swift