消消方块阵换皮表情
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.
 
 
 

227 lines
6.9 KiB

import { _decorator, Component, sp, AssetManager, Asset, assetManager } from 'cc';
import { GEvent } from '../event/GEvent';
import { ensureSpineAssetResetEnums, patchSpineSkeletonResetEnums } from './spineRuntimePatch';
const { ccclass, property } = _decorator;
@ccclass('LangSpine')
export class LangSpine extends Component {
@property
path: string = "";
@property
key: string = "";
@property
animation: string = "";
langSp: sp.Skeleton = null;
curentSpineData: sp.SkeletonData = null;
private isLoading: boolean = false;
private abortController: { aborted: boolean } = null;
/** 每次发起加载自增;用于 finally 里判断是否仍是最新请求,避免并发把 isLoading 错清 */
private loadGeneration = 0;
protected onDestroy(): void {
if (this.abortController) {
this.abortController.aborted = true;
}
this.releaseLoadedSpine();
}
onLoad() {
patchSpineSkeletonResetEnums();
this.langSp = this.getComponent(sp.Skeleton);
}
onEnable() {
this.updateLang();
GEvent.Ins.on(GEvent.EVENT_LANG_CHANGE, this.updateLang, this);
}
onDisable() {
GEvent.Ins.off(GEvent.EVENT_LANG_CHANGE, this.updateLang, this);
if (this.abortController) {
this.abortController.aborted = true;
}
}
async updateLang() {
if (!this.node.isValid || this.langSp == null) return;
if (this.abortController) {
this.abortController.aborted = true;
}
this.abortController = { aborted: false };
const currentAbort = this.abortController;
const generation = ++this.loadGeneration;
this.isLoading = true;
try {
await gg.lang.whenReady();
if (typeof sp.loadWasmModuleSpine === 'function') {
await sp.loadWasmModuleSpine();
}
let bundleName = 'lang_' + gg.lang.Current;
let path = this.path;
let key = this.key;
if (key == "") key = this.node.name;
let loop = this.langSp.loop;
let bundle = await this.loadBundle(bundleName);
if (currentAbort.aborted || !this.node.isValid) {
return;
}
if (!bundle) {
return;
}
let spineAsset = await this.loadRes(bundle, path + key, sp.SkeletonData) as sp.SkeletonData;
if (currentAbort.aborted || !this.node.isValid || !spineAsset) {
if (spineAsset) spineAsset.decRef();
return;
}
if (this.curentSpineData == spineAsset) {
this.prepareSkeletonDataForReuse(spineAsset);
ensureSpineAssetResetEnums(spineAsset);
this.applySkeletonData(spineAsset);
this.playSpineAnimation(loop);
return;
}
let oldres = this.curentSpineData;
this.curentSpineData = spineAsset;
spineAsset.addRef();
this.prepareSkeletonDataForReuse(spineAsset);
ensureSpineAssetResetEnums(spineAsset);
this.applySkeletonData(spineAsset);
this.playSpineAnimation(loop);
if (oldres && oldres != spineAsset) {
this.scheduleOnce(() => {
if (oldres.refCount > 0) {
oldres.decRef();
}
}, 0.1);
}
} catch (error: any) {
const msg = error && (error.message ?? String(error));
console.error('[LangSpine]', this.path, this.key, msg);
} finally {
if (generation === this.loadGeneration) {
this.isLoading = false;
}
}
}
setKey(key: string) {
this.key = key;
this.updateLang();
}
setPath(path: string) {
this.path = path;
this.updateLang();
}
private prepareSkeletonDataForReuse(data: sp.SkeletonData) {
if (data && typeof (data as any).reset === 'function') {
try {
(data as any).reset();
} catch {
/* ignore */
}
}
}
/**
* sp.Skeleton 的 skeletonData setter 在「新值与当前引用相同」时会直接 return,不跑 _updateSkeletonData。
* 第二次进界面 bundle 缓存常为同一 SkeletonData,必须先置 null 再赋值,否则会不播动画且无报错。
*/
private applySkeletonData(spineAsset: sp.SkeletonData) {
const sk = this.langSp;
if (!sk) return;
sk.clearTrack(0);
if (sk.skeletonData === spineAsset) {
sk.skeletonData = null;
}
sk.skeletonData = spineAsset;
}
private playSpineAnimation(loop: boolean) {
const run = () => {
if (!this.node.isValid || !this.langSp || !this.animation) return;
this.langSp.setAnimation(0, this.animation, loop);
};
run();
this.scheduleOnce(run, 0);
}
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?.message ?? String(err));
resolve(null);
} else {
resolve(bundle);
}
})
})
}
/**
* 不使用 bundle 内缓存:通过 loadAny + cacheAsset:false(不能往 bundle.load 第 3 个参数塞对象,那是 onProgress)。
* 与 updateLang 里的 addRef / onDestroy 的 release 成对。
*/
private async loadRes(bundle: AssetManager.Bundle, assetPath: string, type = Asset) {
return new Promise<Asset>((resolve) => {
assetManager.loadAny(
{ path: assetPath, bundle: bundle.name, type },
{ cacheAsset: false },
(err: Error | null, asset: Asset) => {
if (err) {
console.log(`加载Bundle:${bundle.name},path:${assetPath}`, err?.message ?? String(err));
resolve(null);
} else {
resolve(asset);
}
},
);
});
}
private releaseLoadedSpine() {
if (this.langSp && this.langSp.isValid) {
this.langSp.clearTrack(0);
this.langSp.skeletonData = null;
}
const data = this.curentSpineData;
this.curentSpineData = null;
if (!data || !data.isValid) {
return;
}
if (data.refCount > 0) {
data.decRef();
}
assetManager.releaseAsset(data);
}
}