角色视图 数据逻辑 依ecs 框架进行重构

This commit is contained in:
2025-10-30 08:56:37 +08:00
parent edb7f23918
commit a79cb9f35d
10 changed files with 1274 additions and 602 deletions

View File

@@ -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<attrIndex: number, Array<{value, BType}>>
* - BUFFS_TEMP: Record<attrIndex: number, Array<{value, BType, remainTime}>>
* - 每个属性对应一个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; /** 角色阵营 1hero -1 :mon */
type: number = 0; /**角色类型 0近战-需要贴1远程-保持距离 2辅助 */
fac:number=0; //阵营 0hero 1monster
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<BuffInstance> - 允许多个buff实例独立存在
/** 持久型buff数组 - 不会自动过期 */
BUFFS: Record<number, Array<{value: number, BType: BType}>> = {}
/** 临时型buff数组 - 按时间自动过期 */
BUFFS_TEMP: Record<number, Array<{value: number, BType: BType, remainTime: number}>> = {}
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<number, number> = {
[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<number>();
// 更新临时型数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的增益bufffalse时清理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清理增益bufffalse清理减益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