You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SceneKit中组合CAKeyframeAnimation实现SCNNode沿正方形路径移动转向问题

问题原因

  1. keyTimes的作用域理解错误:CAKeyframeAnimation的keyTimes是相对于当前动画自身的duration计算,而非和其他动画的全局时间对齐。你为移动动画设置了12秒总时长,但第一个旋转动画duration为3秒,它的keyTimes [0.2, 0.3]对应实际运行时间是动画启动后0.6~0.9秒,完全对不上移动动画第一个拐角的3秒节点(12*0.25);第二个旋转动画duration为6秒,keyTimes [0.45, 0.55]对应运行时间是2.73.3秒,且动画添加后立即执行,02.7秒会一直保持初始角度值,和第一个旋转动画的加性效果叠加,直接导致初始角度错误。
  2. 多动画叠加逻辑错误:两个旋转动画都作用在同一个eulerAngles.y属性,且都开启了isAdditive = true,动画运行时的计算值会直接叠加,没有做时间隔离,自然会出现逻辑混乱。

正确实现方案

推荐直接把所有旋转逻辑合并为一个和移动动画时长完全对齐的关键帧动画,不需要拆分多个动画,逻辑更简单也不会出现时间对齐问题:

func animatePlaneKey(nodeToAnimate: SCNNode){
    let pos = nodeToAnimate.position
    // 移动动画保持和原有逻辑一致
    let moveAnim = CAKeyframeAnimation(keyPath: "position")
    let pos1 = SCNVector3(pos.x, pos.y, pos.z)
    let pos2 = SCNVector3(pos.x + 1 , pos.y, pos.z)
    let pos3 = SCNVector3(pos.x + 1 , pos.y, pos.z + 1)
    let pos4 = SCNVector3(pos.x - 1 , pos.y, pos.z + 1)
    let pos5 = SCNVector3(pos.x, pos.y, pos.z)
    let easeIn  = CAMediaTimingFunction(controlPoints: 0.35, 0.0, 1.0,  1.0)
    moveAnim.values = [pos1,pos2, pos3,pos4,pos5]
    moveAnim.keyTimes = [0,0.25,0.5,0.75,1]
    moveAnim.timingFunctions = [easeIn]
    // 如果需要硬拐角把calculationMode改成.linear即可
    moveAnim.calculationMode = .cubic
    moveAnim.duration = 12
    moveAnim.repeatCount = 1
    moveAnim.isAdditive = false
    moveAnim.autoreverses = false
    // 动画结束后保持最终状态,避免回弹
    moveAnim.fillMode = .forwards
    moveAnim.isRemovedOnCompletion = false

    // 合并为单个旋转动画,和移动动画时长完全对齐
    let rotateAnim = CAKeyframeAnimation(keyPath: "eulerAngles.y")
    let initialHeading = nodeToAnimate.eulerAngles.y
    rotateAnim.values = [
        initialHeading,
        initialHeading, // 0~0.25时间段保持初始角度,走到第一个拐角
        initialHeading - Float(deg2rad(90)), // 0.25~0.3时间段转90度
        initialHeading - Float(deg2rad(90)), // 0.3~0.45保持角度走到第二个拐角
        initialHeading - Float(deg2rad(180)), // 0.45~0.55再转90度
        initialHeading - Float(deg2rad(180)), // 后续同理补充剩下两个拐角的角度
        initialHeading - Float(deg2rad(270)),
        initialHeading - Float(deg2rad(270)),
        initialHeading
    ]
    rotateAnim.keyTimes = [0, 0.22, 0.28, 0.47, 0.53, 0.72, 0.78, 0.97, 1]
    rotateAnim.duration = 12
    rotateAnim.repeatCount = 1
    rotateAnim.isAdditive = false
    rotateAnim.autoreverses = false
    rotateAnim.fillMode = .forwards
    rotateAnim.isRemovedOnCompletion = false
    
    nodeToAnimate.addAnimation(moveAnim, forKey: "movement")
    nodeToAnimate.addAnimation(rotateAnim, forKey: "rotation")
}

如果确实需要拆分多个动画实现,需要给每个旋转动画手动设置启动延迟和填充模式:

// 第一个旋转动画,在移动动画运行3秒时(12*0.25)启动
firstTurnAnim.beginTime = CACurrentMediaTime() + 3
firstTurnAnim.duration = 1 // 转的时长设为1秒即可,不需要设3秒
firstTurnAnim.fillMode = .backwards // 启动前保持初始值,不会提前生效
firstTurnAnim.isAdditive = false

// 第二个旋转动画,在移动动画运行6秒时(12*0.5)启动
secondTurnAnim.beginTime = CACurrentMediaTime() + 6
secondTurnAnim.duration = 1
secondTurnAnim.fillMode = .backwards
secondTurnAnim.isAdditive = false

内容的提问来源于stack exchange,提问作者Damiano Miazzi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 11:24:08