První novoroční výprodej Java týden
Hledáš brigádu v IT, která bude 100 % home office a 100 % flexibilní? Pak napiš na: redakce [zavináč] itnetwork.cz pro více info!
80 % bodů zdarma díky akci Black Friday! Tento týden rovněž sleva na e-learning Java až 80 %

Lekce 7 - Dokončení kolizí ve SpriteKit

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Přidání fyziky a detekce kolizí ve SpriteKit, jsme skončili u metody didBegin(), která nás informuje o kolizi mezi objekty, pro které jsme notifikace zapnuli. Dnes vše zprovozníme, takže hráč bude moci konečně ničit nepřátele.

Zjištění kolidujícího objektu

Od SpriteKit dostaneme v metodě didBegin() parametr contact typu SKPhysicsContact, který nás informuje o tom, jaké dva objekty kolidovaly. Tradiční implementace této metody v první řadě vytáhne oba SKNode objekty pomocí guard:

guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }

Díky tomu víme, že můžeme kolizi zpracovat, protože máme oba objekty. Jenže, co z toho je raketa? Laser? Loď hráče?

Názvy objektů

Všechny SKNode mají vlastnost name, která nám v tomto pomůže. Vrátíme se tedy nejdříve do třídy Player a v init() nastavíme vlastnost name:

name = "player"

Podobně si nastavíme rakety:

let missile1 = SKSpriteNode(imageNamed: "playerMissile")
missile1.name = "missile"
let missile2 = missile1.copy() as! SKSpriteNode

Využíváme kopírování, takže jméno stačí nastavit pouze jednou.

Naše hra má dost jednoduché kolize a pokud bychom zjistili, že jeden objekt je loď hráče, tak druhý musí být logicky laser. Přesto doporučuji pojmenovat si poctivě všechny objekty, ať je kód ve výsledku čitelnější.

Zbývá nastavit jména ve třídě Enemy:

enemy.name = "enemy"

A laser:

laser.name = "laser"

didBegin()

Po krátké odbočce se můžeme vrátit do metody didBegin(). Zpracování kolize rozdělíme a nejdříve vyřešíme situaci, kdy raketa trefí nepřítele.

Naši partneři možná hledají právě tebe!

Pro přehlednost si připravíme metodu, která se postará právě o tuto situaci. V didBegin() ji tedy stačí pouze správně poslat oba objekty a bod, kde došlo ke kontaktu:

func missileHit(_ missile: SKNode, enemy: SKNode, at point: CGPoint) {
}

A zjištění, jaký z objektů je raketa a jaký nepřítel, vypadá pak následovně:

if nodeA.name == "missile" || nodeB.name == "missile" {
    if nodeA.name == "enemy" {
        missileHit(nodeB, enemy: nodeA, at: contact.contactPoint)
    } else {
        missileHit(nodeA, enemy: nodeB, at: contact.contactPoint)
    }
}

Zásah

Pojďme na kolizi nějak reagovat. V metodě missileHit() pro začátek při kolizi odstraníme oba objekty ze scény:

missile.removeFromParent()
enemy.removeFromParent()

Pole enemies

Jenže nepřátele máme uložené také v poli enemies, které slouží pro snadný přístup k nim, když chceme, aby stříleli. Máme na výběr, buď se starat o to, abychom zasažené nepřátele odstranili také z pole, nebo to vymyslet lépe, aby pole nebylo potřeba.

GameScene nabízí vlastnost children obsahující všechny objekty ve scéně. My ale používáme enemyAnchor pro snadnou animaci pohybu nepřátel, takže už je vlastně v jedné kolekci máme.

Jednoduše tedy proměnnou enemies smažeme. Dále je třeba upravit createEnemyWave() a odstranit přidávání nových nepřátel do již smazaného pole. Poslední nutnou úpravou je metoda enemyFireTimerTick().

