前文已经为各个精灵新增了Physics Body,设置了三个掩码:
categoryBitMask表明了分属类别。 collisionBitMask告知能与哪些物体碰撞。 contactTestBitMask则告知能与哪些物体接触。现在遗留的问题是如何检测碰撞?难道是在update()方法进行检测:遍历所有的节点,通过判断节点的位置是否有交集吗?天呐!这也太麻烦了。确实,如果通过自己实时检测实在过于劳累,何不让Sprite Kit来帮你代劳,每当物体之间发生碰撞了,立马通知你来处理事件。Bingo!! 显然这里要用协议+代理了,设置场景为代理,每当Sprite Kit检测到碰撞事件发生,就通知GameScene来处理,当前哪里事情都是在协议(Protocol)中声明了。
01.游戏状态在正式开始今天的碰撞检测课程之前,谈谈如何划分游戏各时的状态,仅以Flappy bird游戏为例,简单划分如下:
Maimenu。开始一次游戏、查看排名以及游戏帮助。 Tutorial。考虑到新手对于新游戏的上手,在选择进行一次新游戏时,展示玩法教程显然是一个明确且友好的措施。 Play。正处于游戏的状态。 Falling。Player因为不小心碰到障碍物失败下落时刻。注意:接触障碍物,失败掉落才算! Showingscore。显示得分。 GameeOver。告知游戏结束。为此请打开Lecture05的完成工程,打开GameScene.swift文件,新增游戏状态的枚举声明到enum Layer{}
下方:
enum GameState{ case MainMenu case Tutorial case Play case Falling case Showingscore case GameOver}
当然,我们还需要声明一个变量用于存储游戏场景的状态,请找到GameScene类中let sombrero = SKSpriteNode(imagenamed: "Sombrero")
这条代码,在下方新增三个新变量:
//1var hitGround = false//2var hitObstacle = false//3var gameState: GameState = .Play标识符,记录Player是否掉落至地面。 标识符,记录Player是否碰撞了仙人掌。 游戏状态,默认是正在玩。 02.碰撞检测
正如前面提及的协议+代理方式检测物体之间的碰撞情况。首先请使得类GameScene遵循SKPhysicsContactDelegate
协议:
class GameScene: SKScene,SKPhysicsContactDelegate{...}
接着在dIDMovetoVIEw()方法中设置代理为self
,找到physicsWorld.gravity = CGVector(dx: 0,dy: 0)
这行代码,添加该行代码physicsWorld.contactDelegate = self
。
SKPhysicsContactDelegate
协议中定义了两个可选方法,分别是:
optional public func dIDBeginContact(contact: SKPhysicsContact)
optional public func dIDEndContact(contact: SKPhysicsContact)
分别用于反馈两个物体开始接触、结束接触两个时刻。本文采用第一个方法用户处理物体接触事件。
func dIDBeginContact(contact: SKPhysicsContact) { let other = contact.bodyA.categoryBitMask == Physicscategory.Player ? contact.bodyB : contact.bodyA if other.categoryBitMask == Physicscategory.Ground { hitGround = true } if other.categoryBitMask == Physicscategory.Obstacle { hitObstacle = true }}
contact
包含了接触的所有信息,其中bodyA和bodyB代表两个碰撞的物体,显然发生碰撞的结果只有两种可能:1.Player和地面;2.Player和障碍物。可惜我们无法确实bodyA就是Player,亦或是bodyB就是它。这是有不确定性的,我们需要通过categoryBitMask
来区分“阵营”。一旦确定哪个是Player之后,我们就能取到与之发生接触的other,通过判断其类别来分别置为标志位。
一旦标志位设置之后,我们需要在update()方法中进行处理了!
03.根据游戏状态来处理事件请定位到update()
方法,修改其中的内容:
overrIDe func update(currentTime: CFTimeInterval) { if lastUpdateTime > 0 { dt = currentTime - lastUpdateTime } else { dt = 0 } lastUpdateTime = currentTime switch gameState { case .MainMenu: break case .Tutorial: break case .Play: updateForeground() updatePlayer() //1 checkHitObstacle() //Play状态下检测是否碰撞了障碍物 //2 checkHitGround() //Play状态下检测是否碰撞了地面 break case .Falling: updatePlayer() //3 checkHitGround() //Falling状态下检测是否掉落至地面 此时已经失败了 break case .Showingscore: break case .GameOver: break }}
其中1,2,3中三个方法均是通过状态标志位来处理碰撞事件,请添加checkHitObstacle()
以及checkHitGround()
方法到updateForeground()
方法下方:
// 与障碍物发生碰撞func checkHitObstacle() { if hitObstacle { hitObstacle = false switchToFalling() }}// 掉落至地面func checkHitGround() { if hitGround { hitGround = false playerVeLocity = CGPoint.zero player.zRotation = CGfloat(-90).degreesToradians() player.position = CGPoint(x: player.position.x,y: playableStart + player.size.wIDth/2) runAction(hitGroundAction) switchToShowscore() }}// MARK: - Game States// 由Play状态变为Falling状态func switchToFalling() { gameState = .Falling runAction(SKAction.sequence([ whackAction,SKAction.waitForDuration(0.1),fallingAction ])) player.removeAllActions() stopSpawning()}// 显示分数状态func switchToShowscore() { gameState = .Showingscore player.removeAllActions() stopSpawning()}// 重新开始一次游戏func switchToNewGame() { runAction(popAction) let newScene = GameScene(size: size) let Transition = SKTransition.fadeWithcolor(SKcolor.blackcolor(),duration: 0.5) vIEw?.presentScene(newScene,Transition: Transition)}
完成后自然你发现stopSpawning()
方法并未实现,因为我打算好好讲讲这个。早前在dIDMovetoVIEw()
方法中调用startSpawning()
源源不断地产生障碍物,但是一旦游戏结束,我们所要做的事情有两个:1.停止继续产生障碍物;2.已经在场景中的障碍物停止移动。那么如何制定某个动作Action停止呢?答案是先为这个动作命名(简单来说设置一个Key而已),然后用removeActionForKey()
来移除。
OK,找到startSpawning()
方法,将runAction(overallSequence)
替换成runAction(overallSequence,withKey: "spawn")
;定位到spawnObstacle()
方法,分别设置bottomObstacle和topObstacle精灵的名字,方便之后找到它们并进行 *** 作:
...bottomObstacle.name = "BottomObstacle"worldNode.addChild(bottomObstacle)...topObstacle.name = "topObstacle"worldNode.addChild(topObstacle)...
现在来实现stopSpawning()
方法,在startSpawning()
下方添加就好:
func stopSpawning() { removeActionForKey("spawn") worldNode.enumerateChildNodesWithname("topObstacle",usingBlock: { node,stop in node.removeAllActions() }) worldNode.enumerateChildNodesWithname("BottomObstacle",stop in node.removeAllActions() })}
点击运行,我擦!还没来得及点就掉地上了……好吧,只能在游戏进入一瞬间先让Player向上蹦跶下。添加flapPlayer()
到dIDMovetoVIEw()
方法的最下方。
点击运行,Nice!!Player顺利穿过了障碍,不小心碰到了障碍物,再点击,等等!怎么还能动…好吧,看来touchesBegan(touches: Set<UItouch>,withEvent event: UIEvent?)
点击事件中我们并未根据游戏状态来处理,是时候修改了。
overrIDe func touchesBegan(touches: Set<UItouch>,withEvent event: UIEvent?) { switch gameState { case .MainMenu: break case .Tutorial: break case .Play: flapPlayer() break case .Falling: break case .Showingscore: switchToNewGame() break case .GameOver: break }}
点击运行,失败重新开始游戏…等等貌似还有问题,怎么点击想重新开始游戏会突然掉落到地面上…好吧,请看lecture02中的时间间隔图,匆忙的你找找原因,试试解决吧。
总结以上是内存溢出为你收集整理的用Swift做个游戏Lecture06 —— 碰撞的检测全部内容,希望文章能够帮你解决用Swift做个游戏Lecture06 —— 碰撞的检测所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)