diff --git a/assets/core/common/pool/GameNodePool.ts b/assets/core/common/pool/GameNodePool.ts index 4346acb..4f87c8c 100644 --- a/assets/core/common/pool/GameNodePool.ts +++ b/assets/core/common/pool/GameNodePool.ts @@ -13,16 +13,7 @@ import { instantiate, NodePool, Prefab } from 'cc'; * 注意:本类只管理对象池,不管理资源加载与释放 * 资源管理请使用各模块自己的资源管理系统 */ -export class GameNodePool { - private static _instance: GameNodePool; - /** 获取单例实例 */ - static get instance(): GameNodePool { - if (this._instance == null) { - this._instance = new GameNodePool(); - } - return this._instance; - } - +class GameNodePool { /** 对象池集合 - key 为 Prefab 的 UUID */ private _pools: Map = new Map(); @@ -55,7 +46,7 @@ export class GameNodePool { for (let i = 0; i < count; i++) { const node = instantiate(prefab); // @ts-ignore - node._pool_uuid = uuid; + node._oops_pool_uuid = uuid; pool.put(node); } } @@ -79,7 +70,7 @@ export class GameNodePool { if (pool.size() == 0) { node = instantiate(prefab); // @ts-ignore - node._pool_uuid = uuid; + node._oops_pool_uuid = uuid; } // 从池中获取对象 else { @@ -100,7 +91,7 @@ export class GameNodePool { */ put(node: Node) { // @ts-ignore - const uuid = node._pool_uuid; + const uuid = node._oops_pool_uuid; if (uuid) { const pool = this._pools.get(uuid); if (pool) { @@ -138,3 +129,4 @@ export class GameNodePool { } } } +export const gameNodePool = new GameNodePool(); \ No newline at end of file diff --git a/assets/module/common/part/GamePartNodePool.ts b/assets/module/common/part/GamePartNodePool.ts index b4ff43c..a5326e8 100644 --- a/assets/module/common/part/GamePartNodePool.ts +++ b/assets/module/common/part/GamePartNodePool.ts @@ -1,48 +1,24 @@ import type { Node, Vec3 } from 'cc'; import { Animation, ParticleSystem, Prefab, sp } from 'cc'; -import type { GameComponent } from '../GameComponent'; -import { GameNodePool } from '../../../core/common/pool/GameNodePool'; import { resLoader } from '../../../core/common/loader/ResLoader'; +import { gameNodePool } from '../../../core/common/pool/GameNodePool'; +import type { GameComponent } from '../GameComponent'; import { GamePartBase } from '../GamePartBase'; +import { AnimationEffectAutoRelease } from './animator-effect/AnimationEffectAutoRelease'; +import { ParticleEffectAutoRelease } from './animator-effect/ParticleEffectAutoRelease'; +import { SpineEffectAutoRelease } from './animator-effect/SpineEffectAutoRelease'; /** * 自动释放接口 * 实现此接口的组件可以自动播放并在播放完成后自动回收到对象池 - * - * 使用场景: - * 1、Spine 动画组件 - * 2、Cocos Animation 动画组件 - * 3、ParticleSystem 粒子组件 - * 4、自定义动画组件 - * - * 实现示例: - * ```typescript - * class MyAnimation extends Component implements IAutoRelease { - * onAutoRelease(callback: () => void): void { - * // 监听动画完成事件 - * this.animation.once(Animation.EventType.FINISHED, callback); - * } - * - * play(): void { - * // 开始播放动画 - * this.animation.play(); - * } - * } - * ``` */ export interface IAutoRelease { - /** - * 设置自动释放回调 - * 当动画播放完成时调用 callback,对象池会自动回收节点 - * @param callback 释放回调函数 - */ - onAutoRelease(callback: () => void): void; - - /** - * 播放动画 - * 对象池获取节点后自动调用此方法播放动画 - */ + /** 设置自动释放回调 */ + onPlayComplete(callback: () => void): void; + /** 播放动画 */ play(): void; + /** 设置动画播放速度 */ + setSpeed(speed: number): void; } /** 特效参数 */ @@ -74,19 +50,10 @@ export class GamePartNodePool extends GamePartBase { /** 全局动画播放速度 */ private _speed = 1; - /** - * 获取全局动画播放速度 - */ - get speed(): number { - return this._speed; - } - - /** - * 设置全局动画播放速度 - */ - set speed(value: number) { - this._speed = value; - } + /** 获取全局动画播放速度 */ + get speed(): number { return this._speed; } + /** 设置全局动画播放速度 */ + set speed(value: number) { this._speed = value; } /** * 获取指定资源池中对象数量 @@ -96,10 +63,7 @@ export class GamePartNodePool extends GamePartBase { getCount(path: string, bundle?: string): number { const bundleName = bundle ?? resLoader.defaultBundleName; const prefab = this.comp.res.get(path, Prefab, bundleName); - if (!prefab) { - return 0; - } - return GameNodePool.instance.getCount(prefab); + return prefab ? gameNodePool.getCount(prefab) : 0; } /** @@ -110,15 +74,9 @@ export class GamePartNodePool extends GamePartBase { */ async preload(count: number, path: string, params?: IEffectParams): Promise { const bundleName = params?.bundle ?? resLoader.defaultBundleName; - - // 使用 GameResModule 加载资源,自动管理引用计数 const prefab = await this.comp.res.load(bundleName, path, Prefab); - - // 记录已加载的资源 this._loadedPrefabs.add(prefab); - - // 使用 GameNodePool 预加载到对象池 - GameNodePool.instance.preload(count, prefab); + gameNodePool.preload(count, prefab); } /** @@ -129,33 +87,19 @@ export class GamePartNodePool extends GamePartBase { */ show(path: string, parent?: Node, params?: IEffectParams): Node { const bundleName = params?.bundle ?? resLoader.defaultBundleName; - - // 获取已加载的预制资源 const prefab = this.comp.res.get(path, Prefab, bundleName); if (!prefab) { console.warn(`[GamePartNodePool] 预制资源未加载: ${bundleName}/${path}`); return null!; } - - // 记录已加载的资源 this._loadedPrefabs.add(prefab); - - // 使用 GameNodePool 获取节点 - const node = GameNodePool.instance.get(prefab, parent); - - // 应用特效参数 + const node = gameNodePool.get(prefab, parent); this._applyEffectParams(node, params); - return node; } - /** - * 回收对象 - * @param node 节点 - */ - put(node: Node) { - GameNodePool.instance.put(node); - } + /** 回收对象 */ + put(node: Node) { gameNodePool.put(node); } /** * 清除对象池数据(只清除本模块管理的) @@ -164,55 +108,41 @@ export class GamePartNodePool extends GamePartBase { */ clear(path?: string, bundle?: string) { if (path) { - // 只清除本模块管理的指定对象池 const bundleName = bundle ?? resLoader.defaultBundleName; const prefab = this.comp.res.get(path, Prefab, bundleName); if (prefab && this._loadedPrefabs.has(prefab)) { - GameNodePool.instance.clear(prefab); + gameNodePool.clear(prefab); } } else { - // 只清除本模块管理的所有对象池 - this._loadedPrefabs.forEach((p) => { - GameNodePool.instance.clear(p); - }); + this._loadedPrefabs.forEach((p) => gameNodePool.clear(p)); } } - /** + /** * 释放对象池中显示对象的资源内存(只释放本模块管理的) * @param path 预制体资源路径,为空时释放所有本模块管理的资源 * @param bundle 资源包名,默认为 resources */ release(path?: string, bundle?: string) { if (path) { - // 只释放本模块管理的指定资源 const bundleName = bundle ?? resLoader.defaultBundleName; const prefab = this.comp.res.get(path, Prefab, bundleName); if (prefab && this._loadedPrefabs.has(prefab)) { - // 清除对象池 - GameNodePool.instance.clear(prefab); - // 释放资源 + gameNodePool.clear(prefab); this.comp.res.releaseRes(prefab.uuid); this._loadedPrefabs.delete(prefab); } } else { - // 释放本模块加载的所有资源 this._loadedPrefabs.forEach((p) => { - GameNodePool.instance.clear(p); + gameNodePool.clear(p); this.comp.res.releaseRes(p.uuid); }); this._loadedPrefabs.clear(); } } - /** 销毁特效模块 */ - override destroy(): void { - // 释放本模块管理的所有资源 - this.release(); - } - /** * 应用特效参数 * @param node 节点 @@ -220,156 +150,36 @@ export class GamePartNodePool extends GamePartBase { */ private _applyEffectParams(node: Node, params?: IEffectParams) { if (!params) return; - - // 设置位置 if (params.pos) node.position = params.pos; if (params.worldPos) node.worldPosition = params.worldPos; - // 播放完成后自动回收 - if (params.isPlayFinishedRelease) { - // 监听动画完成事件,自动回收 - this.setupAutoRelease(node); - } + const comp = this.getAutoRelease(node); + if (comp) { + // 设置自动回收 + if (params.isPlayFinishedRelease) { + // @ts-ignore + if (node._oops_auto_release) return; + // @ts-ignore + node._oops_auto_release = true; - // 设置动画速度并播放 - this._setSpeed(node); - this._playAnimation(node); - } - - /** - * 设置动画速度 - * @param node 节点 - */ - private _setSpeed(node: Node) { - // Spine动画 - const spine = node.getComponent(sp.Skeleton); - if (spine) { - spine.timeScale = this._speed; - return; - } - - // Cocos动画 - const anims = node.getComponentsInChildren(Animation); - if (anims.length > 0) { - anims.forEach((animator) => { - const aniName = animator.defaultClip?.name; - if (aniName) { - const aniState = animator.getState(aniName); - if (aniState) { - aniState.speed = this._speed; - } - } - }); - return; - } - - // 粒子动画 - const particles = node.getComponentsInChildren(ParticleSystem); - particles.forEach((particle) => { - particle.simulationSpeed = this._speed; - }); - } - - /** - * 播放动画 - * @param node 节点 - */ - private _playAnimation(node: Node) { - // Spine动画 - const spine = node.getComponent(sp.Skeleton); - if (spine) { - // @ts-ignore - const animationName = spine.defaultAnimation ?? spine.animation; - if (animationName) { - spine.setAnimation(0, animationName, false); + comp.onPlayComplete(() => this.put(node)); } - return; - } - - // Cocos Animation动画 - const anim = node.getComponent(Animation); - if (anim && anim.defaultClip) { - anim.play(); - return; - } - - // 粒子动画 - const particles = node.getComponentsInChildren(ParticleSystem); - if (particles.length > 0) { - particles.forEach((particle) => { - particle.play(); - }); + comp.setSpeed(this._speed); + comp.play(); } } - /** - * 设置自动回收 - * 优先使用 IAutoRelease 接口,其次使用内置动画检测 - * 每个节点只设置一次事件监听,避免重复设置 - * @param node 节点 - */ - private setupAutoRelease(node: Node) { - // 检查是否已经设置过自动回收 - // @ts-ignore - if (node._autoRelease) return; - // @ts-ignore - node._autoRelease = true; - - // 优先检查是否实现了 IAutoRelease 接口 - const components = node.components; - for (const comp of components) { - if (this.isAutoRelease(comp)) { - comp.onAutoRelease(() => this.put(node)); - comp.play(); - return; - } - } - - // 内置动画类型检测 - this.setupBuiltinAutoRelease(node); - } - - /** - * 判断组件是否实现 IAutoRelease 接口 - * @param component 组件 - * @returns 是否实现接口 - */ - private isAutoRelease(component: any): component is IAutoRelease { - return component && typeof component.onAutoRelease === 'function'; - } - - /** - * 设置内置动画类型的自动回收 - * 每个节点只调用一次,通过 _autoRelease 标记控制 - * @param node 节点 - */ - private setupBuiltinAutoRelease(node: Node) { - // Spine动画 + /** 获取 IAutoRelease 组件(查找或自动添加) */ + private getAutoRelease(node: Node): IAutoRelease | null { const spine = node.getComponent(sp.Skeleton); - if (spine) { - spine.setCompleteListener(() => { - this.put(node); - }); - return; - } - - // Cocos Animation动画 + if (spine) return node.addComponent(SpineEffectAutoRelease); const anim = node.getComponent(Animation); - if (anim) { - anim.once(Animation.EventType.FINISHED, () => { - this.put(node); - }); - return; - } - - // 粒子动画 + if (anim) return node.addComponent(AnimationEffectAutoRelease); const particle = node.getComponent(ParticleSystem); - if (particle) { - // 粒子没有完成事件,使用持续时间估算 - const duration = particle.duration; - setTimeout(() => { - this.put(node); - }, duration * 1000); - } + if (particle) return node.addComponent(ParticleEffectAutoRelease); + return null; } + + /** 销毁特效模块 */ + override destroy(): void { this.release(); } } diff --git a/assets/module/common/part/animator-effect.meta b/assets/module/common/part/animator-effect.meta new file mode 100644 index 0000000..9e5880c --- /dev/null +++ b/assets/module/common/part/animator-effect.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "88dbf988-791b-4235-918f-d09bd1c15f6e", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/module/common/part/animator-effect/AnimationEffectAutoRelease.ts b/assets/module/common/part/animator-effect/AnimationEffectAutoRelease.ts new file mode 100644 index 0000000..f0a3fb2 --- /dev/null +++ b/assets/module/common/part/animator-effect/AnimationEffectAutoRelease.ts @@ -0,0 +1,44 @@ +import { Animation, Component, _decorator } from 'cc'; +import { IAutoRelease } from '../GamePartNodePool'; + +const { ccclass } = _decorator; + +/** Cocos Animation动画自动释放组件 */ +@ccclass('AnimationEffectAutoRelease') +export class AnimationEffectAutoRelease extends Component implements IAutoRelease { + private callback: (() => void) | null = null; + + onPlayComplete(callback: () => void): void { + this.callback = callback; + } + + play(): void { + const anim = this.getComponent(Animation); + if (anim) { + anim.once(Animation.EventType.FINISHED, () => { + if (this.callback) { + this.callback(); + this.callback = null; + } + }); + anim.play(); + } + } + + setSpeed(speed: number): void { + const anim = this.getComponent(Animation); + if (anim) { + const aniName = anim.defaultClip?.name; + if (aniName) { + const aniState = anim.getState(aniName); + if (aniState) { + aniState.speed = speed; + } + } + } + } + + protected onDisable() { + this.callback = null; + } +} diff --git a/assets/module/common/part/animator-effect/AnimationEffectAutoRelease.ts.meta b/assets/module/common/part/animator-effect/AnimationEffectAutoRelease.ts.meta new file mode 100644 index 0000000..a9f194d --- /dev/null +++ b/assets/module/common/part/animator-effect/AnimationEffectAutoRelease.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "e5ca8e12-f322-41cf-9872-b127ad797aed", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/module/common/part/animator-effect/ParticleEffectAutoRelease.ts b/assets/module/common/part/animator-effect/ParticleEffectAutoRelease.ts new file mode 100644 index 0000000..5ecccf0 --- /dev/null +++ b/assets/module/common/part/animator-effect/ParticleEffectAutoRelease.ts @@ -0,0 +1,46 @@ +import { Component, ParticleSystem, _decorator } from 'cc'; +import { IAutoRelease } from '../GamePartNodePool'; + +const { ccclass } = _decorator; + +/** 粒子动画自动释放组件 */ +@ccclass('ParticleEffectAutoRelease') +export class ParticleEffectAutoRelease extends Component implements IAutoRelease { + private callback: (() => void) | null = null; + private timerId: number | null = null; + + onPlayComplete(callback: () => void): void { + this.callback = callback; + } + + play(): void { + const particle = this.getComponent(ParticleSystem); + if (particle) { + particle.clear(); + particle.stop(); + particle.play(); + + const duration = particle.duration * 1000; + this.timerId = setTimeout(() => { + this.timerId = null; + this.callback && this.callback(); + this.callback = null; + }, duration) as unknown as number; + } + } + + setSpeed(speed: number): void { + const particle = this.getComponent(ParticleSystem); + if (particle) { + particle.simulationSpeed = speed; + } + } + + protected onDisable() { + if (this.timerId !== null) { + clearTimeout(this.timerId); + this.timerId = null; + } + this.callback = null; + } +} diff --git a/assets/module/common/part/animator-effect/ParticleEffectAutoRelease.ts.meta b/assets/module/common/part/animator-effect/ParticleEffectAutoRelease.ts.meta new file mode 100644 index 0000000..3370474 --- /dev/null +++ b/assets/module/common/part/animator-effect/ParticleEffectAutoRelease.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "49fa6e22-37e8-4aa9-aaa8-2513d6da666f", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/module/common/part/animator-effect/SpineEffectAutoRelease.ts b/assets/module/common/part/animator-effect/SpineEffectAutoRelease.ts new file mode 100644 index 0000000..e4a82f4 --- /dev/null +++ b/assets/module/common/part/animator-effect/SpineEffectAutoRelease.ts @@ -0,0 +1,48 @@ +import { Component, _decorator, sp } from 'cc'; +import { IAutoRelease } from '../GamePartNodePool'; + +const { ccclass } = _decorator; + +/** Spine动画自动释放组件 */ +@ccclass('SpineEffectAutoRelease') +export class SpineEffectAutoRelease extends Component implements IAutoRelease { + private callback: (() => void) | null = null; + private spine: sp.Skeleton | null = null; + + onPlayComplete(callback: () => void): void { + this.callback = callback; + } + + play(): void { + this.spine = this.getComponent(sp.Skeleton); + if (this.spine) { + this.spine.setCompleteListener(() => { + this.spine!.setCompleteListener(null!); + if (this.callback) { + this.callback(); + this.callback = null; + } + }); + + const json = (this.spine.skeletonData!.skeletonJson! as any).animations; + for (const name in json) { + this.spine.setAnimation(0, name, false); + break; + } + } + } + + setSpeed(speed: number): void { + if (this.spine) { + this.spine.timeScale = speed; + } + } + + protected onDisable() { + if (this.spine) { + this.spine.setCompleteListener(null!); + this.spine = null; + } + this.callback = null; + } +} diff --git a/assets/module/common/part/animator-effect/SpineEffectAutoRelease.ts.meta b/assets/module/common/part/animator-effect/SpineEffectAutoRelease.ts.meta new file mode 100644 index 0000000..e6680a5 --- /dev/null +++ b/assets/module/common/part/animator-effect/SpineEffectAutoRelease.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "8dea3fc6-e887-4a09-82f8-c77ed7f78d79", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/module/common/part/animator-effect/SpineEffectFinishedRelease.ts.meta b/assets/module/common/part/animator-effect/SpineEffectFinishedRelease.ts.meta new file mode 100644 index 0000000..0837d28 --- /dev/null +++ b/assets/module/common/part/animator-effect/SpineEffectFinishedRelease.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "50312dcd-6477-46e1-bf6b-75485c44d591", + "files": [], + "subMetas": {}, + "userData": {} +}