refactor(hero): 优化英雄与怪物实体加载与注释
- 统一英雄与怪物实体加载流程,增强代码可读性与维护性 - 为 Hero.ts 与 Mon.ts 添加详细方法级注释,说明组件职责与关键逻辑 - 在配置文件中将攻击速度(as)注释更新为技能冷却(skills[0].cd),使配置项含义更清晰 - 修复怪物技能等级未随怪物等级提升的问题,使其与英雄逻辑保持一致 - 优化对象池管理,增加容量统计与调试信息
This commit is contained in:
@@ -86,12 +86,12 @@ export interface HSkillInfo {
|
|||||||
* skills[0]是普通攻击技能
|
* skills[0]是普通攻击技能
|
||||||
* skills[1]是等级1时的技能,skills[2]是等级2时的技能,skills[3]是等级3时的技能,最多3级
|
* skills[1]是等级1时的技能,skills[2]是等级2时的技能,skills[3]是等级3时的技能,最多3级
|
||||||
*
|
*
|
||||||
* 属性基准(cards_lv:1,lv:1) : SPEED:120,AP:30 |HP:300|as:1
|
* 属性基准(cards_lv:1,lv:1) : SPEED:120,AP:30 | HP:300 | skills[0].cd=1
|
||||||
* 坦克(cards_lv:1,lv:1) : SPEED:180,AP:25 |HP:450|as:0.75
|
* 坦克(cards_lv:1,lv:1) : SPEED:180,AP:25 | HP:450 | skills[0].cd=0.75
|
||||||
* 近战dps(cards_lv:1,lv:1) : SPEED:180,AP:50 |HP:250|as:1.1
|
* 近战dps(cards_lv:1,lv:1) : SPEED:180,AP:50 | HP:250 | skills[0].cd=1.1
|
||||||
* 远程dps(cards_lv:1,lv:1) : SPEED:120,AP:60 |HP:150|as:1.3
|
* 远程dps(cards_lv:1,lv:1) : SPEED:120,AP:60 | HP:150 | skills[0].cd=1.3
|
||||||
*远程法dps(cards_lv:1,lv:1) : SPEED:100,AP:60 |HP:150|as:1.4
|
*远程法dps(cards_lv:1,lv:1) : SPEED:100,AP:60 | HP:150 | skills[0].cd=1.4
|
||||||
* 远程辅助(cards_lv:1,lv:1) : SPEED:100,AP:20 |HP:150|as:1
|
* 远程辅助(cards_lv:1,lv:1) : SPEED:100,AP:20 | HP:150 | skills[0].cd=1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const HeroInfo: Record<number, heroInfo> = {
|
export const HeroInfo: Record<number, heroInfo> = {
|
||||||
|
|||||||
@@ -10,14 +10,20 @@ import { GameEvent } from "../common/config/GameEvent";
|
|||||||
import { Attrs} from "../common/config/HeroAttrs";
|
import { Attrs} from "../common/config/HeroAttrs";
|
||||||
import { MoveComp } from "./MoveComp";
|
import { MoveComp } from "./MoveComp";
|
||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
/** 角色实体 */
|
/** 英雄实体:负责英雄节点创建、属性初始化、入场动画与销毁流程 */
|
||||||
@ecs.register(`Hero`)
|
@ecs.register(`Hero`)
|
||||||
|
|
||||||
export class Hero extends ecs.Entity {
|
export class Hero extends ecs.Entity {
|
||||||
|
/** 英雄数据组件引用 */
|
||||||
HeroModel!: HeroAttrsComp;
|
HeroModel!: HeroAttrsComp;
|
||||||
|
/** 英雄表现组件引用 */
|
||||||
View!: HeroViewComp;
|
View!: HeroViewComp;
|
||||||
|
/** 英雄移动组件引用 */
|
||||||
HeroMove!: MoveComp;
|
HeroMove!: MoveComp;
|
||||||
debugMode: boolean = false; // 是否启用调试模式
|
/** 调试开关,开启后输出实体层级等调试信息 */
|
||||||
|
debugMode: boolean = false;
|
||||||
|
|
||||||
|
/** 注册实体必需组件:移动 + 属性 */
|
||||||
protected init() {
|
protected init() {
|
||||||
this.addComponents<ecs.Comp>(
|
this.addComponents<ecs.Comp>(
|
||||||
MoveComp,
|
MoveComp,
|
||||||
@@ -25,88 +31,111 @@ export class Hero extends ecs.Entity {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 销毁实体并释放视图节点,防止残留碰撞体与显示对象 */
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
// 销毁节点,防止视觉残留
|
// 优先销毁节点,避免实体销毁后场景仍残留可见对象
|
||||||
const view = this.get(HeroViewComp);
|
const view = this.get(HeroViewComp);
|
||||||
if (view && view.node && view.node.isValid) {
|
if (view && view.node && view.node.isValid) {
|
||||||
view.node.destroy();
|
view.node.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手动移除组件,确保 ecs 侧引用及时释放
|
||||||
this.remove(HeroViewComp);
|
this.remove(HeroViewComp);
|
||||||
this.remove(HeroAttrsComp);
|
this.remove(HeroAttrsComp);
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** 加载角色 */
|
/**
|
||||||
|
* 加载并初始化英雄
|
||||||
|
* 1) 创建节点并挂到 HERO 层
|
||||||
|
* 2) 初始化表现与属性数据
|
||||||
|
* 3) 播放下落入场并在落地后启用碰撞与移动
|
||||||
|
*/
|
||||||
load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001, dropToY:number = pos.y,hero_lv:number=1) {
|
load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001, dropToY:number = pos.y,hero_lv:number=1) {
|
||||||
|
// 英雄始终朝右,表现缩放固定为正向
|
||||||
scale = 1
|
scale = 1
|
||||||
|
// 英雄等级在当前规则下上限为 3,避免超配表范围
|
||||||
if(hero_lv>3) hero_lv=3
|
if(hero_lv>3) hero_lv=3
|
||||||
// 查找空闲英雄槽位
|
// 英雄尺寸随等级做轻量放大,强化成长反馈
|
||||||
let size=1+0.1*hero_lv
|
let size=1+0.1*hero_lv
|
||||||
|
// 根据配置路径加载英雄预制体
|
||||||
var path = "game/heros/"+HeroInfo[uuid].path;
|
var path = "game/heros/"+HeroInfo[uuid].path;
|
||||||
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
||||||
var node = instantiate(prefab);
|
var node = instantiate(prefab);
|
||||||
var scene = smc.map.MapView.scene;
|
var scene = smc.map.MapView.scene;
|
||||||
|
// 统一挂到实体显示层 HERO 节点下
|
||||||
node.parent = scene.entityLayer!.node!.getChildByName("HERO")!;
|
node.parent = scene.entityLayer!.node!.getChildByName("HERO")!;
|
||||||
const collider = node.getComponent(BoxCollider2D);
|
const collider = node.getComponent(BoxCollider2D);
|
||||||
if (collider) collider.enabled = false; // 先禁用
|
// 入场过程暂不参与碰撞,防止半空触发战斗逻辑
|
||||||
|
if (collider) collider.enabled = false;
|
||||||
node.setScale(size*node.scale.x,size*node.scale.y);
|
node.setScale(size*node.scale.x,size*node.scale.y);
|
||||||
node.setPosition(pos)
|
node.setPosition(pos)
|
||||||
|
|
||||||
// 🔥 设置初始 SiblingIndex - 英雄基础层级 + 位置偏移
|
// 输出节点层级信息,便于排查遮挡与渲染顺序问题
|
||||||
|
|
||||||
mLogger.log(this.debugMode,"hero",node.getSiblingIndex());
|
mLogger.log(this.debugMode,"hero",node.getSiblingIndex());
|
||||||
|
|
||||||
var hv = node.getComponent(HeroViewComp)!;
|
var hv = node.getComponent(HeroViewComp)!;
|
||||||
const model = this.get(HeroAttrsComp);
|
const model = this.get(HeroAttrsComp);
|
||||||
let hero = HeroInfo[uuid]; // 共用英雄数据
|
// 从配置中读取英雄静态数据
|
||||||
|
let hero = HeroInfo[uuid];
|
||||||
|
|
||||||
// 设置 View 层属性(表现相关)
|
// 视图层参数:朝向与碰撞阵营
|
||||||
hv.scale = 1;
|
hv.scale = 1;
|
||||||
hv.box_group = BoxSet.HERO;
|
hv.box_group = BoxSet.HERO;
|
||||||
// 设置 Model 层属性(数据相关)
|
// 模型层参数:身份、阵营、等级、职业
|
||||||
model.hero_uuid = uuid;
|
model.hero_uuid = uuid;
|
||||||
model.hero_name = hero.name;
|
model.hero_name = hero.name;
|
||||||
model.lv = hero_lv;
|
model.lv = hero_lv;
|
||||||
model.type = hero.type;
|
model.type = hero.type;
|
||||||
model.fac = FacSet.HERO;
|
model.fac = FacSet.HERO;
|
||||||
// 只有主角才挂载天赋组件
|
|
||||||
|
|
||||||
// ✅ 初始化技能数据(迁移到 HeroSkillsComp)
|
|
||||||
|
|
||||||
// 设置基础属性
|
// 基础属性按等级倍率初始化
|
||||||
model.ap = hero.ap*model.lv;
|
model.ap = hero.ap*model.lv;
|
||||||
model.hp= model.hp_max = hero.hp*model.lv;
|
model.hp= model.hp_max = hero.hp*model.lv;
|
||||||
model.speed = hero.speed;
|
model.speed = hero.speed;
|
||||||
|
|
||||||
|
// 构建技能表并注入运行时冷却字段 ccd
|
||||||
model.skills = {};
|
model.skills = {};
|
||||||
for (const key in hero.skills) {
|
for (const key in hero.skills) {
|
||||||
const skill = hero.skills[key];
|
const skill = hero.skills[key];
|
||||||
if (!skill) continue;
|
if (!skill) continue;
|
||||||
//用于增量 计算最终技能等级:英雄等级与技能初始等级均从1开始,需各减1抵消,故-2,最低等级时0
|
// 最终技能等级 = 初始技能等级 + 英雄等级增量,且下限为 0
|
||||||
model.skills[skill.uuid] = { ...skill, lv: Math.max(0,skill.lv + hero_lv - 2), ccd: 0 };
|
model.skills[skill.uuid] = { ...skill, lv: Math.max(0,skill.lv + hero_lv - 2), ccd: 0 };
|
||||||
}
|
}
|
||||||
|
// 缓存技能射程等派生数据,减少战斗帧内重复计算
|
||||||
model.updateSkillDistanceCache();
|
model.updateSkillDistanceCache();
|
||||||
|
|
||||||
// 初始化 buff/debuff 系统
|
// 初始化属性系统(buff/debuff 等动态属性容器)
|
||||||
model.initAttrs();
|
model.initAttrs();
|
||||||
|
|
||||||
|
// 将视图组件注册到实体,打通逻辑与表现
|
||||||
this.add(hv);
|
this.add(hv);
|
||||||
|
// 广播主角召唤事件,触发外部系统监听逻辑
|
||||||
oops.message.dispatchEvent(GameEvent.MasterCalled,{uuid:uuid})
|
oops.message.dispatchEvent(GameEvent.MasterCalled,{uuid:uuid})
|
||||||
|
|
||||||
|
// 初始化移动组件:方向、目标 X、站位基准 Y
|
||||||
const move = this.get(MoveComp);
|
const move = this.get(MoveComp);
|
||||||
move.direction = 1; // 向右移动
|
move.direction = 1;
|
||||||
move.targetX = resolveFormationTargetX(model.fac, model.type);
|
move.targetX = resolveFormationTargetX(model.fac, model.type);
|
||||||
move.baseY = dropToY;
|
move.baseY = dropToY;
|
||||||
move.moving = false;
|
move.moving = false;
|
||||||
|
|
||||||
|
// 依据下落距离自适应入场时长,保证手感稳定
|
||||||
const dropDistance = Math.abs(pos.y - dropToY);
|
const dropDistance = Math.abs(pos.y - dropToY);
|
||||||
const dropDuration = Math.max(0.18, Math.min(0.38, dropDistance / 1200));
|
const dropDuration = Math.max(0.18, Math.min(0.38, dropDistance / 1200));
|
||||||
|
|
||||||
|
// 停止旧动画后执行下落 tween,避免复用节点时动画叠加
|
||||||
Tween.stopAllByTarget(node);
|
Tween.stopAllByTarget(node);
|
||||||
tween(node)
|
tween(node)
|
||||||
.to(dropDuration, { position: v3(pos.x, dropToY, 0) })
|
.to(dropDuration, { position: v3(pos.x, dropToY, 0) })
|
||||||
.call(() => {
|
.call(() => {
|
||||||
if (!node || !node.isValid) return;
|
if (!node || !node.isValid) return;
|
||||||
|
// 落地后锁定最终位置,切换到落地完成状态
|
||||||
node.setPosition(pos.x, dropToY, 0);
|
node.setPosition(pos.x, dropToY, 0);
|
||||||
hv.playEnd("down");
|
hv.playEnd("down");
|
||||||
move.moving = true;
|
move.moving = true;
|
||||||
|
// 落地后再启用碰撞,避免空中阶段触发伤害结算
|
||||||
if (collider) {
|
if (collider) {
|
||||||
collider.enabled = true;
|
collider.enabled = true;
|
||||||
collider.group = BoxSet.HERO;
|
collider.group = BoxSet.HERO;
|
||||||
@@ -114,26 +143,29 @@ export class Hero extends ecs.Entity {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.start();
|
.start();
|
||||||
|
// 维护关卡内英雄数量统计
|
||||||
smc.vmdata.mission_data.hero_num++
|
smc.vmdata.mission_data.hero_num++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 重置入口:复用 destroy 的释放流程 */
|
||||||
reset() {
|
reset() {
|
||||||
// 注: 自定义释放逻辑,视图层实现 ecs.IComp 接口的 ecs 组件需要手动释放
|
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 英雄生命周期系统:监听实体进入与移除并输出调试日志 */
|
||||||
@ecs.register('HeroLifecycleSystem')
|
@ecs.register('HeroLifecycleSystem')
|
||||||
export class HeroLifecycleSystem extends ecs.ComblockSystem
|
export class HeroLifecycleSystem extends ecs.ComblockSystem
|
||||||
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
|
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
|
||||||
|
|
||||||
|
/** 仅处理拥有 MoveComp 的实体 */
|
||||||
filter() {
|
filter() {
|
||||||
return ecs.allOf(MoveComp);
|
return ecs.allOf(MoveComp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 实体进入世界时记录日志 */
|
||||||
entityEnter(e: ecs.Entity): void {
|
entityEnter(e: ecs.Entity): void {
|
||||||
// 英雄实体创建时的特殊处理
|
|
||||||
const heroAttrs = e.get(HeroAttrsComp);
|
const heroAttrs = e.get(HeroAttrsComp);
|
||||||
if (heroAttrs) {
|
if (heroAttrs) {
|
||||||
mLogger.log(heroAttrs.debugMode, 'HeroLifecycle', `英雄进入世界: ${heroAttrs.hero_name}`);
|
mLogger.log(heroAttrs.debugMode, 'HeroLifecycle', `英雄进入世界: ${heroAttrs.hero_name}`);
|
||||||
@@ -142,8 +174,8 @@ export class HeroLifecycleSystem extends ecs.ComblockSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 实体离开世界时记录日志 */
|
||||||
entityRemove(e: ecs.Entity): void {
|
entityRemove(e: ecs.Entity): void {
|
||||||
// 英雄实体销毁时的清理工作
|
|
||||||
const heroAttrs = e.get(HeroAttrsComp);
|
const heroAttrs = e.get(HeroAttrsComp);
|
||||||
if (heroAttrs) {
|
if (heroAttrs) {
|
||||||
mLogger.log(heroAttrs.debugMode, 'HeroLifecycle', `英雄离开世界: ${heroAttrs.hero_name}`);
|
mLogger.log(heroAttrs.debugMode, 'HeroLifecycle', `英雄离开世界: ${heroAttrs.hero_name}`);
|
||||||
|
|||||||
@@ -8,18 +8,26 @@ import { HeroAttrsComp } from "./HeroAttrsComp";
|
|||||||
import { HeroViewComp } from "./HeroViewComp";
|
import { HeroViewComp } from "./HeroViewComp";
|
||||||
import { MoveComp } from "./MoveComp";
|
import { MoveComp } from "./MoveComp";
|
||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
/** 角色实体 */
|
/** 怪物实体:负责怪物对象池复用、属性初始化、入场动画与回收 */
|
||||||
@ecs.register(`Monster`)
|
@ecs.register(`Monster`)
|
||||||
export class Monster extends ecs.Entity {
|
export class Monster extends ecs.Entity {
|
||||||
|
/** 怪物数据组件引用 */
|
||||||
HeroModel!: HeroAttrsComp;
|
HeroModel!: HeroAttrsComp;
|
||||||
|
/** 怪物表现组件引用 */
|
||||||
HeroView!: HeroViewComp;
|
HeroView!: HeroViewComp;
|
||||||
|
/** 怪物移动组件引用 */
|
||||||
MonMove!: MoveComp;
|
MonMove!: MoveComp;
|
||||||
private debugMode: boolean = false; // 是否启用调试模式
|
/** 调试开关,控制生命周期日志输出 */
|
||||||
|
private debugMode: boolean = false;
|
||||||
|
|
||||||
// 多键对象池:Map<prefabPath, NodePool>
|
/** 多键对象池:key 为 prefab 路径,value 为对应节点池 */
|
||||||
static pools: Map<string, NodePool> = new Map();
|
static pools: Map<string, NodePool> = new Map();
|
||||||
|
/** 单个路径的池容量上限 */
|
||||||
static readonly MAX_POOL_SIZE: number = 12;
|
static readonly MAX_POOL_SIZE: number = 12;
|
||||||
|
/** 所有路径合计池容量上限 */
|
||||||
static readonly MAX_POOL_TOTAL: number = 60;
|
static readonly MAX_POOL_TOTAL: number = 60;
|
||||||
|
|
||||||
|
/** 计算当前所有对象池节点总量 */
|
||||||
private static totalPoolSize(): number {
|
private static totalPoolSize(): number {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
this.pools.forEach((pool) => {
|
this.pools.forEach((pool) => {
|
||||||
@@ -28,6 +36,7 @@ export class Monster extends ecs.Entity {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 从指定路径对象池取可用节点,取不到返回 null */
|
||||||
static getFromPool(path: string): Node | null {
|
static getFromPool(path: string): Node | null {
|
||||||
if (this.pools.has(path)) {
|
if (this.pools.has(path)) {
|
||||||
const pool = this.pools.get(path)!;
|
const pool = this.pools.get(path)!;
|
||||||
@@ -41,6 +50,7 @@ export class Monster extends ecs.Entity {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 节点回收到对象池,超上限则直接销毁 */
|
||||||
static putToPool(path: string, node: Node) {
|
static putToPool(path: string, node: Node) {
|
||||||
if (!node || !node.isValid) return;
|
if (!node || !node.isValid) return;
|
||||||
if (!this.pools.has(path)) {
|
if (!this.pools.has(path)) {
|
||||||
@@ -54,6 +64,7 @@ export class Monster extends ecs.Entity {
|
|||||||
pool.put(node);
|
pool.put(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 清空所有对象池并销毁池内节点 */
|
||||||
static clearPools() {
|
static clearPools() {
|
||||||
this.pools.forEach((pool) => {
|
this.pools.forEach((pool) => {
|
||||||
while (pool.size() > 0) {
|
while (pool.size() > 0) {
|
||||||
@@ -67,6 +78,7 @@ export class Monster extends ecs.Entity {
|
|||||||
this.pools.clear();
|
this.pools.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取对象池统计信息,用于调试与容量监控 */
|
||||||
static getPoolStats() {
|
static getPoolStats() {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
this.pools.forEach((pool) => {
|
this.pools.forEach((pool) => {
|
||||||
@@ -80,6 +92,7 @@ export class Monster extends ecs.Entity {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 注册实体必需组件:移动 + 属性 */
|
||||||
protected init() {
|
protected init() {
|
||||||
this.addComponents<ecs.Comp>(
|
this.addComponents<ecs.Comp>(
|
||||||
MoveComp,
|
MoveComp,
|
||||||
@@ -87,8 +100,9 @@ export class Monster extends ecs.Entity {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 销毁实体:优先回收节点,然后释放组件 */
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
// 回收节点到对象池
|
// 按英雄路径回收到对象池,提升高频刷怪性能
|
||||||
const model = this.get(HeroAttrsComp);
|
const model = this.get(HeroAttrsComp);
|
||||||
const view = this.get(HeroViewComp);
|
const view = this.get(HeroViewComp);
|
||||||
if (model && view && view.node && view.node.isValid) {
|
if (model && view && view.node && view.node.isValid) {
|
||||||
@@ -96,28 +110,39 @@ export class Monster extends ecs.Entity {
|
|||||||
Monster.putToPool(path, view.node);
|
Monster.putToPool(path, view.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手动移除组件,避免 ecs 引用滞留
|
||||||
this.remove(HeroViewComp);
|
this.remove(HeroViewComp);
|
||||||
this.remove(HeroAttrsComp);
|
this.remove(HeroAttrsComp);
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载角色 */
|
/**
|
||||||
load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001, is_boss:boolean=false, dropToY:number = pos.y) {
|
* 加载并初始化怪物
|
||||||
|
* 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
|
scale=-1
|
||||||
|
// 当前怪物尺寸固定,保留变量便于后续扩展
|
||||||
let size=1
|
let size=1
|
||||||
var scene = smc.map.MapView.scene;
|
var scene = smc.map.MapView.scene;
|
||||||
|
// 根据配置读取怪物预制体路径
|
||||||
var path = "game/heros/"+HeroInfo[uuid].path;
|
var path = "game/heros/"+HeroInfo[uuid].path;
|
||||||
|
|
||||||
// 尝试从池中获取
|
// 优先从对象池取节点,未命中时再实例化
|
||||||
let node = Monster.getFromPool(path);
|
let node = Monster.getFromPool(path);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
||||||
node = instantiate(prefab);
|
node = instantiate(prefab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 统一挂到实体显示层 HERO 节点下
|
||||||
node.parent = scene.entityLayer!.node!.getChildByName("HERO")!;
|
node.parent = scene.entityLayer!.node!.getChildByName("HERO")!;
|
||||||
var view = node.getComponent(HeroViewComp)!;
|
var view = node.getComponent(HeroViewComp)!;
|
||||||
const collider = node.getComponent(BoxCollider2D);
|
const collider = node.getComponent(BoxCollider2D);
|
||||||
|
// 入场期间关闭碰撞,防止下落时提前参与战斗
|
||||||
if (collider) {
|
if (collider) {
|
||||||
collider.enabled = false;
|
collider.enabled = false;
|
||||||
}
|
}
|
||||||
@@ -125,54 +150,64 @@ export class Monster extends ecs.Entity {
|
|||||||
node.setScale(size*node.scale.x,size*node.scale.y);
|
node.setScale(size*node.scale.x,size*node.scale.y);
|
||||||
node.setPosition(pos)
|
node.setPosition(pos)
|
||||||
const model = this.get(HeroAttrsComp);
|
const model = this.get(HeroAttrsComp);
|
||||||
let hero = HeroInfo[uuid]; // 共用英雄数据
|
// 从配置表获取怪物静态数据
|
||||||
// 设置 View 层属性(表现相关)
|
let hero = HeroInfo[uuid];
|
||||||
|
// 视图层参数:朝向与碰撞阵营
|
||||||
view.scale = scale;
|
view.scale = scale;
|
||||||
view.box_group = BoxSet.MONSTER;
|
view.box_group = BoxSet.MONSTER;
|
||||||
// 设置 Model 层属性 基础属性
|
// 模型层参数:身份、阵营、基础数值
|
||||||
model.hero_uuid = uuid;
|
model.hero_uuid = uuid;
|
||||||
model.hero_name = hero.name;
|
model.hero_name = hero.name;
|
||||||
model.hp = model.hp_max = hero.hp;
|
model.hp = model.hp_max = hero.hp;
|
||||||
model.ap = hero.ap;
|
model.ap = hero.ap;
|
||||||
model.speed = hero.speed; // 使用成长后的速度
|
model.speed = hero.speed;
|
||||||
model.type = hero.type;
|
model.type = hero.type;
|
||||||
model.fac = FacSet.MON;
|
model.fac = FacSet.MON;
|
||||||
|
// 标记是否 Boss,非 Boss 默认记作杂兵
|
||||||
model.is_boss =is_boss
|
model.is_boss =is_boss
|
||||||
if(!model.is_boss){
|
if(!model.is_boss){
|
||||||
model.is_kalami = true;
|
model.is_kalami = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建技能表并注入运行时冷却字段 ccd
|
||||||
model.skills = {};
|
model.skills = {};
|
||||||
for (const key in hero.skills) {
|
for (const key in hero.skills) {
|
||||||
const skill = hero.skills[key];
|
const skill = hero.skills[key];
|
||||||
if (!skill) continue;
|
if (!skill) continue;
|
||||||
model.skills[skill.uuid] = { ...skill, ccd: 0 };
|
// 最终技能等级 = 初始技能等级 + 怪物等级增量,且下限为 0
|
||||||
|
model.skills[skill.uuid] = { ...skill, lv: Math.max(0,skill.lv + mon_lv - 2), ccd: 0 };
|
||||||
}
|
}
|
||||||
|
// 缓存技能射程等派生数据,减少战斗帧内重复计算
|
||||||
model.updateSkillDistanceCache();
|
model.updateSkillDistanceCache();
|
||||||
//根据刷怪控制脚本对ap和hp进行加强
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 注册视图组件并重置对象池复用状态
|
||||||
this.add(view);
|
this.add(view);
|
||||||
// 重置视图状态(对象池复用时必须)
|
|
||||||
view.init();
|
view.init();
|
||||||
|
// 广播怪物加载事件,供刷怪与战斗系统联动
|
||||||
oops.message.dispatchEvent("monster_load",this)
|
oops.message.dispatchEvent("monster_load",this)
|
||||||
// 初始化移动参数,包括线路和生成顺序
|
// 初始化移动参数:方向、目标 X、站位基准 Y
|
||||||
const move = this.get(MoveComp);
|
const move = this.get(MoveComp);
|
||||||
move.reset();
|
move.reset();
|
||||||
move.direction = -1; // 向左移动
|
move.direction = -1;
|
||||||
move.targetX = Math.max(-320, Math.min(320, pos.x));
|
move.targetX = Math.max(-320, Math.min(320, pos.x));
|
||||||
move.baseY = dropToY;
|
move.baseY = dropToY;
|
||||||
move.moving = false;
|
move.moving = false;
|
||||||
|
|
||||||
|
// 依据下落距离自适应入场时长,确保观感一致
|
||||||
const dropDistance = Math.abs(pos.y - dropToY);
|
const dropDistance = Math.abs(pos.y - dropToY);
|
||||||
const dropDuration = Math.max(0.18, Math.min(0.38, dropDistance / 1200));
|
const dropDuration = Math.max(0.18, Math.min(0.38, dropDistance / 1200));
|
||||||
|
|
||||||
|
// 停止旧动画后执行下落 tween,避免复用节点时动画叠加
|
||||||
Tween.stopAllByTarget(node);
|
Tween.stopAllByTarget(node);
|
||||||
tween(node)
|
tween(node)
|
||||||
.to(dropDuration, { position: v3(pos.x, dropToY, 0) })
|
.to(dropDuration, { position: v3(pos.x, dropToY, 0) })
|
||||||
.call(() => {
|
.call(() => {
|
||||||
if (!node || !node.isValid) return;
|
if (!node || !node.isValid) return;
|
||||||
|
// 落地后锁定最终位置,切换到落地完成状态
|
||||||
node.setPosition(pos.x, dropToY, 0);
|
node.setPosition(pos.x, dropToY, 0);
|
||||||
view.playEnd("down");
|
view.playEnd("down");
|
||||||
move.moving = true;
|
move.moving = true;
|
||||||
|
// 落地后启用怪物碰撞分组
|
||||||
if (collider) {
|
if (collider) {
|
||||||
collider.enabled = true;
|
collider.enabled = true;
|
||||||
collider.group = BoxSet.MONSTER;
|
collider.group = BoxSet.MONSTER;
|
||||||
@@ -180,27 +215,31 @@ export class Monster extends ecs.Entity {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.start();
|
.start();
|
||||||
|
// 维护关卡内怪物数量统计
|
||||||
smc.vmdata.mission_data.mon_num++
|
smc.vmdata.mission_data.mon_num++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 重置入口:复用 destroy 的释放流程 */
|
||||||
reset() {
|
reset() {
|
||||||
// 注: 自定义释放逻辑,视图层实现 ecs.IComp 接口的 ecs 组件需要手动释放
|
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/** 怪物生命周期系统:监听实体进入与移除并输出调试日志 */
|
||||||
@ecs.register('MonLifecycleSystem')
|
@ecs.register('MonLifecycleSystem')
|
||||||
export class MonLifecycleSystem extends ecs.ComblockSystem
|
export class MonLifecycleSystem extends ecs.ComblockSystem
|
||||||
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
|
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
|
||||||
debugMode: boolean = false; // 是否启用调试模式
|
/** 调试开关,控制系统日志输出 */
|
||||||
|
debugMode: boolean = false;
|
||||||
|
|
||||||
|
/** 仅处理拥有 MoveComp 的实体 */
|
||||||
filter() {
|
filter() {
|
||||||
return ecs.allOf(MoveComp);
|
return ecs.allOf(MoveComp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 实体进入世界时记录日志 */
|
||||||
entityEnter(e: ecs.Entity): void {
|
entityEnter(e: ecs.Entity): void {
|
||||||
// 怪物实体创建时的特殊处理
|
|
||||||
const heroAttrs = e.get(HeroAttrsComp);
|
const heroAttrs = e.get(HeroAttrsComp);
|
||||||
if (heroAttrs) {
|
if (heroAttrs) {
|
||||||
mLogger.log(this.debugMode, 'MonLifecycleSystem', `怪物进入世界: ${heroAttrs.hero_name}`);
|
mLogger.log(this.debugMode, 'MonLifecycleSystem', `怪物进入世界: ${heroAttrs.hero_name}`);
|
||||||
@@ -209,8 +248,8 @@ export class MonLifecycleSystem extends ecs.ComblockSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 实体离开世界时记录日志 */
|
||||||
entityRemove(e: ecs.Entity): void {
|
entityRemove(e: ecs.Entity): void {
|
||||||
// 怪物实体销毁时的清理工作
|
|
||||||
const heroAttrs = e.get(HeroAttrsComp);
|
const heroAttrs = e.get(HeroAttrsComp);
|
||||||
if (heroAttrs) {
|
if (heroAttrs) {
|
||||||
mLogger.log(this.debugMode, 'MonLifecycleSystem', `怪物离开世界: ${heroAttrs.hero_name}`);
|
mLogger.log(this.debugMode, 'MonLifecycleSystem', `怪物离开世界: ${heroAttrs.hero_name}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user