CHUAN2 DEV ENGINE
996 正版授权研发中心 · 360 授权合作教学中心 · 抖音传奇直播合作授权 · 快手推广运营商授权
OFFICIAL LICENSED ACADEMY 查验官方授权证书 →
// 威海旷世互娱教学基地 · 技术文章
进阶实战游戏功能996引擎

Lua惯性滚动:速度衰减的缓停列表

2026-10-02 02:27 作者:996 技术组 996引擎Lua教程传奇脚本进阶实战游戏功能996引擎

【语法算法】

先抛一个坑:列表一松手就急刹,顺滑去哪了?拖的时候跟手,一松手指列表当场钉死——玩家嘴里不说,心里给这界面打了个不及格。惯性滚动补的就是松手之后的那口气:松手不是停,是带着最后一点速度继续滑,速度逐拍衰减,滑到劲道耗尽才稳稳站住——站的位置每一次都不一样,这就是惯性的签名。这篇把整套写法拆开:配置表、初速注入、衰减推进、边界回弹、定时器、调试按钮,一段一段照抄能跑。

一、效果演示:甩一下,滑多远停多稳

演示场二十行条目的长列表。点「甩一下」:列表获得一笔随机初速——条目呼啦啦往上滑,速度一拍一拍变小,滑动越来越慢,最后近乎蠕行,稳稳停在半路,停的位置每一次都不一样,这就是惯性的签名。点「到顶」「到底」直接回两端,边界处速度清零不越界——不越界是清单的规矩,弹回是另一种玩法。左上角实时播报偏移和速度——速度掉到个位数的那几拍,就是"缓停"的手感来源。演示里试两笔账:甩大力和甩小力的滑行距离——初速差一倍,滑程差得更多,衰减是按比例走的——比例的世界里,起点就是命。惯性滚动教的是余劲:停不停不由手指说了算,由最后的速度说了算——这一口余劲,就是拖拽和点按之间最大的手感差。

mermaid
flowchart TD
A[松手 读末段速度] --> B[偏移加速度推进]
B --> C[速度乘衰减系数]
C --> D{速度近乎零}
D -- 否 --> E{越过边界}
E -- 是 --> F[贴边 速度清零]
E -- 否 --> B
F --> B
D -- 是 --> G[缓停 定住]
demo
 fx-inertiascroll

二、底层原理:一笔初速注入加一阶速度衰减

模块的机关是一对搭档。初速注入:松手的那一拍把拖拽末段的位移折算成速度——甩得越狠初速越大,这一笔是惯性的源头——初速为零的版本松手即停,那不叫惯性叫急刹。速度衰减:每一拍速度乘一个小于一的系数——衰减按比例走,速度大掉得快、速度小掉得慢,滑行自然收出一条缓停的曲线——这条曲线玩家未必说得出名字,但手感骗不了人。和分页翻屏的分野在"惯性把停的时机交给物理":翻屏是手指抬起点一下跳一页,惯性是松手后世界自己往前滑——同样是从 A 到 B,一个听指挥,一个有性子。

衰减为什么用指数不用匀减?匀减的滑行是匀减速直线运动,停得很突兀;指数衰减永远走剩余速度的比例,最后一段是温柔地蠕行——缓停的"缓"字就写在指数的曲线上——前段大步流星,后段寸寸挪动,同一个系数管出两种步态。

三、核心代码:完整模块(上·骨架)

lua
-- @file InertiaScroll.lua
-- 惯性滚动 —— 速度衰减的缓停列表

local InertiaScroll = {}

local CONST = {
    VIEW_HEIGHT   = 240,
    ROW_HEIGHT    = 40,
    ROW_COUNT     = 20,
    FLING_SPEED   = 600,
    DECAY         = 0.12,
    STOP_SPEED    = 1,

    COLOR_ROW_A   = "#1c2128",
    COLOR_ROW_B   = "#21262d",

    AUTOINC_BASE  = 1150000,
}

local _offset     = 0
local _velocity   = 0
local _scheduler  = nil
local _autoInc    = 0

local function GenID(prefix)
    _autoInc = _autoInc + 1
    return string.format("%s_%d", prefix or "INS",
        CONST.AUTOINC_BASE + _autoInc)
end

local function ShowTip(msg)
    if msg and msg ~= "" then SL:ShowSystemTips(msg) end
end

local function MaxOffset()
    return CONST.ROW_COUNT * CONST.ROW_HEIGHT
        - CONST.VIEW_HEIGHT
end

function InertiaScroll.Offset()
    return _offset
end

function InertiaScroll.Fling(vel)
    _velocity = vel or CONST.FLING_SPEED
    ShowTip("甩出去了——看速度怎么衰减")
end

四、核心代码:完整模块(下·衰减推进与边界回弹)

