- 为 Monster 和 Hero 实体添加 idle 状态初始化,确保视图组件正确显示 - 调用 Hero 视图组件的 init 方法以完成初始化流程 - 调整关卡配置参数:关闭内存面板显示,减少怪物上限和恢复阈值以优化性能
229 lines
8.3 KiB
TypeScript
229 lines
8.3 KiB
TypeScript
import { instantiate, Node, Prefab, Vec3 ,v3,resources,SpriteFrame,Sprite,SpriteAtlas, BoxCollider2D, NodePool, tween, Tween} from "cc";
|
||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||
import { smc } from "../common/SingletonModuleComp";
|
||
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
|
||
import { HeroInfo } from "../common/config/heroSet";
|
||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||
import { HeroViewComp } from "./HeroViewComp";
|
||
import { MoveComp } from "./MoveComp";
|
||
/** 怪物实体:负责怪物对象池复用、属性初始化、入场动画与回收 */
|
||
@ecs.register(`Monster`)
|
||
export class Monster extends ecs.Entity {
|
||
/** 怪物数据组件引用 */
|
||
HeroModel!: HeroAttrsComp;
|
||
/** 怪物表现组件引用 */
|
||
HeroView!: HeroViewComp;
|
||
/** 怪物移动组件引用 */
|
||
MonMove!: MoveComp;
|
||
/** 调试开关,控制生命周期日志输出 */
|
||
private debugMode: boolean = false;
|
||
|
||
/** 多键对象池:key 为 prefab 路径,value 为对应节点池 */
|
||
static pools: Map<string, NodePool> = new Map();
|
||
/** 单个路径的池容量上限 */
|
||
static readonly MAX_POOL_SIZE: number = 12;
|
||
/** 所有路径合计池容量上限 */
|
||
static readonly MAX_POOL_TOTAL: number = 60;
|
||
|
||
/** 计算当前所有对象池节点总量 */
|
||
private static totalPoolSize(): number {
|
||
let total = 0;
|
||
this.pools.forEach((pool) => {
|
||
total += pool.size();
|
||
});
|
||
return total;
|
||
}
|
||
|
||
/** 从指定路径对象池取可用节点,取不到返回 null */
|
||
static getFromPool(path: string): Node | null {
|
||
if (this.pools.has(path)) {
|
||
const pool = this.pools.get(path)!;
|
||
while (pool.size() > 0) {
|
||
const node = pool.get();
|
||
if (node && node.isValid) {
|
||
return node;
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/** 节点回收到对象池,超上限则直接销毁 */
|
||
static putToPool(path: string, node: Node) {
|
||
if (!node || !node.isValid) return;
|
||
if (!this.pools.has(path)) {
|
||
this.pools.set(path, new NodePool());
|
||
}
|
||
const pool = this.pools.get(path)!;
|
||
if (pool.size() >= this.MAX_POOL_SIZE || this.totalPoolSize() >= this.MAX_POOL_TOTAL) {
|
||
node.destroy();
|
||
return;
|
||
}
|
||
pool.put(node);
|
||
}
|
||
|
||
/** 清空所有对象池并销毁池内节点 */
|
||
static clearPools() {
|
||
this.pools.forEach((pool) => {
|
||
while (pool.size() > 0) {
|
||
const node = pool.get();
|
||
if (node && node.isValid) {
|
||
node.destroy();
|
||
}
|
||
}
|
||
pool.clear();
|
||
});
|
||
this.pools.clear();
|
||
}
|
||
|
||
/** 获取对象池统计信息,用于调试与容量监控 */
|
||
static getPoolStats() {
|
||
let total = 0;
|
||
this.pools.forEach((pool) => {
|
||
total += pool.size();
|
||
});
|
||
return {
|
||
paths: this.pools.size,
|
||
total,
|
||
maxPerPath: this.MAX_POOL_SIZE,
|
||
maxTotal: this.MAX_POOL_TOTAL
|
||
};
|
||
}
|
||
|
||
/** 注册实体必需组件:移动 + 属性 */
|
||
protected init() {
|
||
this.addComponents<ecs.Comp>(
|
||
MoveComp,
|
||
HeroAttrsComp,
|
||
);
|
||
}
|
||
|
||
/** 销毁实体:优先回收节点,然后释放组件 */
|
||
destroy(): void {
|
||
// 按英雄路径回收到对象池,提升高频刷怪性能
|
||
const model = this.get(HeroAttrsComp);
|
||
const view = this.get(HeroViewComp);
|
||
if (model && view && view.node && view.node.isValid) {
|
||
const path = "game/heros/" + HeroInfo[model.hero_uuid].path;
|
||
Monster.putToPool(path, view.node);
|
||
}
|
||
|
||
// 手动移除组件,避免 ecs 引用滞留
|
||
this.remove(HeroViewComp);
|
||
this.remove(HeroAttrsComp);
|
||
super.destroy();
|
||
}
|
||
|
||
/**
|
||
* 加载并初始化怪物
|
||
* 1) 优先对象池复用节点,减少实例化开销
|
||
* 2) 初始化表现、属性、技能与阵营
|
||
* 3) 播放下落入场并在落地后启用碰撞与移动
|
||
*/
|
||
load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001, is_boss:boolean=false, dropToY:number = pos.y,mon_lv:number=1) {
|
||
// 怪物默认朝左,表现缩放固定为负向
|
||
scale=-1
|
||
// 当前怪物尺寸固定,保留变量便于后续扩展
|
||
let size=1
|
||
var scene = smc.map.MapView.scene;
|
||
// 根据配置读取怪物预制体路径
|
||
var path = "game/heros/"+HeroInfo[uuid].path;
|
||
|
||
// 优先从对象池取节点,未命中时再实例化
|
||
let node = Monster.getFromPool(path);
|
||
if (!node) {
|
||
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
||
node = instantiate(prefab);
|
||
}
|
||
|
||
// 统一挂到实体显示层 HERO 节点下
|
||
node.parent = scene.entityLayer!.node!.getChildByName("HERO")!;
|
||
var view = node.getComponent(HeroViewComp)!;
|
||
const collider = node.getComponent(BoxCollider2D);
|
||
// 入场期间关闭碰撞,防止下落时提前参与战斗
|
||
if (collider) {
|
||
collider.enabled = false;
|
||
}
|
||
|
||
node.setScale(size*node.scale.x,size*node.scale.y);
|
||
node.setPosition(pos)
|
||
const model = this.get(HeroAttrsComp);
|
||
// 从配置表获取怪物静态数据
|
||
let hero = HeroInfo[uuid];
|
||
// 视图层参数:朝向与碰撞阵营
|
||
view.scale = scale;
|
||
view.box_group = BoxSet.MONSTER;
|
||
// 模型层参数:身份、阵营、基础数值
|
||
model.hero_uuid = uuid;
|
||
model.hero_name = hero.name;
|
||
model.hp = model.hp_max = hero.hp;
|
||
model.ap = hero.ap;
|
||
model.speed = hero.speed;
|
||
model.type = hero.type;
|
||
model.fac = FacSet.MON;
|
||
// 标记是否 Boss,非 Boss 默认记作杂兵
|
||
model.is_boss =is_boss
|
||
if(!model.is_boss){
|
||
model.is_kalami = true;
|
||
}
|
||
|
||
// 构建技能表并注入运行时冷却字段 ccd
|
||
model.skills = {};
|
||
for (const key in hero.skills) {
|
||
const skill = hero.skills[key];
|
||
if (!skill) continue;
|
||
// 最终技能等级 = 初始技能等级 + 怪物等级增量,且下限为 0
|
||
model.skills[skill.uuid] = { ...skill, lv: Math.max(0,skill.lv + mon_lv - 2), ccd: 0 };
|
||
}
|
||
// 缓存技能射程等派生数据,减少战斗帧内重复计算
|
||
model.updateSkillDistanceCache();
|
||
|
||
// 注册视图组件并重置对象池复用状态
|
||
this.add(view);
|
||
view.init();
|
||
view.as.idle();
|
||
// 广播怪物加载事件,供刷怪与战斗系统联动
|
||
oops.message.dispatchEvent("monster_load",this)
|
||
// 初始化移动参数:方向、目标 X、站位基准 Y
|
||
const move = this.get(MoveComp);
|
||
move.reset();
|
||
move.direction = -1;
|
||
move.targetX = Math.max(-320, Math.min(320, pos.x));
|
||
move.baseY = dropToY;
|
||
move.moving = false;
|
||
|
||
// 依据下落距离自适应入场时长,确保观感一致
|
||
const dropDistance = Math.abs(pos.y - dropToY);
|
||
const dropDuration = Math.max(0.18, Math.min(0.38, dropDistance / 1200));
|
||
|
||
// 停止旧动画后执行下落 tween,避免复用节点时动画叠加
|
||
Tween.stopAllByTarget(node);
|
||
tween(node)
|
||
.to(dropDuration, { position: v3(pos.x, dropToY, 0) })
|
||
.call(() => {
|
||
if (!node || !node.isValid) return;
|
||
// 落地后锁定最终位置,切换到落地完成状态
|
||
node.setPosition(pos.x, dropToY, 0);
|
||
view.playEnd("down");
|
||
move.moving = true;
|
||
// 落地后启用怪物碰撞分组
|
||
if (collider) {
|
||
collider.enabled = true;
|
||
collider.group = BoxSet.MONSTER;
|
||
collider.apply();
|
||
}
|
||
})
|
||
.start();
|
||
// 维护关卡内怪物数量统计
|
||
smc.vmdata.mission_data.mon_num++
|
||
}
|
||
|
||
|
||
/** 重置入口:复用 destroy 的释放流程 */
|
||
reset() {
|
||
super.destroy();
|
||
}
|
||
|
||
}
|