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.
161 lines
5.7 KiB
161 lines
5.7 KiB
|
1 week ago
|
import { Node, assetManager, sp, AssetManager, Vec3 } from 'cc';
|
||
|
|
import { ensureSpineAssetResetEnums } from '../module/lang/spineRuntimePatch';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* SpineLoader 类用于动态加载和播放 Spine 动画。
|
||
|
|
*/
|
||
|
|
export class SpineLoader {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 动态加载并播放 Spine 动画
|
||
|
|
* @param bundleName - Asset Bundle 的名称
|
||
|
|
* @param spinePath - Spine 资源的路径(相对于 bundle 根目录)
|
||
|
|
* @param parentNode - 父节点,用于挂载 Spine 动画
|
||
|
|
* @param animationName - 播放的动画名称
|
||
|
|
* @param loop - 是否循环播放动画
|
||
|
|
* @param onComplete - 动画播放完成时的回调函数(仅在非循环模式下有效)
|
||
|
|
* @returns Promise<sp.Skeleton> - 返回加载完成的 Spine Skeleton 组件
|
||
|
|
*/
|
||
|
|
public static async loadAndPlaySpine(
|
||
|
|
bundleName: string,
|
||
|
|
spinePath: string,
|
||
|
|
animationName: string,
|
||
|
|
parentNode: Node,
|
||
|
|
position: Vec3 = Vec3.ZERO,
|
||
|
|
loop: boolean = true,
|
||
|
|
onComplete?: () => void
|
||
|
|
): Promise<sp.Skeleton> {
|
||
|
|
return new Promise(async (resolve, reject) => {
|
||
|
|
try {
|
||
|
|
// 加载指定的 Asset Bundle
|
||
|
|
const bundle = await this.loadBundle(bundleName);
|
||
|
|
|
||
|
|
// 加载 Spine 资源
|
||
|
|
const spineAsset = await this.loadSpineAsset(bundle, spinePath);
|
||
|
|
spineAsset.addRef();
|
||
|
|
|
||
|
|
// 创建 Spine 节点
|
||
|
|
const spineNode = this.createSpineNode(spineAsset);
|
||
|
|
|
||
|
|
// 将 Spine 节点添加到父节点
|
||
|
|
parentNode.addChild(spineNode);
|
||
|
|
spineNode.setPosition(position);
|
||
|
|
|
||
|
|
// 获取 Spine Skeleton 组件
|
||
|
|
const skeleton = spineNode.getComponent(sp.Skeleton);
|
||
|
|
|
||
|
|
if (!skeleton) {
|
||
|
|
throw new Error("Failed to get Skeleton component from Spine node.");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 播放指定动画
|
||
|
|
skeleton.setAnimation(0, animationName, loop);
|
||
|
|
|
||
|
|
// 如果是非循环模式,监听动画完成事件
|
||
|
|
if (!loop) {
|
||
|
|
skeleton.setCompleteListener(() => {
|
||
|
|
spineAsset.decRef();
|
||
|
|
spineNode.destroy();
|
||
|
|
if (onComplete) onComplete();
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(skeleton);
|
||
|
|
} catch (error) {
|
||
|
|
reject(error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 查询 Spine 动画的时长(不播放动画)
|
||
|
|
* @param bundleName - Asset Bundle 的名称
|
||
|
|
* @param spinePath - Spine 资源的路径(相对于 bundle 根目录)
|
||
|
|
* @param animationName - 动画名称
|
||
|
|
* @returns Promise<number> - 返回动画的时长(单位:秒)
|
||
|
|
*/
|
||
|
|
public static async getAnimationDuration(
|
||
|
|
bundleName: string,
|
||
|
|
spinePath: string,
|
||
|
|
animationName: string
|
||
|
|
): Promise<number> {
|
||
|
|
return new Promise(async (resolve, reject) => {
|
||
|
|
try {
|
||
|
|
// 加载指定的 Asset Bundle
|
||
|
|
const bundle = await this.loadBundle(bundleName);
|
||
|
|
|
||
|
|
// 加载 Spine 资源
|
||
|
|
const spineAsset = await this.loadSpineAsset(bundle, spinePath);
|
||
|
|
|
||
|
|
// 获取动画数据
|
||
|
|
const animation = spineAsset.getRuntimeData().findAnimation(animationName);
|
||
|
|
|
||
|
|
if (!animation) {
|
||
|
|
reject(`Animation "${animationName}" not found in Spine asset.`);
|
||
|
|
} else {
|
||
|
|
resolve(animation.duration); // 返回动画时长
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
reject(error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 加载 Asset Bundle
|
||
|
|
* @param bundleName - Asset Bundle 的名称
|
||
|
|
* @returns Promise<AssetManager.Bundle> - 返回加载完成的 Asset Bundle
|
||
|
|
*/
|
||
|
|
private static loadBundle(bundleName: string): Promise<AssetManager.Bundle> {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
let bundle = assetManager.getBundle(bundleName);
|
||
|
|
if (bundle) {
|
||
|
|
resolve(bundle);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
assetManager.loadBundle(bundleName, (err, bundle) => {
|
||
|
|
if (err) {
|
||
|
|
reject(`Failed to load bundle: ${bundleName}, error: ${err.message}`);
|
||
|
|
} else {
|
||
|
|
resolve(bundle);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 加载 Spine 资源
|
||
|
|
* @param bundle - Asset Bundle
|
||
|
|
* @param spinePath - Spine 资源的路径
|
||
|
|
* @returns Promise<sp.SkeletonData> - 返回加载完成的 Spine 数据
|
||
|
|
*/
|
||
|
|
private static loadSpineAsset(bundle: AssetManager.Bundle, spinePath: string): Promise<sp.SkeletonData> {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
let asset = bundle.get(spinePath, sp.SkeletonData);
|
||
|
|
if (asset) {
|
||
|
|
resolve(asset);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
bundle.load(spinePath, sp.SkeletonData, (err, asset) => {
|
||
|
|
if (err) {
|
||
|
|
reject(`Failed to load Spine asset: ${spinePath}, error: ${err.message}`);
|
||
|
|
} else {
|
||
|
|
resolve(asset);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 创建 Spine 节点
|
||
|
|
* @param spineAsset - Spine 数据
|
||
|
|
* @returns Node - 返回包含 Spine 动画的节点
|
||
|
|
*/
|
||
|
|
private static createSpineNode(spineAsset: sp.SkeletonData): Node {
|
||
|
|
ensureSpineAssetResetEnums(spineAsset);
|
||
|
|
const spineNode = new Node();
|
||
|
|
const skeleton = spineNode.addComponent(sp.Skeleton);
|
||
|
|
skeleton.skeletonData = spineAsset;
|
||
|
|
return spineNode;
|
||
|
|
}
|
||
|
|
}
|