前文已经为各个精灵新增了Physics Body,设置了三个掩码:

  • categoryBitMask表明了分属类别。
  • collisionBitMask告知能与哪些物体碰撞。
  • contactTestBitMask则告知能与哪些物体接触。

现在遗留的问题是如何检测碰撞?难道是在update()方法进行检测:遍历所有的节点,通过判断节点的位置是否有交集吗?天呐!这也太麻烦了。确实,如果通过自己实时检测实在过于劳累,何不让Sprite Kit来帮你代劳,每当物体之间发生碰撞了,立马通知你来处理事件。Bingo!! 显然这里要用协议+代理了,设置场景为代理,每当Sprite Kit检测到碰撞事件发生,就通知GameScene来处理,当前哪里事情都是在协议(Protocol)中声明了。

01.游戏状态

在正式开始今天的碰撞检测课程之前,谈谈如何划分游戏各时的状态,仅以Flappy bird游戏为例,简单划分如下:

  • Maimenu。开始一次游戏、查看排名以及游戏帮助。
  • Tutorial。考虑到新手对于新游戏的上手,在选择进行一次新游戏时,展示玩法教程显然是一个明确且友好的措施。
  • Play。正处于游戏的状态。
  • FallingPlayer因为不小心碰到障碍物失败下落时刻。注意:接触障碍物,失败掉落才算!
  • 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")这条代码,在下方新增三个新变量:

//1
var hitGround = false
//2
var hitObstacle = false
//3
var gameState: GameState = .Play
  1. 标识符,记录Player是否掉落至地面。
  2. 标识符,记录Player是否碰撞了仙人掌。
  3. 游戏状态,默认是正在玩。

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包含了接触的所有信息,其中bodyAbodyB代表两个碰撞的物体,显然发生碰撞的结果只有两种可能: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()方法,分别设置bottomObstacletopObstacle精灵的名字,方便之后找到它们并进行操作:

...
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 —— 碰撞的检测的更多相关文章

  1. Swift开发Sprite Kit游戏实践三:物理推力与碰撞检测

    物理推力为了避免monkey“落下”,需要用物理推力让它重新跳起来。现在轮到为敌人的sprite添加physicsbody了,来制造碰撞效果。首先将如下所示添加至GameScene.swift最顶端:这里要做的就是为每个sprite创建类别。此处执行针对两个physicsbody相撞的method。physicsbody不一定要跟sprite的形状完全吻合,近似就好。这里用的是圆形,半径设为sprite的1/4,免得碰撞效果太猛。把dynamic关掉,实现物理控制sprite。防止引力对sprite的影响

  2. 用Swift做个游戏Lecture06 —— 碰撞的检测

    collisionBitMask告知能与哪些物体碰撞。确实,如果通过自己实时检测实在过于劳累,何不让SpriteKit来帮你代劳,每当物体之间发生碰撞了,立马通知你来处理事件。正处于游戏的状态。Player因为不小心碰到障碍物失败下落时刻。标识符,记录Player是否碰撞了仙人掌。好吧,请看lecture02中的时间间隔图,匆忙的你找找原因,试试解决吧。

  3. angularjs – 为什么在使用离子滚动时需要$parent来启用ng-click功能?

    我使用以下版本:>Ionic,v1.0.0-beta.14>AngularJSv1.3.6路线配置:控制器配置:查看配置:正如您在视图中看到的,我需要在以下字段中使用$parent来使其工作:>ng-model=“$parent.search_contact”>ng-repeat=“联系$parent.filteredContacts=(contactsToBeInvited|filter:sea

  4. angularjs – Angular TypeError:无法在字符串”上创建属性

    单击“添加”后尝试清除输入框,就像这个人在thistutorial中所做的那样.我使用这个简短的代码重新创建了错误,而没有使用API.您还可以查看Plunker.HTMLJS似乎我可以添加1项,但在第二个我得到此错误:您没有以正确的方式清理联系人对象.在Add()函数中,更改:至:ForkedPlunker

  5. angular2的组件传值Input和Output

    #parent-to-child

  6. ios – 雪碧套装套装和最大为跳

    但是有些情况下,当与空气…

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部