SKPhysicsBody

5 Grudnia 2017

Koopa Mario Dragon

Dzie艅 dobry ponownie! 馃憡
To ju偶 czwarty wpis z serii dotycz膮cej podstaw framework’a SpriteKit, s艂u偶膮cego do tworzenia gier 2D na platform臋 iOS. Na pocz膮tek jedna uwaga – je艣li z jaki艣 niewyja艣nionych powod贸w zjawi艂e艣 si臋 tutaj, kompletnie zielony w temacie 馃崗 to odsy艂am Ci臋 do poprzednich wpis贸w z tej serii, gdzie poznasz podstawy dotycz膮ce tworzenia scen, korzystania z edytora oraz podstawy animacji i dodawania tekstur do sceny.

Wcze艣niejsze wpisy z serii:

W dzisiejszym wpisie za艣 zajmiemy si臋 ju偶 konkretn膮 robot膮 馃殌 – bo b臋dziemy do naszych element贸w dodawa膰 tzw. SKPhysicsBody, czyli fizyk臋 naszych postaci. Dzi臋ki temu dowiesz si臋 jak dzia艂a grawitacja w grach iOS, jak wykrywa膰 kolizj臋 mi臋dzy elementami i par臋 innych, ciekawych rzeczy.

Zach臋cony? 馃榿 No to to kodu 馃捇!

Dodawanie SKPhysicsBody do elementu

Jedn膮 z najwi臋kszych zalet u偶ywania SpriteKit jest to, 偶e dostarcza on 艣wietny silnik fizyki dla element贸w dodawanych do 艣wiata gry. G艂贸wn膮 klas膮 przeznaczon膮 do zabawy z tym zagadnieniem jest w艂a艣nie wspomniana klasa SKPhysicsBody.

SKPhysicsBody 鉃★笍 to obiekt, kt贸ry dostarcza symulacj臋 fizyki do node’a. To oznacza, 偶e zawsze obiekt tej klasy musi by膰 powi膮zany z konkretnym node’m przed dodaniem jakiejkolwiek fizyki.

Jak to dzia艂a w rzeczywisto艣ci?
Za艂贸偶my zatem, 偶e tworzymy obiekt na naszej scenie, a potem odwo艂ujemy sie do niego w kodzie. Je艣li nie wisz jeszcze, jak odwo艂a膰 si臋 do elementu sceny z klasy, to znaczy 偶e co艣 przegapi艂e艣 馃榿馃憠 Zerknij do poprzedniego wpisu.

Kiedy wiemy, 偶e obiekt istnieje na naszej scenie, mo偶emy doda膰 do niego SKPhysicsBody 鈫欙笍锔


    override func didMove(to view: SKView) {
        if let kupa = self.childNode(withName: "kupa") as? SKSpriteNode {
            kupa.physicsBody = SKPhysicsBody(rectangleOf: kupa.size)
        }
    }

A oto efekt:

Dlaczego tak? Bo w艂a艣nie tak dzia艂a dodanie cia艂a do elementu. Staje si臋 on m.in. podatny na grawitacj臋 馃巿. Pami臋tasz gdzie ustawia艂o si臋 parametr grawitacji? Mo偶na to zrobi膰 z poziomu edytora scen w Xcode o tu 猬囷笍

Ustawienie grawitacji w edytorze Xcode

W艂a艣ciwo艣ci i funkcje

Kiedy mamy ju偶 dodany obiekt SKPhysicsBody do naszego sprite’a, mo偶emy do niego dodawa膰 ciekawe efekty oraz u偶ywa膰 dost臋pnych metod. Jedna z nich jest np. applyImpulse() kt贸ra pozwala doda膰 wektor ruchu do naszego elementu:

kupa.physicsBody?.applyImpulse(CGVector(dx: -20, dy: 0))

Nasza posta膰 b臋dzie porusza艂膮 si臋 w lew膮 stron臋 z wektorem o warto艣ci -20 dla osi X.

