Files
pixelheros/assets/script/game/hero/Mon.ts
panw 90bf8f7f7c refactor: 合并英雄与怪物的生命周期系统为统一系统
移除 MonLifecycleSystem,将其功能整合至 HeroLifecycleSystem 并重命名为 BattleEntityLifecycleSystem。新系统根据实体的 fac 属性区分英雄与怪物,输出相应的调试日志,消除了重复代码并提高了维护性。
2026-03-24 14:42:40 +08:00

228 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
// 广播怪物加载事件,供刷怪与战斗系统联动
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();
}
}