Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

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:

textúra lasera - Tvorba iOS hier vo Swift

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

Nepriateľské lasery vo Swift hre v SpriteKit - Tvorba iOS hier vo Swift

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

 

Predchádzajúci článok
Strieľanie rakiet a ďalšie časticové efekty vo SpriteKit
Všetky články v sekcii
Tvorba iOS hier vo Swift
Preskočiť článok
(neodporúčame)
Pridanie fyziky a detekcia kolízií vo SpriteKit
Článok pre vás napísal Filip Němeček
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje vývoji iOS aplikací (občas macOS)
Aktivity