从零开始的SpriteKit:约束和动作

介绍

在本教程中,这是SpriteKit从零开始的第二部分,您将了解约束和操作。 这些功能用于在SpriteKit游戏中轻松添加动作和动画,同时限制游戏中节点的位置和方向。

要跟我一起学习,您可以使用在本系列第一个教程中创建的项目,也可以从GitHub下载新副本。

本系列中用于游戏的图形可以在GraphicRiver找到 。 GraphicRiver是查找游戏插图和图形的绝佳资源。

1.自定义节点和场景类

在开始向场景添加约束和动作之前,我们首先需要创建一些类,以便我们可以在代码中使用节点。 根据iOS> Source> Cocoa Touch Class模板创建一个新类PlayerNode ,并确保它是SKSpriteNode的子类。

PlayerNode类

如果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类

在顶部,为SpriteKit框架添加import语句。

import UIKit
import SpriteKit

创建这些类后,打开MainScene.sks ,单击灰色背景以选择场景,打开右侧的Custom Class Inspector ,然后将Custom Class设置为MainScene

MainScene自定义类

选择汽车,并按照与场景相同的方式将其类设置为PlayerNode 。 最后,在仍选择汽车的情况下,打开“ 属性”检查器,然后将“ 名称”更改为Player

玩家精灵名称

现在我们已经设置了基本类,我们可以开始在代码中创建一些约束。

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中可能的约束和范围,建议您看一下SKConstraintSKRange类引用。

我们禁用左右约束,因为我们不希望这些约束在游戏开始时作用于玩家节点。 最后,我们将这些约束分配给玩家节点的constraints属性。 此属性在SKNode类中定义。

在任何模拟器或物理设备上构建并运行游戏。 现在,您应该看到场景已正确缩放,并且汽车居中位于底部。

场景中居中的汽车

您会看到,汽车现在被限制在场景的水平中心,并且一旦我们向游戏中添加了一些运动,就可以将其限制在左右车道。

2.行动

SpriteKit中的动作由功能强大的SKAction类表示。 动作使我们可以轻松地在场景中设置动画和移动精灵。 它们由节点执行,并由SpriteKit API和功能以及约束和物理模拟进行评估。

除了指定动作的作用之外,您还可以通过配置动作来编程动作的工作方式。 例如,您可以暂停和继续操作或配置操作的缓动行为。 由于您可以轻松地加快或减慢某些动作以产生一些有趣的游戏元素,因此可以提供更大程度的控制。

与节点可以具有子节点的方式类似,可以有三种类型的操作可以具有子动作:

  • 顺序动作,一个接一个地执行一系列动作
  • 群组动作,可同时执行一系列动作
  • 重复动作,将单个动作重复给定次数或无限次

您可以以编程方式或在Xcode的场景编辑器中创建动作,我们在上一教程中使用过。 在本教程中,我们将同时使用这两种技术。

打开MainScene.sks ,然后单击场景左下角“ 动画”按钮旁边的图标,以调出“ 动作编辑器视图”

打开动作编辑器视图
动作编辑器视图

接下来,在右侧的对象库中向下滚动并找到“ 移动动作”项。 单击并将其拖动到“ 动作编辑器”视图的时间轴中,并将其放在左边缘,如下所示:

向时间轴添加动作

这将导致动作从0:00开始执行,也就是说,场景一出现就开始执行。 如果放置在其他位置,则该操作将在时间轴顶部显示的时间间隔之后开始执行。

将鼠标悬停在该动作上,然后点击左下方的小箭头图标。 在出现的弹出窗口中,单击左侧的无限按钮。 这将导致该操作永远重复。

永远重复动作

在仍然选择动作的情况下,打开右侧的“ 属性”检查器 ,并将“ Y偏移”值更改为100

设置Y偏移值

其他值指定汽车启动后会立即( 开始时间 )和动画每1秒( 持续时间 )将在Y方向的X方向和100移动0分( 胶印 )。 计时功能属性可用于逐渐开始和/或停止动作。 在这种情况下,我们使用Linear ,这意味着汽车始终以相同的速度行驶。

最后,要测试动作,请单击场景编辑器左下方的“ 动画”按钮。 底部的工具栏应变成蓝色,并且汽车应开始向上移动。

Xcode场景编辑器中的操作

实施移动动作后,就该以编程方式创建水平动作了。 在此之前,我们需要添加一些逻辑,以便游戏中的按钮可以控制汽车。

通过选择iOS>源> Swift文件模板创建一个新文件,并将其命名为LaneStateMachine

迅捷文件模板

将以下代码添加到新文件中:

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(_:) ,我们基于按钮的标签找出用户按下了哪个按钮,并输入当前汽车所在的正确车道。

生成并运行游戏。 按下屏幕底部的向左或向右按​​钮以使汽车行驶。 您应该看到汽车转弯并朝您按下的按钮方向移动。

转弯和移动车

请注意,按钮图标可能不匹配,如下所示。

不匹配的按钮

要解决此问题,请打开资产目录( Image.xcassets),然后为每个图像( 向左箭头向右箭头 )将“ 渲染模式”设置为“ 原始图像”

原始图像渲染模式

结论

现在,您应该有信心在SpriteKit中使用约束和操作。 如您所见,框架的这些功能使向SpriteKit游戏添加动画和动作非常容易。

在本系列的下一个教程中,我们将研究SpriteKit中的相机节点,以使我们的汽车不会总是移出屏幕顶部。 之后,我们将深入研究SpriteKit中的物理模拟系统,重点是物理物体和碰撞检测。

与往常一样,请务必在下面的评论中留下您的评论和反馈。

翻译自: https://code.tutsplus.com/tutorials/spritekit-from-scratch-constraints-and-actions--cms-26340

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值