You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
7.2 KiB
222 lines
7.2 KiB
|
1 week ago
|
|
||
|
|
import { _decorator, Component, Node, Sprite, director, SpriteFrame, sp, AssetManager, Asset, assetManager, isValid } from 'cc';
|
||
|
|
import { GEvent } from '../../mx/module/event/GEvent';
|
||
|
|
import { GBundle } from '../game/ConfigRes';
|
||
|
|
|
||
|
|
const { ccclass, property } = _decorator;
|
||
|
|
|
||
|
|
/**加载 加载动画 皮肤的组件*/
|
||
|
|
@ccclass('lLoadSpine')
|
||
|
|
export class lLoadSpine extends Component {
|
||
|
|
@property(sp.Skeleton)
|
||
|
|
kaichangSpine: sp.Skeleton = null;
|
||
|
|
@property(sp.Skeleton)
|
||
|
|
zhuizhuSpine: sp.Skeleton = null;
|
||
|
|
|
||
|
|
path: string = "";
|
||
|
|
|
||
|
|
key: string = "";
|
||
|
|
|
||
|
|
|
||
|
|
animation: string = "开场动画";
|
||
|
|
skinName: string = "默认皮肤";
|
||
|
|
|
||
|
|
langSp: sp.Skeleton = null;
|
||
|
|
curentSpineData: sp.SkeletonData = null;
|
||
|
|
/** 已成功加载并挂上的资源路径,用于同皮肤重复进入时跳过后台 load */
|
||
|
|
private _lastLoadedAssetPath: string = '';
|
||
|
|
/** 上次已按此 itemid 应用过路径;节点隐藏时 off 了事件,靠它与 CurrentSkin 对比补换装 */
|
||
|
|
private _storedSkinItemId: number | undefined = undefined;
|
||
|
|
private isLoading: boolean = false; // ✅ 添加加载锁
|
||
|
|
private abortController: { aborted: boolean } = null; // ✅ 添加取消机制
|
||
|
|
|
||
|
|
onLoad() {
|
||
|
|
this.langSp = this.getComponent(sp.Skeleton);
|
||
|
|
}
|
||
|
|
|
||
|
|
onEnable() {
|
||
|
|
GEvent.Ins.on(GEvent.CHANGE_SKIN, this.onSkinChange, this);
|
||
|
|
const cur = gg.skin.CurrentSkin;
|
||
|
|
if (this._storedSkinItemId !== cur) {
|
||
|
|
this.applySkinPathAndReload(cur);
|
||
|
|
} else {
|
||
|
|
this.updateLang();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
onDisable() {
|
||
|
|
GEvent.Ins.off(GEvent.CHANGE_SKIN, this.onSkinChange, this);
|
||
|
|
if (this.abortController) {
|
||
|
|
this.abortController.aborted = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** RoleSkin 换装时发出 CHANGE_SKIN(皮肤 itemid) */
|
||
|
|
private onSkinChange(skinItemId?: number) {
|
||
|
|
this.applySkinPathAndReload(skinItemId ?? gg.skin.CurrentSkin);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 按 itemid 更新 loadSpineName 并加载;换皮肤时的释放逻辑只在 updateLang 里处理 */
|
||
|
|
private applySkinPathAndReload(skinItemId: number) {
|
||
|
|
let skinConfig = gg.skin.getSkinConfig(skinItemId);
|
||
|
|
if (!skinConfig) {
|
||
|
|
console.warn('lLoadSpine: 无皮肤配置 itemid=', skinItemId);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._storedSkinItemId = skinItemId;
|
||
|
|
this.path = skinConfig.loadSpineName;
|
||
|
|
this.key = 'spine/入场加载动画';
|
||
|
|
this.updateLang();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 与 load 时一致的 Spine 资源相对路径(不含扩展名) */
|
||
|
|
private getAssetPath(): string {
|
||
|
|
const k = this.key || this.node.name;
|
||
|
|
return `${k}/${this.path}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
async updateLang() {
|
||
|
|
// ✅ 1. 基础检查
|
||
|
|
if (!this.node.isValid || this.langSp == null) return;
|
||
|
|
|
||
|
|
const assetPath = this.getAssetPath();
|
||
|
|
if (
|
||
|
|
this._lastLoadedAssetPath === assetPath &&
|
||
|
|
this.curentSpineData &&
|
||
|
|
isValid(this.curentSpineData) &&
|
||
|
|
this.langSp.skeletonData === this.curentSpineData
|
||
|
|
) {
|
||
|
|
console.log("spineAsset 已加载", this.path, this.key);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✅ 2. 取消之前的加载
|
||
|
|
if (this.abortController) {
|
||
|
|
this.abortController.aborted = true;
|
||
|
|
}
|
||
|
|
this.abortController = { aborted: false };
|
||
|
|
|
||
|
|
// ✅ 3. 防止并发加载
|
||
|
|
if (this.isLoading) return;
|
||
|
|
this.isLoading = true;
|
||
|
|
|
||
|
|
const currentAbort = this.abortController;
|
||
|
|
|
||
|
|
try {
|
||
|
|
console.log("更新 lLoadSpine 组件", this.path, this.key);
|
||
|
|
|
||
|
|
let bundleName = GBundle.Comm2;
|
||
|
|
let loop = this.langSp.loop;
|
||
|
|
let bundle = await this.loadBundle(bundleName);
|
||
|
|
|
||
|
|
// ✅ 4. 检查是否被取消或节点失效
|
||
|
|
if (currentAbort.aborted || !this.node.isValid) {
|
||
|
|
this.isLoading = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!bundle) {
|
||
|
|
this.isLoading = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let spineAsset = await this.loadRes(bundle, assetPath, sp.SkeletonData) as sp.SkeletonData;
|
||
|
|
|
||
|
|
// ✅ 5. 再次检查
|
||
|
|
if (currentAbort.aborted || !this.node.isValid || !spineAsset) {
|
||
|
|
if (spineAsset) spineAsset.decRef(); // 清理未使用的资源
|
||
|
|
this.isLoading = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.curentSpineData == spineAsset) {
|
||
|
|
this._lastLoadedAssetPath = assetPath;
|
||
|
|
this.isLoading = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let oldres = this.curentSpineData;
|
||
|
|
this.curentSpineData = spineAsset;
|
||
|
|
spineAsset.addRef();
|
||
|
|
this._lastLoadedAssetPath = assetPath;
|
||
|
|
|
||
|
|
console.log("spineAsset 加载完成", this.path, this.key);
|
||
|
|
|
||
|
|
// ✅ 6. 设置资源
|
||
|
|
this.langSp.clearTrack(0); // ✅ 先清除动画
|
||
|
|
this.langSp.skeletonData = spineAsset;
|
||
|
|
|
||
|
|
// ✅ 7. 再次检查节点有效性
|
||
|
|
if (this.node.isValid && this.animation) {
|
||
|
|
//设置皮肤
|
||
|
|
|
||
|
|
this.langSp.setSkin(this.skinName);
|
||
|
|
this.langSp.setAnimation(0, this.animation, loop);
|
||
|
|
this.kaichangSpine.setAnimation(0, '开场动画', loop);
|
||
|
|
this.zhuizhuSpine.setAnimation(0, '怪物追逐', loop);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✅ 8. 延迟释放旧资源(给 Spine 足够时间)
|
||
|
|
if (oldres && oldres != spineAsset) {
|
||
|
|
this.scheduleOnce(() => {
|
||
|
|
if (oldres.refCount > 0) {
|
||
|
|
oldres.decRef();
|
||
|
|
}
|
||
|
|
}, 0.1); // ✅ 延迟 0.1 秒,确保 Spine 不再使用
|
||
|
|
}
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.log("更新 lLoadSpine 组件失败", error);
|
||
|
|
} finally {
|
||
|
|
this.isLoading = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
setKey(key: string) {
|
||
|
|
this.key = key;
|
||
|
|
this.updateLang();
|
||
|
|
}
|
||
|
|
|
||
|
|
setPath(path: string) {
|
||
|
|
this.path = path;
|
||
|
|
this.updateLang();
|
||
|
|
}
|
||
|
|
|
||
|
|
private async loadBundle(bundleName) {
|
||
|
|
return new Promise<AssetManager.Bundle>((resolve, reject) => {
|
||
|
|
let bundle = assetManager.getBundle(bundleName);
|
||
|
|
if (bundle) {
|
||
|
|
resolve(bundle);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
assetManager.loadBundle(bundleName, (err, bundle) => {
|
||
|
|
if (err) {
|
||
|
|
console.log(`加载Bundle:${bundleName}错误`, err);
|
||
|
|
resolve(null);
|
||
|
|
} else {
|
||
|
|
resolve(bundle);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
private async loadRes(bundle: AssetManager.Bundle, assetPath, type = Asset) {
|
||
|
|
return new Promise<Asset>((resolve, reject) => {
|
||
|
|
let res = bundle.get(assetPath);
|
||
|
|
if (res) {
|
||
|
|
resolve(res);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
bundle.load(assetPath, type, (err, asset) => {
|
||
|
|
if (err) {
|
||
|
|
console.log(`加载Bundle:${bundle.name},path:${assetPath}资源错误`, err);
|
||
|
|
resolve(null);
|
||
|
|
} else {
|
||
|
|
resolve(asset);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|