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

Lua利萨如曲线:双频振动的参数轨迹

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

【语法算法】

先抛一个坑:两个不同频率的往复振动,画在同一根横轴和同一根纵轴上,会走出什么图形?答案是利萨如曲线——频率比不同图形完全不同,整数比闭合,非整数比永不重复。示波器、老科幻片的装饰纹样、舞台灯光里全是它的身影。这篇把利萨如曲线整套写法拆开:参数方程、频率调节、轨迹收放、调试按钮,一段一段照抄能跑。

一、效果演示

演示场一支光笔在画布上走出利萨如轨迹。点「频率A+1」或「频率B+1」改变频率比——椭圆变八字、八字变花形,轨迹颜色随频率比渐变。当前频率比实时显示在角落,图形走满一个整周期自动收笔重来。

mermaid
flowchart TD
A[参数从零走到整周期] --> B[横坐标按横频振动]
B --> C[纵坐标按纵频振动]
C --> D[两点连成轨迹]
D --> E[画布描线]
E --> F{参数到头了吗}
F -- 否 --> A
F -- 是 --> G[闭合收笔]
demo
 fx-lissajous

二、底层原理

利萨如的机关是一对参数方程——横坐标按一个频率振动,纵坐标按另一个频率振动,参数匀速推进,笔尖的轨迹就是两股振动的合成。频率比决定图形的瓣数:一比一是椭圆或斜线,一比二是八字,二比三是双环花。相位差决定图形的倾斜——零相位差退化成直线,九十度相位差在一比一时变成正圆。
实现上注意采样的步长——步子太大轨迹折线感明显,步子太小画不完一圈就掉帧,按画布尺寸取几百到一千步最顺手。坐标换算只做一次缩放平移:把负一到正一的振动区间映射到画布中央。收尾的判断看参数是否走满整圈——整数比走满公倍周期即闭合。

这套机制的代码量不到八十行,难的不是方程,而是频率比和步长的配合——比选得美、步取得细,图形才立得住。

图形的瓣数可以直接从频率比推——横纵频率约成最简整数比之后,分子加分母的和就是瓣数,数学先算一步,美术就不用逐个试。绘制时把参数方程的采样点预计算成数组,每一帧只负责把数组里的点连成线,三角函数的调用集中在预计算那一次,渲染的负担轻到可以忽略——这是表现层代码省性能的标准手法:算一次,画多次。

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

lua
-- @file Lissajous.lua
-- 利萨如曲线 —— 双频振动的参数轨迹

local Lissajous = {}

local CONST = {
    FREQ_A        = 3,
    FREQ_B        = 2,
    RADIUS        = 110,

    AUTOINC_BASE  = 1198300,
}

local _fa       = CONST.FREQ_A
local _fb       = CONST.FREQ_B
local _phase    = 0
local _autoInc  = 0

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

local function GenID()
    _autoInc = _autoInc + 1
    return CONST.AUTOINC_BASE + _autoInc
end

function Lissajous.SetFreqA(n) _fa = n end
function Lissajous.SetFreqB(n) _fb = n end

function Lissajous.GetPoint(t)
    local x = math.sin(t * _fa + _phase)
    local y = math.sin(t * _fb)
    return x, y
end

四、核心代码:完整模块(下·推进与卸载)

lua
SL:ScheduleOnce(function()
    SL:BindDebugButton("横频加一", function()
        _fa = _fa % 5 + 1
        ShowTip("横频:" .. _fa)
    end)
    SL:BindDebugButton("纵频加一", function()
        _fb = _fb % 5 + 1
        ShowTip("纵频:" .. _fb)
    end)
    ShowTip("技能已加载: 利萨如曲线")
end, 1.0)

function Lissajous.Unload()
end

return Lissajous

五、机制问答

问:频率比不是整数比会怎么样?
答:轨迹永不重复——画满一层又错开一层,长时间渲染糊成一片,工程上限定整数比。

问:相位差是干什么用的?
答:给图形转角度——零度是标准姿态,九十度在等频时变成正圆,调相位就是转图形。

