【问题标题】:How to “crop” SCNAnimationPlayer to a specific start and end time, iOS 11如何将 SCNAnimationPlayer“裁剪”到特定的开始和结束时间,iOS 11
【发布时间】:2018-02-16 23:05:02
【问题描述】:

我有一个动画很长的 .dae 模型。动画包括行走、奔跑、撞击、死亡等片段。我知道每个片段的开始和结束的帧数。我也知道每秒帧数。因此,获取每个段的开始和结束时间非常容易。

我可以将完整的动画作为 SCNAnimationPlayer 对象。我一直在尝试的是制作完整动画的副本,然后设置动画的 timeOffset 和持续时间。

let walkPlayer = fullPlayer.copy() as! SCNAnimationPlayer
walkPlayer.stop()
walkPlayer.animation.timeOffset = walk.offset
walkPlayer.animation.duration = walk.duration

然后我将 walkPlayer 添加回 Bip01 节点(我从中获得完整动画)。

我可以通过调用 animationPlayer(forKey:"walk")?.play() 来轻松播放步行

我可以很容易地更改动画的持续时间和其他方面。但动画总是从第 0 帧开始。无论我在 .timeOffset 中输入什么值,它都会被忽略。

如何从 SCNAnimationPlayer 中找到的 SCNAnimation 从开始帧播放到结束帧?

【问题讨论】:

  • animationPlayer(forKey:) 返回一个带有 CAKeyframeAnimation 类型动画的 CAAnimationGroup。您可以替换数组值和 keyTimes 来裁剪动画,并移动 keyTimes 使其立即开始。不幸的是,animationPlayer(forKey:) 在 iOS11 中已被弃用,并且操作结果将新“导致未定义的行为”:它会清除 keyPath。通过在操作之前对其进行缓存然后替换它来解决该错误。我还看不到 SCNAnimationPlayer 如何替换基于 animationPlayer(forKey:) 的修改,我希望有人发现。
  • 是的。我花了一些时间在 Xcode 8 中使用代码,并且 CAAnimationGroups 运行良好……我对 Xcode 9 有一个想法……将进行一些实验:)

标签: animation scenekit ios11 arkit xcode9-beta


【解决方案1】:

关键是找到

CAAnimation(scnAnimation: animation)

SCNAnimation(caAnimation: animation)

一旦找到这些,我就可以使用 CAAnimationGroup 来“裁剪”整个动画。

这是我正在开发的 Troll.swift。当然,还有很多事情要做,但现在我至少可以让这头可怜的野兽死而复生。

class Troll: SCNNode {
    var body:SCNNode!

    static func timeRange(forStartingAtFrame start:Int, endingAtFrame end:Int, fps:Double = 30) -> (offset:TimeInterval, duration:TimeInterval) {
        let startTime   = self.time(atFrame: start, fps: fps) //TimeInterval(start) / fps
        let endTime     = self.time(atFrame: end, fps: fps) //TimeInterval(end) / fps
        return (offset:startTime, duration:endTime - startTime)
    }

    static func time(atFrame frame:Int, fps:Double = 30) -> TimeInterval {
        return TimeInterval(frame) / fps
    }

    static func animation(from full:CAAnimation, startingAtFrame start:Int, endingAtFrame end:Int, fps:Double = 30) -> CAAnimation {
        let range = self.timeRange(forStartingAtFrame: start, endingAtFrame: end, fps: fps)
        let animation = CAAnimationGroup()
        let sub = full.copy() as! CAAnimation
        sub.timeOffset = range.offset
        animation.animations = [sub]
        animation.duration = range.duration
        return animation
    }


