定位:统一属性汇总、分层计算、属性变更分发、属性快照 分层原则(由底层到顶层,叠加计算)
基础属性 → 装备属性 → Buff属性 → 称号/勋章/坐骑 → 百分比加成 → 最终属性 区分【固定值add】和【百分比mul】,百分比是基于前面所有属性之和,不是原始基础
1. 常量定义(属性ID枚举)
ini
-- attr_def.lua
ATTR = {
HP = 1, -- 生命
MP = 2, -- 魔法
ATTACK = 3, -- 攻击
DEFENSE = 4, -- 防御
MAGIC = 5, -- 魔法
MAGIC_DEF = 6, -- 魔防
HIT = 7, -- 命中
DODGE = 8, -- 闪避
CRIT = 9, -- 暴击
CRIT_DAM = 10, -- 暴击伤害
MOVE_SPEED = 11,-- 移速
}
2. 错误码
ini
ERR.ATTR_INVALID = 13001
3. Proto 协议(属性同步)
ini
message AttrEntry {
int32 attr_id = 1;
int64 val = 2;
}
message SCAttrSync {
uint64 obj_uid = 1;
repeated AttrEntry attrs = 2;
}
4. AttrMgr 完整源码
lua
local AttrMgr = {}
AttrMgr.__index = AttrMgr
-- 分层key
AttrMgr.LAYER = {
BASE = 1, -- 角色基础(等级、转生)
EQUIP = 2, -- 装备、强化、洗练
BUFF = 3, -- Buff
TITLE = 4, -- 称号
MOUNT = 5, -- 坐骑
OTHER = 6, -- 其他活动、勋章
PERCENT = 99, -- 全局百分比层
}
function AttrMgr:New()
local obj = setmetatable({}, self)
obj.cache = {} -- obj_uid -> 最终属性缓存
obj.layer_data = {} -- obj_uid -> layer -> {attr_id=val}
obj.dirty = {} -- 脏标记,是否需要重算
return obj
end
-- 设置某一层属性
-- @param obj_uid 实体uid(玩家/怪物)
-- @param layer 分层
-- @param attr_id 属性id
-- @param val 增加值(add值,百分比单独存)
function AttrMgr:SetLayerAttr(obj_uid, layer, attr_id, val)
if not self.layer_data[obj_uid] then
self.layer_data[obj_uid] = {}
end
if not self.layer_data[obj_uid][layer] then
self.layer_data[obj_uid][layer] = {}
end
local old = self.layer_data[obj_uid][layer][attr_id] or 0
if old == val then
return
end
self.layer_data[obj_uid][layer][attr_id] = val
self.dirty[obj_uid] = true
end
-- 清除一层全部属性(buff过期、脱装备时调用)
function AttrMgr:ClearLayer(obj_uid, layer)
if not self.layer_data[obj_uid] or not self.layer_data[obj_uid][layer] then
return
end
self.layer_data[obj_uid][layer] = nil
self.dirty[obj_uid] = true
end
-- 核心:重新计算全部属性
function AttrMgr:Recalc(obj_uid)
if not self.dirty[obj_uid] then
return self.cache[obj_uid]
end
local layers = self.layer_data[obj_uid]
if not layers then
self.cache[obj_uid] = {}
self.dirty[obj_uid] = nil
return {}
end
local total = {}
local percent = {}
-- 先累加所有固定层
for layer, attr_map in pairs(layers) do
if layer ~= AttrMgr.LAYER.PERCENT then
for aid, v in pairs(attr_map) do
total[aid] = (total[aid] or 0) + v
end
end
end
-- 百分比叠加
local pct_map = layers[AttrMgr.LAYER.PERCENT] or {}
for aid, pct in pairs(pct_map) do
local base = total[aid] or 0
total[aid] = math.floor(base * (1 + pct / 10000)) -- 万分比 10000=100%
end
-- 缓存结果
self.cache[obj_uid] = total
self.dirty[obj_uid] = nil
return total
end
-- 对外接口:重新计算玩家属性 + 发包 + 抛事件
function AttrMgr:RecalcPlayerAttr(player)
local obj_uid = player.uid
local old_attr = self.cache[obj_uid] or {}
local new_attr = self:Recalc(obj_uid)
-- 对比差异,只同步变化属性
local diff = {}
for aid, v in pairs(new_attr) do
if (old_attr[aid] or 0) ~= v then
table.insert(diff, {attr_id = aid, val = v})
end
end
for aid, v in pairs(old_attr) do
if new_attr[aid] == nil then
table.insert(diff, {attr_id = aid, val = 0})
end
end
if #diff > 0 then
-- 发给自己
player:SendMsg(Proto.SCAttrSync, {obj_uid = obj_uid, attrs = diff})
-- AOI广播给周围玩家
AoiMgr:BroadCastWatcher(obj_uid, Proto.SCAttrSync, {obj_uid = obj_uid, attrs = diff})
-- 抛属性变更事件
EventMgr:Fire(EVENT.ATTR_CHANGE, player, old_attr, new_attr)
end
return new_attr
end
-- 获取单个属性
function AttrMgr:GetAttr(obj_uid, attr_id)
local attr = self:Recalc(obj_uid)
return attr[attr_id] or 0
end
-- 获取全部属性快照
function AttrMgr:GetAttrAll(obj_uid)
return self:Recalc(obj_uid)
end
-- 实体销毁,清理内存
function AttrMgr:CleanObj(obj_uid)
self.layer_data[obj_uid] = nil
self.cache[obj_uid] = nil
self.dirty[obj_uid] = nil
end
-- 单例
g_AttrMgr = AttrMgr:New()
5. 使用示例(各个模块调用)
lua
-- 1. 玩家升级,修改基础层
AttrMgr:SetLayerAttr(player.uid, AttrMgr.LAYER.BASE, ATTR.HP, 1000)
-- 2. 穿上装备,设置装备层
AttrMgr:SetLayerAttr(player.uid, AttrMgr.LAYER.EQUIP, ATTR.ATTACK, 50)
-- 脱装备:清空装备层
AttrMgr:ClearLayer(player.uid, AttrMgr.LAYER.EQUIP)
-- 3. 加buff,BuffMgr调用
AttrMgr:SetLayerAttr(player.uid, AttrMgr.LAYER.BUFF, ATTR.ATTACK, 20)
-- buff过期
AttrMgr:ClearLayer(player.uid, AttrMgr.LAYER.BUFF)
-- 4. 设置百分比加成(万分比,2000=20%)
AttrMgr:SetLayerAttr(player.uid, AttrMgr.LAYER.PERCENT, ATTR.ATTACK, 2000)
-- 5. 改动后触发重算、同步、事件
AttrMgr:RecalcPlayerAttr(player)
-- 6. 读取属性
local atk = AttrMgr:GetAttr(player.uid, ATTR.ATTACK)
6. 模块依赖 & 边界说明
- 依赖:
EventMgr(抛ATTR_CHANGE)、AoiMgr(AOI广播属性)、PlayerMgr、Proto - 不负责:属性校验、扣血、伤害计算(伤害交给 FightMgr)
- 缓存+脏标记:只有标记dirty才重算,避免每帧反复计算,性能友好
- 百分比是在固定值总和之后乘,传奇类标准算法
- 怪物也复用这套AttrMgr,怪物uid传入即可,一套代码同时支持玩家+怪物
7. 配套GM指令(查看属性)
lua
GMMgr:RegisterCmd("attr_query", 1, function(gm, args)
local uid = tonumber(args[1])
local player = PlayerMgr:GetPlayerByUid(uid)
if not player then return "玩家不存在" end
local all = AttrMgr:GetAttrAll(player.uid)
local str = ""
for k,v in pairs(all) do
str = str .. string.format("aid:%d=%d ",k,v)
end
return str
end)