6. diel - Pridanie fyziky a detekcia kolízií vo SpriteKit
V predchádzajúcej lekcii, Nepriatelia strieľa späť a dokonca laserom vo SpriteKit , sme v našej iOS hre vo Swift skončili vyzbrojením nepriateľov laserom a pridali zvukové efekty. Naše strely ale stále nič nerobia a práve to napravíme v tomto tutoriálu.
Fyzika vo SpriteKit
Je na čase predstaviť si, ako funguje fyzika vo SpriteKit. Nám pomôže s detekciou kolízií, ale využiť sa dá pre rad ďalších vecí, pretože napríklad môžete veľmi ľahko upravovať gravitáciu herného sveta. Tá je v predvolenom stave nastavená tak, aby emuloval Zem, takže predmety, ktoré majú fyzické telo, automaticky padajú k zemi a odrážajú sa od seba.
Pomocou gravitácie môžete ľahko implementovať ovládanie v hrách pomocou nakláňania zariadení. Jednoducho podľa naklonenie upravíte gravitácii a SpriteKit sa postará o zvyšok.
Aktivácia fyziky
Fyzika už na pozadí našej hry funguje, ale žiadny z našich objektov
nemá nastavené physicsBody
, takže sa na neho nevzťahuje. Ako
prvý teda musíme nastaviť physicsBody
pre všetky naše objekty,
aby sme mohli neskôr detekovať kolízie týchto objektov.
Player
Začneme napríklad u hráčov (súbor Player.swift
) a na koniec
init()
pridáme tento riadok:
physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
To stačí, aby fyzika začala na model hráča realisticky pôsobiť. Fyzikálne reprezentáciu vytvárame z textúry, čo je síce náročnejšie na výkon, ale budeme mať tzv. "Pixel-perfect" kolízie. Laserová strela sa bude musieť skutočne presne dotknúť lodi hráča, aby bola započítaná.
Ešte v triede Player
zostaneme a rovno nastavíme
physicsBody
tiež raketám v metóde
createMissiles()
:
missile1.physicsBody = SKPhysicsBody(rectangleOf: missile1.size) missile2.physicsBody = SKPhysicsBody(rectangleOf: missile2.size)
Pre ukážku som tu použil vytvorenie fyzikálne reprezentácie ako
obdĺžnika, čo je pri detekcii kolízií oveľa rýchlejší ako vytvorenie z
textúry. Výkonovo by nebol problém opäť použiť textúru, chcem ale
ukázať aj ďalší spôsob
V momente, kedy budete mať v hre hromadu fyzikálnych objektov, sa už oplatí
premýšľať, akým štýlom physicsBody
vytvoriť.
Enemy
Presunieme sa do triedy Enemy
a vykonáme tu prakticky to isté.
Najprv v metóde create()
:
enemy.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
A pre laserovú strelu:
laser.physicsBody = SKPhysicsBody(rectangleOf: laser.texture!.size())
laser.texture
je Optional
, pretože
SKSpriteNode
ide vytvoriť len z farby bez textúry. My ale vieme,
že sme textúru použili a výkričník tak nie je problém.
Skúška fyziky
Teraz môžete hru vyskúšať a pozrieť sa, ako sa vlastne zmenila.
Ak ste všetko nastavili správne, videli ste názornú ukážku simulovanej gravitácie. My samozrejme nechceme, aby všetky lode spadli mimo hraciu plochu ...
Vypnutie gravitácie
Fyziku v našej vesmírnej hre síce využijeme, ale gravitácia tu nedáva
moc zmysel. Takže ju v didMove()
vypneme:
physicsWorld.gravity = .zero
Vlastnosť gravity
je typu CGVector
,
ktorý sa používa pre reprezentáciu smeru a intenzity. V predvolenom stave je
gravitácia nastavená na vektor CGVector(dx: 0, dy: -9.8)
.
Hru môžete zapnúť a radovať sa, že naše objekty nepadajú nekonečne nadol. Lenže teraz do seba všetko naráža a rôzne sa otáča. To hneď vyriešime pomocou bitovej masky, až budeme kolízie konfigurovať.
Druhou možnosťou by bolo nastaviť vlastnosť
physicsBody.isDynamic
, ktorá zabráni, aby sa objekt hýbal, či
už vplyvom gravitácie alebo kolízie. Tu vlastnosť nevyužijeme, hodí sa ale
vedieť, na čo slúži. Dajte zároveň pozor na to, že kolízie nebudú
fungovať, ak budú mať oba objekty nastavené idDynamic
na
false
.
Konfigurácia kolízií
Nám vlastne bude stačiť, aby nám SpriteKit povedal, keď dôjde ku
kolízii medzi raketou a nepriateľom a tiež ku kolízii medzi laserom a lodí
hráča. Pre konfiguráciu budeme potrebovať kategórii kolidovaného objektu.
Mohli použiť obyčajná čísla, pripravíme si ale enum
, nech je
výsledok prehľadnejšie.
Enum
Vytvoríme si teda enum
pomenovaný treba
ObjectType
v súbore GameScene.swift
, ale mimo
definíciu triedy GameScene
:
enum ObjectType: UInt32 { case player = 1 case missile = 2 case enemy = 4 case laser = 8 }
Ako typ sme použili UInt32
, pretože práve ten používa
SpriteKit pre definíciu masiek. Hodnota je vždy dvojnásobok
predchádzajúceho, aby išlo tieto hodnoty pre danú masku kombinovať (keď si
predstavíte čísla binárne, je player
0001
,
missile
0010
, enemy
0100
a
laser
1000
, player
a zároveň
laser
by teda teoreticky mohol byť 1001
, aj keď to
tu nedáva zmysel). Kombináciou by sme využili, ak by sme napríklad chceli,
aby dochádzalo ku kolízii medzi hráčom a laserom a tiež medzi hráčom a
nepriateľom, čo v našej hre ale nenastane.
Teraz musíme tieto hodnoty správne priradiť našim objektom. K tomu si ale konečne predstavme ako bitové masky vo SpriteKit fungujú.
Vysvetlenie bitových masiek
Objekty vo SpriteKit majú bitovej masky celkom 3:
categoryBitMask
nám vlastne len určuje, aký typ je daný objekt.- Ďalej nastavujeme
contactTestBitMask
, ktorá slúži na to, aby sme povedali SpriteKit o kolíziách s akou kategóriou nám má dať vedieť. - Tretia maska,
collisionBitMask
, určuje, od akej kategórie sa má automaticky objekt odraziť, ako by došlo ku kolízii v skutočnom svete. My ju všade nastavíme na0
, aby do seba veci nenarážali a rôzne sa nám nehýbali mimo našu kontrolu.
Ak vám v budúcnosti nebudú v nejakej hre fungovať kolízie, mali by bitové masky byť prvá vec, ktorú budete kontrolovať a prípadne skúšať upraviť.
Nastavenie bitových masiek objektom
Začneme v triede Player
a konstruktoru init()
:
physicsBody?.categoryBitMask = ObjectType.player.rawValue
physicsBody?.contactTestBitMask = ObjectType.laser.rawValue
physicsBody?.collisionBitMask = 0
Ďalej musíme tieto masky nastaviť pre 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ákať využiť metódu copy()
a
jednoducho nastavené physicsBody
prvej rakety skopírovať do
druhej. To ale nebude fungovať, pretože metódy copy()
v tomto
prípade vytvorí novú inštanciu SKPhysicsBody
a nedôjde k
preneseniu nastavených vlastností.
Zostáva nastavenia pre nepriateľov:
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ískanie informácií o kolíziách
Keď teraz hru zapnete, malo by všetko fungovať ako by sme nič nové nepridali. Zostáva už však len minimum, aby sme získavali informácie o kolíziách.
V prvom rade musíme našej GameScene
pridať protokol
SKPhysicsContactDelegate
a v didMove()
ho
nastaviť:
physicsWorld.contactDelegate = self
O kolíziách sa dozvieme skrze metódu:
func didBegin(_ contact: SKPhysicsContact) { print("Contact!") }
Pre vyskúšanie som doplnil print()
. Keď teraz hru zapneme,
môžeme sledovať výpis Contact!
ak raketa trafí nepriateľa
alebo laser loď hráča.
V budúcej lekcii, Dokončenie kolízií vo SpriteKit , už môžeme pridať explózie po kontakte a tiež odstrániť zničené nepriateľov a rozhodnúť sa, ako veľmi bude hráč penalizovaný za zásah laserom.
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é 9x (954.48 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Swift