    func load() {

        guard let trollScene = SCNScene(named: "Models.scnassets/troll/troll.dae") else {
            fatalError("Can't load the scene")
        }

        guard let troll_body = trollScene.rootNode.childNode(withName: "troll", recursively: true) else {
            fatalError( "found no troll")
        }

        guard let troll_weapon = trollScene.rootNode.childNode(withName: "troll_weapon", recursively: true) else {
            fatalError( "found no troll_weapon")
        }

        guard let troll_bracelet = trollScene.rootNode.childNode(withName: "troll_bracelet", recursively: true) else {
            fatalError( "found no troll_bracelet")
        }

        guard let bips = trollScene.rootNode.childNode(withName: "Bip01", recursively: true) else {
            fatalError( "found no Bip01")
        }

        guard let fullKey = bips.animationKeys.first else {
            fatalError( "Bip01 got no animation")
        }

        guard let fullPlayer = bips.animationPlayer(forKey: fullKey) else {
            fatalError( "Bip01 got no player for \(fullKey)")
        }
        let fullAnimation = CAAnimation(scnAnimation: fullPlayer.animation)

        self.addChildNode(troll_body)
        self.addChildNode(troll_weapon)
        self.addChildNode(troll_bracelet)
        self.addChildNode(bips)

        self.body = bips
        self.body.removeAllAnimations()

        let walkAnimation = Troll.animation(from: fullAnimation, startingAtFrame: 10, endingAtFrame: 60)
        walkAnimation.repeatCount = .greatestFiniteMagnitude
        walkAnimation.fadeInDuration = 0.3
        walkAnimation.fadeOutDuration = 0.3
        let walkPlayer = SCNAnimationPlayer(animation: SCNAnimation(caAnimation: walkAnimation))
        self.body.addAnimationPlayer(walkPlayer, forKey: "walk")

        let deathAnimation = Troll.animation(from: fullAnimation, startingAtFrame: 1810, endingAtFrame: 1850)
        deathAnimation.isRemovedOnCompletion = false
        deathAnimation.fadeInDuration = 0.3
        deathAnimation.fadeOutDuration = 0.3
        let deathPlayer = SCNAnimationPlayer(animation: SCNAnimation(caAnimation: deathAnimation))
        self.body.addAnimationPlayer(deathPlayer, forKey: "death")

        self.scale     = SCNVector3(0.1,0.1,0.1)

    }

    func walk() {
        print( "+++ walk +++" )
        self.body.animationPlayer(forKey: "walk")?.play()
    }

    func death() {
        print( "+++ death +++" )
        self.body.animationPlayer(forKey: "walk")?.stop(withBlendOutDuration: 0.3)
        self.body.animationPlayer(forKey: "death")?.play()
    }
}

【讨论】:

【解决方案2】:

适用于任何想要在一个镜面反射帧上停止动画的人。顺便说一句,SCNAnimationPlayer.animation 不支持timeOffset 设置很奇怪。

+(SCNAnimationPlayer*)animationPlayer:(SCNAnimationPlayer *)animPlayer onTimeOffset:(CGFloat)timeOffset{
     SCNAnimation *anim = animPlayer.animation;
     CAAnimation *caAnim = [CAAnimation animationWithSCNAnimation:anim];
     caAnim.timeOffset = timeOffset * caAnim.duration;
     caAnim.speed = 0;
     caAnim.usesSceneTimeBase = NO;
     anim = [SCNAnimation animationWithCAAnimation:caAnim];
     animPlayer = [SCNAnimationPlayer animationPlayerWithAnimation:anim];
     return animPlayer;
}

如果您想在进度的一半停止骨骼动画,请将timeOffset 设置为 0.5。

SCNAnimationPlayer *animPlayer = [SCNAnimationPlayer animationPlayer:[node animationPlayerForKey:key] onTimeOffset:0.5];
[node addAnimationPlayer:animPlayer forKey:key];
[animPlayer play];

【讨论】:

    猜你喜欢
    • 2017-04-02
    • 1970-01-01
    • 2015-09-11
    • 1970-01-01
    • 2022-12-08
    • 2011-08-06
    • 2013-12-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多