删除 SKAction 并恢复节点状态
Remove SKAction and restore node state
期望的行为是:当一个动作从一个节点中移除时(例如 removeAction(forKey:)
)它停止动画并且所有由动作引起的变化都被丢弃,所以节点 returns 返回到以前的状态。换句话说,我想实现类似于 CAAnimation
.
的行为
但是当一个SKAction
被删除时,节点仍然改变。这不好,因为要恢复它的状态,我需要确切地知道删除了什么操作。如果我再改变动作,我也将需要更新节点状态恢复。
更新:
特定目的是显示三消游戏中可能的移动。当我展示一个动作时,棋子开始跳动(scale
动作,永远重复)。当用户移动时我想停止显示移动,所以我删除了动作。因此,碎片可能会保持缩小。以后想加更多花哨复杂的动画,所以希望能轻松编辑。
正如@Knight0fDragon 所建议的那样,你最好使用 GKStateMachine
功能,我给你举个例子。
首先在场景中声明player/character的状态
lazy var playerState: GKStateMachine = GKStateMachine(states: [
Idle(scene: self),
Run(scene: self)
])
然后你需要为每个状态创建一个class,在这个例子中我只会给你展示Idle
class
import SpriteKit
import GameplayKit
class Idle: GKState {
weak var scene: GameScene?
init(scene: SKScene) {
self.scene = scene as? GameScene
super.init()
}
override func didEnter(from previousState: GKState?) {
//Here you can make changes to your character when it enters this state, for example, change his texture.
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is Run.Type //This is pretty obvious by the method name, which states can the character go to from this state.
}
override func update(deltaTime seconds: TimeInterval) {
//Here is the update method for this state, lets say you have a button which controls your character velocity, then you can check if the player go over a certain velocity you make it go to the Run state.
if playerVelocity > 500 { //playerVelocity is just an example of a variable to check the player velocity.
scene?.playerState.enter(Run.self)
}
}
}
现在当然在你的场景中你需要做两件事,首先是将角色初始化到某个状态,否则它将保持无状态,所以你可以在 didMove
方法中做到这一点。
override func didMove(to view: SKView) {
playerState.enter(Idle.self)
}
最后但同样重要的是确保场景更新方法调用状态更新方法。
override func update(_ currentTime: TimeInterval) {
playerState.update(deltaTime: currentTime)
}
感谢有用的评论和回答,我找到了自己的解决方案。我认为这里的状态机有点太重了。相反,我创建了一个包装器节点,其主要目的是 运行 动画。它还有一个状态:isAimating
属性。但是,首先,它允许 startAnimating()
和 stopAnimating()
方法彼此靠近、封装,因此更难搞砸。
class ShowMoveAnimNode: SKNode {
let animKey = "showMove"
var isAnimating: Bool = false {
didSet {
guard oldValue != isAnimating else { return }
if isAnimating {
startAnimating()
} else {
stopAnimating()
}
}
}
private func startAnimating() {
let shortPeriod = 0.2
let scaleDown = SKAction.scale(by: 0.75, duration: shortPeriod)
let seq = SKAction.sequence([scaleDown,
scaleDown.reversed(),
scaleDown,
scaleDown.reversed(),
SKAction.wait(forDuration: shortPeriod * 6)])
let repeated = SKAction.repeatForever(seq)
run(repeated, withKey: animKey)
}
private func stopAnimating() {
removeAction(forKey: animKey)
xScale = 1
yScale = 1
}
}
用法:只需将应动画化的所有内容添加到该节点即可。适用于简单的动画效果,例如:淡入淡出、缩放和移动。
期望的行为是:当一个动作从一个节点中移除时(例如 removeAction(forKey:)
)它停止动画并且所有由动作引起的变化都被丢弃,所以节点 returns 返回到以前的状态。换句话说,我想实现类似于 CAAnimation
.
但是当一个SKAction
被删除时,节点仍然改变。这不好,因为要恢复它的状态,我需要确切地知道删除了什么操作。如果我再改变动作,我也将需要更新节点状态恢复。
更新:
特定目的是显示三消游戏中可能的移动。当我展示一个动作时,棋子开始跳动(scale
动作,永远重复)。当用户移动时我想停止显示移动,所以我删除了动作。因此,碎片可能会保持缩小。以后想加更多花哨复杂的动画,所以希望能轻松编辑。
正如@Knight0fDragon 所建议的那样,你最好使用 GKStateMachine
功能,我给你举个例子。
首先在场景中声明player/character的状态
lazy var playerState: GKStateMachine = GKStateMachine(states: [
Idle(scene: self),
Run(scene: self)
])
然后你需要为每个状态创建一个class,在这个例子中我只会给你展示Idle
class
import SpriteKit
import GameplayKit
class Idle: GKState {
weak var scene: GameScene?
init(scene: SKScene) {
self.scene = scene as? GameScene
super.init()
}
override func didEnter(from previousState: GKState?) {
//Here you can make changes to your character when it enters this state, for example, change his texture.
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is Run.Type //This is pretty obvious by the method name, which states can the character go to from this state.
}
override func update(deltaTime seconds: TimeInterval) {
//Here is the update method for this state, lets say you have a button which controls your character velocity, then you can check if the player go over a certain velocity you make it go to the Run state.
if playerVelocity > 500 { //playerVelocity is just an example of a variable to check the player velocity.
scene?.playerState.enter(Run.self)
}
}
}
现在当然在你的场景中你需要做两件事,首先是将角色初始化到某个状态,否则它将保持无状态,所以你可以在 didMove
方法中做到这一点。
override func didMove(to view: SKView) {
playerState.enter(Idle.self)
}
最后但同样重要的是确保场景更新方法调用状态更新方法。
override func update(_ currentTime: TimeInterval) {
playerState.update(deltaTime: currentTime)
}
感谢有用的评论和回答,我找到了自己的解决方案。我认为这里的状态机有点太重了。相反,我创建了一个包装器节点,其主要目的是 运行 动画。它还有一个状态:isAimating
属性。但是,首先,它允许 startAnimating()
和 stopAnimating()
方法彼此靠近、封装,因此更难搞砸。
class ShowMoveAnimNode: SKNode {
let animKey = "showMove"
var isAnimating: Bool = false {
didSet {
guard oldValue != isAnimating else { return }
if isAnimating {
startAnimating()
} else {
stopAnimating()
}
}
}
private func startAnimating() {
let shortPeriod = 0.2
let scaleDown = SKAction.scale(by: 0.75, duration: shortPeriod)
let seq = SKAction.sequence([scaleDown,
scaleDown.reversed(),
scaleDown,
scaleDown.reversed(),
SKAction.wait(forDuration: shortPeriod * 6)])
let repeated = SKAction.repeatForever(seq)
run(repeated, withKey: animKey)
}
private func stopAnimating() {
removeAction(forKey: animKey)
xScale = 1
yScale = 1
}
}
用法:只需将应动画化的所有内容添加到该节点即可。适用于简单的动画效果,例如:淡入淡出、缩放和移动。