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 - 返回加载完成的 Spine Skeleton 组件 */ public static async loadAndPlaySpine( bundleName: string, spinePath: string, animationName: string, parentNode: Node, position: Vec3 = Vec3.ZERO, loop: boolean = true, onComplete?: () => void ): Promise { 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 - 返回动画的时长(单位:秒) */ public static async getAnimationDuration( bundleName: string, spinePath: string, animationName: string ): Promise { 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 - 返回加载完成的 Asset Bundle */ private static loadBundle(bundleName: string): Promise { 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 - 返回加载完成的 Spine 数据 */ private static loadSpineAsset(bundle: AssetManager.Bundle, spinePath: string): Promise { 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; } }