Místo pole enemies budeme přes for cyklus iterovat enemyAnchor.children, který ale vrací pole typu [SKNode]. Je hromada způsobů, jak se s tímto vypořádat. Mohli bychom třeba přetypování provádět přes guard nebo if let v těle cyklu.

My ale využijeme silné možnosti, tzv. pattern matching, pomocí case let zápisu. Rovnou si jej ukážeme celý:

for case let child as Enemy in enemyAnchor.children

Jestli vás for case let za sebou vyděsilo, ani se nevidím. Poprvé jsem na tento zápis koukal dost nevěřícně. Postará se o to, aby lokální proměnná child byla typu Enemy a my s ní mohli pracovat jako dříve. Samozřejmě je nutné přejmenovat dřívější lokální proměnnou enemy v těle cyklu na child.

Pomocí case let můžete ve for cyklu jednoduše rozbalovat Optional hodnoty, stačilo by místo child napsat child?. Pokud bychom pracovali s polem Optional hodnot, v těle cyklu by již Optional nebylo. Stejně tak se case let dá využít v konstrukci switch.

Hru můžeme zapnout a přesvědčit se, že rakety konečně likvidují protivníky:

Kolize raket s protivníky v iOS hře ve Swift

Oprava kolizí

Hádám, že jste si všimli, že kolize fungují divně při zásahu rakety. Někdy dokonce jakoby raketa ignorovala nepřítele a letěla dál.

SpriteKit nám nabízí poměrně jednoduchou možnost, jak ověřit nastavení fyzikální reprezentace pro objekty. Přejdeme tedy do GameViewController.swift a v metodě viewDidLoad() najdeme řádek view.showsNodeCount = true.

Jak prozrazuje název, určuje, jestli má scéna zobrazovat počet objektů. To je pro případ, abychom si ověřili, že korektně odebíráme ty již nepotřebné. Přidáme další řádek a to:

view.showsPhysics = true

Jedná se o další debug konfiguraci pro zobrazení fyziky. Aktuálně nastavené fyzikální reprezentace objektů budou zobrazeny světle modrým obrysem. Zapneme hru a okamžitě vidíme, kde je problém:

Debugging fyziky ve SpriteKit

Jak sami vidíte, fyzikální reprezentace a vykreslení nepřátel nesedí, zbytek funguje dobře. Problém je v nastavení anchorPoint pro naše nepřátele. Neuvědomil jsem si v dřívější lekci, že jde pouze o vizuální nastavení vykreslování a později nastane problém s fyzikou. Tak jsme si alespoň mohli ukázat, jak problémy hledat.

Změna anchorPoint

Pokud bychom vytvářeli fyzikální reprezentaci jako obdélník nebo kruh, šlo by posunout střed a tím problém vyřešit. My ale používáme texturu, takže nám nezbývá nic jiného, než nastavení anchorPoint vrátit zpět a upravit kód generování vlny nepřátel, aby byly na středu.

Úpravy tedy provedeme v metodě createEnemyWave(). Nejdříve odstraníme nastavení anchorPoint a potom upravíme počítání xPosition v těle cyklu takto:

let xPosition = xOffset + CGFloat(i) * (enemySize + enemySpacing) + enemySize / 2

Vlastně jsme přidali přičtení poloviny šířky nepřítele, jako kompenzaci za upravený anchorPoint.

Můžeme zkusit:

Opravené kolize v iOS hře ve SpriteKit

Teď už budou kolize fungovat podle očekávání :-) V příští lekci, Další částicové efekty ve SpriteKit, přidáme částicové efekty exploze a laseru :)


 

Stáhnout

Staženo 3x (987.57 kB)
Aplikace je včetně zdrojových kódů v jazyce 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
Předchozí článek
Přidání fyziky a detekce kolizí ve SpriteKit
Všechny články v sekci
Tvorba iOS her ve Swift
Miniatura
Následující článek
Další částicové efekty ve SpriteKit
Aktivity (7)

 

 

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í!