如何将 SCNAnimationPlayer“裁剪”到特定的开始和结束时间,iOS 11
How to “crop” SCNAnimationPlayer to a specific start and end time, iOS 11
我有一个带有长动画的 .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 的开始帧播放到结束帧?
关键是找到
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()
}
}
对于任何想在一个镜面反射帧上停止动画的人。顺便说一句,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];
我有一个带有长动画的 .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 的开始帧播放到结束帧?
关键是找到
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()
}
}
对于任何想在一个镜面反射帧上停止动画的人。顺便说一句,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];