介绍
继上一篇SpriteKit基础,这一篇将学习constraints和actions。这些功能用于在SpriteKit中为游戏添加动作和动画,同时限制游戏中节点的位置和方向。
您可以使用在本系列的第一个教程中创建的项目,或者在GitHub上下载新副本。
1.自定义Node和Scene
在向Scene中添加constraints和actions之前,我们首先需要创建一些类,以便我们代码中可以使用Node。根据iOS> Source> Cocoa Touch Class模板创建一个新类PlayerNode, 并确保它是SKSpriteNode
的子类。
如果Xcode在创建类后引发错误,请在SpriteKit框架下的语句下面添加导入语句import UIKit
。
import UIKit
import SpriteKit
然后在PlayerNode类中声明以下三个属性。这些属性将用于限制汽车水平运动。
import UIKit
import SpriteKitenum ButtonDirection:Int {case Left = 1case Right = 2
}class PlayerNode: SKSpriteNode {var leftConstraint: SKConstraint!var middleConstraint: SKConstraint!var rightConstraint: SKConstraint!}
创建另一个Cocoa Touch类,并将其称为MainScene,使其成为SKScene
的子类。
在顶部,为SpriteKit框架添加import语句。
import UIKit
import SpriteKit
创建这些类后,打开MainScene.sks,单击灰色背景并选择场景,打开右侧的Custom Class Inspector并将MainScene分配给Custom Class。
选中汽车,并按照与场景相同的方式将PlayerNode类设置为该汽车。最后,在仍然选择汽车的情况下,打开“属性”检查器,然后将“名称”更改为Player。
现在我们已经设置了基本类,我们可以从在代码中创建一些限制开始。
2.Constraints
SpriteKit中的约束由该类表示,SKConstraint
用于限制特定节点的位置和方向。使用Constraints可以实现多种限制,例如,相对于scene或者相对其他nodes。限制除了适用于恒定值之外,还适用于值范围,以便可以将场景中的子画面固定在特定位置或允许它们在特定区域内移动。
PlayerNode中新增加的3个约束属性,这些约束将用于将汽车锁定在游戏的三个轨道上。
打开MainScene.swift并为type player创建一个属性PlayerNode!
。此属性将存储PlayerNode的引用。
import UIKit
import SpriteKitclass MainScene: SKScene {var player: PlayerNode!}
接下来,我们重写MainScene的didMove(_:)
方法:
override func didMove(to view: SKView) {super.didMove(to: view)let size = view.frame.sizeif let foundPlayer = childNode(withName: "Player") as? PlayerNode {player = foundPlayer}let center = size.width * 0.5, difference = CGFloat(70.0)player.leftCOnstraint= SKConstraint.positionX(SKRange(constantValue: center - difference))player.middleCOnstraint= SKConstraint.positionX(SKRange(constantValue: center))player.rightCOnstraint= SKConstraint.positionX(SKRange(constantValue: center + difference))player.leftConstraint.enabled = falseplayer.rightConstraint.enabled = falseplayer.cOnstraints= [player.leftConstraint, player.middleConstraint, player.rightConstraint]}
让我们逐步看一下代码。每当通过view呈现Scene时didMove(_:)方法都会被调用。当调用完super class的didMove(_:)后,
我们将场景调整为与当前视图相同的大小。这样可以确保场景始终正确扩展到当前设备的屏幕尺寸和scales属性。
使用childNode(withName:)查找MainScene.sks中添加的名称为player的sprite。然后,我们将此值分配给属性player
。
在计算了场景的中心并指定了以外的约束之后70.0
,我们创建了精灵约束。使用的SKConstraint的
类方法positionX(_:)
,我们为玩家的精灵左,中,右的约束。此方法需要一个实例SKRange
作为参数,在我们的情况下,该实例是一个具有恒定值的范围。如果要查看SpriteKit中可能的约束和范围,建议您参考类SKRange
e的引用SKConstraint
。
我们禁用左右限制,因为我们不希望它们在游戏开始时起作用。最后,我们将这些约束分配给player的
constraints属性。
在任何模拟器或真机上编译并运行游戏。现在,您可以看到场景的大小正确,汽车位于底部的中心。
您可以看到,一旦我们向游戏中添加了一些运动,汽车就可以被限制在场景的水平中心,并且可以被限制在左右车道上。
2.Actions
SpriteKit动作由功能强大的类表示SKAction
。动作使我们可以轻松地在场景中设置动画和移动精灵。它们由我们执行并由SpriteKit API计算得出,并与物理限制和模拟一起工作。
除了指定动作的作用之外,您还可以配置和编程动作的工作方式。例如,您可以暂停和继续操作或为该操作配置减弱的行为。这提供了更大程度的控制,因此您可以轻松地加快或减慢某些动作以产生一些有趣的游戏元素。
与nodes可以具有child nodes的方式相同,有三种Actions可以具有child Actions:
- sequence actions,一组actions,一个接着一个执行
- group actions,一组actions,同时执行
- repeating actions,重复执行一个action,可以指定执行次数或者无限次
您可以以编程方式或在上一教程中使用的Xcode场景编辑器中创建动作。在本教程中,我们将同时使用这两种技术。
打开MainScene.sks并单击场景左下角“Animate”按钮旁边的图标以打开“Action Editor View”。
然后,导航到右侧“对象库”的末尾并找到移动操作“Move Action”项。单击并将其拖动到“动作编辑器视图”时间轴,并将其放置在左下角,如下所示。
这将使动作从0:00开始播放,即在场景出现时开始播放。如果将其放在其他位置,则该操作将在时间轴顶部显示的时间间隔之后开始执行。
鼠标右键该动作,然后单击左下角的小箭头图标。将出现一个弹出窗口,单击左侧的无限按钮。这会使动作永远重复下去。
在仍然选择动作的情况下,打开右侧的“属性”检查器,并将“ Y偏移”值更改为100。
其他属性设定方面,如Start Time,该车将立即启动动画(开始时间),每1秒(持续时间),它会在X方向不移动0,而在Y方向移动100。 Timeing Funcation属性可用于逐渐开始和/或停止动作。在这种情况下,我们使用Linear,这意味着汽车始终以相同的速度行驶。
最后,测试动作,单击场景编辑器左下方的“Animate”按钮。底部的工具栏将变为蓝色,汽车将开始向上移动。
有了动作动作之后,就该以编程方式创建水平动作了。在此之前,我们需要添加一些逻辑,以便游戏按钮可以控制汽车。
通过选择iOS> Source> Swift File模板创建一个新文件,并将其命名为LaneStateMachine。
将以下代码添加到新文件中:
import GameplayKitclass LaneStateMachine: GKStateMachine {}class LaneState: GKState {var playerNode: PlayerNodeinit(player: PlayerNode) {playerNode = player}
}class LeftLane: LaneState {override func isValidNextState(_ stateClass: AnyClass) -> Bool {if stateClass == MiddleLane.self {return true}return false}override func didEnter(from previousState: GKState?) {playerNode.moveInDirection(direction: .Left, toLane: self)}
}class MiddleLane: LaneState {override func isValidNextState(_ stateClass: AnyClass) -> Bool {if stateClass == LeftLane.self || stateClass == RightLane.self {return true}return false}override func didEnter(from previousState: GKState?) {if previousState is LeftLane {playerNode.moveInDirection(direction: .Right, toLane: self)} else if previousState is RightLane {playerNode.moveInDirection(direction: .Left, toLane: self)}}
}class RightLane: LaneState {override func isValidNextState(_ stateClass: AnyClass) -> Bool {if stateClass == MiddleLane.self {return true}return false}override func didEnter(from previousState: GKState?) {playerNode.moveInDirection(direction: .Right, toLane: self)}
}
所有这些代码所做的就是使用新的GameplayKit框架创建一个状态机,该状态机表示游戏中三个轨迹以及它们之间的运动。如果您想更好地了解这段代码在做什么,请查看我的GameplayKit教程。
然后打开PlayerNode.swift并将以下两个方法添加到类中PlayerNode
。
func disableAllConstraints() {leftConstraint.enabled = falsemiddleConstraint.enabled = falserightConstraint.enabled = false}func moveInDirection(direction: ButtonDirection, toLane lane: LaneState) {disableAllConstraints()let changeInX = (direction == .Left) ? -70.0 : 70.0let rotation = (direction == .Left) ? Double.pi/4 : -Double.pi/4let duration = 0.5let moveAction = SKAction.moveBy(x: CGFloat(changeInX), y: 0.0, duration: duration)let rotateAction = SKAction.rotate(byAngle: CGFloat(rotation), duration: duration/2)rotateAction.timingMode = .easeInEaseOutlet rotateSequence = SKAction.sequence([rotateAction, rotateAction.reversed()])let moveGroup = SKAction.group([moveAction, rotateSequence])let completion = SKAction.run { () -> Void inswitch lane {case is LeftLane:self.leftConstraint.enabled = truecase is MiddleLane:self.middleConstraint.enabled = truecase is RightLane:self.rightConstraint.enabled = truedefault:break}}let sequenceAction = SKAction.sequence([moveGroup, completion])run(sequenceAction)}
该方法disableAllConstraints()
是用于禁用player node上的限制的便捷方法。
在中moveInDirection(_:toLane:)
,我们确定汽车水平移动的方向,向左移动时为-70.0,向右移动时为+70.0。然后,我们计算出正确的角度(以弧度为单位)以使汽车在运动中旋转。注意,正数表示逆时针旋转。
指定持续时间后,我们使用classmoveByX(_:duration:)
和method创建运动和旋转动作rotateByAngle(_:duration)
。我们创建了一个旋转序列,以将汽车旋转回运动之前的状态。该方法会reversedAction()
自动为您创建操作的逆过程。
然后,我们创建一组动作动作以执行水平移动并同时旋转。最后,我们创建一个补充动作以在执行时执行闭包。在此关闭中,我们确定汽车在哪个车道上并为该车道激活正确的限制。
打开GameViewController.swift并向类添加stateMachine
类型LaneStateMachine!
的属性ViewController
。
替换方法 viewDidLoad()
和 didPressButton(_:)
类的实现GameViewController.,如下所示:
var stateMachine: LaneStateMachine!override func viewDidLoad() {super.viewDidLoad()let skView = SKView(frame: self.view.frame)let scene:MainScene = SKScene(fileNamed: "MainScene") as! MainSceneskView.presentScene(scene)view.insertSubview(skView, at: 0)let left = LeftLane(player: scene.player)let middle = MiddleLane(player: scene.player)let right = RightLane(player: scene.player)stateMachine = LaneStateMachine(states: [left, middle, right])stateMachine.enter(MiddleLane.self)}@IBAction func didPressButton(sender: UIButton) {switch sender.tag {case ButtonDirection.Left.rawValue:switch stateMachine.currentState {case is RightLane:stateMachine.enter(MiddleLane.self)case is MiddleLane:stateMachine.enter(LeftLane.self)default:break}case ButtonDirection.Right.rawValue:switch stateMachine.currentState {case is LeftLane:stateMachine.enter(MiddleLane.self)case is MiddleLane:stateMachine.enter(RightLane.self)default:break}default:break}}
在中viewDidLoad()
,我们将SKView插入到
索引0处添加对象,以便控制按钮可见,并且还初始化状态机。
在didPressButton(_:)
,我们会根据按钮标签确定用户按下的按钮,并输入汽车所在的正确车道。
编译并运行游戏。同时按下屏幕底部的向左和向右按钮以使汽车行驶。您可以看到汽车转弯并朝所按按钮的方向移动。
请注意,按钮图标可能不兼容,如下所示。
要更正此问题,请打开资产目录 (Image.xcassets),并为两个图像(向左箭头和向右箭头)将“渲染模式”更改为“原始图像”。
结论
现在,您应该有信心使用SpriteKit的限制和操作。如您所见,这些框架功能使在SpriteKit上向游戏添加动画和动作非常容易。
在本系列的下一个教程中,我们将看一下SpriteKit的相机节点,这样我们的汽车就不会出现在屏幕顶部。之后,我们将深入研究SpriteKit的物理仿真系统,重点关注物理物体和碰撞检测。
与往常一样,在下面的评论中留下您的评论和反馈。