SKPhysicsBody posiada szereg w艂a艣ciwo艣ci i metod, kt贸re mog臋 Ci臋 interesowa膰 w zale偶no艣ci od tego, jakiego zachowania oczekujesz od swojej postaci. Wszystkie w艂a艣ciwo艣ci s膮 oczywi艣cie opisane w dokuemntacji od Apple tutaj.

Jak widzisz, mo偶esz tak naprawd臋 zdefiniowa膰 wi臋kszo艣膰 w艂a艣ciwo艣ci jakie potrzebujesz do symulacji zachowania swojego bohatera, jak masa czy g臋sto艣膰. Patrz膮c na dokumentacj臋 przypomnia艂em sobie o jeszcze jednej wa偶nej funkcji – isDynamic().

Funkcja ta definiuje, czy cia艂o dla naszego node’a ma by膰 dynamiczne, czy statyczne. Jaka r贸偶nica?馃

Cia艂o statyczne ignoguje wszystkie impulsy i si艂y na nie dzia艂aj膮ce, jak np. w艂a艣nie si艂a grawitacji. Czyli w prostych s艂owach – bohaterowie, wrogowie, 偶yj膮ce stworzonka etc. powinny posiada膰 t臋 w艂a艣no艣膰 ustawion膮 na true (domy艣lna warto艣膰鈽濓笍).

Kolizje

Skoro wiemy ju偶, 偶e dodanie cia艂a do node’a powoduje, 偶e staje si臋 on podatny na dzia艂anie r贸偶nych si艂, to czas zadzia艂a膰 na niego r贸wnie偶 innym obiektem 馃樇.

Jednak偶e, trzeba przemy艣le膰 spos贸b odr贸偶niania od siebie poszczeg贸lnych element贸w. Poniewa偶 SKPhysicsBody to osobne obiekty, nie wystarczy 偶e b臋dziemy pos艂ugiwa膰 si臋 identyfikatorami powi膮zanych node’贸w.

Do identyfikowania cia艂 naszych elementow b臋d膮 s艂u偶y艂y Bitmaski – czyli indentyfikatory w postaci liczb, zapisane szestnastkowo, np. 0x00000001 (dziesi臋tnie “1”).
Bitmaski mo偶emy podzieli膰 w naszym przypadku na kilka kategorii:

  • Kategorie – mo偶emy odentyfikowa膰 nasze cia艂a po kategoriach, np gracz, wr贸g, moneta etc.
  • Kolizje – bitmaski reprezentuj膮ce kolizj臋 mi臋dzy elementami.
  • Contact Test – bitmaska s艂u偶膮ca przechwytywaniu zdarze艅 kolizji.

Je艣li uczysz si臋 SpriteKit, pewnie nie wiele rozumiesz w tym momencie z tej teorii, ale to normalne 馃懡 na tym etapie. Og贸lnie na ten moment pami臋taj, 偶e:

  • CategoryBitmask – Do jakiej kategorii nale偶臋?
  • CollisionBitmast – Z kim mog臋 kolidowa膰
  • ContactTestBitmask – Daj zna膰 systemowi 偶e zderzy艂em si臋 z…

Praktyka czyni mistrza

Ok, czas na troch臋 praktyki 馃挭馃捇馃憤. 呕eby zobaczy膰 jak dzia艂aj膮 kolizj臋, musz臋 stworzy膰 najpierw kilka obiekt贸w, kt贸re b臋d膮 ze sob膮 kolidowa艂y. Dla mnie b臋dzie to kr贸l Kupa (Koopa) z mario, most oraz grzybek, kt贸ry nie koliduje z mostem, czyli spadnie do lawy 馃槃.

Dla ka偶dego z tych trzech obiekt贸w tworz臋 CategoryBitmask (zmienne typu UInt32).


    private var kupa: SKSpriteNode?
    private var mushroom: SKSpriteNode?
    private var brigde: SKSpriteNode?
    
    let kupaCategory: UInt32 =      0x00000001 << 0  
    let mushroomCategory: UInt32 =  0x0000001 << 1   
    let bridgeCategory: UInt32 =    0x00000001 << 2 
    

