refactor(战斗): 重构英雄与怪物属性系统,简化数据结构

- 移除 HeroSkillsComp 组件,将技能逻辑合并到 HeroAttrsComp
- 将属性从 Attrs 枚举映射改为 HeroAttrsComp 中的独立字段
- 为 HeroAttrsComp 添加攻击和技能冷却时间管理功能
- 统一英雄和怪物的属性初始化方式,简化配置数据
- 在 GameSet 中添加击退概率配置项
- 修复 SkillView 中属性名大小写错误
This commit is contained in:
walkpan
2026-03-11 23:13:21 +08:00
parent 9d6075be6e
commit a544f65d73
9 changed files with 85 additions and 202 deletions

View File

@@ -4,11 +4,10 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { smc } from "../common/SingletonModuleComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { BoxSet, FacSet, IndexSet } from "../common/config/GameSet";
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
import { HeroInfo, HeroPos, HType } from "../common/config/heroSet";
import { GameEvent } from "../common/config/GameEvent";
import { getNeAttrs, getAttrs ,Attrs, defaultAttrs} from "../common/config/HeroAttrs";
import { HeroSkillsComp } from "./HeroSkills";
import { Attrs} from "../common/config/HeroAttrs";
import { HeroMoveComp } from "./HeroMove";
import { mLogger } from "../common/Logger";
import { HeroMasterComp } from "./HeroMasterComp";
@@ -17,7 +16,6 @@ import { HeroMasterComp } from "./HeroMasterComp";
export class Hero extends ecs.Entity {
HeroModel!: HeroAttrsComp;
HeroSkills!: HeroSkillsComp;
View!: HeroViewComp;
HeroMove!: HeroMoveComp;
debugMode: boolean = false; // 是否启用调试模式
@@ -25,7 +23,6 @@ export class Hero extends ecs.Entity {
this.addComponents<ecs.Comp>(
HeroMoveComp,
HeroAttrsComp,
HeroSkillsComp,
);
}
@@ -38,7 +35,6 @@ export class Hero extends ecs.Entity {
this.remove(HeroViewComp);
this.remove(HeroAttrsComp);
this.remove(HeroSkillsComp);
this.remove(HeroMasterComp)
super.destroy();
}
@@ -65,7 +61,6 @@ export class Hero extends ecs.Entity {
var hv = node.getComponent(HeroViewComp)!;
const model = this.get(HeroAttrsComp);
const skillsComp = this.get(HeroSkillsComp);
let hero = HeroInfo[uuid]; // 共用英雄数据
// 设置 View 层属性(表现相关)
@@ -84,29 +79,22 @@ export class Hero extends ecs.Entity {
// 只有主角才挂载天赋组件
// ✅ 初始化技能数据(迁移到 HeroSkillsComp
skillsComp.initSkills(hero.skills, uuid);
// 设置基础属性
model.base_ap = hero.ap;
model.base_def = hero.def;
model.base_hp = hero.hp;
model.base_mp = hero.mp;
model.base_speed = hero.speed;
// 初始化属性数组
model.Attrs = getAttrs(); // 属性
model.NeAttrs = getNeAttrs(); //负面属性
model.hp = model.Attrs[Attrs.HP_MAX] = model.base_hp;
model.mp = model.Attrs[Attrs.MP_MAX] = model.base_mp;
model.Attrs[Attrs.DEF] = model.base_def;
model.Attrs[Attrs.AP] = model.base_ap;
model.Attrs[Attrs.SPEED] = hero.speed;
model.ap = hero.ap;
model.hp= model.hp_max = hero.hp;
model.speed = hero.speed;
model.a_cd_max=hero.as
model.s_cd_max=hero.ss
// 初始化技能信息数组
if(hero.skills[0]) model.atk_id=hero.skills[0]
if(hero.skills[1]) {
model.skill_id=hero.skills[1]
}
// 初始化 buff/debuff 系统
model.initAttrs();
model.Attrs[Attrs.REVIVE_COUNT]=1 // 复活次数
model.Attrs[Attrs.BACK_CHANCE]=defaultAttrs[Attrs.BACK_CHANCE]
model.Attrs[Attrs.CON_RES]=defaultAttrs[Attrs.CON_RES] // 控制抗性
model.back_chance=FightSet.BACK_CHANCE
this.add(hv);
oops.message.dispatchEvent(GameEvent.MasterCalled,{uuid:uuid})
const move = this.get(HeroMoveComp);

View File

@@ -96,25 +96,6 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
/**
* 执行攻击计算 - 核心伤害计算逻辑
*
* 属性使用规范(重要!):
*
* ✅ 正确使用施法者属性damageEvent.Attrs - 快照):
* - CRITICAL: 暴击率判定
* - CRITICAL_DMG: 暴击伤害加成
* - BACK_CHANCE: 击退概率
* - HIT: 命中率(用于闪避计算)
* - AP 攻击力(基础伤害计算)
*
* ✅ 正确使用被攻击者属性TAttrsComp - 实时):
* - DODGE: 闪避率(用于闪避计算)
* - SHIELD_MAX: 护盾最大值(护盾吸收)
* - hp: 当前生命值(伤害应用)
* - 各种抗性属性(预留扩展)
*
* ❌ 错误使用案例(已修复):
* - 不要混用施法者和被攻击者的属性进行同一计算
* - 暴击伤害应该基于施法者的暴击伤害属性
*
* @param target 目标实体(被攻击者)
* @param damageEvent 伤害事件数据(包含施法者信息和属性快照)
* @returns 最终伤害数据(包含伤害值、暴击标记、闪避标记)
@@ -142,7 +123,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
// 暴击判定
// 使用施法者的暴击率属性damageEvent.Attrs 快照),- 被攻击者的暴击抗性属
const isCrit = this.checkChance(damageEvent.Attrs[Attrs.CRITICAL]);
const isCrit = this.checkChance(damageEvent.Attrs[Attrs.critical]);
// 计算基础伤害
let damage = this.dmgCount(damageEvent,TAttrsComp);
@@ -151,7 +132,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
// 暴击伤害计算
// 使用施法者的暴击伤害加成属性damageEvent.Attrs 快照)
// 公式:最终伤害 = 基础伤害 * (1 + 系统暴击倍率 + 施法者暴击伤害加成)
const casterCritDmg = damageEvent.Attrs[Attrs.CRITICAL_DMG] || 0;
const casterCritDmg = damageEvent.Attrs[Attrs.critical_dmg] || 0;
damage = Math.floor(damage * (1 + (FightSet.CRIT_DAMAGE + casterCritDmg) / 100));
reDate.isCrit=true;
@@ -178,12 +159,10 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
const casterName = CAttrsComp?.hero_name || "未知";
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 英雄${TAttrsComp.hero_name} (uuid: ${TAttrsComp.hero_uuid}) 受到 ${casterName}(eid: ${casterEid})的 伤害 ${damage},${isCrit?"暴击":"普通"}攻击,技能ID ${damageEvent.s_uuid}`);
//反伤判定 并应用到施法者
this.check_thorns(TAttrsComp, caster, damage);
// 击退判定
// 使用施法者的击退概率属性damageEvent.Attrs 快照) - 被攻击者的控制抗性
// 击退成功后需要清理施法者的相关天赋buff
const isBack = this.checkChance((damageEvent.Attrs[Attrs.BACK_CHANCE] || 0));
const isBack = this.checkChance((damageEvent.Attrs[Attrs.back_chance] || 0));
// ✅ 触发视图层表现(伤害数字、受击动画、后退)
@@ -234,56 +213,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
reDate.damage=damage;
return reDate;
}
check_thorns(TAttrsComp:HeroAttrsComp, caster: ecs.Entity, damage:number) {
// 检查目标是否有反伤属性,这里受伤的时时施法者
// 修复:如果施法者已销毁(caster为空),则不执行反伤逻辑
if (!caster || damage<=0) return;
let thornsDamage=0;
thornsDamage=TAttrsComp.Attrs[Attrs.THORNS]||0+TAttrsComp.useCountValTal(Attrs.THORNS);
let CAttrs=caster.get(HeroAttrsComp);
// 计算反伤伤害
let thornsDmg=Math.floor(thornsDamage*damage/100);
// 应用反伤伤害到数据层
// CAttrs.hp -= thornsDmg;
CAttrs.add_hp(-thornsDmg, true);
CAttrs.atked_count++;
let CView=caster.get(HeroViewComp);
// ✅ 触发视图层表现(伤害数字、受击动画、后退)
if (CView) CView.do_atked(thornsDmg, false, SkillSet[5000].uuid, false);
// 检查死亡
if (CAttrs.hp <= 0) {
// 复活机制
if (CAttrs.is_master && CAttrs.revive_count >= 1) {
CAttrs.revive_count--;
CAttrs.is_reviving = true;
smc.mission.stop_mon_action = true;
if (CView) {
CView.do_dead();
CView.scheduleRevive(1.0);
}
mLogger.log(this.debugMode, 'HeroAtkSystem', ` Hero waiting to revive from Thorns! Lives left: ${CAttrs.revive_count}`);
return;
}
// 玩家英雄死亡后,怪物停止刷新和移动
if (CAttrs.is_master&&CAttrs.revive_count <= 0) {
smc.mission.stop_mon_action = true;
oops.message.dispatchEvent(GameEvent.HeroDead, { hero_uuid: CAttrs.hero_uuid});
mLogger.log(this.debugMode, 'HeroAtkSystem', " Hero died from thorns, stopping monster action (spawn/move)");
}
this.doDead(caster);
// 增加击杀计数
if (caster) TAttrsComp.killed_count++;
// ✅ 触发死亡视图表现
if (CView) {
CView.do_dead();
}
}
}
/**
* 详细伤害计算核心方法
*
@@ -311,67 +241,18 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 伤害处理对象`,CAttrs,TAttrs);
// 2. 计算原始物理伤害和魔法伤害
// 物理伤害基础值 = 技能物理倍率 * (施法者物理攻击力 + 额外伤害) / 100 * 额外伤害比例
let apBase = (sConf.ap||0)*(CAttrs[Attrs.AP]+damageEvent.ext_dmg)/100*damageEvent.dmg_ratio;
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 物理伤害基础值: ${apBase}, 技能ap=${sConf.ap},施法者物理攻击力: ${CAttrs[Attrs.AP]},}
let apBase = (sConf.ap||0)*(CAttrs[Attrs.ap]+damageEvent.ext_dmg)/100*damageEvent.dmg_ratio;
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 物理伤害基础值: ${apBase}, 技能ap=${sConf.ap},施法者物理攻击力: ${CAttrs[Attrs.ap]},}
额外伤害:${damageEvent.ext_dmg}, 额外伤害比例:${damageEvent.dmg_ratio}`);
// 易伤
let DMG_INVUL = TAttrs[Attrs.DMG_INVUL]||0
// 免伤 属性免伤+天赋免伤
let DMG_RED = TAttrsComp.useCountValTal(Attrs.DEF);
// 4. 确保伤害值非负
let total = Math.max(0, apBase);
// 5. 应用防御减免 (百分比型)
// 计算公式:基础伤害 * (1 + (易伤% - 免伤%) / 100)
// 易伤增加伤害,免伤减少伤害
let damageRatio = 1 + (DMG_INVUL - DMG_RED) / 100;
damageRatio = Math.max(0, damageRatio); // 确保伤害系数不为负即最多减免至0伤害
total = Math.max(1,Math.floor(total * damageRatio-TAttrs[Attrs.DEF]));
if (this.debugMode) mLogger.log(this.debugMode, 'HeroAtkSystem', ` 最终伤害: ${total} (Base: ${apBase}, Def: ${DMG_RED}%, Invul: ${DMG_INVUL}%, Ratio: ${damageRatio})`);
if (this.debugMode) mLogger.log(this.debugMode, 'HeroAtkSystem', ` 最终伤害: ${total} (Base: ${apBase}`);
return total;
}
/**
* 应用攻击力加成和抗性减免的通用计算方法
*
* 这是一个核心的伤害修正计算方法,用于处理各种类型的攻击力加成和抗性减免。
* 该方法采用乘法叠加的方式,分别应用攻击者的加成属性和目标的抗性属性。
*
* 计算公式:
* 最终值 = 基础值 × (1 + 攻击力加成% / 100) × (1 - 抗性% / 100)
*
* 适用场景:
* - 物理攻击力加成和物理抗性减免
* - 魔法攻击力加成和魔法抗性减免
* - 元素攻击力加成和元素抗性减免
* - 其他需要同时考虑加成和减免的属性修正
*
* 计算逻辑说明:
* 1. 首先将百分比转换为小数形式除以100
* 2. 应用攻击者的加成:基础值 × (1 + 加成系数)
* 3. 应用目标的抗性:上一步结果 × (1 - 抗性系数)
* 4. 向下取整,确保结果为整数
*
* @param base 基础值(通常是经过防御减免后的伤害值)
* @param power 攻击者的攻击力加成值百分比形式如50表示50%
* @param res 目标的抗性值百分比形式如30表示30%
* @returns 经过攻击力加成和抗性减免后的最终值(向下取整)
*
* @important 注意事项:
* - 当抗性值大于100时可能导致最终值为负数或零
* - 所有计算结果会向下取整,确保游戏中的数值为整数
* - 此方法可以被多次调用,以叠加不同类型的加成和减免
*/
private applyPR(base: number, power: number, res: number): number {
// 计算公式:基础值 × (1 + 攻击力加成%) × (1 - 抗性%)
// 1. 将百分比转换为小数power/100 和 res/100
// 2. 应用攻击力加成1 + (power/100)
// 3. 应用抗性减免1 - (res/100)
// 4. 最终计算并向下取整
return Math.floor(base * (1 + (power/100)) * (1 - (res/100)));
}
/**
* 处理角色死亡
*
@@ -425,6 +306,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
*/
private checkChance(rate: number): boolean {
if (rate <= 0) return false;
if (rate >= 70) rate = 70
const r = Math.random() * 100;
return r < rate;
}
@@ -453,7 +335,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
TAttrsComp.shield -= damage;
if (TAttrsComp.shield <= 0) {
TAttrsComp.shield = 0;
TAttrsComp.Attrs[Attrs.SHIELD_MAX] = 0;
TAttrsComp.shield_max = 0;
}
TAttrsComp.dirty_shield = true;
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 护盾值完全吸收伤害 ${damage}`);
@@ -462,7 +344,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
const absorbedDamage = TAttrsComp.shield;
const remainingDamage = damage - TAttrsComp.shield;
TAttrsComp.shield = 0;
TAttrsComp.Attrs[Attrs.SHIELD_MAX] = 0;
TAttrsComp.shield_max = 0;
TAttrsComp.dirty_shield = true;
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 护盾值部分吸收伤害 ${absorbedDamage}`);
return {remainingDamage, absorbedDamage};

View File

@@ -96,7 +96,8 @@ export class HeroAttrsComp extends ecs.Comp {
killed_count:number=0;
atk_id:number=0; //普通攻击技能id
skill_id:number=0; //技能攻击技能id
can_atk=false
can_skill=false
start(){
}
// ==================== BUFF 系统初始化 ====================
@@ -260,7 +261,17 @@ export class HeroAttrsComp extends ecs.Comp {
if (attr === Attrs.hp) this.dirty_hp = true;
if (attr === Attrs.shield) this.dirty_shield = true;
}
//======更新cd========//
updateCD(dt: number){
if(this.atk_id !=0&&!this.can_atk){
this.a_cd+=dt
if(this.a_cd >= this.a_cd_max) this.can_atk = true
}
if(this.skill_id !=0&&this.can_skill){
this.s_cd+=dt
if(this.s_cd >= this.s_cd_max) this.can_skill = true
}
}
// ==================== 临时 BUFF/DEBUFF 更新 ====================
/**
* 更新临时 buff/debuff 的剩余时间
@@ -419,7 +430,8 @@ export class HeroAttrsComp extends ecs.Comp {
this.killed_count =0;
this.atk_id = 0;
this.skill_id = 0;
this.can_atk=false
this.can_skill=false
// 重置脏标签
this.dirty_hp = false;
this.dirty_shield = false;

View File

@@ -1,12 +1,5 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { Attrs, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs";
import { BuffConf, SkillRange } from "../common/config/SkillSet";
import { HeroInfo, AttrSet } from "../common/config/heroSet";
import { HeroSkillsComp } from "./HeroSkills";
import { smc } from "../common/SingletonModuleComp";
import { AttrCards, PotionCards } from "../common/config/AttrSet";
import { mLogger } from "../common/Logger";
import { _decorator } from "cc";

View File

@@ -2,21 +2,18 @@ import { instantiate, Node, Prefab, Vec3 ,v3,resources,SpriteFrame,Sprite,Sprite
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, IndexSet } from "../common/config/GameSet";
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
import { HeroInfo } from "../common/config/heroSet";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { BuffConf, SkillSet } from "../common/config/SkillSet";
import { getNeAttrs, getAttrs ,Attrs, defaultAttrs} from "../common/config/HeroAttrs";
import { getMonAttr, MonType } from "../map/RogueConfig";
import { HeroViewComp } from "./HeroViewComp";
import { HeroSkillsComp } from "./HeroSkills";
import { MonMoveComp } from "./MonMove";
import { mLogger } from "../common/Logger";
/** 角色实体 */
@ecs.register(`Monster`)
export class Monster extends ecs.Entity {
HeroModel!: HeroAttrsComp;
HeroSkills!: HeroSkillsComp;
HeroView!: HeroViewComp;
MonMove!: MonMoveComp;
private debugMode: boolean = false; // 是否启用调试模式
@@ -45,7 +42,6 @@ export class Monster extends ecs.Entity {
this.addComponents<ecs.Comp>(
MonMoveComp,
HeroAttrsComp,
HeroSkillsComp,
);
}
@@ -60,7 +56,6 @@ export class Monster extends ecs.Entity {
this.remove(HeroViewComp);
this.remove(HeroAttrsComp);
this.remove(HeroSkillsComp);
super.destroy();
}
@@ -102,7 +97,6 @@ export class Monster extends ecs.Entity {
node.setScale(size*node.scale.x,size*node.scale.y);
node.setPosition(pos)
const model = this.get(HeroAttrsComp);
const skillsComp = this.get(HeroSkillsComp);
let hero = HeroInfo[uuid]; // 共用英雄数据
// 设置 View 层属性(表现相关)
view.scale = scale;
@@ -120,18 +114,18 @@ export class Monster extends ecs.Entity {
model.is_kalami = true;
}
// 根据等级和类型获取怪物属性(使用新的动态成长系统)
const {hp, mp, ap, def, speed} = getMonAttr(lv, uuid, monType, gameTime);
const {hp,ap, speed} = getMonAttr(lv, uuid, monType, gameTime);
// 初始化属性数组
model.Attrs = getAttrs();
model.hp = model.Attrs[Attrs.HP_MAX] = hp;
model.mp = model.Attrs[Attrs.MP_MAX] = mp;
model.Attrs[Attrs.DEF] = def;
model.Attrs[Attrs.AP] = ap;
model.Attrs[Attrs.SPEED] = speed; // 使用成长后的速度
model.Attrs[Attrs.BACK_CHANCE]=defaultAttrs[Attrs.BACK_CHANCE]
model.Attrs[Attrs.CON_RES]=defaultAttrs[Attrs.CON_RES]
model.hp = model.hp_max = hp;
model.ap = ap;
model.speed = speed; // 使用成长后的速度
model.a_cd_max=hero.as
model.s_cd_max=hero.ss
model.back_chance=FightSet.BACK_CHANCE
// ✅ 初始化技能数据(迁移到 HeroSkillsComp
skillsComp.initSkills(hero.skills, uuid);
if(hero.skills[0]) model.atk_id=hero.skills[0]
if(hero.skills[1]) model.skill_id=hero.skills[1]
this.add(view);
// 重置视图状态(对象池复用时必须)