2018-01-29 scene editor與代碼掛鉤及物理碰撞檢測

Scene editor與代碼掛鉤

  1. 在編輯器中拖相應(yīng)的組件并設(shè)定屬性值,步驟如下圖所示:


    image.png
  2. 掛鉤后,編輯器中的木頭都屬于WoodNode類的子節(jié)點,然后在GameScene中創(chuàng)建了他們對應(yīng)的實例對象:


    image.png

然后通過節(jié)點名稱將節(jié)點賦值于對應(yīng)的對象

childNode(withName: String)  as! WoodNode
image.png

然后就可以為所欲為地操作相應(yīng)的節(jié)點啦(比如上圖所示的woodNodeH1.setScale(1.5))

學(xué)到這里的時候遇到一個很有用的函數(shù)

enumerateChildNodes(withName: "http://woodV*", using: {node, _ in
            
            print("\(node.name)")
            
        })

其作用是可以通過節(jié)點名稱遍歷并統(tǒng)一進行相應(yīng)的操作,這里加上//的意思是從根目錄從上到下依次遍歷所有的樹形結(jié)構(gòu)節(jié)點,如果以后這個樹形結(jié)構(gòu)非常龐大,這樣搜索的效率肯定會很低,不過現(xiàn)在作為練習(xí)項目就先這樣吧,//woodV* 后面有一個*號是指可以通過數(shù)字増序依次搜索woodV1、woodV2...

因為這四塊木頭具有很多的共同屬性,所以我將他們都列入WoodNode中,不僅僅如此,其實他們還單獨存在于一個sks文件中,然后通過reference的方式添加到GameScene.sks中。設(shè)置reference的視覺化操作很簡單,就是拖一個reference的控件,然后將reference屬性設(shè)置為之前編輯的sks文件即可

image.png

物理碰撞檢測

使木頭自由下落及設(shè)置GameScene的邊框物理屬性

然后我將這幾塊木頭的isDynamic屬性值打開,他們就會收到重力影響自由下落,但是默認他們會掉出屏幕之外,所以我需要在func didMove(to view: SKView)中將GameScene邊框的physicsBody設(shè)置好,他們就能妥妥地掉在屏幕下邊框上了:


image.png

添加physics body

有以下三種方法添加physics body

  1. Creating simple bodies in the scene editor
image.png
  1. Creating simple bodies from code


    image.png
  1. Creating custom bodies
    第三種方式就是解決物理外形不一定是texture外形的問題。比如一直貓貓,可能他的物理有效碰撞區(qū)域只是身體的那一部分,頭和尾巴不是有效的物理碰撞,所以我們可以運用一張純色的texture并設(shè)置為貓貓的有效物理碰撞區(qū)域


    image.png

好了,現(xiàn)在我們設(shè)置一個小需求并予以實現(xiàn),如下圖所示的一組木塊,默認狀態(tài)在屏幕上方。開始時,他們會自由下落到屏幕的下邊框。

  1. 現(xiàn)在我們需要檢測woodNodeS分別于woodNodeH1、Edge的碰撞情況并打印日志
  2. 點擊木塊后,木塊都會從屏幕中消失
  3. 所有木塊消失2秒后,游戲回到初始化的狀態(tài)


    image.png

檢測碰撞

檢測碰撞的步驟如下:

  1. 將碰撞體分類:雖然游戲中的四塊木頭都屬于WoodNode,但是在檢測碰撞時它們是四個獨立的個體,所以我們需要將他們設(shè)置成四個獨立的category。
    首先通過一個struct建好碰撞體的目錄結(jié)構(gòu)如下:
struct PhysicsBodyCategory {
    
    static let WH1:  UInt32 = 0b1   //1
    
    static let WS :  UInt32 = 0b10  //2
    
    static let WV1:  UInt32 = 0b100 //4
    
    static let WV2:  UInt32 = 0b1000 //8
    
    static let Edge: UInt32 = 0b10000 //16
    
}
  1. 設(shè)置category bit mask
    然后將四個木塊及場景歸屬到對應(yīng)的碰撞體結(jié)構(gòu),可通過編輯器及代碼的方式實現(xiàn)。
    編輯器:默認的category mask為極大的值,比如我要將woodH1分類到剛剛建好的WH1中,我只需將category mask值設(shè)置為2即可


    image.png

代碼:其實更偏向于通過代碼來統(tǒng)一管理,既然之前已經(jīng)通過struct定義好碰撞體的category,那么我只需要將各個木塊的category賦值即可,這樣可能會更好管理吧?

 woodNodeH1 = childNode(withName: "http://woodH1") as! WoodNode
        
        woodNodeH1.physicsBody!.categoryBitMask = PhysicsBodyCategory.WH1

