上节我们用Xcode内置的动作编辑器来让我们的Mr. Pig跳了起来, 这节我们用代码来实现更多的动作.
- 首先我们要把车流给动起来
- 然后我们要给手势创建一些基础动作以便让猪先生在场景里自由的跳来跳去.
让车流动起来
我们有两条公路, 一条往左一条往右. 要使车流在一段时间内移动一段距离, 一个Move Action可以满足我们的需求.
我们有条比其他车辆跑的慢的公交专用线, 比起创建两个Action, 你可以创建一个Action然后调整速度.
在代码中, 动作用SCNAction类的实例来表示, 理所当然之前在Object Library中看到的各种Action都可以用SCNAction的类方法来创建. 比如SCNAction.move(by:duration:)
打开ViewController.swift并添加下面属性:
var driveLeftAction: SCNAction!
var driveRightAction: SCNAction!
这两个属性用来持有向左和向右的动作以便随时复用.
添加下面代码到setupActions()
driveLeftAction = SCNAction.repeatForever(SCNAction.move(by:
SCNVector3Make(-2.0, 0, 0), duration: 1.0))
driveRightAction = SCNAction.repeatForever(SCNAction.move(by:
SCNVector3Make(2.0, 0, 0), duration: 1.0))
SCNAction.repeatForever(_:)这个类方法创建一个特殊的动作来无限循环另一个action.
SCNAction.move(by:duration:)这个类方法创建一个动作来让node按指定的向量移动指定的时间.
添加下面代码到setupTraffic()
//1
for node in trafficNode.childNodes {
//2
if node.name?.contains("Bus") == true {
driveLeftAction.speed = 1.0
driveRightAction.speed = 1.0
} else {
driveLeftAction.speed = 2.0
driveRightAction.speed = 2.0
}
// 3
if node.eulerAngles.y > 0 {
node.runAction(driveLeftAction)
} else {
node.runAction(driveRightAction)
}
}
- 此时setupNodes()已经初始化了trafficNode, SCNNode的childNode属性返回一个子node的数组以供遍历. 这个数组里包含了路上的所有车辆node.
- 如果子node为bus, 你设置 SCNAction.speed 为1.0, 其他情况为2.0. 这样小车的速度就是大巴的两倍.
- 基于车辆只会向左或向右的假设, 这里粗略的判断下车辆的朝向, 然后执行对应的action到相应的node.
command + R检验一下
但稍等, 车辆很快就跑完了, 留下两条空旷的马路等猪先生穿过. 这可不行, 毫无英雄气概.
不用担心, 这个小问题留在游戏渲染循环章节处理, 现在, 先接受目前的状态.
添加猪的移动
接下来的任务是让猪先生可以前后左右的跳起来. 要实现这些, 你需要创建一系列动作并将它们串联/并联起来.
添加下面属性到ViewController
var jumpLeftAction: SCNAction!
var jumpRightAction: SCNAction!
var jumpForwardAction: SCNAction!
var jumpBackwardAction: SCNAction!
添加下面代码到setupActions()的底部
// 1
let duration = 0.2
// 2
let bounceUpAction = SCNAction.moveBy(x: 0, y: 1.0, z: 0, duration:duration * 0.5)
let bounceDownAction = SCNAction.moveBy(x: 0, y: -1.0, z: 0, duration:duration * 0.5)
// 3
bounceUpAction.timingMode = .easeOut
bounceDownAction.timingMode = .easeIn
// 4
let bounceAction = SCNAction.sequence([bounceUpAction, bounceDownAction])
// 5
let moveLeftAction = SCNAction.moveBy(x: -1.0, y: 0, z: 0, duration:duration)
let moveRightAction = SCNAction.moveBy(x: 1.0, y: 0, z: 0, duration:duration)
let moveForwardAction = SCNAction.moveBy(x: 0, y: 0, z: -1.0, duration:duration)
let moveBackwardAction = SCNAction.moveBy(x: 0, y: 0, z: 1.0, duration:duration)
// 6
let turnLeftAction = SCNAction.rotateTo(x: 0, y: convertToRadians(angle:
-90), z: 0, duration: duration, usesShortestUnitArc: true)
let turnRightAction = SCNAction.rotateTo(x: 0, y: convertToRadians(angle:
90), z: 0, duration: duration, usesShortestUnitArc: true)
let turnForwardAction = SCNAction.rotateTo(x: 0, y:
convertToRadians(angle: 180), z: 0, duration: duration, usesShortestUnitArc: true)
let turnBackwardAction = SCNAction.rotateTo(x: 0, y:
convertToRadians(angle: 0), z: 0, duration: duration, usesShortestUnitArc: true)
// 7
jumpLeftAction = SCNAction.group([turnLeftAction, bounceAction, moveLeftAction])
jumpRightAction = SCNAction.group([turnRightAction, bounceAction, moveRightAction])
jumpForwardAction = SCNAction.group([turnForwardAction, bounceAction, moveForwardAction])
jumpBackwardAction = SCNAction.group([turnBackwardAction, bounceAction, moveBackwardAction])
满屏的代码, 一步步来看
- 为了方便
- 创建了两个基础动作可以让猪先生弹起, 落地.
- 更新了一下时间功能这样猪先生跳起时更像受重力影响那样有段滞空时间.
- 串联起弹起和落下两个动作来创建一个弹跳动作.
- 创建了4个Move Action来朝4个方向移动.
- 创建了4个Rotate Action来朝4个方向转身.
- 最终, 通过并联移动, 转身, 弹跳来创建猪先生的跳跃动作
添加移动手势
所有的动作现在都已经准备好了. 剩下的就是要让猪先生响应玩家不同的手势.
首先, 当检测到滑动手势时, 你需要开始动作. 添加下面方法到ViewController
// 1
@objc func handleGesture(_ sender: UISwipeGestureRecognizer) {
// 2
guard game.state == .playing else {
return
}
// 3
switch sender.direction {
case UISwipeGestureRecognizerDirection.up:
pigNode.runAction(jumpForwardAction)
case UISwipeGestureRecognizerDirection.down:
pigNode.runAction(jumpBackwardAction)
case UISwipeGestureRecognizerDirection.left:
if pigNode.position.x > -15 {
pigNode.runAction(jumpLeftAction)
}
case UISwipeGestureRecognizerDirection.right:
if pigNode.position.x < 15 {
pigNode.runAction(jumpRightAction)
}
default:
break
}
}
- 这里将UIGestureRecognizer作为输入定义了一个手势处理器. 你需要检查UIGestureRecognizer来判断用户具体用了哪个手势.
- 当游戏状态不为playing时不响应手势.
- 这里检查用户具体朝哪个方向滑动. 然后执行正确的跳跃动作到MrPig, 并有一个小检查来防止玩家跳出游戏区.
然后, 添加下面4个手势到setupGestures()
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.handleGesture(_:)))
swipeRight.direction = .right
scnView.addGestureRecognizer(swipeRight)
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.handleGesture(_:)))
swipeLeft.direction = .left
scnView.addGestureRecognizer(swipeLeft)
let swipeForward = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.handleGesture(_:)))
swipeForward.direction = .up
scnView.addGestureRecognizer(swipeForward)
let swipeBackward = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.handleGesture(_:)))
swipeBackward.direction = .down
scnView.addGestureRecognizer(swipeBackward)
这把handleGesture(:)注册为向上下左右滑动的手势的事件处理器. 所以当用户滑动时, handleGesture(:)触发接下来的动作.
command + R来测试我们的手势控制系统.
稍等, 又有个问题! 镜头没有跟着猪先生所以他会跳出我们的视线. 不要急, 这是另外一个小问题, 会在游戏渲染循环章节处理.
创建游戏结束序列
还剩一个动作你就能完成本节了 - triggerGameOver动作.
这个小动作由几个基础动作组成, 送我们的猪先生去猪的天国, 然后触发startSplash()来回到splash scene
首先添加属性到ViewController
var triggerGameOver: SCNAction!
添加下面代码到setupActions()
// 1
let spinAround = SCNAction.rotateBy(x: 0, y: convertToRadians(angle: 720), z: 0, duration: 2.0)
let riseUp = SCNAction.moveBy(x: 0, y: 10, z: 0, duration: 2.0)
let fadeOut = SCNAction.fadeOpacity(to: 0, duration: 2.0)
let goodByePig = SCNAction.group([spinAround, riseUp, fadeOut])
// 2
let gameOver = SCNAction.run { (node:SCNNode) -> Void in
self.pigNode.position = SCNVector3(x:0, y:0, z:0)
self.pigNode.opacity = 1.0
self.startSplash()
}
// 3
triggerGameOver = SCNAction.sequence([goodByePig, gameOver])
- 创建了几个基础动作, 一个转720°, 一个向天空移动, 一个慢慢消失. 把这三个动作并联为一个新的动作叫goodByePig, 把猪先生送上天国让他面对自己的命运 :]
- SCNAction.runAction(_:) 创建一个特殊的动作让你可以把逻辑代码像动作一样插入到动作中.
- 创建了一个最终的triggerGameOver动作序列, 首先执行goodByePig动作, 然后执行gameOver动作来返回Splash scene
最后, 添加下面代码到stopGame()底部
pigNode.runAction(triggerGameOver)
可以把下面代码添加到handleGesture(_:):来预览一下升天动作.
stopGame()
return
command + R, 进入游戏后往任意方向滑动来触发的死亡动画. 可怜的猪先生 : [
测试完别忘了删除刚才的测试代码.
目录 (不定期更新中 :] )
1 准备工作、创建项目、Splash Scene
2 过场动画 Transition
3 搭建游戏场景 Game Scene
4 用场景编辑器添加动作
5 用代码添加动作