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

如何计算SKShader添加到精灵后的当前时长?

这个问题我之前在做SpriteKit动画的时候也碰到过!确实,苹果推荐复用着色器对象来提升性能,但全局的u_time变量在这里就成了绊脚石——它的计时起点是着色器第一次被添加到场景的时刻,所有共用这个着色器的精灵都会共享同一个时间值,完全没法实现每个精灵独立的淡入效果。

下面是我整理的解决方案,既符合苹果的复用最佳实践,又能让每个精灵拥有独立的计时:

核心思路

放弃依赖着色器自带的u_time全局变量,转而给每个精灵添加自定义uniform变量,用来存储该精灵自身的生命周期时长,然后在代码中实时更新这个变量。


步骤1:修改着色器代码

把原来依赖u_time的逻辑,替换成自定义的uniform变量(比如u_spriteElapsedTime)。举个黑白淡入效果的例子:

原着色器代码(问题版本):

void main() {
    vec4 textureColor = texture2D(u_texture, v_tex_coord);
    float gray = dot(textureColor.rgb, vec3(0.299, 0.587, 0.114));
    // 用全局u_time控制淡入,所有精灵共用同一个计时
    float fadeFactor = clamp(u_time / 2.0, 0.0, 1.0);
    gl_FragColor = vec4(mix(vec3(gray), textureColor.rgb, fadeFactor), textureColor.a);
}

修改后的着色器代码:

// 自定义uniform:每个精灵独立的流逝时间
uniform float u_spriteElapsedTime;

void main() {
    vec4 textureColor = texture2D(u_texture, v_tex_coord);
    float gray = dot(textureColor.rgb, vec3(0.299, 0.587, 0.114));
    // 用每个精灵自己的计时控制淡入
    float fadeFactor = clamp(u_spriteElapsedTime / 2.0, 0.0, 1.0);
    gl_FragColor = vec4(mix(vec3(gray), textureColor.rgb, fadeFactor), textureColor.a);
}

步骤2:自定义精灵类管理独立计时

创建一个SKSpriteNode的子类,在其中维护每个精灵自身的流逝时间,并在update方法中实时更新着色器的uniform变量:

import SpriteKit

class TimedSpriteNode: SKSpriteNode {
    private var startTime: TimeInterval?
    
    override init(texture: SKTexture?, color: UIColor, size: CGSize) {
        super.init(texture: texture, color: color, size: size)
        // 关联共享的着色器对象(提前创建好复用)
        guard let sharedShader = SKShader(fileNamed: "FadeBW.fsh") else { return }
        self.shader = sharedShader
        // 初始化自定义uniform的初始值
        self.setValue(0.0, forKey: "u_spriteElapsedTime")
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func update(_ currentTime: TimeInterval) {
        super.update(currentTime)
        // 第一次触发update时记录精灵的出生时间
        if startTime == nil {
            startTime = currentTime
        }
        // 计算当前精灵已经存在的时长
        let elapsed = currentTime - (startTime ?? currentTime)
        // 更新着色器的uniform变量
        self.setValue(elapsed, forKey: "u_spriteElapsedTime")
    }
}

步骤3:在场景中使用自定义精灵

现在你可以正常复用同一个着色器对象,同时每个精灵都会按照自己的出生时间开始计时:

// 假设已经提前创建好共享的着色器实例(也可以在子类内部统一管理)
let sprite1 = TimedSpriteNode(texture: yourTexture, color: .clear, size: CGSize(width: 100, height: 100))
sprite1.position = CGPoint(x: frame.midX - 150, y: frame.midY)
addChild(sprite1)

// 延迟2秒添加第二个精灵,它会从自己的出生时间开始淡入
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    let sprite2 = TimedSpriteNode(texture: yourTexture, color: .clear, size: CGSize(width: 100, height: 100))
    sprite2.position = CGPoint(x: frame.midX + 150, y: frame.midY)
    self.addChild(sprite2)
}

这种方式既遵循了苹果复用着色器的性能建议,又完美解决了全局u_time无法独立控制每个精灵动画的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:58:27