【游戏功能】
上个月一笔充值的到账被投诉了:钱到账的瞬间数字从四位数直接跳成七位数——玩家截图发群里说"到账异常,肯定是补丁"。钱一分没少,跳变的样子太吓人——账实相符的到账也会被当成事故,呈现的粗糙是要还账的。数字滚动就是把跳变改成翻牌:旧值到新值之间加一段缓动,数字像计价器一样滚上去,滚到哪算哪,滚完稳稳站住,站住的数字才算数。这篇把整套写法拆开:配置表、起止记账、缓动插值、上屏输出、定时器、调试按钮,一段一段照抄能跑。
一、效果演示:旧值到新值的缓动翻牌
演示场一块数字面板。点「加一万」:数字从当前值滚向一万——起步快、收尾慢,中途还带一点缩放的呼吸;滚完停在整数位上,稳稳的——落定的那一下还有一声轻响,账目到齐的仪式感就差这一声。点「加一百万」:跨度的滚法更明显——前半段数字翻得飞快,后半段一格一格落定。点「随机跳」:跳到哪算哪,滚动的弧线永远是从旧值滑向新值的一条减速线——弧线不挑起点,跳多少都能接住。演示里试两笔账:跳变上屏和滚动的上屏——钱数一样,吓人的程度天差地别。数字滚动教的是过渡:数值会跳,呈现不能跳——账目上的突变是严谨,屏幕上的突变是惊吓,两回事要分开待。
flowchart TD
A[记下旧值和新值] --> B[计时从零到一]
B --> C[缓动曲线折算进度]
C --> D[显示值 = 旧值加差值乘进度]
D --> E{计满了吗}
E -- 否 --> B
E -- 是 --> F[显示值钉死新值]
fx-numroll
二、底层原理:一次起止记账加一阶缓动插值
模块的机关是一对搭档。起止记账:滚动发起的那一拍把旧值和新值都记下——滚动的原料是两个数和一段时长,账本三行写完。缓动插值:计时的进度过一遍缓动曲线再折算显示值——起步快收尾慢,数字的滚动自带减速的体面。和直接赋值的分野在"滚动的数字有过程":直接赋值是结果没有过程,滚动把变化的过程演给玩家看——到账、结算、计分,凡是金额类的跳变,有过程就不吓人——玩家盯着数字滚完的那一秒,信任感就是这时候攒下的——数字会说话,滚着说比跳着说中听。
缓动为什么选先快后慢不选匀速?匀速的滚动像秒表走字,机械;先快后慢符合"大数来得快、零头慢慢对"的直觉,收尾的那几格慢动作是滚动感的灵魂——前段翻多快都行,最后几格一定要让人看清落定在哪。
三、核心代码:完整模块(上·骨架)
-- @file NumberRoll.lua
-- 数字滚动 —— 旧值到新值的缓动翻牌
local NumberRoll = {}
local CONST = {
ROLL_LIFE = 1.0,
PULSE_SCALE = 0.06,
COLOR_NUM = "#c9d1d9",
COLOR_PANEL = "#161b22",
ZORDER_ROLL = 150,
AUTOINC_BASE = 1152000,
}
local _shown = 0
local _from = 0
local _to = 0
local _rollT = 0
local _rolling = false
local _label = nil
local _scheduler = nil
local _autoInc = 0
local function GenID(prefix)
_autoInc = _autoInc + 1
return string.format("%s_%d", prefix or "NRL",
CONST.AUTOINC_BASE + _autoInc)
end
local function ShowTip(msg)
if msg and msg ~= "" then SL:ShowSystemTips(msg) end
end
local function EaseOutCubic(k)
return 1 - math.pow(1 - k, 3)
end
function NumberRoll.Shown()
return _shown
end
四、核心代码:完整模块(下·滚动发起与上屏输出)
-- 滚动发起:记旧记新,开滚
function NumberRoll.RollTo(target)
_from = _shown
_to = target
_rollT = 0
_rolling = true
end
-- 上屏输出:缓动折算的显示值逐拍刷新
if not _scheduler then
_scheduler = SL:Schedule(function(dt)
if not _rolling then return end
_rollT = _rollT + dt
local k = math.min(1, _rollT / CONST.ROLL_LIFE)
_shown = math.floor(
_from + (_to - _from) * EaseOutCubic(k))
if _label and not GUI:Widget_IsNull(_label) then
GUI:setText(_label,
tostring(string.format("%d", _shown)))
end
if k >= 1 then
_rolling = false
_shown = _to
ShowTip("滚完——稳稳停在 " .. _to)
end
end, 0.02)
end
function NumberRoll.Bind(label)
SafeRemove(label)
_label = label
end
local function SafeRemove(node)
if node and not GUI:Widget_IsNull(node) then
GUI:removeFromParent(node)
end
end
SL:ScheduleOnce(function()
NumberRoll.Bind(GUI:GetCashLabel())
ShowTip("技能已加载: 数字滚动 (调试按钮触发)")
end, 1.0)
function NumberRoll.Unload()
if _scheduler then SL:Unschedule(_scheduler) end
SafeRemove(_label)
_rolling = false
end
return NumberRoll
五、机制问答
问:滚动到一半又来一笔新账怎么办?
答:从当前显示值续滚——旧值改成此刻的显示值,新值照换,滚动不断档也不回跳。
问:旧值和新值一样还滚吗?
答:不滚——零差值的滚动是白耗拍子,起止记账时顺手判一下,相等直接钉死。
问:负数和缩水的账能滚吗?
答:能——缓动插值不挑方向,缩小照样走曲线,扣钱的滚动同样体面。
问:滚动的时长为什么定一秒?
答:一秒是"看得清又不等得烦"的折中——跨度大的账可以按比例放宽,但别超过两秒,等数字滚完的人会烦。
问:缩放的呼吸是给数字加的吗?
答:是——滚动的过程中面板轻轻一缩一放,收尾时归位,呼吸只在滚动期有,站定的数字不喘。
六、调参与实战怎么用
第四笔是续滚的口径:从显示值续滚是连账的规矩——从旧值续滚的版本两笔账会跳变一次,口径定在显示值,连着到账也顺滑。第三笔是时长的档位:一秒的底账随跨度浮动——跨度小的账滚太快看不清过程,跨度大的账滚太久惹人等,按时长封顶加跨度分档是折中。第二笔是缓动的选择:先快后慢的三次曲线是金额的默认——计数类用先慢后快也有张力,但金额先快后慢最像计价器。第一笔是站定的钉死:滚完那一拍显示值必须钉死新值——浮点误差会让最后一位差一,钉死是账面的清白。常见坑三个:滚动到一半的续账没有改起点导致回跳;负数账的取整方向写错少滚一位;缩放呼吸的锚点没设中心向左上放大。
实战里数字滚动是金额类界面的通用件:到账、结算、经验条读数、拍卖出价,全是"旧值滚向新值"同一副骨架。组里的约定是:凡是一千以上的金额跳变一律滚动,小额直接赋值——大账要体面,小账要利索,体面和利索各管一段。这篇的模块照抄能跑,改的就是 CONST 那张表。
写完留一句给做账面系的同学:数字滚动卖的是"变化要有过程"——那一段先快后慢的翻牌和稳稳落定的最后一格,是把吓人的跳变折成体面的过场的一台计价器。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【语法算法】 上一版的炮台索敌逻辑有个隐蔽报错:炮台永远打同一个怪——即使那只怪已经死了,索敌的游标也不挪窝。追到底索敌的游…
【语法算法】 先抛一个坑:打不完的火系怪怎么办?火抗怪火打不动,换冰系技能要切装备要换面板——切完黄花菜都凉了。元素转换的答…
【语法算法】 上个月的事故复盘会上有个数字被念了三遍:四成——策划写的是"同伴陪疼四成",代码落下去成了"陪疼四十点",两只…
【语法算法】 单行代码拆解:弹射初速=-420——弹射的全部动力就这一行的负初速。负号朝上、四百二十是弹射的初速大小——踩上…
【语法算法】 上一版的减速类模块全按"乘以零点五"来写,帧率无关的版本照搬了这套写法——结果高帧率机上减速效果好,低帧率机上…
【语法算法】 先抛一个坑:怎么把散在四处的怪聚到一起打?逐个拉是笨办法,一个范围技又只能打一片——引力球的答案是一个会动的吸…