问:采样步长取多少合适?
答:几百到一千步——太疏折线感重,太密白花时间,按画布大小微调。

问:和圆有什么关系?
答:等频同相是斜线,等频九十度相位差是正圆——圆是利萨如家族的特例。

问:游戏里哪里用得上?
答:魔法阵纹样、轨迹光效、背景装饰、音游波形——一切要规律又不对称的图案都是它的地盘。

六、调参与实战怎么用

参数层面就三个量——横频、纵频、相位。先定频率比再调相位,次序反了会在错的图形上白调半天。实战里这类机制是纯装饰层,性能预算给得极少,采样点预计算成数组,每帧只描线不重算三角函数。常见坑两个:三角函数的参数忘了换算成弧度,图形直接畸形;画布纵横比不是正方形,正圆画成椭圆——记得按短边归一。

补充一个设计层面的思考——这类机制的核心难点不在于代码怎么写,而在于"什么时候触发、触发后做什么、做完了怎么收场"这三个问题的答案。答案清楚了,代码只是把答案翻译成脚本的过程;答案含糊,写出来的东西改来改去都在原地打转。动手前先把这三个问题各用一句话答出来,答不出的那个就是设计缺口,先补设计再动手。

补充一个维护层面的思考——所有的状态变量都收在模块的闭包里,不摆到全局表。全局表的变量在热重载时会残留旧值,闭包的变量每次加载都是新的,这层隔离是热重载环境里防事故的第一道墙。卸载入口要把定时器、连接、标记一揽子清干净,清不干净的下场就是旧事故原样重演。这篇的模块照抄能跑,改的就是参数表。

补充一个工程层面的思考——模块的可测试性和耦合度成反比。耦合越低越能单独验证,越高越要整套环境才能跑起来。降耦合的通用手法是依赖注入:框架接口通过参数传进来,而不是在模块里直接调死,验证的时候传一组假接口就能脱离框架独立运行。平时多花一分钟做注入,排查的时候省下的是一整晚。

写完留一句给做表现系的同学:两股振动拧成一股绳——频率比选对,笔尖自己会画出花纹。

作者履历与出处

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

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

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

LATEST ARTICLES

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

进阶实战游戏功能

走一格为什么是半秒?拆开996引擎走路状态机

接手二开的兄弟几乎都撞过同一堵墙:玩家嫌走路"一格一格像机器人",老板拍板要"丝滑移动",改服务端速度?不对。改客户端动画帧…

2026-10-03 05:31 996 技术组
进阶实战游戏功能

特效总挂在脚后跟上?挂点表选对一行顶十行

【游戏功能】 先看一个熟场面:法师的火球特效挂在怪的脚后跟上,明明技能说明写着"焚烧其面",火星子却在地上打滚。群里有位策划…

2026-10-03 05:30 996 技术组
进阶实战游戏功能

刀刀都判命中,玩家为什么喊挥空?命中表现差的那一拍

【游戏功能】 上周有个开服的老板在群里发了一段录屏:他的战士明明刀刀都砍中了,观众却一片"这刀挥空了吧"。他原话是"判定日志…

2026-10-03 05:30 996 技术组
进阶实战游戏功能

怪的皮一会儿绿一会儿红?状态染色的色号表就三列

【游戏功能】 先说结论放在开头:怪的皮一会儿绿一会儿红,不是美术换了几套贴图,是染色系统在怪身上盖了一层"色镜"。开服群里有…

2026-10-03 05:30 996 技术组
进阶实战游戏功能

996引擎Buff系统解剖:中毒变绿、冰冻发白是怎么挂上去的

前两天群里有个架设的朋友问了个需求:中毒的人要变绿、每两秒掉一次血、毒还能叠五层,问是不是要在服务端把整套状态机重写。我说你…

2026-10-03 05:25 996 技术组
进阶实战游戏功能

编钟纹:五口钟排着敲,一口更比一口疼

【游戏功能】 先抛一个坑:五口钟一模一样大,为什么一口比一口疼?先后次序动不得吗?倒过来敲会怎样?这篇把编钟纹整套写法拆开:…

2026-10-03 05:01 996 技术组