如何为SKActions动作序列设置固定总时长?现有实现存在局限
解决固定总时长的衰减旋转动画问题
我完全懂你的困扰——想用SKAction实现一个总时长严格控制在24秒的衰减摆动动画,结果现有API没法直接指定总时长,只能靠手动硬编码初始时长,每次调整弹性系数都要重新计算,实在太不灵活了。
现有实现的痛点
你的现有代码逻辑是对的,但确实存在这些问题:
- 依赖手动设定的
START_DURATION,完全是“hack”式的硬编码,没有理论依据 - 一旦修改
SPRING_TENSION,必须重新计算初始时长才能保持总时长24秒 - 循环终止条件只看角度大小,和总时长没有直接关联,无法精准控制
#define START_ANGLE M_PI/4.0 #define SPRING_TENSION 0.9 #define START_DURATION 1.3 // Hack! NSMutableArray *animationA = [NSMutableArray array]; double a = START_ANGLE; double d = START_DURATION; while (a * a > 0.01) { // 向另一侧摆动 SKAction *a1 = [SKAction rotateToAngle:a duration:d]; a1.timingMode = SKActionTimingEaseIn; [animationA addObject:a1]; // 回到初始位置 SKAction *a2 = [SKAction rotateToAngle:0 duration:d]; a2.timingMode = SKActionTimingEaseOut; [animationA addObject:a2]; // 根据弹性系数降低摆动幅度 a *= -SPRING_TENSION; d *= SPRING_TENSION; } SKAction *seqA = [SKAction sequence:animationA]; [mySprite runAction:seqA];
更灵活的解决方案:通过数学推导自动计算初始时长
我们可以利用等比数列求和的原理,先推导总时长和初始时长的关系,然后自动计算出符合24秒总时长的初始值,彻底摆脱手动硬编码。
推导过程
每次摆动循环(摆到目标角度+摆回初始位置)的时长是2*d,而d是按SPRING_TENSION(记为k)的幂次衰减的:d0, d0*k, d0*k², d0*k³,...
当0 < k < 1时,这个无穷等比数列的和是d0/(1 - k),所以总时长T的公式是:
T = 2 * (d0 + d0*k + d0*k² + ...) = 2*d0/(1 - k)
反过来就能算出初始时长:
d0 = T*(1 - k)/2
这里T就是我们要的24秒,k是SPRING_TENSION。
改进后的代码
#define TOTAL_DURATION 24.0 #define START_ANGLE M_PI/4.0 #define SPRING_TENSION 0.9 NSMutableArray *animationA = [NSMutableArray array]; double k = SPRING_TENSION; // 自动计算初始时长,保证总时长接近24秒 double d = TOTAL_DURATION * (1 - k) / 2.0; double a = START_ANGLE; // 可以调整终止条件,比如当剩余时长占总时长的比例小于0.1%时停止 double remainingDurationThreshold = TOTAL_DURATION * 0.001; double currentTotalDuration = 0.0; while (currentTotalDuration + 2*d < TOTAL_DURATION - remainingDurationThreshold) { // 向另一侧摆动 SKAction *a1 = [SKAction rotateToAngle:a duration:d]; a1.timingMode = SKActionTimingEaseIn; [animationA addObject:a1]; // 回到初始位置 SKAction *a2 = [SKAction rotateToAngle:0 duration:d]; a2.timingMode = SKActionTimingEaseOut; [animationA addObject:a2]; currentTotalDuration += 2*d; // 更新角度和时长 a *= -k; d *= k; } // 最后如果还有剩余时长,补一个小幅度的摆动来凑满总时长(可选) double remainingDuration = TOTAL_DURATION - currentTotalDuration; if (remainingDuration > 0) { SKAction *finalSwing = [SKAction rotateToAngle:a duration:remainingDuration/2]; finalSwing.timingMode = SKActionTimingEaseIn; SKAction *finalReturn = [SKAction rotateToAngle:0 duration:remainingDuration/2]; finalReturn.timingMode = SKActionTimingEaseOut; [animationA addObjectsFromArray:@[finalSwing, finalReturn]]; } SKAction *seqA = [SKAction sequence:animationA]; [mySprite runAction:seqA];
方案优势
- 完全不需要手动计算初始时长,修改
SPRING_TENSION或TOTAL_DURATION后代码会自动适配 - 终止条件关联总时长,动画结束时的总时长更精准
- 可选的补时逻辑可以让总时长严格等于24秒,避免微小误差
内容的提问来源于stack exchange,提问作者Dylan
相关产品推荐
相关产品推荐

