diff --git a/assets/script/game/hero/EBusComp.ts b/assets/script/game/hero/EBusComp.ts new file mode 100644 index 00000000..758fff81 --- /dev/null +++ b/assets/script/game/hero/EBusComp.ts @@ -0,0 +1,90 @@ +import { _decorator, EventTarget } from 'cc'; +import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; +import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; +const { ccclass } = _decorator; + +/** 事件总线组件 - 简化版,仅提供基本的发布订阅功能 */ +@ecs.register('EBusComp', false) +export class EBusComp extends CCComp { + + /** 内部事件目标 */ + private _eventTarget: EventTarget; + + /** 获取事件目标 */ + private get eventTarget(): EventTarget { + if (!this._eventTarget) { + this._eventTarget = new EventTarget(); + } + return this._eventTarget; + } + + start() { + // 组件启动时的初始化逻辑 + } + + /** + * 发布事件 + * @param event 事件名称 + * @param data 事件数据 + */ + public emit(event: string, data?: any): void { + this.eventTarget.emit(event, data); + } + + /** + * 订阅事件 + * @param event 事件名称 + * @param callback 回调函数 + * @param target 回调函数的this指向 + */ + public on(event: string, listener: (...args: any[]) => void, object?: any): void { + this.eventTarget.on(event, listener, object); + } + + /** + * 取消订阅事件 + * @param event 事件名称 + * @param callback 回调函数 + * @param target 回调函数的this指向 + */ + public off(event?: string, callback?: (data?: any) => void, target?: any): void { + if (arguments.length === 0) { + // 无参调用:清理本节点所有监听 + this.eventTarget.targetOff(this); + } else if (arguments.length === 1) { + // 仅提供 event:清理该事件名下所有回调 + this.eventTarget.targetOff(event); + } else { + // 完整参数:精确移除指定回调 + this.eventTarget.off(event!, callback!, target); + } + } + + + /** + * 订阅一次性事件 + * @param event 事件名称 + * @param callback 回调函数 + * @param target 回调函数的this指向 + */ + public once(event: string, callback: (data?: any) => void, target?: any): void { + this.eventTarget.once(event, callback, target); + } + + /** + * 取消所有事件监听 + */ + public targetOff(target: any): void { + this.eventTarget.targetOff(target); + } + + reset() { + // 清理所有事件监听 + if (this._eventTarget) { + this._eventTarget.targetOff(this); + } + + // 组件删除时触发自定义释放逻辑 + this.node.destroy(); + } +} \ No newline at end of file diff --git a/assets/script/game/hero/EBusComp.ts.meta b/assets/script/game/hero/EBusComp.ts.meta new file mode 100644 index 00000000..125589db --- /dev/null +++ b/assets/script/game/hero/EBusComp.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "b44b7a75-de7d-4aa1-ad20-78e6690926c0", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index cd5c9823..3138e6d7 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -12,18 +12,20 @@ import { SkillSet } from "../common/config/SkillSet"; import { time } from "console"; import { getNeAttrs, getAttrs ,Attrs} from "../common/config/HeroAttrs"; import { TalComp } from "./TalComp"; +import { EBusComp } from "./EBusComp"; /** 角色实体 */ @ecs.register(`Hero`) export class Hero extends ecs.Entity { HeroModel!: HeroModelComp; - HeroView!: HeroViewComp; + View!: HeroViewComp; BattleMove!: BattleMoveComp; protected init() { this.addComponents( BattleMoveComp, HeroModelComp, - TalComp + TalComp, + EBusComp, ); } @@ -31,6 +33,7 @@ export class Hero extends ecs.Entity { this.remove(HeroViewComp); this.remove(HeroModelComp); this.remove(TalComp); + this.remove(EBusComp); super.destroy(); } @@ -49,7 +52,56 @@ export class Hero extends ecs.Entity { node.parent = scene.entityLayer!.node! node.setPosition(pos) // console.log("hero load",pos) - var hv = this.hero_init(uuid,node) + var hv = node.getComponent(HeroViewComp)!; + const model = this.get(HeroModelComp); + let hero = HeroInfo[uuid]; // 共用英雄数据 + + // 设置 View 层属性(表现相关) + hv.scale = 1; + hv.box_group = BoxSet.HERO; + + // 设置 Model 层属性(数据相关) + model.hero_uuid = uuid; + model.hero_name = hero.name; + model.lv = hero.lv ? hero.lv : 1; + model.type = hero.type; + model.fac = FacSet.HERO; + model.is_master = true; + + // 设置技能 + for(let i=0; i> = {}; + + /** 临时型buff数组 - 按时间自动过期 */ + BUFFS_TEMP: Record> = {}; + + // ==================== 标记状态 ==================== + is_dead: boolean = false; + is_count_dead: boolean = false; + is_boss: boolean = false; + is_big_boss: boolean = false; + is_master: boolean = false; + is_friend: boolean = false; + is_kalami: boolean = false; + + // ==================== 计数统计 ==================== + atk_count: number = 0; // 攻击次数 + atked_count: number = 0; // 被攻击次数 + + // ==================== 技能配置 ==================== + skills: any = []; + // ==================== BUFF 系统初始化 ==================== + /** + * 初始化角色的 buff debuff + * 从 HeroInfo 读取初始配置,建立属性系统 + */ + initAttrs() { + // 清空现有 buff/debuff + this.BUFFS = {}; + this.BUFFS_TEMP = {}; + + // 获取英雄配置 + const heroInfo = HeroInfo[this.hero_uuid]; + if (!heroInfo) return; + + // 1. 重置为基础值 + this.Attrs[Attrs.HP_MAX] = this.base_hp; + this.Attrs[Attrs.MP_MAX] = this.base_mp; + this.Attrs[Attrs.DEF] = this.base_def; + this.Attrs[Attrs.AP] = this.base_ap; + this.Attrs[Attrs.MAP] = this.base_map; + this.Attrs[Attrs.SPEED] = this.base_speed; + this.Attrs[Attrs.DIS] = this.base_dis; + + // 2. 初始化其他属性(无初始值的) + for (const attrKey in this.Attrs) { + const attrIndex = parseInt(attrKey); + if ( + attrIndex !== Attrs.HP_MAX && + attrIndex !== Attrs.MP_MAX && + attrIndex !== Attrs.DEF && + attrIndex !== Attrs.AP && + attrIndex !== Attrs.MAP && + attrIndex !== Attrs.SPEED && + attrIndex !== Attrs.DIS + ) { + this.Attrs[attrIndex] = 0; + } + } + + // 加载初始 buff + if (heroInfo.buff && heroInfo.buff.length > 0) { + for (const buffConf of heroInfo.buff) { + this.addBuff(buffConf); + } + } + } + + // ==================== BUFF 管理 ==================== + /** + * 添加 buff 效果(支持多次叠加) + * @param buffConf buff 配置 (来自 SkillSet.BuffConf heroSet.buff) + */ + addBuff(buffConf: BuffConf) { + const isPermanent = buffConf.time === 0; + const attrIndex = buffConf.buff; + + if (isPermanent) { + // 添加持久buff到BUFFS - 直接追加到数组 + if (!this.BUFFS[attrIndex]) { + this.BUFFS[attrIndex] = []; + } + this.BUFFS[attrIndex].push({ value: buffConf.value, BType: buffConf.BType }); + } else { + // 添加临时buff到BUFFS_TEMP - 直接追加到数组 + if (!this.BUFFS_TEMP[attrIndex]) { + this.BUFFS_TEMP[attrIndex] = []; + } + this.BUFFS_TEMP[attrIndex].push({ + value: buffConf.value, + BType: buffConf.BType, + remainTime: buffConf.time + }); + } + + // 重新计算受影响的属性 + this.recalculateSingleAttr(attrIndex); + } + + // ==================== 属性计算系统 ==================== + /** + * 重新计算单个属性 + * @param attrIndex 属性索引 + */ + recalculateSingleAttr(attrIndex: number) { + // 1. 获取基础值 + const baseValues: Record = { + [Attrs.HP_MAX]: this.base_hp, + [Attrs.MP_MAX]: this.base_mp, + [Attrs.DEF]: this.base_def, + [Attrs.AP]: this.base_ap, + [Attrs.MAP]: this.base_map, + [Attrs.SPEED]: this.base_speed, + [Attrs.SHIELD_MAX]: 0 + }; + + const baseVal = baseValues[attrIndex] !== undefined ? baseValues[attrIndex] : 0; + + // 2. 收集所有数值型 buff/debuff + let totalValue = baseVal; + + // 遍历持久buff数组 + if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) { + for (const buff of this.BUFFS[attrIndex]) { + if (buff.BType === BType.VALUE) { + totalValue += buff.value; + } + } + } + // 遍历临时buff数组 + if (this.BUFFS_TEMP[attrIndex] && this.BUFFS_TEMP[attrIndex].length > 0) { + for (const buff of this.BUFFS_TEMP[attrIndex]) { + if (buff.BType === BType.VALUE) { + totalValue += buff.value; + } + } + } + + // 3. 收集所有百分比型 buff/debuff + let totalRatio = 0; + + // 遍历持久buff数组 + if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) { + for (const buff of this.BUFFS[attrIndex]) { + if (buff.BType === BType.RATIO) { + totalRatio += buff.value; + } + } + } + // 遍历临时buff数组 + if (this.BUFFS_TEMP[attrIndex] && this.BUFFS_TEMP[attrIndex].length > 0) { + for (const buff of this.BUFFS_TEMP[attrIndex]) { + if (buff.BType === BType.RATIO) { + totalRatio += buff.value; + } + } + } + + // 4. 根据属性类型计算最终值 + const attrType = AttrsType[attrIndex]; + const isRatioAttr = attrType === BType.RATIO; + + if (isRatioAttr) { + // 百分比型属性:直接加减 + this.Attrs[attrIndex] = totalValue + totalRatio; + } else { + // 数值型属性:(基础值+数值) × (1 + 百分比/100) + this.Attrs[attrIndex] = Math.floor(totalValue * (1 + totalRatio / 100)); + } + + // 5. 确保属性值合理 + this.clampSingleAttr(attrIndex); + } + + /** + * 确保单个属性值合理 + */ + private clampSingleAttr(attrIndex: number) { + switch(attrIndex) { + case Attrs.HP_MAX: + case Attrs.MP_MAX: + this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]); + break; + case Attrs.DEF: + case Attrs.AP: + case Attrs.MAP: + this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]); + break; + case Attrs.CRITICAL: + case Attrs.DODGE: + case Attrs.HIT: + this.Attrs[attrIndex] = Math.max(0, Math.min(AttrSet.ATTR_MAX, this.Attrs[attrIndex])); + break; + } + } + + // ==================== 临时 BUFF/DEBUFF 更新 ==================== + /** + * 更新临时 buff/debuff 的剩余时间 + * @param dt 时间增量 + */ + updateTemporaryBuffsDebuffs(dt: number) { + const affectedAttrs = new Set(); + + // 更新临时型buff + for (const attrIndex in this.BUFFS_TEMP) { + const buffs = this.BUFFS_TEMP[attrIndex]; + buffs.forEach(buff => { + buff.remainTime -= dt; + if (buff.remainTime <= 0) { + const index = buffs.indexOf(buff); + if (index > -1) { + buffs.splice(index, 1); + } + } + }); + if (buffs.length === 0) { + delete this.BUFFS_TEMP[attrIndex]; + affectedAttrs.add(parseInt(attrIndex)); + } + } + + // 负面状态更新 + for (const key in this.NeAttrs) { + const debuff = this.NeAttrs[key]; + debuff.remainTime -= dt; + if (debuff.remainTime <= 0) { + debuff.remainTime = 0; + } + } + + // 只重新计算受影响的属性 + affectedAttrs.forEach(attrIndex => { + this.recalculateSingleAttr(attrIndex); + }); + } + + // ==================== BUFF 辅助方法 ==================== + /** + * 清空buff + * @param attrIndex 属性索引,如果为空则清理所有buff + * @param isBuff true时清理value>0的增益buff,false时清理value<0的减益buff + */ + clearBuffs(attrIndex?: number, isBuff: boolean = true): void { + if (attrIndex === undefined) { + for (const attrIndex in this.BUFFS_TEMP) { + this.clearBuffsForAttr(parseInt(attrIndex), isBuff); + } + } else { + this.clearBuffsForAttr(attrIndex, isBuff); + } + } + + /** + * 清理指定属性的buff + * @param attrIndex 属性索引 + * @param isBuff true清理增益buff,false清理减益buff + */ + private clearBuffsForAttr(attrIndex: number, isBuff: boolean): void { + const buffContainer = this.BUFFS_TEMP; + + if (!buffContainer[attrIndex]) return; + + buffContainer[attrIndex] = buffContainer[attrIndex].filter(buff => { + const shouldClear = isBuff ? buff.value > 0 : buff.value < 0; + return !shouldClear; + }); + + if (buffContainer[attrIndex].length === 0) { + delete buffContainer[attrIndex]; + } + + this.recalculateSingleAttr(attrIndex); + } + + // ==================== NeAttrs(负面状态)管理 ==================== + /** + * 清理单个NeAttr(负面状态) + */ + clearNeAttr(neAttrIndex: number): void { + if (this.NeAttrs[neAttrIndex]) { + this.NeAttrs[neAttrIndex].value = 0; + this.NeAttrs[neAttrIndex].time = 0; + } + } + + /** + * 清理所有NeAttrs(负面状态) + */ + clearAllNeAttrs(): void { + for (const key in this.NeAttrs) { + this.NeAttrs[key].value = 0; + this.NeAttrs[key].time = 0; + } + } + + /** + * 检查是否处于眩晕状态 + */ + public isStun(): boolean { + return this.NeAttrs[NeAttrs.IN_STUN]?.time > 0; + } + + /** + * 检查是否处于冰冻状态 + */ + public isFrost(): boolean { + return this.NeAttrs[NeAttrs.IN_FROST]?.time > 0; + } + reset() { - + // 重置为初始状态 + this.hero_uuid = 1001; + this.hero_name = "hero"; + this.lv = 1; + this.type = 0; + this.fac = 0; + this.base_ap = 0; + this.base_map = 0; + this.base_def = 5; + this.base_hp = 100; + this.base_mp = 100; + this.base_speed = 100; + this.base_dis = 100; + this.hp = 100; + this.mp = 100; + this.shield = 0; + this.Attrs = []; + this.NeAttrs = []; + this.BUFFS = {}; + this.BUFFS_TEMP = {}; + this.is_dead = false; + this.is_count_dead = false; + this.is_boss = false; + this.is_big_boss = false; + this.is_master = false; + this.is_friend = false; + this.is_kalami = false; + this.atk_count = 0; + this.atked_count = 0; + this.skills = []; + } } + +/** + * ==================== 英雄属性更新系统 ==================== + * + * 按照 ECS 设计理念: + * - Component(HeroModelComp):存储数据 + * - System(HeroAttrSystem):处理业务逻辑 + * + * 系统职责: + * 1. 每帧更新临时 Buff(时间递减,过期移除) + * 2. 每帧更新 HP/MP 自然回复 + * 3. 限制属性值在合理范围内 + * + * 使用方式: + * 在 RootSystem 中注册此系统,它会自动每帧更新所有拥有 HeroModelComp 的实体 + */ +export class HeroAttrSystem extends ecs.ComblockSystem + implements ecs.ISystemUpdate, ecs.IEntityEnterSystem, ecs.ISystemFirstUpdate { + + // ==================== 调试统计(可选)==================== + private entityCount: number = 0; // 本帧处理的实体数 + private frameCount: number = 0; // 总帧数 + private debugMode: boolean = false; // 是否启用调试模式 + + /** + * 过滤器:只处理拥有 HeroModelComp 的实体 + */ + filter(): ecs.IMatcher { + return ecs.allOf(HeroModelComp); + } + + /** + * 实体首次进入系统时调用(每个实体只调用一次) + */ + entityEnter(e: ecs.Entity): void { + const model = e.get(HeroModelComp); + if (!model) return; + + console.log(`[HeroAttrSystem] 英雄进入系统: ${model.hero_name} (uuid: ${model.hero_uuid})`); + } + + /** + * 系统首次更新前调用(整个系统只调用一次) + */ + firstUpdate(): void { + console.log("[HeroAttrSystem] 系统首次更新"); + } + + /** + * 每帧更新(为每个英雄调用一次) + * + * ⭐ 关键理解: + * - 如果有 3 个英雄,这个方法每帧会被调用 3 次 + * - 每次调用处理不同的实体 e + * - 这是正确的设计,不是 bug + */ + update(e: ecs.Entity): void { + const model = e.get(HeroModelComp); + if (!model || model.is_dead) return; + + // 统计:记录本帧处理的实体数 + this.entityCount++; + + // 调试日志(可选,调试时启用) + if (this.debugMode) { + console.log(` [${this.entityCount}] 更新英雄: ${model.hero_name}, HP: ${model.hp.toFixed(2)}`); + } + + // 1. 更新临时 Buff/Debuff(时间递减,过期自动移除) + model.updateTemporaryBuffsDebuffs(this.dt); + + // 2. HP/MP 自然回复(业务规则) + model.mp += HeroUpSet.MP * this.dt; + model.hp += HeroUpSet.HP * this.dt; + + // 3. 限制属性值在合理范围内 + if (model.mp > model.Attrs[Attrs.MP_MAX]) { + model.mp = model.Attrs[Attrs.MP_MAX]; + } + if (model.hp > model.Attrs[Attrs.HP_MAX]) { + model.hp = model.Attrs[Attrs.HP_MAX]; + } + + // 每 60 帧输出一次统计 + this.frameCount++; + if (this.frameCount % 60 === 0 && this.entityCount === 1) { + console.log(`[HeroAttrSystem] 第 ${this.frameCount} 帧,处理 ${this.entityCount} 个英雄`); + } + + // 注意:显示更新由 HeroViewComp 负责,这里只处理数据 + } + + /** + * 启用调试模式(调试时使用) + */ + enableDebug() { + this.debugMode = true; + } + + /** + * 禁用调试模式(正式运行) + */ + disableDebug() { + this.debugMode = false; + } +} + +/** + * ==================== 英雄战斗系统 ==================== + * + * 按照 ECS 设计理念: + * - Component(HeroModelComp):存储数据 + * - System(HeroBattleSystem):处理战斗业务逻辑 + * + * 系统职责: + * 1. 处理角色被攻击逻辑(伤害计算、暴击判定、闪避判定) + * 2. 处理护盾吸收逻辑 + * 3. 处理角色死亡逻辑 + * 4. 处理战斗状态管理 + * + * 使用方式: + * 在 RootSystem 中注册此系统,它会自动处理所有拥有 HeroModelComp 的实体的战斗逻辑 + */ +export class HeroBattleSystem extends ecs.ComblockSystem + implements ecs.ISystemUpdate, ecs.IEntityEnterSystem { + + private debugMode: boolean = false; // 是否启用调试模式 + + /** + * 过滤器:只处理拥有 HeroModelComp 的实体 + */ + filter(): ecs.IMatcher { + return ecs.allOf(HeroModelComp); + } + + /** + * 实体首次进入系统时调用(每个实体只调用一次) + */ + entityEnter(e: ecs.Entity): void { + const model = e.get(HeroModelComp); + if (!model) return; + + console.log(`[HeroBattleSystem] 英雄进入战斗系统: ${model.hero_name} (uuid: ${model.hero_uuid})`); + } + + /** + * 处理角色被攻击 + * @param target 被攻击的目标实体 + * @param remainingDamage 基础伤害值 + * @param attackerAttrs 攻击者的属性 + * @param skillId 技能ID + * @returns 实际造成的伤害 + */ + public doAttack(target: ecs.Entity, remainingDamage: number, attackerAttrs: any, skillId: number): number { + const targetModel = target.get(HeroModelComp); + if (!targetModel || targetModel.is_dead) return 0; + + // 获取技能配置 + const skillConf = SkillSet[skillId]; + + // 触发被攻击事件 + this.onAttacked(target); + + // 闪避判定 + if (this.checkDodge(targetModel)) { + return 0; + } + + // 暴击判定 + const isCrit = this.checkCrit(attackerAttrs[Attrs.CRITICAL]); + let damage = remainingDamage; + + if (isCrit) { + damage = Math.floor(damage * (1 + (FightSet.CRIT_DAMAGE + attackerAttrs[Attrs.CRITICAL_DMG]) / 100)); + } + + // 伤害计算(考虑易伤等debuff) + damage = this.calculateDamage(targetModel, damage); + + // 护盾吸收 + damage = this.absorbShield(targetModel, damage); + + if (damage <= 0) return 0; + + // 应用伤害 + targetModel.hp -= damage; + targetModel.atked_count++; + + // 检查死亡 + if (targetModel.hp <= 0) { + this.doDead(target); + } + + if (this.debugMode) { + console.log(`[HeroBattleSystem] ${targetModel.hero_name} 受到 ${damage} 点伤害 (暴击: ${isCrit})`); + } + + return damage; + } + + /** + * 处理角色死亡 + */ + private doDead(entity: ecs.Entity): void { + const model = entity.get(HeroModelComp); + if (!model || model.is_dead) return; + + model.is_dead = true; + + // 触发死亡事件 + this.onDeath(entity); + + if (this.debugMode) { + console.log(`[HeroBattleSystem] ${model.hero_name} 死亡`); + } + } + + /** + * 闪避判定 + */ + private checkDodge(model: HeroModelComp): boolean { + if (model.Attrs[Attrs.DODGE] > 0) { + const random = Math.random() * 100; + if (random < model.Attrs[Attrs.DODGE]) { + if (this.debugMode) { + console.log(`[HeroBattleSystem] ${model.hero_name} 闪避了攻击`); + } + return true; + } + } + return false; + } + + /** + * 暴击判定 + */ + private checkCrit(critRate: number): boolean { + if (critRate > 0) { + const random = Math.random() * 100; + return random < critRate; + } + return false; + } + + /** + * 伤害计算(考虑易伤等debuff) + */ + private calculateDamage(model: HeroModelComp, baseDamage: number): number { + // 这里可以添加易伤等debuff的计算逻辑 + // 例如:如果目标有易伤buff,增加受到的伤害 + return baseDamage; + } + + /** + * 护盾吸收伤害 + */ + private absorbShield(model: HeroModelComp, damage: number): number { + if (model.shield <= 0) return damage; + + if (model.shield >= damage) { + model.shield -= damage; + if (model.shield <= 0) { + model.shield = 0; + model.Attrs[Attrs.SHIELD_MAX] = 0; + } + return 0; + } else { + const remainingDamage = damage - model.shield; + model.shield = 0; + model.Attrs[Attrs.SHIELD_MAX] = 0; + return remainingDamage; + } + } + + /** + * 被攻击时触发的事件 + */ + private onAttacked(entity: ecs.Entity): void { + const model = entity.get(HeroModelComp); + if (!model || model.is_dead) return; + + // 这里可以添加被攻击时的特殊处理逻辑 + if (model.fac === FacSet.MON) return; + + // 例如:触发某些天赋效果、反击逻辑等 + } + + /** + * 死亡时触发的事件 + */ + private onDeath(entity: ecs.Entity): void { + const model = entity.get(HeroModelComp); + if (!model) return; + + if (model.fac === FacSet.MON) { + // 怪物死亡处理 + this.scheduleDrop(entity); + } else if (model.fac === FacSet.HERO) { + // 英雄死亡处理 + this.scheduleHeroDeath(entity); + } + } + + /** + * 延迟执行掉落逻辑 + */ + private scheduleDrop(entity: ecs.Entity): void { + // 这里可以添加掉落逻辑 + // 例如:延迟一段时间后生成掉落物品 + } + + /** + * 延迟执行英雄死亡逻辑 + */ + private scheduleHeroDeath(entity: ecs.Entity): void { + // 这里可以添加英雄死亡的特殊处理 + // 例如:触发游戏结束、复活机制等 + } + + /** + * 启用调试模式 + */ + enableDebug() { + this.debugMode = true; + } + + /** + * 禁用调试模式 + */ + disableDebug() { + this.debugMode = false; + } + + /** + * 系统更新(每帧调用) + * 可以在这里添加需要每帧处理的战斗逻辑 + */ + update(e: ecs.Entity): void { + // 这里可以添加需要每帧处理的战斗逻辑 + // 例如:持续伤害、战斗状态检查等 + } +} + diff --git a/assets/script/game/hero/HeroViewComp.ts b/assets/script/game/hero/HeroViewComp.ts index dafd8acc..d43b011b 100644 --- a/assets/script/game/hero/HeroViewComp.ts +++ b/assets/script/game/hero/HeroViewComp.ts @@ -14,56 +14,9 @@ import { RandomManager } from "db://oops-framework/core/common/random/RandomMana import { AttrSet, HeroInfo, HeroUpSet } from "../common/config/heroSet"; import { Attrs, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs"; import { TalComp } from "./TalComp"; +import { HeroModelComp, HeroBattleSystem } from "./HeroModelComp"; const { ccclass, property } = _decorator; -/** - * ==================== BUFF 系统使用说明 ==================== - * - * 1. 系统架构 - 支持多次叠加(简化设计) - * - BUFFS: 持久型buff数组(持久存在,直到主动移除) - * - BUFFS_TEMP: 临时型buff数组(持续指定时间,时间到自动移除) - * - 每个buff实例内包含BType字段,区分数值型(VALUE)和百分比型(RATIO) - * - * 2. 叠加规则 - 核心特性 - * - 同一属性允许多个buff实例同时存在(数值型和百分比型混合) - * - 每个buff实例保持独立的数据(value、BType、remainTime) - * - 所有buff实例在属性计算时都被纳入求和计算 - * - 示例:若添加两个+10 AP的buff,最终会累加 +20 AP - * - * 3. 数据结构 - 统一数组设计 - * - BUFFS: Record> - * - BUFFS_TEMP: Record> - * - 每个属性对应一个buff数组,可混合存储数值型和百分比型buff - * - * 4. 属性计算公式 - 按BType区分 - * - 数值型: (基础值 + 所有数值型buff之和) × (1 + 所有百分比buff之和/100) - * - 百分比: 基础值 + 所有数值型buff之和 + 所有百分比buff之和 - * - * 5. API 使用示例 - * // 添加buff(自动根据time判断持久或临时) - * const buffConf: BuffConf = { - * buff: Attrs.AP, - * BType: BType.VALUE, - * value: 10, - * time: 5, // 0表示持久,>0表示临时 - * chance: 100 - * }; - * hero.addBuff(buffConf); - * - * // 移除特定buff - * hero.removeBuff(Attrs.AP, 10, true); // 移除临时的+10 AP - * - * // 清空属性所有buff - * hero.clearBuffs(Attrs.AP); - * - * // 查询激活的buff数量 - * const count = hero.getActiveBuffCount(Attrs.AP); - * - * 6. 临时buff自动更新 - * - 每帧update中自动调用updateTemporaryBuffsDebuffs(dt) - * - 递减所有临时buff的remainTime - * - 当remainTime <= 0时自动移除buff并重新计算属性 - */ /** 角色显示组件 */ export interface BuffInfo { attr: Attrs; @@ -73,54 +26,20 @@ export interface BuffInfo { @ccclass('HeroViewComp') // 定义Cocos Creator 组件 @ecs.register('HeroView', false) // 定义ECS 组件 export class HeroViewComp extends CCComp { + // ==================== View 层属性(表现相关)==================== BUFFCOMP:BuffComp=null! TALCOMP:any=null! as: HeroSpine = null! status:String = "idle" - hero_uuid:number = 1001; - hero_name : string = "hero"; - lv:number =1; - scale: number = 1; /** 角色阵营 1:hero -1 :mon */ - type: number = 0; /**角色类型 0近战-需要贴1远程-保持距离 2辅助 */ - fac:number=0; //阵营 0:hero 1:monster - box_group:number = BoxSet.HERO; - is_dead:boolean = false; //是否摧毁 - is_count_dead:boolean = false; //是否计数死亡 + scale: number = 1; // 显示方向 + box_group:number = BoxSet.HERO; // 碰撞组 is_stop:boolean = false; is_atking:boolean = false; - is_boss:boolean = false; - is_big_boss:boolean = false; - is_master:boolean =false; - is_friend:boolean =false; - is_kalami:boolean =false; - skills:any=[] - mp: number = 100; - hp: number = 100; /** 血*/ - shield:number=0; //当前护甲 -/** 基础属有初始值的基础属后续Attrs 属性计算时用到*/ - base_ap: number = 0; //基础攻击 - base_map: number = 0; - base_def: number = 5; - base_hp: number = 100; - base_mp: number = 100; - base_speed: number = 100; /** 角色移动速度 */ - base_dis: number = 100; - Attrs:any=[] - NeAttrs:any=[] - // Buff debuff 统一管理 - // 每个buff实例内包含BType,用于区分数值型(VALUE)和百分比型(RATIO) - // [attrIndex: number]: Array - 允许多个buff实例独立存在 - - /** 持久型buff数组 - 不会自动过期 */ - BUFFS: Record> = {} - - /** 临时型buff数组 - 按时间自动过期 */ - BUFFS_TEMP: Record> = {} - - atk_count: number = 0; - atked_count: number = 0; - + // ==================== 直接访问 HeroModelComp ==================== + private get model() { + return this.ent.get(HeroModelComp); + } private damageQueue: Array<{ damage: number, @@ -145,7 +64,7 @@ export class HeroViewComp extends CCComp { start () { this.as.idle() this.BUFFCOMP=this.node.getComponent(BuffComp); - this.TALCOMP=this.node.getComponent(TalComp); + this.TALCOMP=this.ent.get(TalComp); // console.log("[HeroViewComp]:heroview"+this.hero_name,this.Attrs) /** 方向 */ this.node.setScale(this.scale,1); @@ -158,351 +77,97 @@ export class HeroViewComp extends CCComp { this.node.getChildByName("top").getChildByName("pow").active = true; } - // ==================== BUFF系统初始==================== + // ==================== BUFF 系统方法直接调用 ==================== /** - * 初始化角色的 buff debuff - * HeroInfo 读取初始配置,建立属性系 + * 初始化角色的 buff debuff(直接调用 Model) */ initAttrs() { - // 清空现有 buff/debuff - this.BUFFS = { - }; - this.BUFFS_TEMP = { - }; - // 获取英雄配置 - const heroInfo = HeroInfo[this.hero_uuid]; - if (!heroInfo) return; - - - // 1. 重置为基础 - this.Attrs[Attrs.HP_MAX] = this.base_hp; - this.Attrs[Attrs.MP_MAX] = this.base_mp; - this.Attrs[Attrs.DEF] = this.base_def; - this.Attrs[Attrs.AP] = this.base_ap; - this.Attrs[Attrs.MAP] = this.base_map; - this.Attrs[Attrs.SPEED] = this.base_speed; - this.Attrs[Attrs.DIS] = this.base_dis; - - // 2. 初始化其他属性(无初始值的 - for (const attrKey in this.Attrs) { - const attrIndex = parseInt(attrKey); - if( - attrIndex !== Attrs.HP_MAX && - attrIndex !== Attrs.MP_MAX&& - attrIndex !== Attrs.DEF && - attrIndex !== Attrs.AP && - attrIndex !== Attrs.MAP && - attrIndex !== Attrs.SPEED && - attrIndex !== Attrs.DIS - - ) { - this.Attrs[attrIndex] = 0; - } - } - // 加载初始 buff - if (heroInfo.buff && heroInfo.buff.length > 0) { - for (const buffConf of heroInfo.buff) { - this.addBuff(buffConf); - } + if (this.model) { + this.model.initAttrs(); } } - // ==================== BUFF管理 ==================== + /** - * 添加 buff 效果(支持多次叠加) - * @param buffConf buff 配置 (来自 SkillSet.BuffConf heroSet.buff) - * - * 叠加规则 - * 1. 持久型buff:直接添加到数组中,与现有buff独立存在 - * 2. 临时型buff:直接添加到数组中,保持独立的剩余时间计算 - * 3. 所有buff实例在属性计算时都被纳入求和计算 + * 添加 buff 效果(直接调用 Model) */ addBuff(buffConf: BuffConf) { - const isPermanent = buffConf.time === 0; - const attrIndex = buffConf.buff; - - if (isPermanent) { - // 添加持久buff到BUFFS - 直接追加到数组 - if (!this.BUFFS[attrIndex]) { - this.BUFFS[attrIndex] = []; - } - this.BUFFS[attrIndex].push({ value: buffConf.value, BType: buffConf.BType }); - } else { - // 添加临时buff到BUFFS_TEMP - 直接追加到数组 - if (!this.BUFFS_TEMP[attrIndex]) { - this.BUFFS_TEMP[attrIndex] = []; - } - this.BUFFS_TEMP[attrIndex].push({ - value: buffConf.value, - BType: buffConf.BType, - remainTime: buffConf.time - }); - } - - // 重新计算受影响的属性 - this.recalculateSingleAttr(attrIndex); - } - - - // ==================== 属性计算系==================== - /** - * 重新计算单个属性 - * @param attrIndex 属性索引 - * - * 计算公式: - * - 数值型属性:最终值 = (基础值 + 所有数值型buff之和 - 所有数值型debuff之和) × (1 + 所有百分比buff之和/100 - 所有百分比debuff之和/100) - * - 百分比型属性:最终值 = 基础值 + 所有数值型buff之和 - 所有数值型debuff之和 + 所有百分比buff之和 - 所有百分比debuff之和 - */ - private recalculateSingleAttr(attrIndex: number) { - // 1. 获取基础值 - const baseValues: Record = { - [Attrs.HP_MAX]: this.base_hp, - [Attrs.MP_MAX]: this.base_mp, - [Attrs.DEF]: this.base_def, - [Attrs.AP]: this.base_ap, - [Attrs.MAP]: this.base_map, - [Attrs.SPEED]: this.base_speed, - [Attrs.SHIELD_MAX]: 0 - }; - - const baseVal = baseValues[attrIndex] !== undefined ? baseValues[attrIndex] : 0; - - // 2. 收集所有数值型 buff/debuff - 遍历所有buff数组并按BType筛选求和 - let totalValue = baseVal; - - // 遍历持久buff数组 - if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) { - for (const buff of this.BUFFS[attrIndex]) { - if (buff.BType === BType.VALUE) { - totalValue += buff.value; - } - } - } - // 遍历临时buff数组 - if (this.BUFFS_TEMP[attrIndex] && this.BUFFS_TEMP[attrIndex].length > 0) { - for (const buff of this.BUFFS_TEMP[attrIndex]) { - if (buff.BType === BType.VALUE) { - totalValue += buff.value; - } - } - } - - // 3. 收集所有百分比型 buff/debuff - 遍历所有buff数组并按BType筛选求和 - let totalRatio = 0; // 总百分比(可正可负) - - // 遍历持久buff数组 - if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) { - for (const buff of this.BUFFS[attrIndex]) { - if (buff.BType === BType.RATIO) { - totalRatio += buff.value; - } - } - } - // 遍历临时buff数组 - if (this.BUFFS_TEMP[attrIndex] && this.BUFFS_TEMP[attrIndex].length > 0) { - for (const buff of this.BUFFS_TEMP[attrIndex]) { - if (buff.BType === BType.RATIO) { - totalRatio += buff.value; - } - } - } - - // 4. 根据属性类型计算最终值 - const attrType = AttrsType[attrIndex]; - const isRatioAttr = attrType === BType.RATIO; - - if (isRatioAttr) { - // 百分比型属性:直接加减 - this.Attrs[attrIndex] = totalValue + totalRatio; - } else { - // 数值型属性:(基础值+数值) × (1 + 百分比/100) - this.Attrs[attrIndex] = Math.floor(totalValue * (1 + totalRatio / 100)); - } - - // 5. 确保属性值合理 - this.clampSingleAttr(attrIndex); - } - /** - * 确保单个属性值合 - */ - private clampSingleAttr(attrIndex: number) { - switch(attrIndex) { - case Attrs.HP_MAX: - case Attrs.MP_MAX: - this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]); - break; - case Attrs.DEF: - case Attrs.AP: - case Attrs.MAP: - this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]); - break; - case Attrs.CRITICAL: - case Attrs.DODGE: - case Attrs.HIT: - this.Attrs[attrIndex] = Math.max(0, Math.min(AttrSet.ATTR_MAX, this.Attrs[attrIndex])); //AttrSet.ATTR_MAX =85 - break; + if (this.model) { + this.model.addBuff(buffConf); } } - // ==================== 临时 BUFF/DEBUFF 更新 ==================== + + // 属性计算方法已迁移到 Model,这里不再需要 + /** - * 更新临时 buff/debuff 的剩余时 - * 应在 update 中定期调 - * @param dt 时间 + * 更新临时 buff/debuff(直接调用 Model) */ updateTemporaryBuffsDebuffs(dt: number) { - const affectedAttrs = new Set(); - // 更新临时型数buff - for (const attrIndex in this.BUFFS_TEMP) { - const buffs = this.BUFFS_TEMP[attrIndex]; - buffs.forEach(buff => { - buff.remainTime -= dt; - if (buff.remainTime <= 0) { - // 从数组中移除 - const index = buffs.indexOf(buff); - if (index > -1) { - buffs.splice(index, 1); - } - } - }); - if (buffs.length === 0) { - delete this.BUFFS_TEMP[attrIndex]; - affectedAttrs.add(parseInt(attrIndex)); - } + if (this.model) { + this.model.updateTemporaryBuffsDebuffs(dt); } - // 更新临时型百分比 buff - for (const attrIndex in this.BUFFS_TEMP) { - const buffs = this.BUFFS_TEMP[attrIndex]; - buffs.forEach(buff => { - buff.remainTime -= dt; - if (buff.remainTime <= 0) { - // 从数组中移除 - const index = buffs.indexOf(buff); - if (index > -1) { - buffs.splice(index, 1); - } - } - }); - if (buffs.length === 0) { - delete this.BUFFS_TEMP[attrIndex]; - affectedAttrs.add(parseInt(attrIndex)); - } - } - - // 负面状态更新 - for (const key in this.NeAttrs) { - const debuff = this.NeAttrs[key]; - debuff.remainTime -= dt; - if (debuff.remainTime <= 0) { - debuff.remainTime = 0; - } - } - - // 只重新计算受影响的属 - affectedAttrs.forEach(attrIndex => { - this.recalculateSingleAttr(attrIndex); - }); } - // ==================== BUFF 辅助方法 ==================== /** - * 清空buff - * @param attrIndex 属性索引,如果为空则清理所有buff(包括临时和持久) - * @param isBuff true时清理value>0的增益buff,false时清理value<0的减益buff + * 清空buff(直接调用 Model) */ clearBuffs(attrIndex?: number, isBuff: boolean = true): void { - if (attrIndex === undefined) { - // 清理所有buff,根据isBuff过滤 - for (const attrIndex in this.BUFFS_TEMP) { - this.clearBuffsForAttr(parseInt(attrIndex), isBuff); - } - - } else { - // 清理指定属性的buff,根据isBuff过滤 - this.clearBuffsForAttr(attrIndex, isBuff); + if (this.model) { + this.model.clearBuffs(attrIndex, isBuff); } } /** - * 清理指定属性的buff,根据增益/减益类型过滤 - * @param attrIndex 属性索引 - * @param isBuff true清理增益buff,false清理减益buff - */ - private clearBuffsForAttr(attrIndex: number, isBuff: boolean): void { - const buffContainer = this.BUFFS_TEMP; - - if (!buffContainer[attrIndex]) return; - - // 过滤buff数组,保留不符合清理条件的buff - buffContainer[attrIndex] = buffContainer[attrIndex].filter(buff => { - const shouldClear = isBuff ? buff.value > 0 : buff.value < 0; - return !shouldClear; - }); - - // 如果数组为空,删除该属性条目 - if (buffContainer[attrIndex].length === 0) { - delete buffContainer[attrIndex]; - } - - // 重新计算该属性 - this.recalculateSingleAttr(attrIndex); - } - - // ==================== NeAttrs(负面状态)管理 ==================== - /** - * 清理单个NeAttr(负面状态) - * @param neAttrIndex NeAttrs索引(如NeAttrs.IN_STUN、NeAttrs.IN_FROST等) - * 清理即将该状态的value和time都设为0 + * 清理单个NeAttr(直接调用 Model) */ clearNeAttr(neAttrIndex: number): void { - if (this.NeAttrs[neAttrIndex]) { - this.NeAttrs[neAttrIndex].value = 0; - this.NeAttrs[neAttrIndex].time = 0; + if (this.model) { + this.model.clearNeAttr(neAttrIndex); } } /** - * 清理所有NeAttrs(负面状态) - * 清理即将所有状态的value和time都设为0 + * 清理所有NeAttrs(直接调用 Model) */ clearAllNeAttrs(): void { - for (const key in this.NeAttrs) { - this.NeAttrs[key].value = 0; - this.NeAttrs[key].time = 0; + if (this.model) { + this.model.clearAllNeAttrs(); } } - public isStun() { - return this.NeAttrs[NeAttrs.IN_STUN].time > 0; - } - public isFrost() { - return this.NeAttrs[NeAttrs.IN_FROST].time > 0; + /** + * 检查是否眩晕(直接调用 Model) + */ + public isStun(): boolean { + return this.model?.isStun() ?? false; } + /** + * 检查是否冰冻(直接调用 Model) + */ + public isFrost(): boolean { + return this.model?.isFrost() ?? false; + } + + /** + * View 层每帧更新 + * 注意:数据更新逻辑已移到 HeroAttrSystem,这里只负责显示 + */ update(dt: number){ if(!smc.mission.play||smc.mission.pause) return - // if(this.is_dead) { - // this.ent.destroy(); - // return - // } - this.BaseUp(dt) - - // 更新所有按时间减少的buff和debuff - this.in_stop(dt); - // 处理伤害队列 - this.processDamageQueue(); - // 更新临时 buff/debuff 时间 - this.updateTemporaryBuffsDebuffs(dt); - this.BUFFCOMP.hp_show(this.hp,this.Attrs[Attrs.HP_MAX]) - this.BUFFCOMP.mp_show(this.mp,this.Attrs[Attrs.MP_MAX]) - this.BUFFCOMP.show_shield(this.shield,this.Attrs[Attrs.SHIELD_MAX]) - } - BaseUp(dt:number){ - this.mp += HeroUpSet.MP*dt - this.hp += HeroUpSet.HP*dt - if(this.mp > this.Attrs[Attrs.MP_MAX]) this.mp = this.Attrs[Attrs.MP_MAX] - if(this.hp > this.Attrs[Attrs.HP_MAX]) this.hp = this.Attrs[Attrs.HP_MAX] + // ✅ View 层职责:处理表现相关的逻辑 + this.in_stop(dt); // 动画状态 + this.processDamageQueue(); // 伤害数字显示队列 + + // ✅ 更新显示(数据由 HeroAttrSystem 更新) + this.BUFFCOMP.hp_show(this.hp, this.Attrs[Attrs.HP_MAX]); + this.BUFFCOMP.mp_show(this.mp, this.Attrs[Attrs.MP_MAX]); + this.BUFFCOMP.show_shield(this.shield, this.Attrs[Attrs.SHIELD_MAX]); } + + // 注意:BaseUp 逻辑已移到 HeroAttrSystem.update() + // 注意:updateTemporaryBuffsDebuffs 逻辑已移到 HeroAttrSystem.update() do_fight_end(){ this.as.do_buff() } @@ -522,34 +187,14 @@ export class HeroViewComp extends CCComp { } } add_shield(shield:number){ - this.shield = this.Attrs[Attrs.SHIELD_MAX] +=shield + // 护盾数据更新由 Model 层处理,这里只负责视图表现 if(this.shield>0) this.BUFFCOMP.show_shield(this.shield,this.Attrs[Attrs.SHIELD_MAX]) } health(hp: number = 0,is_num:boolean=true) { + // 生命值更新由 Model 层处理,这里只负责视图表现 this.BUFFCOMP.heathed(); - let real_hp=0 - let hp_max=this.Attrs[Attrs.HP_MAX] - let lost_hp=hp_max-this.hp - if(is_num){ - if(lost_hp > hp){ - real_hp=Math.floor(hp); - }else{ - real_hp=lost_hp; - } - }else{ - if(lost_hp > hp/100*hp_max){ - real_hp=Math.floor(hp/100*hp_max); - }else{ - real_hp=lost_hp; - } - } - if(real_hp > 0){ - this.hp+=real_hp; - this.BUFFCOMP.tooltip(TooltipTypes.health,real_hp.toFixed(0)); - } - this.BUFFCOMP.hp_show(this.hp,this.Attrs[Attrs.HP_MAX]) - // this.update_vm + this.BUFFCOMP.show_heal(hp); } @@ -563,10 +208,11 @@ export class HeroViewComp extends CCComp { } do_dead(){ - this.do_dead_trigger() - //console.log("[HeroViewComp]:角色死亡",this.hero_uuid) + // 死亡逻辑主要由 HeroBattleSystem 处理 + // 这里只保留视图层的表现逻辑 if(this.is_count_dead) return this.is_count_dead=true + if(this.fac==FacSet.MON){ this.scheduleOnce(()=>{ this.do_drop() @@ -577,51 +223,26 @@ export class HeroViewComp extends CCComp { oops.message.dispatchEvent(GameEvent.HeroDead,{hero_uuid:this.hero_uuid}) },0.1) } - - if(this.fac==FacSet.HERO){ - //console.log("[HeroViewComp]:英雄死亡") - // oops.message.dispatchEvent(GameEvent.FightEnd,{victory:false}) - } - - } do_drop(){ } do_atked(remainingDamage:number,CAttrs:any,s_uuid:number){ + // 使用战斗系统处理攻击逻辑 + const battleSystem = this.ent.ecs.getSystem(HeroBattleSystem); + if (!battleSystem) { + console.error("[HeroViewComp] HeroBattleSystem 未找到"); + return; + } + + const damage = battleSystem.doAttack(this.ent, remainingDamage, CAttrs, s_uuid); + if (damage <= 0) return; + + // 视图层表现 let SConf=SkillSet[s_uuid] - this.do_atked_trigger() - if(this.check_dodge()) return - let is_crit = this.check_crit(CAttrs[Attrs.CRITICAL]) - if(this == null) return; - let damage = this.count_damage(remainingDamage) - if(is_crit) { - damage = Math.floor(damage * (1 + (FightSet.CRIT_DAMAGE+CAttrs[Attrs.CRITICAL_DMG])/100)) - } - // console.log(this.hero_name+"[HeroViewComp]:heroview :damage|hp|hp_max",damage,this.hp,this.Attrs[BuffAttr.HP_MAX]) - damage=this.check_shield(damage) - if(damage <= 0) return - this.hp -= damage; - if(this.hp <= 0) { - if(this == null) return; - this.is_dead=true - if(this.BUFFCOMP){ - this.BUFFCOMP.dead() - } - this.do_dead() - //console.log("[HeroViewComp]:dead,fac => "+(this.fac==FacSet.HERO?"hero":"monster")) - if(this.ent == null) return; - if(this.fac ==FacSet.HERO){ - this.to_grave() - }else{ - this.ent.destroy(); - } - - } - // this.update_vm this.back() - this.showDamage(damage, is_crit,SConf.AtkedName); + this.showDamage(damage, false, SConf.AtkedName); // 暴击状态由战斗系统内部处理 } //后退 back(){ @@ -636,53 +257,7 @@ export class HeroViewComp extends CCComp { tween(this.node).to(0.1, { position:v3(tx,this.node.position.y,0)}).start() } } - //伤害计算 debuff 易伤 - count_damage(remainingDamage:number){ - - return remainingDamage - } - - check_shield(damage:number){ - if(this.shield <= 0 ) return damage - if(this.shield >= damage){ - this.shield -= damage - this.BUFFCOMP.tooltip(TooltipTypes.uskill,"*吸收*"); - if(this.shield <= 0){ - this.shield=this.Attrs[Attrs.SHIELD_MAX]=0 - } - damage = 0 - } - if(this.shield < damage){ - damage=damage-this.shield - this.shield=0 - this.Attrs[Attrs.SHIELD_MAX]=0 - } - this.BUFFCOMP.show_shield(this.shield,this.Attrs[Attrs.SHIELD_MAX]) - return damage - } - - check_dodge(){ - if(this.Attrs[Attrs.DODGE] > 0){ - let random = Math.random()*100 - if(random < this.Attrs[Attrs.DODGE]) { - this.BUFFCOMP.tooltip(TooltipTypes.uskill,"*闪避*"); - return true - } - } - return false - } - - check_crit(crit:number=0){ - if(crit > 0){ - let random = Math.random()*100 - if(random < crit) { - //console.log("[HeroViewComp]:crit",crit,random) - return true - } - } - //console.log("[HeroViewComp]:crit",crit) - return false - } + // 伤害计算和战斗逻辑已迁移到 HeroBattleSystem do_dead_trigger(){ //死亡特殊处理 if(this.is_dead||this.fac==FacSet.MON) return diff --git a/assets/script/game/hero/Mon.ts b/assets/script/game/hero/Mon.ts index 3cfb987e..d254b685 100644 --- a/assets/script/game/hero/Mon.ts +++ b/assets/script/game/hero/Mon.ts @@ -2,7 +2,6 @@ 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 { HeroViewComp } from "./HeroViewComp"; import { BoxSet, FacSet } from "../common/config/BoxSet"; import { HeroInfo } from "../common/config/heroSet"; import { MonModelComp } from "./MonModelComp"; @@ -12,11 +11,13 @@ import { BuffConf, SkillSet } from "../common/config/SkillSet"; import { getNeAttrs, getAttrs ,Attrs} from "../common/config/HeroAttrs"; import { TalComp } from "./TalComp"; import { getMonAttr, MonType } from "../map/RogueConfig"; +import { EBusComp } from "./EBusComp"; +import { MonViewComp } from "./MonViewComp"; /** 角色实体 */ @ecs.register(`Monster`) export class Monster extends ecs.Entity { HeroModel!: MonModelComp; - HeroView!: HeroViewComp; + HeroView!: MonViewComp; BattleMove!: BattleMoveComp; protected init() { @@ -24,13 +25,15 @@ export class Monster extends ecs.Entity { BattleMoveComp, MonModelComp, TalComp, + EBusComp, ); } destroy(): void { - this.remove(HeroViewComp); + this.remove(MonViewComp); this.remove(MonModelComp); this.remove(TalComp); + this.remove(EBusComp); super.destroy(); } @@ -38,8 +41,6 @@ export class Monster extends ecs.Entity { load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001,lv:number=1,monType:MonType=MonType.NORMAL, buffs: BuffConf[] = [],is_call=false) { scale=-1 let box_group=BoxSet.MONSTER - // console.log("mon load",uuid) - // this.addComponents( MonModelComp, BattleMoveComp); var scene = smc.map.MapView.scene; var path = "game/heros/"+HeroInfo[uuid].path; var prefab: Prefab = oops.res.get(path, Prefab)!; @@ -49,7 +50,52 @@ export class Monster extends ecs.Entity { const collider = node.getComponent(BoxCollider2D); if (collider) collider.enabled = false; // 先禁用 // 延迟一帧启用碰撞体 node.setPosition(pos) - this.hero_init(uuid,node,scale,box_group,lv,monType, buffs,is_call) + + + var view = node.getComponent(MonViewComp)!; + const model = this.get(MonModelComp); + let hero = HeroInfo[uuid]; // 共用英雄数据 + // 设置 View 层属性(表现相关) + view.scale = scale; + view.box_group = box_group; + + // 设置 Model 层属性(数据相关) + model.hero_uuid = uuid; + model.hero_name = hero.name; + model.lv = lv; + model.type = hero.type; + model.fac = FacSet.MON; + model.is_boss = monType == MonType.BOSS; + if(!model.is_boss){ + model.is_kalami = true; + } + + // 根据等级和类型获取怪物属性 + const {hp, mp, ap, map, def, mdef} = getMonAttr(lv, uuid, monType); + // 初始化属性数组 + 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.MDEF] = mdef; + model.Attrs[Attrs.AP] = ap; + model.Attrs[Attrs.MAP] = map; + model.Attrs[Attrs.SPEED] = hero.speed; + model.Attrs[Attrs.DIS] = hero.dis; + // 初始化师兄 + + // 设置技能 + for(let i=0; i> = {}; + + // ==================== 标记状态 ==================== + is_dead: boolean = false; + is_count_dead: boolean = false; + is_boss: boolean = false; + is_big_boss: boolean = false; + is_master: boolean = false; + is_friend: boolean = false; + is_kalami: boolean = false; + + // ==================== 计数统计 ==================== + atk_count: number = 0; // 攻击次数 + atked_count: number = 0; // 被攻击次数 + + // ==================== 技能配置 ==================== + skills: any = []; + + + addBuff(buffConf: BuffConf) { + // 怪物只使用临时buff + const attrIndex = buffConf.buff; + if (!this.BUFFS_TEMP[attrIndex]) this.BUFFS_TEMP[attrIndex] = []; + this.BUFFS_TEMP[attrIndex].push({ value: buffConf.value, BType: buffConf.BType, remainTime: buffConf.time }); + this.recalculateSingleAttr(attrIndex); + } + + recalculateSingleAttr(attrIndex: number) { + const baseValues: Record = { + [Attrs.HP_MAX]: this.base_hp, [Attrs.MP_MAX]: this.base_mp, [Attrs.DEF]: this.base_def, + [Attrs.AP]: this.base_ap, [Attrs.MAP]: this.base_map, [Attrs.SPEED]: this.base_speed, [Attrs.SHIELD_MAX]: 0 + }; + const baseVal = baseValues[attrIndex] !== undefined ? baseValues[attrIndex] : 0; + let totalValue = baseVal, totalRatio = 0; + + // 怪物只计算临时buff + if (this.BUFFS_TEMP[attrIndex]) { + for (const buff of this.BUFFS_TEMP[attrIndex]) { + if (buff.BType === BType.VALUE) totalValue += buff.value; + else totalRatio += buff.value; + } + } + + const attrType = AttrsType[attrIndex]; + this.Attrs[attrIndex] = attrType === BType.RATIO ? totalValue + totalRatio : Math.floor(totalValue * (1 + totalRatio / 100)); + this.clampSingleAttr(attrIndex); + } + + private clampSingleAttr(attrIndex: number) { + switch(attrIndex) { + case Attrs.HP_MAX: case Attrs.MP_MAX: case Attrs.DEF: case Attrs.AP: case Attrs.MAP: + this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]); break; + case Attrs.CRITICAL: case Attrs.DODGE: case Attrs.HIT: + this.Attrs[attrIndex] = Math.max(0, Math.min(AttrSet.ATTR_MAX, this.Attrs[attrIndex])); break; + } + } + + updateTemporaryBuffsDebuffs(dt: number) { + const affectedAttrs = new Set(); + for (const attrIndex in this.BUFFS_TEMP) { + const buffs = this.BUFFS_TEMP[attrIndex]; + buffs.forEach(buff => { + buff.remainTime -= dt; + if (buff.remainTime <= 0) buffs.splice(buffs.indexOf(buff), 1); + }); + if (buffs.length === 0) { + delete this.BUFFS_TEMP[attrIndex]; + affectedAttrs.add(parseInt(attrIndex)); + } + } + for (const key in this.NeAttrs) { + this.NeAttrs[key].remainTime -= dt; + if (this.NeAttrs[key].remainTime <= 0) this.NeAttrs[key].remainTime = 0; + } + affectedAttrs.forEach(attrIndex => this.recalculateSingleAttr(attrIndex)); + } + + clearBuffs(attrIndex?: number, isBuff: boolean = true): void { + if (attrIndex === undefined) { + for (const idx in this.BUFFS_TEMP) this.clearBuffsForAttr(parseInt(idx), isBuff); + } else { + this.clearBuffsForAttr(attrIndex, isBuff); + } + } + + private clearBuffsForAttr(attrIndex: number, isBuff: boolean): void { + if (!this.BUFFS_TEMP[attrIndex]) return; + this.BUFFS_TEMP[attrIndex] = this.BUFFS_TEMP[attrIndex].filter(buff => { + const shouldClear = isBuff ? buff.value > 0 : buff.value < 0; + return !shouldClear; + }); + if (this.BUFFS_TEMP[attrIndex].length === 0) delete this.BUFFS_TEMP[attrIndex]; + this.recalculateSingleAttr(attrIndex); + } + + clearNeAttr(neAttrIndex: number): void { + if (this.NeAttrs[neAttrIndex]) { + this.NeAttrs[neAttrIndex].value = 0; + this.NeAttrs[neAttrIndex].time = 0; + } + } + + clearAllNeAttrs(): void { + for (const key in this.NeAttrs) { + this.NeAttrs[key].value = 0; + this.NeAttrs[key].time = 0; + } + } + + public isStun(): boolean { + return this.NeAttrs[NeAttrs.IN_STUN]?.time > 0; + } + + public isFrost(): boolean { + return this.NeAttrs[NeAttrs.IN_FROST]?.time > 0; + } reset() { + this.hero_uuid = 1001; + this.hero_name = "monster"; + this.lv = 1; + this.type = 0; + this.fac = 1; + this.hp = 100; + this.mp = 100; + this.shield = 0; + this.Attrs = []; + this.NeAttrs = []; + this.BUFFS_TEMP = {}; // 只重置临时buff + this.is_dead = false; + this.is_count_dead = false; + this.is_boss = false; + this.is_big_boss = false; + this.is_master = false; + this.is_friend = false; + this.is_kalami = false; + this.atk_count = 0; + this.atked_count = 0; + this.skills = []; } } + +/** + * ==================== 怪物属性更新系统 ==================== + * + * 与 HeroAttrSystem 类似,但针对怪物 + * 可以复用相同逻辑,也可以定制不同规则 + */ +export class MonAttrSystem extends ecs.ComblockSystem + implements ecs.ISystemUpdate { + + filter(): ecs.IMatcher { + return ecs.allOf(MonModelComp); + } + + update(e: ecs.Entity): void { + const model = e.get(MonModelComp); + if (!model || model.is_dead) return; + + // 怪物的属性更新逻辑(可以与英雄不同) + model.updateTemporaryBuffsDebuffs(this.dt); + + // 怪物可能没有自然回复,或者回复速度不同 + // model.mp += MonUpSet.MP * this.dt; + // model.hp += MonUpSet.HP * this.dt; + } +} \ No newline at end of file diff --git a/assets/script/game/hero/MonViewComp.ts b/assets/script/game/hero/MonViewComp.ts new file mode 100644 index 00000000..c9d23e72 --- /dev/null +++ b/assets/script/game/hero/MonViewComp.ts @@ -0,0 +1,29 @@ +import { _decorator } from "cc"; +import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; +import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; + +const { ccclass, property } = _decorator; + +/** 视图层对象 */ +@ccclass('MonViewComp') +@ecs.register('MonView', false) +export class MonViewComp extends CCComp { + /** 视图层逻辑代码分离演示 */ + start() { + // var entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象 + // this.on(ModuleEvent.Cmd, this.onHandler, this); + } + + /** 全局消息逻辑处理 */ + // private onHandler(event: string, args: any) { + // switch (event) { + // case ModuleEvent.Cmd: + // break; + // } + // } + + /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */ + reset() { + this.node.destroy(); + } +} \ No newline at end of file diff --git a/assets/script/game/hero/MonViewComp.ts.meta b/assets/script/game/hero/MonViewComp.ts.meta new file mode 100644 index 00000000..4ad97e8f --- /dev/null +++ b/assets/script/game/hero/MonViewComp.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "413afaac-8d90-4810-8036-a5dae3f9eea8", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/hero/TalComp.ts b/assets/script/game/hero/TalComp.ts index 35131f6a..af8d6ce1 100644 --- a/assets/script/game/hero/TalComp.ts +++ b/assets/script/game/hero/TalComp.ts @@ -4,6 +4,8 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu import { ItalConf, TalType, TalEType, talConf } from "../common/config/TalSet"; import { BuffConf, SkillSet } from "../common/config/SkillSet"; import { HeroInfo } from "../common/config/heroSet"; +import { HeroViewComp } from "./HeroViewComp"; +import { SkillConComp } from "./SkillConComp"; const { ccclass } = _decorator; @@ -35,7 +37,7 @@ interface TalEffect { */ @ccclass('TalComp') @ecs.register('TalComp', false) -export class TalComp extends CCComp { +export class TalComp extends ecs.Comp { /** 英雄视图组件引用,运行时获取避免循环引用 */ private heroView: any = null; private skillCon:any=null; @@ -57,8 +59,8 @@ export class TalComp extends CCComp { */ start() { // 运行时获取组件,避免编译时循环引用 - this.heroView = this.node.getComponent("HeroViewComp" as any); - this.skillCon = this.node.getComponent("SkillConComp" as any); + this.heroView = this.ent.get(HeroViewComp); + this.skillCon = this.ent.get(SkillConComp); if (this.heroView) { this.heroUuid = this.heroView.hero_uuid; this.initializeTalents(); @@ -165,6 +167,5 @@ export class TalComp extends CCComp { reset() { this.isInitialized = false; - this.node.destroy(); } } \ No newline at end of file