世界首领战里玩家最关心的是伤害前十名:榜单要求随时插入、随时能按序取出,每来一条伤害记录都重新排序一次太浪费。二叉堆只保证堆顶有序,插入与替换的开销都是对数级,配合固定容量的"只留前十"策略,千人格斗也能稳定刷新榜单。本文给出服务端 Lua 的堆实现,以及首领战伤害榜的接入写法。
小顶堆配合容量上限是取前十的常用做法:堆里最多留十条,新纪录比堆顶还小就直接丢弃,否则替换堆顶后下沉。核心只有上浮、下沉两个函数,代码如下:
-- 小顶堆:容量固定,只保留最大的十条记录
local heap = {n = 0}
local function siftUp(h, i)
while i > 1 do
local p = math.floor(i / 2)
if h[p].dmg <= h[i].dmg then break end
h[p], h[i] = h[i], h[p]
i = p
end
end
local function siftDown(h, i)
while i * 2 <= h.n do
local c = i * 2
if c + 1 <= h.n and h[c + 1].dmg < h[c].dmg then c = c + 1 end
if h[i].dmg <= h[c].dmg then break end
h[i], h[c] = h[c], h[i]
i = c
end
end
首领受到攻击时把伤害累加到攻击者名下,再走一次"进堆或丢弃"的判定;广播用定时器按周期拉取堆内数据,排序后一次性发出,不需要每刀都推送。示例代码如下:
-- 伤害入堆:超过容量先和堆顶比较
local function pushDamage(name, dmg)
local item = {name = name, dmg = dmg}
if heap.n < 10 then
heap.n = heap.n + 1
heap[heap.n] = item
siftUp(heap, heap.n)
elseif dmg > heap[1].dmg then
heap[1] = item
siftDown(heap, 1)
end
end
-- QF触发: killmon 首领倒下后广播榜单
local player = class(actor)
local snap = {}
for i = 1, heap.n do snap[i] = heap[i] end
table.sort(snap, function(a, b) return a.dmg > b.dmg end)
local lines = {}
for i = 1, #snap do
lines[#lines + 1] = snap[i].name .. ":" .. snap[i].dmg
end
sendmsg(player, 1, 0, table.concat(lines, " "))
先写十组随机数据做单元对照:与 table.sort 的结果逐一比对名次,容量边界各跑一遍,第十一条、第十二条数据进来时确认堆容量恒为十。线上监控首领战期间的内存曲线与榜单刷新耗时,若刷新耗时随人数增长明显,通常是伤害累加表没有按首领分桶,全服记录写进了同一张表。
有次榜单把已离队玩家的旧纪录也显示出来,追查发现伤害累加表按名字做键,玩家改名将导致同一人占两条纪录。改为进本时记录角色编号,榜单展示时再取当前名字,改名不再影响名次。另一处是堆比较函数里写了取反逻辑,小顶堆被写成了大顶堆,前十名变成了前十名开外,上线前的对照测试把这类问题挡在了测试环境,代价只是一次冒烟。
堆结构在整场战斗内复用,首领重置时只清计数不清结构,避免反复建表带来的内存抖动;多首领并行时按首领编号各建一份,禁止共用,防止榜单串场。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
实战应用:用在哪里 装备回收商 NPC 每天按评分收购玩家的装备,裁决之杖、骨玉权杖按攻击、持久加权打分,榜上只要前五名。数…
实战应用:用在哪里 沙巴克攻城是全服行会战的最高舞台:攻方砸开城门、抢占皇宫,守方依托城墙与复活点反打。规则设计的关键是节奏…
实战应用:用在哪里 影分身玩法给法师职业补上生存短板:危急时刻召唤四个影分身分担仇恨,敌人需要在六格范围内分辨真身,猜错就浪…
实战应用:用在哪里 组队邀请、行会宣战这类关键信令丢了没有兜底,玩家只能反复手点,高峰期体验很差。重试机制给信令加确认回执:…
实战应用:用在哪里 新资料片上线首日预约量常是日常十倍,一口气全开会让数据库与登录网关同时过载。滚动开区把开区动作排成队列:…
实战应用:用在哪里 百人同屏的沙巴克外围战场,每个玩家每秒跑动会触发数次位置更新,全部即时下发时广播量随人数平方增长,网关先…