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

Scene editor与代码挂钩

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


    image.png
  2. 挂钩后,编辑器中的木头都属于WoodNode类的子节点,然后在GameScene中创建了他们对应的实例对象:


    image.png

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

childNode(withName: String)  as! WoodNode
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文件即可

image.png

物理碰撞检测

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

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


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外形的问题。比如一直猫猫,可能他的物理有效碰撞区域只是身体的那一部分,头和尾巴不是有效的物理碰撞,所以我们可以运用一张纯色的texture并设置为猫猫的有效物理碰撞区域


    image.png

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

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


    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即可


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


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<UITouch>, 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中的,所以暂时不知道怎么去解决这个问题,先留个坑,等“日”后再来填吧

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容