swift 如何在SpriteKit中实现Move操作

3pvhb19x  于 2023-04-04  发布在  Swift
关注(0)|答案(1)|浏览(134)

SpriteKit有一个名为.moveBy(x:y:duration:)的动作,我想自己尝试并实现这个动作,发现它出奇的困难。

static func moveX(by amount: CGFloat, duration: TimeInterval) -> SKAction {
    let speed = amount / duration
    var lastElapsedTime = 0.0
    return .customAction(withDuration: duration) { node, elapsedTime in
        let timePassed = elapsedTime - lastElapsedTime
        node.position.x += timePassed * speed
        lastElapsedTime = elapsedTime
    }
}

最初的实现看似简单,但是,当您考虑到如果这个SKAction的同一个示例被同一个节点执行多次,但开始时间不同,会发生什么情况时,问题就开始出现了。
例如:

let move = SKAction.moveX(by: 5.0, duration: 1)
node.run(.group[move, .sequence[.wait(forDuration: 0.25), move]])

在这种情况下,lastElapsedTime变量在同一个节点上运行时被写入两次时会中断 (我知道即使在不同的节点上运行也会存在这个问题。但是,我通过使用Key-Value对来解决这个问题。所以这不是一个问题)。在这种情况下,timePassed将不准确。
有没有人知道如何实现这个自定义操作,即使同一个示例在不同的时间运行在同一个节点上也能正常工作?请记住,elapsedTime之间的间隔并不相同,因为计时模式并不总是线性的。任何想法都将受到极大的赞赏。

voase2hg

voase2hg1#

正如你所发现的,你的尝试不起作用的原因是因为只有一个lastElapsedTime。一旦第二个动作(0.25秒后开始的动作)开始,单个lastElapsedTime变量就会被第二个动作的运行时间覆盖。
换句话说,闭包同时接收第一个和第二个操作的运行时间,您无法区分它是哪一个。
解决这个问题的一个简单方法是不要重复使用move操作两次。

let move1 = SKAction.moveX(by: 5.0, duration: 1)
let move2 = SKAction.moveX(by: 5.0, duration: 1)
node.run(.group([move1, .sequence([.wait(forDuration: 0.25), move2]])))

现在有两个lastElapsedTime
内置的move不是使用customAction实现的。比较这些方法创建的类型:

type(of: SKAction.moveBy(x: 100, y: 100, duration: 1)) // SKMove
type(of: SKAction.customAction(withDuration: 1, actionBlock: { _, _ in })) // SKCustomAction

你可以在这里找到这些标题:SKMove.hSKCustomAction.h。它们分别有SKCMoveSKCCustomAction作为其成员。
似乎每个内置动作都有这样一个类,SKAction中的工厂可能只是调用了这些类中的工厂。
我还发现在SKCMove中有一个名为cpp_updateWithTargetForTime(SKCNode*, double)的方法。这似乎也存在于所有以SKC为前缀的action类中。据推测,如何执行action的实际实现都写在这个方法中。
该方法可能可以访问我们的代码无法访问的内容,包括但不限于自上一帧以来的时间,因此能够计算节点应该移动多少。
从技术上讲,您也可以获得自场景中最后一帧以来的时间,如下所示:

// in MyScene
var timeSinceLastFrame: Double = 0
var lastFrame: Double?
override func update(_ currentTime: TimeInterval) {
    if let lastFrame = self.lastFrame {
        timeSinceLastFrame = currentTime - lastFrame
    }
    lastFrame = currentTime
}

然后,您可以编写一个moveX方法,其工作方式与内置方法非常相似,但仅限于MyScene

static func moveX(by amount: CGFloat, duration: CGFloat) -> SKAction {
    let speed = amount / duration
    return .customAction(withDuration: duration) { node, elapsedTime in
        if elapsedTime > 0 {
            let timePassed = (node.scene as! MyScene).timeSinceLastFrame
            node.position.x += timePassed * speed
        }
    }
}

相关问题