5. diel - Nepriatelia strieľa späť a dokonca laserom vo SpriteKit
V predchádzajúcej lekcii, Strieľanie rakiet a ďalšie časticové efekty vo SpriteKit , sme vyzbrojili hráča raketami, ktorým sme pridali časticové efekty a ozvučenie. Hráč už je vyzbrojený a je jedine fér, aby nepriatelia mohli strieľať tiež. Aby sme nerobili znovu to isté, vyzbrojíme nepriateľov lasery. Pre zmenu budú strieľať len jednu strelu.
Trieda Enemy
Postup bude pomerne podobný ako v prípade hráčov, takže si v zložke
Models
vytvoríme triedu Enemy
. Nezabudnite pridať
import SpriteKit
.
Instanciace
Pretože konštruktory sú vo Swiftu celkom komplikované (pravidlá,
convenience init
, required init
a tak), vyriešime
vytváranie nových nepriateľov pomocou statickej metódy. Tá nám dovolí pri
vytváraní určiť, akú variantu nepriateľa chceme:
class Enemy: SKSpriteNode { static func create(variant: Int) -> Enemy { precondition(1...3 ~= variant, "Invalid enemy variant") let texture = SKTexture(imageNamed: "enemyBlack\(variant)") let enemy = Enemy(texture: texture, color: .clear, size: texture.size()) return enemy } override init(texture: SKTexture?, color: UIColor, size: CGSize) { super.init(texture: texture, color: .clear, size: texture?.size() ?? CGSize.zero) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
Ja mám tri typy nepriateľov, tak som rovno pridal možnosť určiť, aký sa má použiť.
Pretože je možné ako parameter zadať akékoľvek celé číslo, tak som
pridal kontrolu pomocou precondition
. Je to taká šikovná metóda
na overovanie vstupov. Ak podmienka neplatí, tak dôjde k vyvolaniu výnimky.
Swift ju veľa používa interne, napríklad keď použijete zlý index pre
prístup do poľa.
A tiež som ju použil ako takú zámienku pre predstavenie štýlového
operátora ~=
Skvele sa hodí pre prípad, keď chceme zistiť, či dané číslo spadá medzi
určité rozmedzie. Inak by sme potrebovali 2x if
a
&&
.
Samozrejme, keby sme mali komplexnejší systém nepriateľov,
tak by sme ich typy mohli vyjadriť pomocou enum
a následne im
nastaviť iné vlastnosti a pod. Pre naše potreby ale toto riešenie
dostačuje.
Laserová strela
Laser som použil tento:
Ako vždy je od skvelého Kenney.nl. Má verzia je otočená, aby zodpovedala smeru nepriateľov. Otočenie vášho lasera môžete vykonať skrze program Preview, ktorý je súčasťou MacOS.
Vytvorenie lasera
A ideme na metódu createLaserFire()
. Tá bude jednoduchšie,
pretože budeme strieľať iba jeden laser:
func createLaserFire(at point: CGPoint) -> SKNode { let laser = SKSpriteNode(imageNamed: "laser") laser.zPosition = -2 laser.position = CGPoint(x: point.x + size.width / 2, y: point.y) laser.alpha = 0 return laser }
Prečo je potreba point
ako parameter, keď u hráča a rakiet
sme ho nepotrebovali? Pretože nepriatelia sú súčasťou
enemyAnchor
a ich pozícia je vzhľadom k tejto kotve, takže sa
nedá použiť pre pridanie lasera do hernej scény. Tiež som urobil laser
zatiaľ neviditeľný, ďalej uvidíte prečo
Pohyb lasera
Pridáme si ešte akciu pre pohyb lasera:
static var laserMovement: SKAction { let randomWait = SKAction.wait(forDuration: Double.random(in: 0...2)) 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, fadeIn, move, remove]) }
Je trochu komplikovanejšie ako u hráča. Začíname náhodným čakaním,
aby nepriatelia nestrieľali rovnako a máme tu fadeIn()
akciu,
pretože inak by boli v scéne vidieť pripravenej laser strely. Respektíve
neboli, pretože im nastavujeme alpha
na 0. Zároveň sa jedná o
fajn ukážku sily SKAction
a ich reťazenie do sekvencií.
Toto čakanie by potenciálne mohlo byť problém, pretože sa nepriatelia stihnú po vytvorení pohnúť, tí naši sa ale nehýbu toľko, takže to nevadí.
Streľba
Presunieme sa do GameScene.swift
a začneme strieľať lasery.
Čo vlastne potrebujeme? Vlnu nepriateľov už máme, pokiaľ ale všetci naraz
vystrelí laser, tak náš hráč bude nadávať, že hra je moc ťažká a už
ju nezapne ... Zatiaľ tiež nemáme možnosť, ako sa v kóde k objektom
nepriateľov dostať.
Poľa nepriateľov
Vytvoríme si premennú, do ktorej všetkých nepriateľov uložíme:
var enemies = [Enemy]()
A teraz stačí pole naplniť pri vytváraní. V cykle v metóde
createEnemyWave()
najskôr upravíme prvý riadok v cykle, pretože
po použití copy()
musíme objekt pretypovať na
Enemy
a nie na SKSpriteNode
:
let newEnemy = enemyTemplate.copy() as! Enemy
A potom, pred pridaním nepriateľov do scény pomocou
addChild()
, doplníme pridania do poľa:
enemies.append(newEnemy)
Časovač
Podobne ako v prípade hráčov si vytvoríme Timer
:
var enemyFireTimer: Timer?
Pripravíme metódu, ktorú timer bude spúšťať:
@objc func enemyFireTimerTick() {
}
A nastavíme náš nový timer v didMove()
:
enemyFireTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(enemyFireTimerTick), userInfo: nil, repeats: true)
A zostáva vyriešiť, ako urobiť, aby vždy vystrelila len časť nepriateľov.
Celkom som si obľúbil špeciálne premenné, ktoré slúžia ako určitá
pravdepodobnosť. Napríklad šancu 1
z 10
môžeme
naprogramovať nasledovne:
var oneInTenChance: Bool { return Int.random(in: 1...10) < 2 }
A teraz implementácia enemyFireTimerTick()
:
@objc func enemyFireTimerTick() { for enemy in enemies { guard oneInTenChance else { continue } let laser = enemy.createLaserFire(at: enemy.convert(enemy.anchorPoint, to: self)) addChild(laser) laser.run(Enemy.laserMovement) } }
Prejdeme cyklom všetkých nepriateľov a skontrolujeme
oneInTenChance
. Ak náhoda dovolia, tak vytvoríme laserovú strelu
na pozíciu nepriateľa. Všimnite si metódy convert()
dostupné
na SKNode
. Vďaka nej môžeme anchorPoint
v
súradnicovom systéme enemyAnchor
previesť na súradnici v
scéne.
Samozrejme pravidlo pre strieľanie si môžete urobiť podľa seba. To už je potom otázka návrhu hrateľnosti. Trebárs môžete timer spúšťať menej často, ale zvýšiť pravdepodobnosť výstrelu.
Môžeme zapnúť a vyskúšať:
Ak by ste chceli "spravodlivú náhodnosti" alebo nejakú rozumnú distribúciu, máme k dispozícii prepracovanú knižnicu GameplayKit, ktorá ponúka naozaj hromadu možností. Lenže jej zakomponovanie by bolo celkom náročné. Ide skôr o to, aby ste vedeli, že niečo také existuje.
Zvuky laserov
Zvuky výstrelu lasera som použil z audio balíčka od Kenney.nl, kde ich je hneď deväť.
Trebárs päť si ich pridáme a budeme vyberať náhodne, nech stále neznie
rovnako. Bohužiaľ SpriteKit nevie prehrávať súbory .ogg
,
takže som zvuky musel previesť do .wav
.
Osobne používam utilitu ffmpeg, ktorú som nainštaloval cez brew. Potom môžete priamo cez Terminal prenášať audio a video súbory.
Prehranie zvukov
Najskôr si teda pripravíme pole SKAction
az neho potom
náhodne vyberieme, akú zvukovú akciu prehráme pri výstrele.
Najkratšia zápis môže vyzerať takto:
let laserSoundActions = (1...5).map({ SKAction.playSoundFileNamed("laser\($0)", waitForCompletion: false)})
Najskôr vytvoríme rozmedzí (čiže Range
) od 1
do 5
a potom pomocou funkcie map()
využijeme tieto
čísla k vytvoreniu názvov našich zvukových efektov. Výsledkom je
konštantná pole piatich SKAction
.
Ak sa vám tento zápis nepáči, chápem, že nie je pre každého, môžete
si treba pripraviť pole názvov vašich zvukových efektov a map()
volať nad ním.
A teraz už stačí náhodnú akciu spustiť, aby sme efekt počuli. Na
koniec tela cyklu v metóde enemyFireTimerTick()
si prijme tento
riadok:
run(laserSoundActions.randomElement()!)
Prečo výkričník? Pretože randomElement()
vracia optional,
pretože môže byť metóda zavolaná nad prázdnym poľom a potom nejde
vrátiť náhodný prvok. My ale máme konštantné pole, ktoré sme si sami
vytvorili, takže je 100% isté, že v ňom prvky sú
V budúcom pokračovaní, Pridanie fyziky a detekcia kolízií vo SpriteKit , sa dostaneme k fyzike. Tá nám totiž umožní detekovať kolízie a naše pracne pripravené rakety a lasery budú mať konečne nejaký úžitok.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 11x (953.92 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Swift