//        woodNodeH1.setScale(1.5)
        
        woodNodeS = childNode(withName: "http://woodS")   as! WoodNode
        
        woodNodeS.physicsBody!.categoryBitMask = PhysicsBodyCategory.WS
        
        woodNodeV1 = childNode(withName: "http://woodV1") as! WoodNode
        
        woodNodeV1.physicsBody!.categoryBitMask = PhysicsBodyCategory.WV1
        
        woodNodeV2 = childNode(withName: "http://woodV2") as! WoodNode
        
        woodNodeV2.physicsBody!.categoryBitMask = PhysicsBodyCategory.WV2

        physicsBody!.categoryBitMask = PhysicsBodyCategory.Edge
  1. 設(shè)置collision bit mask
    默認的category bit mask 和collision都是4294967295,換做二進制數(shù)為11111111111111111111111111111111(32個1),也就是節(jié)點之間默認加上物理體后,彼此是有效碰撞的,除非你指定想讓某節(jié)點與具體的一些節(jié)點才會有效碰撞,默認是不需要設(shè)置此參數(shù),比如,我想讓woodNodeS只與woodNodeH1有效碰撞,那么我設(shè)置如下
        woodNodeS.physicsBody!.collisionBitMask = PhysicsBodyCategory.WH1

一旦將woodNodeH1消除掉,woodNodeS將掉出屏幕以外。

  1. 檢測碰撞并回調(diào)didBegin

我現(xiàn)在是需要在woodNodeS與woodNodeH1、Edge碰撞時檢測碰撞并回調(diào)相應(yīng)函數(shù),那么首先我就需要將woodNodeS的contactTestBitMask設(shè)置為Edge | WH1,如下所示:

woodNodeS.physicsBody!.contactTestBitMask = PhysicsBodyCategory.Edge | PhysicsBodyCategory.WH1

讓GameScene遵從SKPhysicsContactDelegate協(xié)議,這樣在碰撞時才會調(diào)用didBegin函數(shù)

class GameScene: SKScene,SKPhysicsContactDelegate 

physicsWorld.contactDelegate = self

最后在didBegin中檢測碰撞

 func didBegin(_ contact: SKPhysicsContact) {
        
        let collision = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
        
            if collision == PhysicsBodyCategory.WS | PhysicsBodyCategory.Edge {
                
                print("square hits the floor")
                
                
            } else if collision == PhysicsBodyCategory.WS | PhysicsBodyCategory.WH1 {
                
                print("sqaure hits the wh1")
        }
        
    }

運行代碼后,woodNodeS掉落下來后默認就會和woodNodeH1進行碰撞,碰撞檢測后的日志也打印出來了


image.png

點擊木塊后,木塊都會從屏幕中消失

然后我們就來做點擊消除木塊的功能,教程中的原話是這么描述的:

To distinguish nodes you can tap on from nodes that are just static decoration you will add a new protocol. Open GameScene.swift and add under the existing protocol declaration for EventListenerNode:

protocol InteractiveNode {
    
    func interact()
    
}

大概的意思是加了一個InteractiveNode的協(xié)議讓W(xué)oodNode中的實例化對象來遵守,這樣就可以區(qū)分出你是點的哪個node了。作為新手的我來說,看到這個有點一臉懵逼(即使查了protocol的用法以后也有點懵逼,protocol里面的變量如果不是optional,在實例化的時候比如賦值。但是func呢?為什么我自己另外建了demo發(fā)現(xiàn)func不會自動執(zhí)行呢?留個大大的問號?),先暫時就局限于知其然吧,反正后面會頻繁地用到點擊事件,在后面的探索中希望能知其所以然。

在WoodNode中設(shè)置了以下的點擊事件,點擊事件會觸發(fā)將這個node從父節(jié)點移除

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        super.touchesEnded(touches, with: event)
        
        interact()
    }


func interact() {
       
        print("touches take action")
        
        self.removeFromParent()
        

    }

所有木塊消失2秒后,游戲回到初始化的狀態(tài)

所有木塊都屬于同一個parent,所以我可以通過判斷parent.children是否為空來判斷師傅所有木塊都消失了,

override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered
        if woodNodeS.parent?.children == nil {
            
                      //DO SOMETHING HERE

           
        }
    }

然后定義初始化場景的函數(shù)并在//DO SOMETHING HERE的位置
通過run(SKAction.sequence)的方法等2秒后執(zhí)行初始化場景的操作

func newGame() {
        
        let scene = GameScene(fileNamed: "GameScene")
        
        scene!.scaleMode = scaleMode
        
        view!.presentScene(scene)
        
    }


run(SKAction.sequence([SKAction.wait(forDuration: 2.0),SKAction.run(newGame)]))

差不多跌跌撞撞就這樣了,中間有一些坑,比如判斷子節(jié)點是否為空我放在了update函數(shù)里,這樣多多少少會犧牲一些效率吧?正確的做法應(yīng)該是在減少children的時候interact()再判斷一下children是否已經(jīng)為nil。以事件為導(dǎo)向處理肯定比每一幀調(diào)用時來處理效率高吧?但是interact()方法我又放到WoodNode中的,所以暫時不知道怎么去解決這個問題,先留個坑,等“日”后再來填吧

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容