- 移除 Hero.ts 中手动维护的 hero_num 计数逻辑 - 在 SingletonModuleComp 的 mission_data 中添加 hero_max_num、hero_extend_max_num 字段 - 重构 MissionCardComp,使其通过 smc.vmdata.mission_data 读写英雄数量、金币等状态 - 新增辅助方法统一数据访问与同步,避免状态分散管理
208 lines
8.0 KiB
TypeScript
208 lines
8.0 KiB
TypeScript
import { instantiate, Node, Prefab, Vec3 ,v3,resources,SpriteFrame,Sprite,SpriteAtlas, BoxCollider2D, 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 { HeroAttrsComp } from "./HeroAttrsComp";
|
||
import { HeroViewComp } from "./HeroViewComp";
|
||
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
|
||
import { HeroInfo, HeroPos, resolveFormationTargetX } from "../common/config/heroSet";
|
||
import { GameEvent } from "../common/config/GameEvent";
|
||
import { Attrs} from "../common/config/HeroAttrs";
|
||
import { MoveComp } from "./MoveComp";
|
||
import { mLogger } from "../common/Logger";
|
||
/** 英雄实体:负责英雄节点创建、属性初始化、入场动画与销毁流程 */
|
||
@ecs.register(`Hero`)
|
||
|
||
export class Hero extends ecs.Entity {
|
||
/** 英雄数据组件引用 */
|
||
HeroModel!: HeroAttrsComp;
|
||
/** 英雄表现组件引用 */
|
||
View!: HeroViewComp;
|
||
/** 英雄移动组件引用 */
|
||
HeroMove!: MoveComp;
|
||
/** 调试开关,开启后输出实体层级等调试信息 */
|
||
debugMode: boolean = false;
|
||
|
||
/** 注册实体必需组件:移动 + 属性 */
|
||
protected init() {
|
||
this.addComponents<ecs.Comp>(
|
||
MoveComp,
|
||
HeroAttrsComp,
|
||
);
|
||
}
|
||
|
||
/** 销毁实体并释放视图节点,防止残留碰撞体与显示对象 */
|
||
destroy(): void {
|
||
// 优先销毁节点,避免实体销毁后场景仍残留可见对象
|
||
const view = this.get(HeroViewComp);
|
||
if (view && view.node && view.node.isValid) {
|
||
view.node.destroy();
|
||
}
|
||
|
||
// 手动移除组件,确保 ecs 侧引用及时释放
|
||
this.remove(HeroViewComp);
|
||
this.remove(HeroAttrsComp);
|
||
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) {
|
||
// 英雄始终朝右,表现缩放固定为正向
|
||
scale = 1
|
||
// 英雄等级在当前规则下上限为 3,避免超配表范围
|
||
if(hero_lv>3) hero_lv=3
|
||
// 英雄尺寸随等级做轻量放大,强化成长反馈
|
||
let size=1+0.1*hero_lv
|
||
// 根据配置路径加载英雄预制体
|
||
var path = "game/heros/"+HeroInfo[uuid].path;
|
||
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
||
var node = instantiate(prefab);
|
||
var scene = smc.map.MapView.scene;
|
||
// 统一挂到实体显示层 HERO 节点下
|
||
node.parent = scene.entityLayer!.node!.getChildByName("HERO")!;
|
||
const collider = node.getComponent(BoxCollider2D);
|
||
// 入场过程暂不参与碰撞,防止半空触发战斗逻辑
|
||
if (collider) collider.enabled = false;
|
||
node.setScale(size*node.scale.x,size*node.scale.y);
|
||
node.setPosition(pos)
|
||
|
||
// 输出节点层级信息,便于排查遮挡与渲染顺序问题
|
||
mLogger.log(this.debugMode,"hero",node.getSiblingIndex());
|
||
|
||
var hv = node.getComponent(HeroViewComp)!;
|
||
const model = this.get(HeroAttrsComp);
|
||
// 从配置中读取英雄静态数据
|
||
let hero = HeroInfo[uuid];
|
||
|
||
// 视图层参数:朝向与碰撞阵营
|
||
hv.scale = 1;
|
||
hv.box_group = BoxSet.HERO;
|
||
// 模型层参数:身份、阵营、等级、职业
|
||
model.hero_uuid = uuid;
|
||
model.hero_name = hero.name;
|
||
model.lv = hero_lv;
|
||
model.type = hero.type;
|
||
model.fac = FacSet.HERO;
|
||
|
||
// 基础属性按等级倍率初始化
|
||
model.ap = hero.ap*model.lv;
|
||
model.hp= model.hp_max = hero.hp*model.lv;
|
||
model.speed = hero.speed;
|
||
|
||
// 构建技能表并注入运行时冷却字段 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 + hero_lv - 2), ccd: 0 };
|
||
}
|
||
// 缓存技能射程等派生数据,减少战斗帧内重复计算
|
||
model.updateSkillDistanceCache();
|
||
|
||
// 初始化属性系统(buff/debuff 等动态属性容器)
|
||
model.initAttrs();
|
||
|
||
// 将视图组件注册到实体,打通逻辑与表现
|
||
this.add(hv);
|
||
hv.init();
|
||
// 广播主角召唤事件,触发外部系统监听逻辑
|
||
oops.message.dispatchEvent(GameEvent.MasterCalled, {
|
||
eid: this.eid,
|
||
uuid,
|
||
hero_lv,
|
||
model
|
||
})
|
||
|
||
// 初始化移动组件:方向、目标 X、站位基准 Y
|
||
const move = this.get(MoveComp);
|
||
move.direction = 1;
|
||
move.targetX = resolveFormationTargetX(model.fac, model.type);
|
||
move.baseY = dropToY;
|
||
move.moving = false;
|
||
hv.as.idle();
|
||
|
||
// 依据下落距离自适应入场时长,保证手感稳定
|
||
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);
|
||
hv.playEnd("down");
|
||
move.moving = true;
|
||
// 落地后再启用碰撞,避免空中阶段触发伤害结算
|
||
if (collider) {
|
||
collider.enabled = true;
|
||
collider.group = BoxSet.HERO;
|
||
collider.apply();
|
||
}
|
||
})
|
||
.start();
|
||
}
|
||
|
||
/** 重置入口:复用 destroy 的释放流程 */
|
||
reset() {
|
||
super.destroy();
|
||
}
|
||
|
||
}
|
||
|
||
/** 统一生命周期系统:按 fac 区分英雄与怪物并输出日志 */
|
||
@ecs.register('BattleEntityLifecycleSystem')
|
||
export class BattleEntityLifecycleSystem extends ecs.ComblockSystem
|
||
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
|
||
|
||
/** 仅处理拥有 MoveComp 的实体 */
|
||
filter() {
|
||
return ecs.allOf(MoveComp);
|
||
}
|
||
|
||
/** 基于阵营生成日志名称 */
|
||
private resolveLabel(heroAttrs: HeroAttrsComp | null) {
|
||
if (!heroAttrs) return "未知";
|
||
if (heroAttrs.fac === FacSet.HERO) return "英雄";
|
||
if (heroAttrs.fac === FacSet.MON) return "怪物";
|
||
return "未知";
|
||
}
|
||
|
||
/** 实体进入世界时记录日志 */
|
||
entityEnter(e: ecs.Entity): void {
|
||
const heroAttrs = e.get(HeroAttrsComp);
|
||
const label = this.resolveLabel(heroAttrs);
|
||
if (heroAttrs) {
|
||
mLogger.log(heroAttrs.debugMode, 'BattleEntityLifecycle', `${label}进入世界: ${heroAttrs.hero_name}`);
|
||
} else {
|
||
mLogger.log(true, 'BattleEntityLifecycle', `${label}进入世界: 实体ID ${e.eid}`);
|
||
}
|
||
}
|
||
|
||
/** 实体离开世界时记录日志 */
|
||
entityRemove(e: ecs.Entity): void {
|
||
const heroAttrs = e.get(HeroAttrsComp);
|
||
const label = this.resolveLabel(heroAttrs);
|
||
if (heroAttrs) {
|
||
mLogger.log(heroAttrs.debugMode, 'BattleEntityLifecycle', `${label}离开世界: ${heroAttrs.hero_name}`);
|
||
if (heroAttrs.fac === FacSet.HERO) {
|
||
oops.message.dispatchEvent(GameEvent.HeroDead, {
|
||
eid: e.eid,
|
||
model: heroAttrs
|
||
});
|
||
}
|
||
} else {
|
||
mLogger.log(true, 'BattleEntityLifecycle', `${label}离开世界: 实体ID ${e.eid}`);
|
||
}
|
||
}
|
||
}
|