角色视图 数据逻辑 依ecs 框架进行重构
This commit is contained in:
90
assets/script/game/hero/EBusComp.ts
Normal file
90
assets/script/game/hero/EBusComp.ts
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
assets/script/game/hero/EBusComp.ts.meta
Normal file
9
assets/script/game/hero/EBusComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "b44b7a75-de7d-4aa1-ad20-78e6690926c0",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -12,18 +12,20 @@ import { SkillSet } from "../common/config/SkillSet";
|
|||||||
import { time } from "console";
|
import { time } from "console";
|
||||||
import { getNeAttrs, getAttrs ,Attrs} from "../common/config/HeroAttrs";
|
import { getNeAttrs, getAttrs ,Attrs} from "../common/config/HeroAttrs";
|
||||||
import { TalComp } from "./TalComp";
|
import { TalComp } from "./TalComp";
|
||||||
|
import { EBusComp } from "./EBusComp";
|
||||||
/** 角色实体 */
|
/** 角色实体 */
|
||||||
@ecs.register(`Hero`)
|
@ecs.register(`Hero`)
|
||||||
|
|
||||||
export class Hero extends ecs.Entity {
|
export class Hero extends ecs.Entity {
|
||||||
HeroModel!: HeroModelComp;
|
HeroModel!: HeroModelComp;
|
||||||
HeroView!: HeroViewComp;
|
View!: HeroViewComp;
|
||||||
BattleMove!: BattleMoveComp;
|
BattleMove!: BattleMoveComp;
|
||||||
protected init() {
|
protected init() {
|
||||||
this.addComponents<ecs.Comp>(
|
this.addComponents<ecs.Comp>(
|
||||||
BattleMoveComp,
|
BattleMoveComp,
|
||||||
HeroModelComp,
|
HeroModelComp,
|
||||||
TalComp
|
TalComp,
|
||||||
|
EBusComp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +33,7 @@ export class Hero extends ecs.Entity {
|
|||||||
this.remove(HeroViewComp);
|
this.remove(HeroViewComp);
|
||||||
this.remove(HeroModelComp);
|
this.remove(HeroModelComp);
|
||||||
this.remove(TalComp);
|
this.remove(TalComp);
|
||||||
|
this.remove(EBusComp);
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +52,56 @@ export class Hero extends ecs.Entity {
|
|||||||
node.parent = scene.entityLayer!.node!
|
node.parent = scene.entityLayer!.node!
|
||||||
node.setPosition(pos)
|
node.setPosition(pos)
|
||||||
// console.log("hero load",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<hero.skills.length; i++){
|
||||||
|
let skill = {
|
||||||
|
uuid: SkillSet[hero.skills[i]].uuid,
|
||||||
|
cd_max: SkillSet[hero.skills[i]].cd,
|
||||||
|
cost: SkillSet[hero.skills[i]].cost,
|
||||||
|
cd: 0
|
||||||
|
};
|
||||||
|
model.skills.push(skill);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置基础属性
|
||||||
|
model.base_ap = hero.ap;
|
||||||
|
model.base_map = hero.mp;
|
||||||
|
model.base_def = hero.def;
|
||||||
|
model.base_hp = hero.hp;
|
||||||
|
model.base_mp = hero.mp;
|
||||||
|
model.base_dis = hero.dis;
|
||||||
|
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.MAP] = model.base_map;
|
||||||
|
model.Attrs[Attrs.SPEED] = hero.speed;
|
||||||
|
model.Attrs[Attrs.DIS] = hero.dis;
|
||||||
|
|
||||||
|
// 初始化 buff/debuff 系统
|
||||||
|
model.initAttrs();
|
||||||
|
|
||||||
this.add(hv);
|
this.add(hv);
|
||||||
oops.message.dispatchEvent(GameEvent.MasterCalled,{uuid:uuid})
|
oops.message.dispatchEvent(GameEvent.MasterCalled,{uuid:uuid})
|
||||||
const move = this.get(BattleMoveComp);
|
const move = this.get(BattleMoveComp);
|
||||||
@@ -63,40 +115,28 @@ export class Hero extends ecs.Entity {
|
|||||||
}
|
}
|
||||||
smc.vmdata.mission_data.hero_num++
|
smc.vmdata.mission_data.hero_num++
|
||||||
}
|
}
|
||||||
hero_init(uuid:number=1001,node:Node) {
|
|
||||||
var hv = node.getComponent(HeroViewComp)!;
|
|
||||||
let hero= HeroInfo[uuid] // 共用英雄数据
|
|
||||||
hv.scale = 1;
|
|
||||||
hv.is_master=true;
|
|
||||||
hv.lv=hero.lv?hero.lv:1
|
|
||||||
hv.fac = FacSet.HERO;
|
|
||||||
hv.type = hero.type;
|
|
||||||
hv.box_group = BoxSet.HERO;
|
|
||||||
hv.hero_uuid= uuid;
|
|
||||||
hv.hero_name= hero.name;
|
|
||||||
for(let i=0;i<hero.skills.length;i++){
|
|
||||||
let skill={ uuid:SkillSet[hero.skills[i]].uuid, cd_max:SkillSet[hero.skills[i]].cd,cost:SkillSet[hero.skills[i]].cost,cd:0 }
|
|
||||||
hv.skills.push(skill)
|
|
||||||
}
|
|
||||||
hv.base_ap=hero.ap
|
|
||||||
hv.base_map=hero.mp
|
|
||||||
hv.base_def=hero.def
|
|
||||||
hv.base_hp=hero.hp
|
|
||||||
hv.base_mp=hero.mp
|
|
||||||
hv.base_dis=hero.dis
|
|
||||||
hv.base_speed=hero.speed
|
|
||||||
hv.Attrs=getAttrs()
|
|
||||||
hv.NeAttrs=getNeAttrs()
|
|
||||||
hv.hp=hv.Attrs[Attrs.HP_MAX]=hv.base_hp
|
|
||||||
hv.mp=hv.Attrs[Attrs.MP_MAX]=hv.base_mp
|
|
||||||
hv.Attrs[Attrs.DEF]=hv.base_def
|
|
||||||
hv.Attrs[Attrs.AP]=hv.base_ap
|
|
||||||
hv.Attrs[Attrs.MAP]=hv.base_map
|
|
||||||
hv.Attrs[Attrs.SPEED]=hero.speed
|
|
||||||
hv.Attrs[Attrs.DIS]=hero.dis
|
|
||||||
|
|
||||||
// 初始化 buff/debuff 系统
|
reset() {
|
||||||
hv.initAttrs();
|
// 注: 自定义释放逻辑,视图层实现 ecs.IComp 接口的 ecs 组件需要手动释放
|
||||||
return hv
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HeroLifecycleSystem extends ecs.ComblockSystem
|
||||||
|
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
|
||||||
|
|
||||||
|
filter() {
|
||||||
|
return ecs.allOf(HeroModelComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityEnter(e: ecs.Entity): void {
|
||||||
|
// 英雄实体创建时的特殊处理
|
||||||
|
console.log(`英雄进入世界: ${e.get(HeroModelComp).hero_name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityRemove(e: ecs.Entity): void {
|
||||||
|
// 英雄实体销毁时的清理工作
|
||||||
|
console.log(`英雄离开世界: ${e.get(HeroModelComp).hero_name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,724 @@
|
|||||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
|
import { Attrs, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs";
|
||||||
|
import { BuffConf, SkillSet } from "../common/config/SkillSet";
|
||||||
|
import { HeroInfo, AttrSet, HeroUpSet } from "../common/config/heroSet";
|
||||||
|
import { MonModelComp } from "./MonModelComp";
|
||||||
|
import { FacSet } from "../common/config/BoxSet";
|
||||||
|
import { FightSet } from "../common/config/Mission";
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 角色属性数据
|
|
||||||
*/
|
|
||||||
@ecs.register('HeroModel')
|
@ecs.register('HeroModel')
|
||||||
export class HeroModelComp extends ecs.Comp {
|
export class HeroModelComp extends ecs.Comp {
|
||||||
|
|
||||||
|
// ==================== 角色基础信息 ====================
|
||||||
|
hero_uuid: number = 1001;
|
||||||
|
hero_name: string = "hero";
|
||||||
|
lv: number = 1;
|
||||||
|
type: number = 0; // 0近战 1远程 2辅助
|
||||||
|
fac: number = 0; // 0:hero 1:monster
|
||||||
|
|
||||||
|
// ==================== 基础属性(有初始值) ====================
|
||||||
|
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; // 基础距离
|
||||||
|
|
||||||
|
// ==================== 动态属性值 ====================
|
||||||
|
hp: number = 100; // 当前血量
|
||||||
|
mp: number = 100; // 当前魔法值
|
||||||
|
shield: number = 0; // 当前护盾
|
||||||
|
Attrs: any = []; // 最终属性数组(经过Buff计算后)
|
||||||
|
NeAttrs: any = []; // 负面状态数组
|
||||||
|
|
||||||
|
// ==================== Buff/Debuff 系统 ====================
|
||||||
|
/** 持久型buff数组 - 不会自动过期 */
|
||||||
|
BUFFS: Record<number, Array<{value: number, BType: BType}>> = {};
|
||||||
|
|
||||||
|
/** 临时型buff数组 - 按时间自动过期 */
|
||||||
|
BUFFS_TEMP: Record<number, Array<{value: number, BType: BType, remainTime: number}>> = {};
|
||||||
|
|
||||||
|
// ==================== 标记状态 ====================
|
||||||
|
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<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
|
||||||
|
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<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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 负面状态更新
|
||||||
|
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() {
|
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 {
|
||||||
|
// 这里可以添加需要每帧处理的战斗逻辑
|
||||||
|
// 例如:持续伤害、战斗状态检查等
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,56 +14,9 @@ import { RandomManager } from "db://oops-framework/core/common/random/RandomMana
|
|||||||
import { AttrSet, HeroInfo, HeroUpSet } from "../common/config/heroSet";
|
import { AttrSet, HeroInfo, HeroUpSet } from "../common/config/heroSet";
|
||||||
import { Attrs, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs";
|
import { Attrs, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs";
|
||||||
import { TalComp } from "./TalComp";
|
import { TalComp } from "./TalComp";
|
||||||
|
import { HeroModelComp, HeroBattleSystem } from "./HeroModelComp";
|
||||||
const { ccclass, property } = _decorator;
|
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 {
|
export interface BuffInfo {
|
||||||
attr: Attrs;
|
attr: Attrs;
|
||||||
@@ -73,54 +26,20 @@ export interface BuffInfo {
|
|||||||
@ccclass('HeroViewComp') // 定义Cocos Creator 组件
|
@ccclass('HeroViewComp') // 定义Cocos Creator 组件
|
||||||
@ecs.register('HeroView', false) // 定义ECS 组件
|
@ecs.register('HeroView', false) // 定义ECS 组件
|
||||||
export class HeroViewComp extends CCComp {
|
export class HeroViewComp extends CCComp {
|
||||||
|
// ==================== View 层属性(表现相关)====================
|
||||||
BUFFCOMP:BuffComp=null!
|
BUFFCOMP:BuffComp=null!
|
||||||
TALCOMP:any=null!
|
TALCOMP:any=null!
|
||||||
as: HeroSpine = null!
|
as: HeroSpine = null!
|
||||||
status:String = "idle"
|
status:String = "idle"
|
||||||
hero_uuid:number = 1001;
|
scale: number = 1; // 显示方向
|
||||||
hero_name : string = "hero";
|
box_group:number = BoxSet.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; //是否计数死亡
|
|
||||||
is_stop:boolean = false;
|
is_stop:boolean = false;
|
||||||
is_atking:boolean = false;
|
is_atking:boolean = false;
|
||||||
|
|
||||||
is_boss:boolean = false;
|
// ==================== 直接访问 HeroModelComp ====================
|
||||||
is_big_boss:boolean = false;
|
private get model() {
|
||||||
is_master:boolean =false;
|
return this.ent.get(HeroModelComp);
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
private damageQueue: Array<{
|
private damageQueue: Array<{
|
||||||
damage: number,
|
damage: number,
|
||||||
@@ -145,7 +64,7 @@ export class HeroViewComp extends CCComp {
|
|||||||
start () {
|
start () {
|
||||||
this.as.idle()
|
this.as.idle()
|
||||||
this.BUFFCOMP=this.node.getComponent(BuffComp);
|
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)
|
// console.log("[HeroViewComp]:heroview"+this.hero_name,this.Attrs)
|
||||||
/** 方向 */
|
/** 方向 */
|
||||||
this.node.setScale(this.scale,1);
|
this.node.setScale(this.scale,1);
|
||||||
@@ -158,351 +77,97 @@ export class HeroViewComp extends CCComp {
|
|||||||
this.node.getChildByName("top").getChildByName("pow").active = true;
|
this.node.getChildByName("top").getChildByName("pow").active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== BUFF系统初始====================
|
// ==================== BUFF 系统方法直接调用 ====================
|
||||||
/**
|
/**
|
||||||
* 初始化角色的 buff debuff
|
* 初始化角色的 buff debuff(直接调用 Model)
|
||||||
* HeroInfo 读取初始配置,建立属性系
|
|
||||||
*/
|
*/
|
||||||
initAttrs() {
|
initAttrs() {
|
||||||
// 清空现有 buff/debuff
|
if (this.model) {
|
||||||
this.BUFFS = {
|
this.model.initAttrs();
|
||||||
};
|
|
||||||
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 效果(支持多次叠加)
|
* 添加 buff 效果(直接调用 Model)
|
||||||
* @param buffConf buff 配置 (来自 SkillSet.BuffConf heroSet.buff)
|
|
||||||
*
|
|
||||||
* 叠加规则
|
|
||||||
* 1. 持久型buff:直接添加到数组中,与现有buff独立存在
|
|
||||||
* 2. 临时型buff:直接添加到数组中,保持独立的剩余时间计算
|
|
||||||
* 3. 所有buff实例在属性计算时都被纳入求和计算
|
|
||||||
*/
|
*/
|
||||||
addBuff(buffConf: BuffConf) {
|
addBuff(buffConf: BuffConf) {
|
||||||
const isPermanent = buffConf.time === 0;
|
if (this.model) {
|
||||||
const attrIndex = buffConf.buff;
|
this.model.addBuff(buffConf);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 临时 BUFF/DEBUFF 更新 ====================
|
|
||||||
|
// 属性计算方法已迁移到 Model,这里不再需要
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新临时 buff/debuff 的剩余时
|
* 更新临时 buff/debuff(直接调用 Model)
|
||||||
* 应在 update 中定期调
|
|
||||||
* @param dt 时间
|
|
||||||
*/
|
*/
|
||||||
updateTemporaryBuffsDebuffs(dt: number) {
|
updateTemporaryBuffsDebuffs(dt: number) {
|
||||||
const affectedAttrs = new Set<number>();
|
if (this.model) {
|
||||||
// 更新临时型数buff
|
this.model.updateTemporaryBuffsDebuffs(dt);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 更新临时型百分比 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
|
* 清空buff(直接调用 Model)
|
||||||
* @param attrIndex 属性索引,如果为空则清理所有buff(包括临时和持久)
|
|
||||||
* @param isBuff true时清理value>0的增益buff,false时清理value<0的减益buff
|
|
||||||
*/
|
*/
|
||||||
clearBuffs(attrIndex?: number, isBuff: boolean = true): void {
|
clearBuffs(attrIndex?: number, isBuff: boolean = true): void {
|
||||||
if (attrIndex === undefined) {
|
if (this.model) {
|
||||||
// 清理所有buff,根据isBuff过滤
|
this.model.clearBuffs(attrIndex, isBuff);
|
||||||
for (const attrIndex in this.BUFFS_TEMP) {
|
|
||||||
this.clearBuffsForAttr(parseInt(attrIndex), isBuff);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// 清理指定属性的buff,根据isBuff过滤
|
|
||||||
this.clearBuffsForAttr(attrIndex, isBuff);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清理指定属性的buff,根据增益/减益类型过滤
|
* 清理单个NeAttr(直接调用 Model)
|
||||||
* @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
|
|
||||||
*/
|
*/
|
||||||
clearNeAttr(neAttrIndex: number): void {
|
clearNeAttr(neAttrIndex: number): void {
|
||||||
if (this.NeAttrs[neAttrIndex]) {
|
if (this.model) {
|
||||||
this.NeAttrs[neAttrIndex].value = 0;
|
this.model.clearNeAttr(neAttrIndex);
|
||||||
this.NeAttrs[neAttrIndex].time = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清理所有NeAttrs(负面状态)
|
* 清理所有NeAttrs(直接调用 Model)
|
||||||
* 清理即将所有状态的value和time都设为0
|
|
||||||
*/
|
*/
|
||||||
clearAllNeAttrs(): void {
|
clearAllNeAttrs(): void {
|
||||||
for (const key in this.NeAttrs) {
|
if (this.model) {
|
||||||
this.NeAttrs[key].value = 0;
|
this.model.clearAllNeAttrs();
|
||||||
this.NeAttrs[key].time = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isStun() {
|
/**
|
||||||
return this.NeAttrs[NeAttrs.IN_STUN].time > 0;
|
* 检查是否眩晕(直接调用 Model)
|
||||||
}
|
*/
|
||||||
public isFrost() {
|
public isStun(): boolean {
|
||||||
return this.NeAttrs[NeAttrs.IN_FROST].time > 0;
|
return this.model?.isStun() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否冰冻(直接调用 Model)
|
||||||
|
*/
|
||||||
|
public isFrost(): boolean {
|
||||||
|
return this.model?.isFrost() ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View 层每帧更新
|
||||||
|
* 注意:数据更新逻辑已移到 HeroAttrSystem,这里只负责显示
|
||||||
|
*/
|
||||||
update(dt: number){
|
update(dt: number){
|
||||||
if(!smc.mission.play||smc.mission.pause) return
|
if(!smc.mission.play||smc.mission.pause) return
|
||||||
// if(this.is_dead) {
|
|
||||||
// this.ent.destroy();
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
this.BaseUp(dt)
|
|
||||||
|
|
||||||
// 更新所有按时间减少的buff和debuff
|
// ✅ View 层职责:处理表现相关的逻辑
|
||||||
this.in_stop(dt);
|
this.in_stop(dt); // 动画状态
|
||||||
// 处理伤害队列
|
this.processDamageQueue(); // 伤害数字显示队列
|
||||||
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]
|
|
||||||
|
|
||||||
|
// ✅ 更新显示(数据由 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(){
|
do_fight_end(){
|
||||||
this.as.do_buff()
|
this.as.do_buff()
|
||||||
}
|
}
|
||||||
@@ -522,34 +187,14 @@ export class HeroViewComp extends CCComp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_shield(shield:number){
|
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])
|
if(this.shield>0) this.BUFFCOMP.show_shield(this.shield,this.Attrs[Attrs.SHIELD_MAX])
|
||||||
}
|
}
|
||||||
|
|
||||||
health(hp: number = 0,is_num:boolean=true) {
|
health(hp: number = 0,is_num:boolean=true) {
|
||||||
|
// 生命值更新由 Model 层处理,这里只负责视图表现
|
||||||
this.BUFFCOMP.heathed();
|
this.BUFFCOMP.heathed();
|
||||||
let real_hp=0
|
this.BUFFCOMP.show_heal(hp);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -563,10 +208,11 @@ export class HeroViewComp extends CCComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do_dead(){
|
do_dead(){
|
||||||
this.do_dead_trigger()
|
// 死亡逻辑主要由 HeroBattleSystem 处理
|
||||||
//console.log("[HeroViewComp]:角色死亡",this.hero_uuid)
|
// 这里只保留视图层的表现逻辑
|
||||||
if(this.is_count_dead) return
|
if(this.is_count_dead) return
|
||||||
this.is_count_dead=true
|
this.is_count_dead=true
|
||||||
|
|
||||||
if(this.fac==FacSet.MON){
|
if(this.fac==FacSet.MON){
|
||||||
this.scheduleOnce(()=>{
|
this.scheduleOnce(()=>{
|
||||||
this.do_drop()
|
this.do_drop()
|
||||||
@@ -577,51 +223,26 @@ export class HeroViewComp extends CCComp {
|
|||||||
oops.message.dispatchEvent(GameEvent.HeroDead,{hero_uuid:this.hero_uuid})
|
oops.message.dispatchEvent(GameEvent.HeroDead,{hero_uuid:this.hero_uuid})
|
||||||
},0.1)
|
},0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.fac==FacSet.HERO){
|
|
||||||
//console.log("[HeroViewComp]:英雄死亡")
|
|
||||||
// oops.message.dispatchEvent(GameEvent.FightEnd,{victory:false})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
do_drop(){
|
do_drop(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do_atked(remainingDamage:number,CAttrs:any,s_uuid:number){
|
do_atked(remainingDamage:number,CAttrs:any,s_uuid:number){
|
||||||
let SConf=SkillSet[s_uuid]
|
// 使用战斗系统处理攻击逻辑
|
||||||
this.do_atked_trigger()
|
const battleSystem = this.ent.ecs.getSystem(HeroBattleSystem);
|
||||||
if(this.check_dodge()) return
|
if (!battleSystem) {
|
||||||
let is_crit = this.check_crit(CAttrs[Attrs.CRITICAL])
|
console.error("[HeroViewComp] HeroBattleSystem 未找到");
|
||||||
if(this == null) return;
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
const damage = battleSystem.doAttack(this.ent, remainingDamage, CAttrs, s_uuid);
|
||||||
// this.update_vm
|
if (damage <= 0) return;
|
||||||
|
|
||||||
|
// 视图层表现
|
||||||
|
let SConf=SkillSet[s_uuid]
|
||||||
this.back()
|
this.back()
|
||||||
this.showDamage(damage, is_crit,SConf.AtkedName);
|
this.showDamage(damage, false, SConf.AtkedName); // 暴击状态由战斗系统内部处理
|
||||||
}
|
}
|
||||||
//后退
|
//后退
|
||||||
back(){
|
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()
|
tween(this.node).to(0.1, { position:v3(tx,this.node.position.y,0)}).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//伤害计算 debuff 易伤
|
// 伤害计算和战斗逻辑已迁移到 HeroBattleSystem
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
do_dead_trigger(){ //死亡特殊处理
|
do_dead_trigger(){ //死亡特殊处理
|
||||||
if(this.is_dead||this.fac==FacSet.MON) return
|
if(this.is_dead||this.fac==FacSet.MON) return
|
||||||
|
|||||||
@@ -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 { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { HeroViewComp } from "./HeroViewComp";
|
|
||||||
import { BoxSet, FacSet } from "../common/config/BoxSet";
|
import { BoxSet, FacSet } from "../common/config/BoxSet";
|
||||||
import { HeroInfo } from "../common/config/heroSet";
|
import { HeroInfo } from "../common/config/heroSet";
|
||||||
import { MonModelComp } from "./MonModelComp";
|
import { MonModelComp } from "./MonModelComp";
|
||||||
@@ -12,11 +11,13 @@ import { BuffConf, SkillSet } from "../common/config/SkillSet";
|
|||||||
import { getNeAttrs, getAttrs ,Attrs} from "../common/config/HeroAttrs";
|
import { getNeAttrs, getAttrs ,Attrs} from "../common/config/HeroAttrs";
|
||||||
import { TalComp } from "./TalComp";
|
import { TalComp } from "./TalComp";
|
||||||
import { getMonAttr, MonType } from "../map/RogueConfig";
|
import { getMonAttr, MonType } from "../map/RogueConfig";
|
||||||
|
import { EBusComp } from "./EBusComp";
|
||||||
|
import { MonViewComp } from "./MonViewComp";
|
||||||
/** 角色实体 */
|
/** 角色实体 */
|
||||||
@ecs.register(`Monster`)
|
@ecs.register(`Monster`)
|
||||||
export class Monster extends ecs.Entity {
|
export class Monster extends ecs.Entity {
|
||||||
HeroModel!: MonModelComp;
|
HeroModel!: MonModelComp;
|
||||||
HeroView!: HeroViewComp;
|
HeroView!: MonViewComp;
|
||||||
BattleMove!: BattleMoveComp;
|
BattleMove!: BattleMoveComp;
|
||||||
|
|
||||||
protected init() {
|
protected init() {
|
||||||
@@ -24,13 +25,15 @@ export class Monster extends ecs.Entity {
|
|||||||
BattleMoveComp,
|
BattleMoveComp,
|
||||||
MonModelComp,
|
MonModelComp,
|
||||||
TalComp,
|
TalComp,
|
||||||
|
EBusComp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.remove(HeroViewComp);
|
this.remove(MonViewComp);
|
||||||
this.remove(MonModelComp);
|
this.remove(MonModelComp);
|
||||||
this.remove(TalComp);
|
this.remove(TalComp);
|
||||||
|
this.remove(EBusComp);
|
||||||
super.destroy();
|
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) {
|
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
|
scale=-1
|
||||||
let box_group=BoxSet.MONSTER
|
let box_group=BoxSet.MONSTER
|
||||||
// console.log("mon load",uuid)
|
|
||||||
// this.addComponents<ecs.Comp>( MonModelComp, BattleMoveComp);
|
|
||||||
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;
|
||||||
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
||||||
@@ -49,7 +50,52 @@ export class Monster extends ecs.Entity {
|
|||||||
const collider = node.getComponent(BoxCollider2D);
|
const collider = node.getComponent(BoxCollider2D);
|
||||||
if (collider) collider.enabled = false; // 先禁用 // 延迟一帧启用碰撞体
|
if (collider) collider.enabled = false; // 先禁用 // 延迟一帧启用碰撞体
|
||||||
node.setPosition(pos)
|
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<hero.skills.length; i++){
|
||||||
|
let skill = {
|
||||||
|
uuid: SkillSet[hero.skills[i]].uuid,
|
||||||
|
cd_max: SkillSet[hero.skills[i]].cd,
|
||||||
|
cost: SkillSet[hero.skills[i]].cost,
|
||||||
|
cd: 0
|
||||||
|
};
|
||||||
|
model.skills.push(skill);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.add(view);
|
||||||
oops.message.dispatchEvent("monster_load",this)
|
oops.message.dispatchEvent("monster_load",this)
|
||||||
|
|
||||||
// 初始化移动参数
|
// 初始化移动参数
|
||||||
@@ -57,53 +103,29 @@ export class Monster extends ecs.Entity {
|
|||||||
move.direction = -1; // 向左移动
|
move.direction = -1; // 向左移动
|
||||||
move.targetX = -800; // 左边界
|
move.targetX = -800; // 左边界
|
||||||
smc.vmdata.mission_data.mon_num++
|
smc.vmdata.mission_data.mon_num++
|
||||||
// console.log("[Mon] mission_data.mon_num:",smc.vmdata.mission_data.mon_num)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hero_init(uuid:number=1001,node:Node,scale:number=1,box_group=BoxSet.HERO,lv:number=1,monType:MonType=MonType.NORMAL,buffs: BuffConf[] = [],is_call=false) {
|
|
||||||
var hv = node.getComponent(HeroViewComp)!;
|
|
||||||
|
|
||||||
|
reset() {
|
||||||
// console.log("hero_init",buff)
|
// 注: 自定义释放逻辑,视图层实现 ecs.IComp 接口的 ecs 组件需要手动释放
|
||||||
let hero= HeroInfo[uuid] // 共用英雄数据
|
super.destroy();
|
||||||
|
|
||||||
hv.scale = scale;
|
|
||||||
hv.fac = FacSet.MON;
|
|
||||||
hv.type = hero.type;
|
|
||||||
hv.is_boss = monType==MonType.BOSS;
|
|
||||||
if(!hv.is_boss){
|
|
||||||
hv.is_kalami=true
|
|
||||||
}
|
|
||||||
hv.box_group = box_group;
|
|
||||||
hv.hero_uuid= uuid;
|
|
||||||
hv.hero_name= hero.name;
|
|
||||||
const {hp,mp,ap,map,def,mdef}=getMonAttr(lv,uuid,monType)
|
|
||||||
// 初始化基础属性,并根据强度倍率调整
|
|
||||||
|
|
||||||
for(let i=0;i<hero.skills.length;i++){
|
|
||||||
let skill={ uuid:SkillSet[hero.skills[i]].uuid, cd_max:SkillSet[hero.skills[i]].cd,cost:SkillSet[hero.skills[i]].cost,cd:0 }
|
|
||||||
hv.skills.push(skill)
|
|
||||||
}
|
|
||||||
hv.base_ap=ap
|
|
||||||
hv.base_map=map
|
|
||||||
hv.base_def=def
|
|
||||||
hv.base_hp=hp
|
|
||||||
hv.base_mp=mp
|
|
||||||
hv.hp=hv.base_hp
|
|
||||||
hv.mp=hv.base_mp
|
|
||||||
hv.Attrs=getAttrs()
|
|
||||||
hv.NeAttrs=getNeAttrs()
|
|
||||||
hv.Attrs[Attrs.HP_MAX]=hv.base_hp
|
|
||||||
hv.Attrs[Attrs.MP_MAX]=hv.base_mp
|
|
||||||
hv.Attrs[Attrs.DEF]=hv.base_def
|
|
||||||
hv.Attrs[Attrs.AP]=hv.base_ap
|
|
||||||
hv.Attrs[Attrs.MAP]=hv.base_map
|
|
||||||
hv.Attrs[Attrs.SPEED]=hero.speed
|
|
||||||
hv.Attrs[Attrs.DIS]=hero.dis
|
|
||||||
// 初始化 buff/debuff 系统
|
|
||||||
hv.initAttrs();
|
|
||||||
|
|
||||||
this.add(hv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
export class MonLifecycleSystem extends ecs.ComblockSystem
|
||||||
|
implements ecs.IEntityEnterSystem, ecs.IEntityRemoveSystem {
|
||||||
|
|
||||||
|
filter() {
|
||||||
|
return ecs.allOf(MonModelComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityEnter(e: ecs.Entity): void {
|
||||||
|
// 英雄实体创建时的特殊处理
|
||||||
|
console.log(`怪物进入世界: ${e.get(MonModelComp).hero_name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityRemove(e: ecs.Entity): void {
|
||||||
|
// 英雄实体销毁时的清理工作
|
||||||
|
console.log(`怪物离开世界: ${e.get(MonModelComp).hero_name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,15 +5,200 @@
|
|||||||
* @LastEditTime: 2022-08-17 13:43:25
|
* @LastEditTime: 2022-08-17 13:43:25
|
||||||
*/
|
*/
|
||||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
|
import { Attrs, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs";
|
||||||
|
import { BuffConf } from "../common/config/SkillSet";
|
||||||
|
import { HeroInfo, AttrSet } from "../common/config/heroSet";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 角色属性数据
|
* 怪物属性数据模型 - 存储纯数据,不含表现逻辑
|
||||||
|
* 简化版本:只保留临时buff系统,移除持久型buff
|
||||||
|
*
|
||||||
|
* 注意:HeroModelComp 中有详细的 ECS 架构分析,迁移方案见那个文件
|
||||||
*/
|
*/
|
||||||
@ecs.register('MonModel')
|
@ecs.register('MonModel')
|
||||||
export class MonModelComp extends ecs.Comp {
|
export class MonModelComp extends ecs.Comp {
|
||||||
/** 角色编号 */
|
|
||||||
|
|
||||||
|
// ==================== 角色基础信息 ====================
|
||||||
|
hero_uuid: number = 1001;
|
||||||
|
hero_name: string = "monster";
|
||||||
|
lv: number = 1;
|
||||||
|
type: number = 0; // 0近战 1远程 2辅助
|
||||||
|
fac: number = 1; // 0:hero 1:monster(默认为怪物)
|
||||||
|
|
||||||
|
|
||||||
|
// ==================== 动态属性值 ====================
|
||||||
|
hp: number = 100; // 当前血量
|
||||||
|
mp: number = 100; // 当前魔法值
|
||||||
|
shield: number = 0; // 当前护盾
|
||||||
|
Attrs: any = []; // 最终属性数组(经过Buff计算后)
|
||||||
|
NeAttrs: any = []; // 负面状态数组
|
||||||
|
|
||||||
|
// ==================== Buff/Debuff 系统 ====================
|
||||||
|
/** 临时型buff数组 - 按时间自动过期(怪物只使用临时buff) */
|
||||||
|
BUFFS_TEMP: Record<number, Array<{value: number, BType: BType, remainTime: number}>> = {};
|
||||||
|
|
||||||
|
// ==================== 标记状态 ====================
|
||||||
|
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<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;
|
||||||
|
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<number>();
|
||||||
|
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() {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
29
assets/script/game/hero/MonViewComp.ts
Normal file
29
assets/script/game/hero/MonViewComp.ts
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
assets/script/game/hero/MonViewComp.ts.meta
Normal file
9
assets/script/game/hero/MonViewComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "413afaac-8d90-4810-8036-a5dae3f9eea8",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu
|
|||||||
import { ItalConf, TalType, TalEType, talConf } from "../common/config/TalSet";
|
import { ItalConf, TalType, TalEType, talConf } from "../common/config/TalSet";
|
||||||
import { BuffConf, SkillSet } from "../common/config/SkillSet";
|
import { BuffConf, SkillSet } from "../common/config/SkillSet";
|
||||||
import { HeroInfo } from "../common/config/heroSet";
|
import { HeroInfo } from "../common/config/heroSet";
|
||||||
|
import { HeroViewComp } from "./HeroViewComp";
|
||||||
|
import { SkillConComp } from "./SkillConComp";
|
||||||
|
|
||||||
const { ccclass } = _decorator;
|
const { ccclass } = _decorator;
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ interface TalEffect {
|
|||||||
*/
|
*/
|
||||||
@ccclass('TalComp')
|
@ccclass('TalComp')
|
||||||
@ecs.register('TalComp', false)
|
@ecs.register('TalComp', false)
|
||||||
export class TalComp extends CCComp {
|
export class TalComp extends ecs.Comp {
|
||||||
/** 英雄视图组件引用,运行时获取避免循环引用 */
|
/** 英雄视图组件引用,运行时获取避免循环引用 */
|
||||||
private heroView: any = null;
|
private heroView: any = null;
|
||||||
private skillCon:any=null;
|
private skillCon:any=null;
|
||||||
@@ -57,8 +59,8 @@ export class TalComp extends CCComp {
|
|||||||
*/
|
*/
|
||||||
start() {
|
start() {
|
||||||
// 运行时获取组件,避免编译时循环引用
|
// 运行时获取组件,避免编译时循环引用
|
||||||
this.heroView = this.node.getComponent("HeroViewComp" as any);
|
this.heroView = this.ent.get(HeroViewComp);
|
||||||
this.skillCon = this.node.getComponent("SkillConComp" as any);
|
this.skillCon = this.ent.get(SkillConComp);
|
||||||
if (this.heroView) {
|
if (this.heroView) {
|
||||||
this.heroUuid = this.heroView.hero_uuid;
|
this.heroUuid = this.heroView.hero_uuid;
|
||||||
this.initializeTalents();
|
this.initializeTalents();
|
||||||
@@ -165,6 +167,5 @@ export class TalComp extends CCComp {
|
|||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
this.node.destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user