lua
-- 衰减推进与边界回弹:每一拍按速度滑
if not _scheduler then
    _scheduler = SL:Schedule(function(dt)
        if math.abs(_velocity) <= CONST.STOP_SPEED then
            return
        end
        _offset = _offset + _velocity * dt
        _velocity = _velocity
            * math.pow(CONST.DECAY, dt)
        local maxOff = MaxOffset()
        if _offset < 0 then
            _offset = 0
            _velocity = 0
        elseif _offset > maxOff then
            _offset = maxOff
            _velocity = 0
        end
    end, 0.02)
end

SL:ScheduleOnce(function()
    SL:BindDebugButton("甩一下", function()
        InertiaScroll.Fling(
            -(600 + math.random() * 500))
    end)
    ShowTip("技能已加载: 惯性滚动 (调试按钮触发)")
end, 1.0)

function InertiaScroll.Unload()
    if _scheduler then SL:Unschedule(_scheduler) end
    _offset = 0
    _velocity = 0
end

return InertiaScroll

五、机制问答

问:衰减系数越小越跟手吗?
答:反过来——系数越小速度掉得越快,滑程越短,跟手但不顺滑;要顺滑就把系数放大,代价是收得慢。

问:速度小到多少算停?
答:阈值定一——低于一的速度每拍挪不了半个像素,继续算只会白耗拍子,到阈值直接归零定住。

问:边界处为什么要清速度?
答:不清会抖——顶到底还带着速度,下一拍又往回算,画面在边上嗡嗡震,清零是边界唯一的礼貌。

问:惯性滚动的速度从哪来?
答:松手前最后几拍的位移折算——拖拽模块把末段位移除以时间喂给惯性,两段要共用一个坐标系。

问:滚动的条目要全画吗?
答:不用——只画视口内首尾几行,滚出视口的跳过,惯性感来自偏移的推进,不来自画了多少行。

六、调参与实战怎么用

第四笔是衰减的档位:零点一二的系数是"甩一下滑半屏"的手感——系数太大滑不到头就停像断电,太小一甩滑出三条街收不住,档位要拿最常见的甩动力度校。第三笔是初速的封顶:甩得再狠也六百起——不封顶的版本狂甩一次飞到表尾,初速封顶保住"滑得远但不离谱"。第二笔是停阈的位置:一是"半像素都懒得挪"的收笔线——阈值调大停得干脆但末端发顿,调小尾段绵长,一是两头的平衡位。第一笔是边界的清零:越界即贴边清速是唯一的边界礼节——弹回效果的版本另算,不弹回的清单清零是最稳的收法。常见坑三个:衰减按帧算的帧率依赖;边界清零漏了负方向;滚动偏移喂给渲染前没取整导致条目边缘发虚。

实战里这套衰减不止滚列表:轮盘的转停、摇杆的回中、抽屉的滑收,全是"初速加衰减"同一副骨架。组里的约定是:凡是松手后还要自己动一段的界面,一律走惯性不走瞬移——顺滑是白捡的体面,玩家未必夸,但急刹一定被骂。这篇的模块照抄能跑,改的就是 CONST 那张表。

写完留一句给做手感系的同学:惯性滚动卖的是"松手之后的余韵"——那一段越滑越慢的蠕行和速度归零时的稳稳站住,是把急刹的生硬折成缓停的一口气。

作者履历与出处

本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。

← 返回文章地图返回研学路径

幂尔框架 · 实战干货 · 接口调用

LATEST ARTICLES

全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →

进阶实战游戏功能

Lua自动炮台:架一个自动索敌开火

【语法算法】 上一版的炮台索敌逻辑有个隐蔽报错:炮台永远打同一个怪——即使那只怪已经死了,索敌的游标也不挪窝。追到底索敌的游…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua元素转换:火伤转冰伤附带减速

【语法算法】 先抛一个坑:打不完的火系怪怎么办?火抗怪火打不动,换冰系技能要切装备要换面板——切完黄花菜都凉了。元素转换的答…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua连锁引燃:一个着火引燃下一个

【语法算法】 上个月的事故复盘会上有个数字被念了三遍:四成——策划写的是"同伴陪疼四成",代码落下去成了"陪疼四十点",两只…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua弹射地垫:踩上去弹飞到空中

【语法算法】 单行代码拆解:弹射初速=-420——弹射的全部动力就这一行的负初速。负号朝上、四百二十是弹射的初速大小——踩上…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua时间减缓:流速减半的局部时差

【语法算法】 上一版的减速类模块全按"乘以零点五"来写,帧率无关的版本照搬了这套写法——结果高帧率机上减速效果好,低帧率机上…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua引力球:丢出去把怪吸过来

【语法算法】 先抛一个坑:怎么把散在四处的怪聚到一起打?逐个拉是笨办法,一个范围技又只能打一片——引力球的答案是一个会动的吸…

2026-10-02 04:50 996 技术组