refactor(buff系统): 重构英雄的buff管理逻辑,支持多次叠加和临时效果

- 统一管理持久型和临时型buff,简化buff的添加和移除逻辑
- 更新buff的叠加规则,允许同一属性的多个buff实例共存
- 优化属性计算公式,确保所有buff在计算时被纳入考虑
- 新增清空buff和移除特定buff的辅助方法,提升管理灵活性
- 详细更新文档,提供API使用示例和数据结构说明
This commit is contained in:
2025-10-25 15:29:25 +08:00
parent 11f1f08c1d
commit e62eecd214

View File

@@ -18,17 +18,50 @@ const { ccclass, property } = _decorator;
/** /**
* ==================== BUFF 系统使用说明 ==================== * ==================== BUFF 系统使用说明 ====================
* *
* 1. 系统架构 * 1. 系统架构 - 支持多次叠加(简化设计)
* - BUFF_V/BUFFS_V: 数值型 buff持临时 * - BUFFS: 持久型buff数组持久存在直到主动移除
* - BUFF_R/BUFFS_R: 百分比型 buff持临时 * - BUFFS_TEMP: 临时型buff数组持续指定时间时间到自动移除
* - DBUFF_V/DBUFFS_V: 数值型 debuff持临时 * - 每个buff实例内包含BType字段区分数值型(VALUE)和百分比型(RATIO)
* - DBUFF_R/DBUFFS_R: 百分比型 debuff持临时
* *
* 2. 智能覆盖规则 * 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 {
@@ -73,13 +106,15 @@ export class HeroViewComp extends CCComp {
base_dis: number = 100; base_dis: number = 100;
Attrs:any=[] Attrs:any=[]
NeAttrs:any=[] NeAttrs:any=[]
// Buff debuff 统一管理, value是正,debuff 是负数 // Buff debuff 统一管理
// 结构: { [attrIndex: number]: { value: number, remainTime?: number } } // 每个buff实例内包含BType用于区分数值型(VALUE)和百分比型(RATIO)
BUFF_V: Record<number, {value: number}> = {} // 持久型数buff // [attrIndex: number]: Array<BuffInstance> - 允许多个buff实例独立存在
BUFF_R: Record<number, {value: number}> = {} // 持久型百分比 buff
BUFFS_V: Record<number, {value: number, remainTime: number}> = {} // 临时型数buff /** 持久型buff数组 - 不会自动过期 */
BUFFS_R: Record<number, {value: number, remainTime: number}> = {} // 临时型百分比 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; atk_count: number = 0;
atked_count: number = 0; atked_count: number = 0;
@@ -127,13 +162,9 @@ export class HeroViewComp extends CCComp {
*/ */
initAttrs() { initAttrs() {
// 清空现有 buff/debuff // 清空现有 buff/debuff
this.BUFF_V = { this.BUFFS = {
}; };
this.BUFFS_V = { this.BUFFS_TEMP = {
};
this.BUFF_R = {
};
this.BUFFS_R = {
}; };
// 获取英雄配置 // 获取英雄配置
const heroInfo = HeroInfo[this.hero_uuid]; const heroInfo = HeroInfo[this.hero_uuid];
@@ -174,63 +205,38 @@ export class HeroViewComp extends CCComp {
} }
// ==================== BUFF管理 ==================== // ==================== BUFF管理 ====================
/** /**
* 添加 buff 效果(智能覆盖 * 添加 buff 效果(支持多次叠加
* @param buffConf buff 配置 (来自 SkillSet.BuffConf heroSet.buff) * @param buffConf buff 配置 (来自 SkillSet.BuffConf heroSet.buff)
* *
* 智能覆盖规则 * 叠加规则
* 1. 值更小:不添 * 1. 持久型buff直接添加到数组中与现有buff独立存在
* 2. 值相同且都是临时:叠加时 * 2. 临时型buff直接添加到数组中保持独立的剩余时间计算
* 3. 值更大:更新为新值(临时则更新值和时间 * 3. 所有buff实例在属性计算时都被纳入求和计算
*/ */
addBuff(buffConf: BuffConf) { addBuff(buffConf: BuffConf) {
const isValue = buffConf.BType === BType.VALUE;
const isPermanent = buffConf.time === 0; const isPermanent = buffConf.time === 0;
const attrIndex = buffConf.buff; const attrIndex = buffConf.buff;
// 根据类型选择对应buff 字典
const permanentBuffs = isValue ? this.BUFF_V : this.BUFF_R;
const temporaryBuffs = isValue ? this.BUFFS_V : this.BUFFS_R;
if (isPermanent) { if (isPermanent) {
// 添加持久buff // 添加持久buff到BUFFS - 直接追加到数组
const existing = permanentBuffs[attrIndex]; if (!this.BUFFS[attrIndex]) {
if (existing) { this.BUFFS[attrIndex] = [];
// 值更小,不添
if (buffConf.value <= existing.value) {
return;
} }
// 值更大,更新 this.BUFFS[attrIndex].push({ value: buffConf.value, BType: buffConf.BType });
existing.value = buffConf.value;
} else { } else {
// 没有同类型,直接添加 // 添加临时buff到BUFFS_TEMP - 直接追加到数组
permanentBuffs[attrIndex] = { value: buffConf.value }; if (!this.BUFFS_TEMP[attrIndex]) {
this.BUFFS_TEMP[attrIndex] = [];
} }
} else { this.BUFFS_TEMP[attrIndex].push({
// 添加临时buff
const existing = temporaryBuffs[attrIndex];
if (existing) {
if (buffConf.value < existing.value) {
// 值更小,不添
return;
} else if (buffConf.value === existing.value) {
// 值相同,叠加时间
existing.remainTime += buffConf.time;
return; // 时间叠加不需要重算属
} else {
// 值更大,更新值和时间
existing.value = buffConf.value;
existing.remainTime = buffConf.time;
}
} else {
// 没有同类型,直接添加
temporaryBuffs[attrIndex] = {
value: buffConf.value, value: buffConf.value,
BType: buffConf.BType,
remainTime: buffConf.time remainTime: buffConf.time
}; });
}
} }
this.recalculateSingleAttr(buffConf.buff); // 重新计算受影响的属性
this.recalculateSingleAttr(attrIndex);
} }
@@ -240,8 +246,8 @@ export class HeroViewComp extends CCComp {
* @param attrIndex 属性索引 * @param attrIndex 属性索引
* *
* 计算公式: * 计算公式:
* - 数值型属性:最终值 = (基础值 + 数值型buff - 数值型debuff) × (1 + 百分比buff/100 - 百分比debuff/100) * - 数值型属性:最终值 = (基础值 + 所有数值型buff之和 - 所有数值型debuff之和) × (1 + 所有百分比buff之和/100 - 所有百分比debuff之和/100)
* - 百分比型属性:最终值 = 基础值 + 数值型buff - 数值型debuff + 百分比buff - 百分比debuff * - 百分比型属性:最终值 = 基础值 + 所有数值型buff之和 - 所有数值型debuff之和 + 所有百分比buff之和 - 所有百分比debuff之和
*/ */
private recalculateSingleAttr(attrIndex: number) { private recalculateSingleAttr(attrIndex: number) {
// 1. 获取基础值 // 1. 获取基础值
@@ -257,29 +263,45 @@ export class HeroViewComp extends CCComp {
const baseVal = baseValues[attrIndex] !== undefined ? baseValues[attrIndex] : 0; const baseVal = baseValues[attrIndex] !== undefined ? baseValues[attrIndex] : 0;
// 2. 收集所有数值型 buff/debuff // 2. 收集所有数值型 buff/debuff - 遍历所有buff数组并按BType筛选求和
let totalValue = baseVal; let totalValue = baseVal;
// Buff直接使用 Attrs 索引 // 遍历持久buff数组
if (this.BUFF_V[attrIndex]) { if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) {
totalValue += this.BUFF_V[attrIndex].value; 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;
}
} }
if (this.BUFFS_V[attrIndex]) {
totalValue += this.BUFFS_V[attrIndex].value;
} }
// 3. 收集所有百分比型 buff/debuff - 遍历所有buff数组并按BType筛选求和
// 3. 收集所有百分比型 buff/debuff
let totalRatio = 0; // 总百分比(可正可负) let totalRatio = 0; // 总百分比(可正可负)
// Buff直接使用 Attrs 索引 // 遍历持久buff数组
if (this.BUFF_R[attrIndex]) { if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) {
totalRatio += this.BUFF_R[attrIndex].value; 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;
}
} }
if (this.BUFFS_R[attrIndex]) {
totalRatio += this.BUFFS_R[attrIndex].value;
} }
// 4. 根据属性类型计算最终值 // 4. 根据属性类型计算最终值
const attrType = AttrsType[attrIndex]; const attrType = AttrsType[attrIndex];
@@ -327,20 +349,38 @@ export class HeroViewComp extends CCComp {
updateTemporaryBuffsDebuffs(dt: number) { updateTemporaryBuffsDebuffs(dt: number) {
const affectedAttrs = new Set<number>(); const affectedAttrs = new Set<number>();
// 更新临时型数buff // 更新临时型数buff
for (const attrIndex in this.BUFFS_V) { for (const attrIndex in this.BUFFS_TEMP) {
const buff = this.BUFFS_V[attrIndex]; const buffs = this.BUFFS_TEMP[attrIndex];
buffs.forEach(buff => {
buff.remainTime -= dt; buff.remainTime -= dt;
if (buff.remainTime <= 0) { if (buff.remainTime <= 0) {
delete this.BUFFS_V[attrIndex]; // 从数组中移除
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)); affectedAttrs.add(parseInt(attrIndex));
} }
} }
// 更新临时型百分比 buff // 更新临时型百分比 buff
for (const attrIndex in this.BUFFS_R) { for (const attrIndex in this.BUFFS_TEMP) {
const buff = this.BUFFS_R[attrIndex]; const buffs = this.BUFFS_TEMP[attrIndex];
buffs.forEach(buff => {
buff.remainTime -= dt; buff.remainTime -= dt;
if (buff.remainTime <= 0) { if (buff.remainTime <= 0) {
delete this.BUFFS_R[attrIndex]; // 从数组中移除
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)); affectedAttrs.add(parseInt(attrIndex));
} }
} }
@@ -360,6 +400,74 @@ export class HeroViewComp extends CCComp {
}); });
} }
// ==================== BUFF 辅助方法 ====================
/**
* 移除特定属性的某个buff实例
* @param attrIndex 属性索引
* @param value buff效果值
* @param isTemporary 是否为临时bufftrue=临时false=持久)
* @return 是否移除成功
*/
removeBuff(attrIndex: number, value: number, isTemporary: boolean = true): boolean {
const buffDict = isTemporary ? this.BUFFS_TEMP : this.BUFFS;
if (!buffDict[attrIndex] || buffDict[attrIndex].length === 0) {
return false;
}
const buffs = buffDict[attrIndex];
const index = buffs.findIndex(buff => buff.value === value);
if (index > -1) {
buffs.splice(index, 1);
// 如果该属性所有buff都被移除删除该属性键
if (buffs.length === 0) {
delete buffDict[attrIndex];
}
// 重新计算属性
this.recalculateSingleAttr(attrIndex);
return true;
}
return false;
}
/**
* 清空某个属性的所有buff
* @param attrIndex 属性索引
* @param clearTemporaryOnly 是否仅清空临时bufftrue=仅临时false=全部)
*/
clearBuffs(attrIndex: number, clearTemporaryOnly: boolean = false): void {
if (!clearTemporaryOnly) {
delete this.BUFFS[attrIndex];
}
delete this.BUFFS_TEMP[attrIndex];
this.recalculateSingleAttr(attrIndex);
}
// ==================== NeAttrs负面状态管理 ====================
/**
* 清理单个NeAttr负面状态
* @param neAttrIndex NeAttrs索引如NeAttrs.IN_STUN、NeAttrs.IN_FROST等
* 清理即将该状态的value和time都设为0
*/
clearNeAttr(neAttrIndex: number): void {
if (this.NeAttrs[neAttrIndex]) {
this.NeAttrs[neAttrIndex].value = 0;
this.NeAttrs[neAttrIndex].time = 0;
}
}
/**
* 清理所有NeAttrs负面状态
* 清理即将所有状态的value和time都设为0
*/
clearAllNeAttrs(): void {
for (const key in this.NeAttrs) {
this.NeAttrs[key].value = 0;
this.NeAttrs[key].time = 0;
}
}
public isStun() { public isStun() {
return this.NeAttrs[NeAttrs.IN_STUN].time > 0; return this.NeAttrs[NeAttrs.IN_STUN].time > 0;
} }