NOTE: Symbol “<<" oznacza przesuni臋cie bitowe w zapisie szestnastkowym. W skr贸cie dzia艂a to tak, 偶e ostatni bit zostaje przesuni臋ty o jedn膮 pozycj臋 w lewo, zatem pierwsza zmienna jest przesuni臋ta o 0 pozycji w lewo, wi臋c w zasadzie

0x00000001 << 0 = 1

Druga jest ju偶 przesuni臋ta o 1 bit w lewo, wi臋c:

(0x00000001 << 1) = 2 bo (0x00000001 << 1) = 0x00000010 = 2

End NOTE 馃憞

Teraz musz臋 ka偶demu z element贸w przypisa膰 kategori臋 do powi膮zanego z nim obiektu SKPhysicsBody:


    func createNodes(){
        if let kupa = self.childNode(withName: "kupa") as? SKSpriteNode,
            let mushroom = self.childNode(withName: "mushroom") as? SKSpriteNode,
            let bridge = self.childNode(withName: "bridge") as? SKSpriteNode{
            
            kupa.physicsBody = SKPhysicsBody(rectangleOf: kupa.size)
            kupa.physicsBody?.categoryBitMask = kupaCategory
            
            mushroom.physicsBody = SKPhysicsBody(rectangleOf: mushroom.size)
            mushroom.physicsBody?.categoryBitMask = mushroomCategory
            
            bridge.physicsBody = SKPhysicsBody(rectangleOf: bridge.size)
            bridge.physicsBody?.isDynamic = false
            bridge.physicsBody?.restitution = 0.75
            bridge.physicsBody?.categoryBitMask = bridgeCategory
        
        }
    }

Ok, mamy to. Teraz na ko艅cu tej funkcji dopisuj臋 linie odpowiadaj膮ce za przypisanie bitmaski dla kolizji. Przypominam - chc臋 偶eby smok zatrzyma艂 si臋 na mo艣cie, grzybek ma spa艣膰. Zatem musz臋 przyporz膮dkowa膰 collisionBitMask mostu do smoka, za艣 dla grzybka przyporz膮dkowa膰 jaki艣 randomowy numer. Dopisuj臋 dwie linie:


    // Collision
    kupa.physicsBody?.collisionBitMask = bridgeCategory
    
    // Test - Set random mask to see no collision
    mushroom.physicsBody?.collisionBitMask = 0x1 << 5

Teraz odpalam to w symulatorze i... 馃槑. Master!

Wykonywanie akcji w momencie kolizji

Teraz rzecz ostatnia na dzi艣, ale napewno dla Ciebie bardzo ciekawa. Mianowicie chodzi o wykrycie konkretnej kolizji i wykonanie jakiej艣 akcji. To mo偶e by膰 np. kolizja z monet膮, przez co powiniene艣 zwi臋kszy膰 ilo艣膰 posiadanych monet przez gracza, albo z przeciwnikiem i wtedy 鉃★笍 dead 鈽狅笍.

Do tego w艂a艣nie s艂u偶y ostatnia z wymienionych wcze艣niej typ贸w bitmask贸w 鉃★笍 ContactTestBitmask. I tutaj wa偶ny, wa偶ny NOTE: Nie u偶ywaj ich zbyt cz臋sto dla zbyt du偶ej liczby obiekt贸w. Ten typ wykrywania kolizji jest bardzo wydajno艣cio偶erny, i b臋dzie powodowa艂 spadek wydajno艣ci programu.

Ale do rzeczy - do wykrycia zdarze艅 kolizji potrzebujemy zaadoptowa膰 protok贸艂 posiadaj膮cy odpowiednie metody. Ten protok贸艂 to SKPhysicsContactDelegate, implementuj膮cy funkcj臋 didBegin(_ contact: SKPhysicsContact)

呕eby przyporz膮dkowa膰 nasz膮 klas臋 do prokoto艂u, u偶yjemy zmiennej physicsWorld kt贸r膮 posiada nasza klasa SKScene. Czyli w metodzie didMove wklejamy linijk臋:

