2018-01-29 scene editor与代码挂钩及物理碰撞检测

Scene editor与代码挂钩

  1. 在编辑器中拖相应的组件并设定属性值,步骤如下图所示:


    2018-01-29 scene editor与代码挂钩及物理碰撞检测_第1张图片
    image.png
  2. 挂钩后,编辑器中的木头都属于WoodNode类的子节点,然后在GameScene中创建了他们对应的实例对象:


    2018-01-29 scene editor与代码挂钩及物理碰撞检测_第2张图片
    image.png

然后通过节点名称将节点赋值于对应的对象

childNode(withName: String)  as! WoodNode
2018-01-29 scene editor与代码挂钩及物理碰撞检测_第3张图片
image.png

然后就可以为所欲为地操作相应的节点啦(比如上图所示的woodNodeH1.setScale(1.5))

学到这里的时候遇到一个很有用的函数

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

其作用是可以通过节点名称遍历并统一进行相应的操作,这里加上//的意思是从根目录从上到下依次遍历所有的树形结构节点,如果以后这个树形结构非常庞大,这样搜索的效率肯定会很低,不过现在作为练习项目就先这样吧,//woodV* 后面有一个*号是指可以通过数字増序依次搜索woodV1、woodV2...

因为这四块木头具有很多的共同属性,所以我将他们都列入WoodNode中,不仅仅如此,其实他们还单独存在于一个sks文件中,然后通过reference的方式添加到GameScene.sks中。设置reference的视觉化操作很简单,就是拖一个reference的控件,然后将reference属性设置为之前编辑的sks文件即可

2018-01-29 scene editor与代码挂钩及物理碰撞检测_第4张图片
image.png

物理碰撞检测

使木头自由下落及设置GameScene的边框物理属性

然后我将这几块木头的isDynamic属性值打开,他们就会收到重力影响自由下落,但是默认他们会掉出屏幕之外,所以我需要在func didMove(to view: SKView)中将GameScene边框的physicsBody设置好,他们就能妥妥地掉在屏幕下边框上了:


2018-01-29 scene editor与代码挂钩及物理碰撞检测_第5张图片
image.png

添加physics body

有以下三种方法添加physics body

  1. Creating simple bodies in the scene editor
2018-01-29 scene editor与代码挂钩及物理碰撞检测_第6张图片
image.png
  1. Creating simple bodies from code


    image.png
  1. Creating custom bodies
    第三种方式就是解决物理外形不一定是texture外形的问题。比如一直猫猫,可能他的物理有效碰撞区域只是身体的那一部分,头和尾巴不是有效的物理碰撞,所以我们可以运用一张纯色的texture并设置为猫猫的有效物理碰撞区域


    2018-01-29 scene editor与代码挂钩及物理碰撞检测_第7张图片
    image.png

好了,现在我们设置一个小需求并予以实现,如下图所示的一组木块,默认状态在屏幕上方。开始时,他们会自由下落到屏幕的下边框。

  1. 现在我们需要检测woodNodeS分别于woodNodeH1、Edge的碰撞情况并打印日志
  2. 点击木块后,木块都会从屏幕中消失
  3. 所有木块消失2秒后,游戏回到初始化的状态


    2018-01-29 scene editor与代码挂钩及物理碰撞检测_第8张图片
    image.png

检测碰撞

检测碰撞的步骤如下:

  1. 将碰撞体分类:虽然游戏中的四块木头都属于WoodNode,但是在检测碰撞时它们是四个独立的个体,所以我们需要将他们设置成四个独立的category。
    首先通过一个struct建好碰撞体的目录结构如下:
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. 设置category bit mask
    然后将四个木块及场景归属到对应的碰撞体结构,可通过编辑器及代码的方式实现。
    编辑器:默认的category mask为极大的值,比如我要将woodH1分类到刚刚建好的WH1中,我只需将category mask值设置为2即可


    2018-01-29 scene editor与代码挂钩及物理碰撞检测_第9张图片
    image.png

代码:其实更偏向于通过代码来统一管理,既然之前已经通过struct定义好碰撞体的category,那么我只需要将各个木块的category赋值即可,这样可能会更好管理吧?

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

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

        physicsBody!.categoryBitMask = PhysicsBodyCategory.Edge
  1. 设置collision bit mask
    默认的category bit mask 和collision都是4294967295,换做二进制数为11111111111111111111111111111111(32个1),也就是节点之间默认加上物理体后,彼此是有效碰撞的,除非你指定想让某节点与具体的一些节点才会有效碰撞,默认是不需要设置此参数,比如,我想让woodNodeS只与woodNodeH1有效碰撞,那么我设置如下
        woodNodeS.physicsBody!.collisionBitMask = PhysicsBodyCategory.WH1

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

  1. 检测碰撞并回调didBegin

我现在是需要在woodNodeS与woodNodeH1、Edge碰撞时检测碰撞并回调相应函数,那么首先我就需要将woodNodeS的contactTestBitMask设置为Edge | WH1,如下所示:

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

让GameScene遵从SKPhysicsContactDelegate协议,这样在碰撞时才会调用didBegin函数

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进行碰撞,碰撞检测后的日志也打印出来了


2018-01-29 scene editor与代码挂钩及物理碰撞检测_第10张图片
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的协议让WoodNode中的实例化对象来遵守,这样就可以区分出你是点的哪个node了。作为新手的我来说,看到这个有点一脸懵逼(即使查了protocol的用法以后也有点懵逼,protocol里面的变量如果不是optional,在实例化的时候比如赋值。但是func呢?为什么我自己另外建了demo发现func不会自动执行呢?留个大大的问号?),先暂时就局限于知其然吧,反正后面会频繁地用到点击事件,在后面的探索中希望能知其所以然。

在WoodNode中设置了以下的点击事件,点击事件会触发将这个node从父节点移除

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


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

    }

所有木块消失2秒后,游戏回到初始化的状态

所有木块都属于同一个parent,所以我可以通过判断parent.children是否为空来判断师傅所有木块都消失了,

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

           
        }
    }

然后定义初始化场景的函数并在//DO SOMETHING HERE的位置
通过run(SKAction.sequence)的方法等2秒后执行初始化场景的操作

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


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

差不多跌跌撞撞就这样了,中间有一些坑,比如判断子节点是否为空我放在了update函数里,这样多多少少会牺牲一些效率吧?正确的做法应该是在减少children的时候interact()再判断一下children是否已经为nil。以事件为导向处理肯定比每一帧调用时来处理效率高吧?但是interact()方法我又放到WoodNode中的,所以暂时不知道怎么去解决这个问题,先留个坑,等“日”后再来填吧

你可能感兴趣的:(2018-01-29 scene editor与代码挂钩及物理碰撞检测)