介绍
在本教程中,这是SpriteKit从零开始的第二部分,您将了解约束和操作。 这些功能用于在SpriteKit游戏中轻松添加动作和动画,同时限制游戏中节点的位置和方向。
要跟我一起学习,您可以使用在本系列的第一个教程中创建的项目,也可以从GitHub下载新副本。
本系列中用于游戏的图形可以在GraphicRiver上找到 。 GraphicRiver是查找游戏插图和图形的绝佳资源。
1.自定义节点和场景类
在开始向场景添加约束和动作之前,我们首先需要创建一些类,以便我们可以在代码中使用节点。 根据iOS> Source> Cocoa Touch Class模板创建一个新类PlayerNode ,并确保它是SKSpriteNode
的子类。
如果Xcode在创建类后引发错误,请在import UIKit
语句下方为SpriteKit框架添加import UIKit
语句:
import UIKit
import SpriteKit
接下来,在PlayerNode
类中声明以下三个属性。 这些属性将保留用于限制汽车水平运动的约束。
import UIKit
import SpriteKit
class PlayerNode: SKSpriteNode {
var leftConstraint: SKConstraint!
var middleConstraint: SKConstraint!
var rightConstraint: SKConstraint!
}
创建另一个Cocoa Touch类,并将其命名为MainScene ,使其成为以下类的子类 SKScene
。
![MainScene类](https://i-blog.csdnimg.cn/blog_migrate/da78975a5b7ca965f2b136b0de6c2bf9.png)
在顶部,为SpriteKit框架添加import语句。
import UIKit
import SpriteKit
创建这些类后,打开MainScene.sks ,单击灰色背景以选择场景,打开右侧的Custom Class Inspector ,然后将Custom Class设置为MainScene 。
选择汽车,并按照与场景相同的方式将其类设置为PlayerNode 。 最后,在仍选择汽车的情况下,打开“ 属性”检查器,然后将“ 名称”更改为Player 。
![玩家精灵名称](https://i-blog.csdnimg.cn/blog_migrate/deb4c426f924bb93c0146d65af450b2f.png)
现在我们已经设置了基本类,我们可以开始在代码中创建一些约束。
2.约束
由SKConstraint
类表示的SKConstraint
约束用于限制特定节点的位置和方向。 约束条件可以实现很多变化,因为它们可以相对于场景或相对于其他节点。 约束除了常量值外,还可以使用值的范围,因此场景中的精灵可以固定到特定位置或允许在特定区域内移动。
我们要添加的约束是我们在 PlayerNode
类。 这些约束将用于将汽车锁定在游戏中的三个车道上。
打开MainScene.swift并为PlayerNode!
类型的播放器创建一个属性PlayerNode!
。 此属性将存储对玩家节点的引用。
import UIKit
import SpriteKit
class MainScene: SKScene {
var player: PlayerNode!
}
接下来,我们重写MainScene
类的didMoveToView(_:)
方法:
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
size = view.frame.size
if let foundPlayer = childNodeWithName("Player") as? PlayerNode {
player = foundPlayer
}
let center = size.width/2.0, 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 = false
player.rightConstraint.enabled = false
player.constraints = [player.leftConstraint, player.middleConstraint, player.rightConstraint]
}
让我们逐步看一下代码。 每当视图显示场景时,就会调用didMoveToView(_:)
方法。 调用超类的didMoveToView(_:)
方法后,我们将场景调整为与当前视图相同的大小。 这样可以确保场景始终充满当前设备屏幕的大小并正确缩放。
通过按我们之前给它的名称搜索它,可以访问在Xcode场景编辑器中添加的播放器精灵。 然后,我们将此值分配给player
属性。
在计算了场景的中心并指定了70.0
的常数差之后,我们创建了精灵的约束。 通过使用SKConstraint
类的positionX(_:)
类方法,我们为播放器精灵创建了左,中和右约束。 此方法需要一个SKRange
实例作为参数,在我们的例子中,该实例是一个具有恒定值的范围。 如果要查看SpriteKit中可能的约束和范围,建议您看一下SKConstraint
和SKRange
类引用。
我们禁用左右约束,因为我们不希望这些约束在游戏开始时作用于玩家节点。 最后,我们将这些约束分配给玩家节点的constraints
属性。 此属性在SKNode
类中定义。
在任何模拟器或物理设备上构建并运行游戏。 现在,您应该看到场景已正确缩放,并且汽车居中位于底部。
![场景中居中的汽车](https://i-blog.csdnimg.cn/blog_migrate/33f1d03ad8e0fc94ca02ee8d2ab75503.png)
您会看到,汽车现在被限制在场景的水平中心,并且一旦我们向游戏中添加了一些运动,就可以将其限制在左右车道。
2.行动
SpriteKit中的动作由功能强大的SKAction
类表示。 动作使我们可以轻松地在场景中设置动画和移动精灵。 它们由节点执行,并由SpriteKit API和功能以及约束和物理模拟进行评估。
除了指定动作的作用之外,您还可以通过配置动作来编程动作的工作方式。 例如,您可以暂停和继续操作或配置操作的缓动行为。 由于您可以轻松地加快或减慢某些动作以产生一些有趣的游戏元素,因此可以提供更大程度的控制。
与节点可以具有子节点的方式类似,可以有三种类型的操作可以具有子动作:
- 顺序动作,一个接一个地执行一系列动作
- 群组动作,可同时执行一系列动作
- 重复动作,将单个动作重复给定次数或无限次
您可以以编程方式或在Xcode的场景编辑器中创建动作,我们在上一教程中使用过。 在本教程中,我们将同时使用这两种技术。
打开MainScene.sks ,然后单击场景左下角“ 动画”按钮旁边的图标,以调出“ 动作编辑器视图” 。
![打开动作编辑器视图](https://i-blog.csdnimg.cn/blog_migrate/81ca0b0b034c6521ac6f346efb06aa5e.png)
![动作编辑器视图](https://i-blog.csdnimg.cn/blog_migrate/0e927f131810203e2e4c1549d19bbfec.png)
接下来,在右侧的对象库中向下滚动并找到“ 移动动作”项。 单击并将其拖动到“ 动作编辑器”视图的时间轴中,并将其放在左边缘,如下所示:
![向时间轴添加动作](https://i-blog.csdnimg.cn/blog_migrate/c4f75a06f6d4ebe5d0e13ab0e26d49fb.png)
这将导致动作从0:00开始执行,也就是说,场景一出现就开始执行。 如果放置在其他位置,则该操作将在时间轴顶部显示的时间间隔之后开始执行。
将鼠标悬停在该动作上,然后点击左下方的小箭头图标。 在出现的弹出窗口中,单击左侧的无限按钮。 这将导致该操作永远重复。
![永远重复动作](https://i-blog.csdnimg.cn/blog_migrate/ffd5d5cdee0e4496f3b19ea3a86f86a7.png)
在仍然选择动作的情况下,打开右侧的“ 属性”检查器 ,并将“ Y偏移”值更改为100 。
![设置Y偏移值](https://i-blog.csdnimg.cn/blog_migrate/9bf1485b3e0480c3e032d99924c2369c.png)
其他值指定汽车启动后会立即( 开始时间 )和动画每1秒( 持续时间 )将在Y方向的X方向和100移动0分( 胶印 )。 计时功能属性可用于逐渐开始和/或停止动作。 在这种情况下,我们使用Linear ,这意味着汽车始终以相同的速度行驶。
最后,要测试动作,请单击场景编辑器左下方的“ 动画”按钮。 底部的工具栏应变成蓝色,并且汽车应开始向上移动。
![Xcode场景编辑器中的操作](https://cms-assets.tutsplus.com/uploads/users/855/posts/26340/image/Screen%20Shot%202016-04-17%20at%206.47.57%20PM.png)
实施移动动作后,就该以编程方式创建水平动作了。 在此之前,我们需要添加一些逻辑,以便游戏中的按钮可以控制汽车。
通过选择iOS>源> Swift文件模板创建一个新文件,并将其命名为LaneStateMachine 。
![迅捷文件模板](https://i-blog.csdnimg.cn/blog_migrate/ef8fe3e5c1cc33af5ca9e0987c4755db.png)
将以下代码添加到新文件中:
import GameplayKit
class LaneStateMachine: GKStateMachine {
}
class LaneState: GKState {
var playerNode: PlayerNode
init(player: PlayerNode) {
playerNode = player
}
}
class LeftLane: LaneState {
override func isValidNextState(stateClass: AnyClass) -> Bool {
if stateClass == MiddleLane.self {
return true
}
return false
}
override func didEnterWithPreviousState(previousState: GKState?) {
playerNode.moveInDirection(.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 didEnterWithPreviousState(previousState: GKState?) {
if previousState is LeftLane {
playerNode.moveInDirection(.Right, toLane: self)
} else if previousState is RightLane {
playerNode.moveInDirection(.Left, toLane: self)
}
}
}
class RightLane: LaneState {
override func isValidNextState(stateClass: AnyClass) -> Bool {
if stateClass == MiddleLane.self {
return true
}
return false
}
override func didEnterWithPreviousState(previousState: GKState?) {
playerNode.moveInDirection(.Right, toLane: self)
}
}
这些代码所做的全部工作就是利用新的GameplayKit框架创建一个状态机,该状态机代表游戏中三个通道及其之间的移动。 如果您想更好地了解这段代码在做什么,请查看有关GameplayKit的教程 。
接下来,打开PlayerNode.swift并将以下两个方法添加到PlayerNode
类:
func disableAllConstraints() {
leftConstraint.enabled = false
middleConstraint.enabled = false
rightConstraint.enabled = false
}
func moveInDirection(direction: ButtonDirection, toLane lane: LaneState) {
disableAllConstraints()
let changeInX = (direction == .Left) ? -70.0 : 70.0
let rotation = (direction == .Left) ? M_PI/4 : -M_PI/4
let duration = 0.5
let moveAction = SKAction.moveByX(CGFloat(changeInX), y: 0.0, duration: duration)
let rotateAction = SKAction.rotateByAngle(CGFloat(rotation), duration: duration/2)
rotateAction.timingMode = .EaseInEaseOut
let rotateSequence = SKAction.sequence([rotateAction, rotateAction.reversedAction()])
let moveGroup = SKAction.group([moveAction, rotateSequence])
let completion = SKAction.runBlock { () -> Void in
switch lane {
case is LeftLane:
self.leftConstraint.enabled = true
case is MiddleLane:
self.middleConstraint.enabled = true
case is RightLane:
self.rightConstraint.enabled = true
default:
break
}
}
let sequenceAction = SKAction.sequence([moveGroup, completion])
runAction(sequenceAction)
}
disableAllConstraints()
方法是禁用播放器节点约束的便捷方法。
在moveInDirection(_:toLane:)
,我们确定汽车应水平移动的方向, 向左移动时为-70.0 ,向右移动时为+70.0 。 然后,我们计算正确的角度(以弧度为单位)以使汽车在行驶时旋转。 请注意,正数表示逆时针旋转。
在指定了恒定的持续时间之后,我们分别使用moveByX(_:y:duration:)
和rotateByAngle(_:duration:)
类方法来创建移动和旋转动作。 我们创建了一个旋转序列,将汽车旋转回运动之前的状态。 reversedAction()
方法会自动为您创建操作的反向操作。
接下来,我们创建一个运动组动作来同时执行水平移动和旋转。 最后,我们创建一个完成动作以在执行时执行闭包。 在此关闭中,我们找出汽车当前所在的车道,并为该车道启用正确的约束。
打开ViewController.swift并添加stateMachine
类型的属性LaneStateMachine!
到ViewController
类。
class ViewController: UIViewController {
var stateMachine: LaneStateMachine!
...
}
用以下代码替换ViewController
类中的viewDidLoad()
和didPressButton(_:)
的实现:
override func viewDidLoad() {
super.viewDidLoad()
let skView = SKView(frame: view.frame)
let scene = MainScene(fileNamed: "MainScene")!
skView.presentScene(scene)
view.insertSubview(skView, atIndex: 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.enterState(MiddleLane)
}
@IBAction func didPressButton(sender: UIButton) {
switch sender.tag {
case ButtonDirection.Left.rawValue:
switch stateMachine.currentState {
case is RightLane:
stateMachine.enterState(MiddleLane)
case is MiddleLane:
stateMachine.enterState(LeftLane)
default:
break
}
case ButtonDirection.Right.rawValue:
switch stateMachine.currentState {
case is LeftLane:
stateMachine.enterState(MiddleLane)
case is MiddleLane:
stateMachine.enterState(RightLane)
default:
break
}
default:
break
}
}
在viewDidLoad()
,我们在索引0处插入SKView
对象,以便显示控制按钮,并且还初始化状态机。
在didPressButton(_:)
,我们基于按钮的标签找出用户按下了哪个按钮,并输入当前汽车所在的正确车道。
生成并运行游戏。 按下屏幕底部的向左或向右按钮以使汽车行驶。 您应该看到汽车转弯并朝您按下的按钮方向移动。
![转弯和移动车](https://cms-assets.tutsplus.com/uploads/users/855/posts/26340/image/IMG_0620.jpg)
请注意,按钮图标可能不匹配,如下所示。
![不匹配的按钮](https://cms-assets.tutsplus.com/uploads/users/855/posts/26340/image/IMG_0617.jpg)
要解决此问题,请打开资产目录( Image.xcassets),然后为每个图像( 向左箭头和向右箭头 )将“ 渲染模式”设置为“ 原始图像” 。
![原始图像渲染模式](https://i-blog.csdnimg.cn/blog_migrate/ddc5d1455649728c4e57c01a32a43335.png)
结论
现在,您应该有信心在SpriteKit中使用约束和操作。 如您所见,框架的这些功能使向SpriteKit游戏添加动画和动作非常容易。
在本系列的下一个教程中,我们将研究SpriteKit中的相机节点,以使我们的汽车不会总是移出屏幕顶部。 之后,我们将深入研究SpriteKit中的物理模拟系统,重点是物理物体和碰撞检测。
与往常一样,请务必在下面的评论中留下您的评论和反馈。
翻译自: https://code.tutsplus.com/tutorials/spritekit-from-scratch-constraints-and-actions--cms-26340