mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-05-30 18:39:18 +08:00
优化
This commit is contained in:
@@ -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<string, NodePool> = 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();
|
||||
@@ -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<void> {
|
||||
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(); }
|
||||
}
|
||||
|
||||
9
assets/module/common/part/animator-effect.meta
Normal file
9
assets/module/common/part/animator-effect.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "88dbf988-791b-4235-918f-d09bd1c15f6e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "e5ca8e12-f322-41cf-9872-b127ad797aed",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "49fa6e22-37e8-4aa9-aaa8-2513d6da666f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "8dea3fc6-e887-4a09-82f8-c77ed7f78d79",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "50312dcd-6477-46e1-bf6b-75485c44d591",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user