Lekce 6 - Přidání fyziky a detekce kolizí ve SpriteKit
V předchozí lekci, Nepřátelé střílí zpět a dokonce laserem ve SpriteKit, jsme v naší iOS hře ve Swift skončili vyzbrojením nepřátel laserem a přidali zvukové efekty.
Naše střely pořád nic nedělají a právě to napravíme v tomto tutoriálu.
Fyzika ve SpriteKit
Je na čase představit si, jak funguje fyzika ve SpriteKit. Nám pomůže s detekcí kolizí, ale využít se dá pro řadu dalších věcí, protože například můžete velmi snadno upravovat gravitaci herního světa. Ta je ve výchozím stavu nastavena tak, aby emulovala Zemi, takže předměty, které mají fyzické tělo, automaticky padají k zemi a odrážejí se od sebe.
Pomocí gravitace můžete snadno implementovat ovládání ve hrách pomocí naklánění zařízení. Jednoduše podle naklonění upravíte gravitaci a SpriteKit se postará o zbytek.
Aktivace fyziky
Fyzika již na pozadí naší hry funguje, ale žádný z našich objektů
nemá nastavené physicsBody
, takže se na něj nevztahuje. Jako
první tedy musíme nastavit physicsBody
pro všechny naše
objekty, abychom mohli později detekovat kolize těchto objektů.
Player
Začneme třeba u hráče (soubor Player.swift
) a na konec
init()
přidáme tento řádek:
physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
To stačí, aby fyzika začala na model hráče realisticky působit. Fyzikální reprezentaci vytváříme z textury, což je sice náročnější na výkon, ale budeme mít tzv. "pixel-perfect" kolize. Laserová střela se bude muset skutečně přesně dotknout lodi hráče, aby byla započítána.
Ještě ve třídě Player
zůstaneme a rovnou nastavíme
physicsBody
také raketám v metodě
createMissiles()
:
missile1.physicsBody = SKPhysicsBody(rectangleOf: missile1.size) missile2.physicsBody = SKPhysicsBody(rectangleOf: missile2.size)
Pro ukázku jsem zde použil vytvoření fyzikální reprezentace jako
obdélníku, což je při detekci kolizí mnohem rychlejší než vytvoření z
textury. Výkonově by nebyl problém opět použít texturu, chci ale ukázat
také další způsob V
momentě, kdy budete mít ve hře hromadu fyzikálních objektů, se již
vyplatí přemýšlet, jakým stylem
physicsBody
vytvořit.
Enemy
Přesuneme se do třídy Enemy
a provedeme zde prakticky to
stejné. Nejprve v metodě create()
:
enemy.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
A pro laserovou střelu:
laser.physicsBody = SKPhysicsBody(rectangleOf: laser.texture!.size())
laser.texture
je Optional
, protože
SKSpriteNode
jde vytvořit pouze z barvy bez textury. My ale víme,
že jsme texturu použili a vykřičník tak není problém.
Zkouška fyziky
Nyní můžete hru vyzkoušet a podívat se, jak se vlastně změnila.
Pokud jste vše nastavili správně, viděli jste názornou ukázku simulované gravitace. My samozřejmě nechceme, aby všechny lodě spadly mimo hrací plochu...
Vypnutí gravitace
Fyziku v naší vesmírné hře sice využijeme, ale gravitace zde nedává
moc smysl. Takže ji v didMove()
vypneme:
physicsWorld.gravity = .zero
Vlastnost gravity
je typu CGVector
,
který se používá pro reprezentaci směru a intenzity. Ve výchozím stavu je
gravitace nastavena na vektor CGVector(dx: 0, dy: -9.8)
.
Hru můžete zapnout a radovat se, že naše objekty nepadají nekonečně dolů. Jenže teď do sebe vše naráží a různě se otáčí. To hned vyřešíme pomocí bitové masky, až budeme kolize konfigurovat.
Druhou možností by bylo nastavit vlastnost
physicsBody.isDynamic
, která zabrání, aby se objekt hýbal, ať
již vlivem gravitace nebo kolize. Zde vlastnost nevyužijeme, hodí se ale
vědět, k čemu slouží. Dejte zároveň pozor na to, že kolize nebudou
fungovat, pokud budou mít oba objekty nastavené idDynamic
na
false
.
Konfigurace kolizí
Nám vlastně bude stačit, aby nám SpriteKit řekl, když dojde ke kolizi
mezi raketou a nepřítelem a také ke kolizi mezi laserem a lodí hráče. Pro
konfiguraci budeme potřebovat kategorii kolidovaného objektu. Mohli použít
obyčejná čísla, připravíme si ale enum
, ať je výsledek
přehlednější.
Enum
Vytvoříme si tedy enum
pojmenovaný třeba
ObjectType
v souboru GameScene.swift
, ale mimo
definici třídy GameScene
:
enum ObjectType: UInt32 { case player = 1 case missile = 2 case enemy = 4 case laser = 8 }
Jako typ jsme použili UInt32
, protože právě ten používá
SpriteKit pro definici masek. Hodnota je vždy dvojnásobek předchozího, aby
šlo tyto hodnoty pro danou masku kombinovat (když si představíte čísla
binárně, je player
0001
, missile
0010
, enemy
0100
a laser
1000
, player
a zároveň laser
by tedy
teoreticky mohl být 1001
, i když to zde nedává smysl).
Kombinací bychom využili, pokud bychom třeba chtěli, aby docházelo ke
kolizi mezi hráčem a laserem a také mezi hráčem a nepřítelem, což v
naší hře ale nenastane.
Teď musíme tyto hodnoty správně přiřadit našim objektům. K tomu si ale konečně představme jak bitové masky ve SpriteKit fungují.
Vysvětlení bitových masek
Objekty ve SpriteKit mají bitové masky celkem 3:
categoryBitMask
nám vlastně pouze určuje, jaký typ je daný objekt.- Dále nastavujeme
contactTestBitMask
, která slouží k tomu, abychom řekli SpriteKit o kolizích s jakou kategorií nám má dát vědět. - Třetí maska,
collisionBitMask
, určuje, od jaké kategorie se má automaticky objekt odrazit, jako by došlo ke kolizi ve skutečném světě. My ji všude nastavíme na0
, aby do sebe věci nenarážely a různě se nám nehýbaly mimo naši kontrolu.
Pokud vám v budoucnu nebudou v nějaké hře fungovat kolize, měly by bitové masky být první věc, kterou budete kontrolovat a případně zkoušet upravit.
Nastavení bitových masek objektům
Začneme ve třídě Player
a konstruktoru
init()
:
physicsBody?.categoryBitMask = ObjectType.player.rawValue
physicsBody?.contactTestBitMask = ObjectType.laser.rawValue
physicsBody?.collisionBitMask = 0
Dále musíme tyto masky nastavit pro rakety:
missile1.physicsBody?.categoryBitMask = ObjectType.missile.rawValue missile1.physicsBody?.contactTestBitMask = ObjectType.enemy.rawValue missile1.physicsBody?.collisionBitMask = 0 missile2.physicsBody?.categoryBitMask = ObjectType.missile.rawValue missile2.physicsBody?.contactTestBitMask = ObjectType.enemy.rawValue missile2.physicsBody?.collisionBitMask = 0
Může vás lákat využít metodu copy()
a prostě
nastavené physicsBody
první rakety zkopírovat do druhé. To ale
nebude fungovat, protože metody copy()
v tomto případě
vytvoří novou instanci SKPhysicsBody
a nedojde k přenesení
nastavených vlastností.
Zbývá nastavení pro nepřátele:
enemy.physicsBody?.categoryBitMask = ObjectType.enemy.rawValue
enemy.physicsBody?.contactTestBitMask = ObjectType.missile.rawValue
enemy.physicsBody?.collisionBitMask = 0
A laser:
laser.physicsBody?.categoryBitMask = ObjectType.laser.rawValue
laser.physicsBody?.contactTestBitMask = ObjectType.player.rawValue
laser.physicsBody?.collisionBitMask = 0
Získání informací o kolizích
Když teď hru zapnete, mělo by vše fungovat jako bychom nic nového nepřidali. Zbývá již ovšem jen minimum, abychom získávali informace o kolizích.
V první řadě musíme naší GameScene
přidat protokol
SKPhysicsContactDelegate
a v didMove()
ho
nastavit:
physicsWorld.contactDelegate = self
O kolizích se dozvíme skrze metodu:
func didBegin(_ contact: SKPhysicsContact) { print("Contact!") }
Pro vyzkoušení jsem doplnil print()
. Když teď hru zapneme,
můžeme sledovat výpis Contact!
pokud raketa strefí nepřítele
nebo laser loď hráče.
V příští lekci, Dokončení kolizí ve SpriteKit, již můžeme přidat exploze po kontaktu a také odstranit zničené nepřátele a rozhodnout se, jak moc bude hráč penalizovaný za zásah laserem.
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 9x (954.48 kB)
Aplikace je včetně zdrojových kódů v jazyce Swift