self.physicsWorld.contactDelegate = self

No i teraz trzeba jeszcze doda膰 ContactTestBitmask do jakiego艣 elementu. Zosta艅my w zamku kr贸la Koopy i niech nasz sprite reprezentuj膮cy most dostanie tak膮 bitmaske:


    // ContactTestBitMask
    bridge.physicsBody?.contactTestBitMask = kupaCategory

to oznacza, 偶e zostanie przechwycone zdarzenie kolizji mi臋dzy mostem a smoczkiem. Teraz w metodzie didBegin mo偶emy jako艣 przechwyci膰 to zdarzenie i spowodowa膰 akcj臋. Ale hm... 馃 w tej metodzie jest tylko parametr contact... to troch臋 ma艂o.

No ale musimy wykorzysta膰 co mamy. Zmienna typu SKPhysicsContact posiada parametry bodyA oraz bodyB, czyli dwa elementy, kt贸re skolidowa艂y si臋 ze sob膮. W takim razie przy pomocy operatora por贸wnania ( | ) sprawdz臋, czy te dwa elementy posiadaj膮 t臋 sam膮 kategori臋 bitmaski:


    let collison: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
    

I ostatnia rzecz to por贸wnanie kategorii, kt贸re mia艂y si臋 ze sob膮 spotka膰. W moim przypadku to by艂a kupaCategory oraz brigdeCategory. Je偶eli wynik operacji por贸wnania jest ten sam - mamy kolizje!


    func didBegin(_ contact: SKPhysicsContact) {
        let collison: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
        
        if collison == kupaCategory | bridgeCategory{
            print("COLLISION")
        }
    }

Test i jest! Wszystko dzia艂a pi臋knie 馃槑 przynajmniej u mnie...

Przechwytywanie zdarze艅 kolizji

Podsumowanie

Dzisiaj by艂 kawa艂 naprawd臋 warto艣ciowej wiedzy, bo tak naprawd臋 ca艂y core tworzenia gier na iOS to zabawa z fizyk膮 node'贸w. Dlatego w艂a艣nie obiekt SKPhysicsBody jest wa偶ny i to w艂a艣nie tutaj zaczyna si臋 prawdziwa zabawa.

Podsumowauj膮c 鈭 do tworzenia fizyki s艂u偶y obiekt SKPhysicsBody, kt贸ry musi by膰 powi膮zany z konkretnym node'm. SKPhysicsBody mo偶e przyjmowa膰 3 rodzaje kategorii BitMaski, kt贸re odpowiadaj膮 za odpowiednio - kategori臋, kolizj臋 i zdarzenie.

SKPhysicsBody posiada parametr isDynamic, kt贸ry okre艣la czy element ma by膰 odporny na dzia艂anie si艂 fizyki (false) czy te偶 nie (true).

I teraz 馃憠 sta艂a formu艂ka znajduj膮ca si臋 pod ka偶dym postem w tej serii 鉃★笍 wszystko czego si臋 tutaj uczysz przek艂adaj na sw贸j projekt! Pami臋taj 偶e przez praktyk臋 si臋 cz艂owiek uczy, nie przez czytanie suchej teorii.

Na koniec jak zawsze prosz臋 o jaki艣 feedback 馃檹 czy si臋 podoba艂o, co s膮dzisz o ca艂ej serii. Je艣li realizujesz jaki艣 projekt, podziel si臋 tym r贸wnie偶 wrzucaj膮c link do GIT'a etc. Mo偶esz r贸wnie偶 podaga膰 ze mn膮 na Tweetku, Insta lub napisa膰 co艣 ciekawego na fanpage'u na Facebook'u.

Jeszcze raz dzi臋ki i do nast臋pnego!
@jkornat

Wprowadzenie do SpriteKit #4

Dodaj komentarz

Tw贸j adres email nie zostanie opublikowany. Pola, kt贸rych wype艂nienie jest wymagane, s膮 oznaczone symbolem *