const { ccclass, property, requireComponent } = cc._decorator; /** 挂到带 Sprite 的节点上,并在 Inspector 中指定 jungle_shine.effect。 */ @ccclass @requireComponent(cc.Sprite) export default class JungleShine extends cc.Component { @property(cc.EffectAsset) effectAsset: cc.EffectAsset = null; private material: cc.Material = null; private retainedEffect: cc.EffectAsset = null; private delayRemaining: number = 0.15; private elapsed: number = 0; protected onLoad(): void { let sprite = this.getComponent(cc.Sprite); if (!sprite) return; if (this.isEffectReady(this.effectAsset)) { this.createMaterial(sprite, this.effectAsset); } else { // 旧预制体可能仍持有已释放的 EffectAsset,非空不代表可用。 cc.assetManager.loadBundle("publicUI", (error, bundle) => { if (!cc.isValid(this, true)) return; if (error) { cc.error("[JungleShine] 加载 publicUI 失败", error); return; } bundle.load("jungle_shine", cc.EffectAsset, (error, effect: cc.EffectAsset) => { if (!cc.isValid(this, true)) return; if (error || !this.isEffectReady(effect)) { cc.error("[JungleShine] 加载扫光特效失败", error || "EffectAsset 数据无效"); return; } this.effectAsset = effect; this.createMaterial(sprite, effect); }); }); } } private isEffectReady(effect: cc.EffectAsset): boolean { if (!cc.isValid(effect, true)) return false; // Creator 2.4 的类型声明未包含 techniques。 const techniques = (effect as any).techniques; return Array.isArray(techniques) && techniques.length > 0 && techniques.every(technique => technique && Array.isArray(technique.passes) && technique.passes.length > 0); } private createMaterial(sprite: cc.Sprite, effect: cc.EffectAsset): void { // 运行时创建的材质由本组件持有,同时保留其特效资源。 this.retainedEffect = effect.addRef(); let material = new cc.Material(); // @ts-ignore Cocos Creator 2.4 通过 effectAsset 创建运行时材质。 material.effectAsset = effect; material.setProperty("shinePosition", -0.35); material.setProperty("shineWidth", 0.075); material.setProperty("shineStrength", 1.8); material.setProperty("shineAngle", 0.2); material.setProperty("shineColor", [1.0, 0.95, 0.68, 1.0]); this.material = material; sprite.setMaterial(0, material); } protected update(dt: number): void { if (!this.material) { return; } if (this.delayRemaining > 0) { this.delayRemaining = Math.max(0, this.delayRemaining - dt); return; } this.elapsed += dt; let progress = Math.min(1, this.elapsed / 0.72); this.material.setProperty("shinePosition", -0.35 + 1.7 * progress); if (progress >= 1) { this.elapsed = 0; this.delayRemaining = 1.25; this.material.setProperty("shinePosition", -0.35); } } protected onDestroy(): void { if (this.material && cc.isValid(this.material)) { this.material.destroy(); } this.material = null; if (cc.isValid(this.retainedEffect, true)) { this.retainedEffect.decRef(); } this.retainedEffect = null; } }