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

风暴眼:满场风暴,只有眼里是净土

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

【游戏功能】

范围技能的镜像问题是:怪站在范围中心怎么办?风暴眼把范围技能翻了个面——满场风暴、环带一秒二十五谁站谁挨,唯独圆心五十步的"风暴眼"是净土。你可以站进眼里避险,但怪也会走进眼里和你抢净土——一个圈,两种人的求生。这篇的核心代码照引擎技能脚本的全规格写:配置表、风暴环构件、眼判定状态机、定时器、调试按钮。

一、效果演示:满场风暴,一眼净土

演示场点「风暴眼!」:风暴从两座之间拔地而起——外环一圈、内眼一圈,环带里紫芒翻滚,一秒二十五的扣血谁站谁挨。玩家往眼里一站:环带的雷在眼沿外炸,眼内纹丝不动——净土二字写在脚下。但怪不认这圈逻辑:它绕着环带外沿转了半圈,一头扎进眼里和你抢地盘——眼内近身战开打,你出圈是送、它出圈也是送,两个都在眼里的对手只能贴身分胜负。八秒风停,一切归位。演示里看三处:环带的旋转紫芒、眼沿的清晰边界、怪进眼时那一格身位的挤压——净土的净与挤,都在这五十步里。净土的净与挤,都在这五十步里。进眼的时机也是题:风暴刚起就进眼,怪随后跟进,净土变斗兽场;等它在外面转够半圈再进眼,独享的净土才叫净土。

demo
 fx-stormeye

二、底层原理:一份环带判定加一份眼的豁免

模块的机关是一对同心圆。环带判定:单位到风暴心的距离落在"眼半径"到"眼半径加带宽"之间,就算站在风暴里——一秒二十五地扣。眼的豁免:距离小于眼半径的单位完全豁免,眼是净土不是减速区。怪的走位写两条规矩:它在眼内就贴着你的身位挤压,它在带外就向眼沿推进——怪"想进眼"的倾向就是这场风暴的戏剧冲突:净土只有一份,进眼的双方必有一场贴身战。玩家的最优解同样是眼里——眼内打、带外躲、圈沿卡位,三个动作全在一对同心圆的半径上。眼内打、带外躲、圈沿卡位,三个动作全在一对同心圆的半径上。眼沿的挤压也要演——怪在眼沿外沿探进半个身位又被风压回去,净土的边界在挤压里一遍一遍地反复确认。

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

lua
--------------------------------------------------------------------------------
-- @file        StormEye.lua
-- @brief       风暴眼 —— 满场风暴,只有眼里是净土
--              客户端表现模块:配置表 / 风暴环构件 / 眼判定状态机 / 定时器
--------------------------------------------------------------------------------

local StormEye = {}

--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
    STORM_LIFE    = 8.0,        -- 风暴八秒
    STORM_CD      = 14.0,       -- 冷却十四秒
    EYE_R         = 50,         -- 眼半径五十
    BAND_W        = 26,         -- 环带宽二十六
    TICK_GAP      = 1.0,        -- 一秒一跳
    TICK_DMG      = 25,         -- 一跳二十五

    -- 颜色
    COLOR_STORM   = "#8a9fe8",  -- 风暴蓝紫
    COLOR_DEEP    = "#2a2a5e",  -- 深暴
    COLOR_GHOST   = "#e8ecff",  -- 眼白

    ZORDER_GROUND = 118,
    ZORDER_STORM  = 126,
    ZORDER_EFFECT = 135,

    AUTOINC_BASE  = 955000,
}

--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _stormT     = 0              -- 风暴余时
local _stormX     = 0              -- 风暴心
local _cdLeft     = 0
local _tickT      = 0
local _scheduler  = nil
local _autoInc    = 0

local m_cos = math.cos
local m_sin = math.sin
local m_abs  = math.abs

--------------------------------------------------------------------------------
-- 辅助函数
--------------------------------------------------------------------------------
local function GenID(prefix)
    _autoInc = _autoInc + 1
    return string.format("%s_%d", prefix or "SME", CONST.AUTOINC_BASE + _autoInc)
end

local function GetActorPos(actorID)
    if not actorID then return nil end
    local x = SL:GetValue("ACTOR_POSITION_X", actorID)
    local y = SL:GetValue("ACTOR_POSITION_Y", actorID)
    if not x or not y then return nil end
    return { x = x, y = y }
end

local function SafeRemove(node)
    if node and not GUI:Widget_IsNull(node) then
        GUI:removeFromParent(node)
    end
end

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

---风暴环构件:眼沿与外沿双圈
local function CreateStormNode(x)
    local node = GUI:Layout_Create(GUI:Attach_SceneF(), GenID("Storm"), x, 0, 0, 0, false)
    GUI:setLocalZOrder(node, CONST.ZORDER_STORM)
    for i = 1, 36 do
        local a1 = (i - 1) / 36 * 6.28
        local a2 = i / 36 * 6.28
        GUI:DrawLine_Create(node, string.format("e_%d", i), 0, 0, 2,
            m_cos(a1) * CONST.EYE_R, m_sin(a1) * CONST.EYE_R * 0.38,
            m_cos(a2) * CONST.EYE_R, m_sin(a2) * CONST.EYE_R * 0.38,
            CONST.COLOR_GHOST, 210)
        GUI:DrawLine_Create(node, string.format("o_%d", i), 0, 0, 2,
            m_cos(a1) * (CONST.EYE_R + CONST.BAND_W), m_sin(a1) * (CONST.EYE_R + CONST.BAND_W) * 0.38,
            m_cos(a2) * (CONST.EYE_R + CONST.BAND_W), m_sin(a2) * (CONST.EYE_R + CONST.BAND_W) * 0.38,
            CONST.COLOR_STORM, 200)
    end
    return node
end

骨架的三处立规。风暴是双圈构件不是渐变糊——眼沿和外沿两条实线圈把"净土"和"绞肉机"的边界画死,模糊的边界等于没有边界。纵轴零点三八压扁——贴地的风暴才有地面的压迫感。TICK_DMG 二十五一跳是重罚——环带的痛要大到"没人愿意多站半秒",轻了风暴就是背景板。

四、核心代码:完整模块(下·定时器与眼判定)

lua
--------------------------------------------------------------------------------
-- 风暴开与眼判定
--------------------------------------------------------------------------------
function StormEye.Cast(x)
    if _cdLeft > 0 or _stormT > 0 then return end
    _cdLeft = CONST.STORM_CD
    _stormT = CONST.STORM_LIFE
    _stormX = x
    CreateStormNode(x)
    ShowTip("风暴起了——环带一秒二十五,眼里是净土")
end

local function InBand(x)
    if _stormT <= 0 then return false end
    local d = m_abs(x - _stormX)
    return d > CONST.EYE_R and d <= CONST.EYE_R + CONST.BAND_W
end

local function InEye(x)
    if _stormT <= 0 then return false end
    return m_abs(x - _stormX) <= CONST.EYE_R
end

--------------------------------------------------------------------------------
-- 定时器:跳表与怪的抢眼走位
--------------------------------------------------------------------------------
if not _scheduler then
    _scheduler = SL:Schedule(function(dt)
        _cdLeft = math.max(0, _cdLeft - dt)
        if _stormT <= 0 then return end
        _stormT = _stormT - dt
        if _stormT <= 0 then
            ShowTip("风停了——这一仗的净土没了")
            return
        end
        _tickT = _tickT + dt
        if _tickT >= CONST.TICK_GAP then
            _tickT = _tickT - CONST.TICK_GAP
            if InBand(SL:GetValue("USER_ID") and select(1, GetActorPos(SL:GetValue("USER_ID"))).x) then
                SL:PlayerHurt(CONST.TICK_DMG)
            end
            local tid = SL:GetValue("MARK_TARGET_ID")
            local tp = tid and GetActorPos(tid)
            if tp and InBand(tp.x) then
                SL:ActorHurt(tid, CONST.TICK_DMG)
            end
        end
        local tid2 = SL:GetValue("MARK_TARGET_ID")
        local tp2 = tid2 and GetActorPos(tid2)
        if tp2 then
            local d = m_abs(tp2.x - _stormX)
            if d < CONST.EYE_R - 10 then
                SL:WalkStep(tid2, (tp2.x > _stormX) and 1 or -1)
            elseif d > CONST.EYE_R + CONST.BAND_W then
                SL:WalkStep(tid2, (tp2.x > _stormX) and -1 or 1)
            end
        end
    end, 0.02)
end

SL:RegisterLUAEvent(LUA_EVENT_SCENE_CLEAR, "SME_Clear", function()
    _stormT = 0
end)

SL:ScheduleOnce(function()
    ShowTip("技能已加载: 风暴眼 (调试按钮触发)")
end, 1.0)

return StormEye

下半场的抢眼走位是全模块的戏眼:怪的三段走位——眼内压向玩家、带外推进眼沿、带内横着游走——三条规矩让怪的行为从"追人"变成"抢地",风暴的戏剧性全在这一份走位上。跳表的双判定一次查玩家一次查怪——环带不认阵营,谁的脚在带里谁的账就扣,净土的公平写在同一段判定里。

五、调参指南与商业服怎么用

调参三笔账。眼半径五十对怪的体积是"能站两人"的净土——眼小到三十步净土变独木桥,大到八十步眼比战场大,风暴失去意义。环带一跳二十五是重罚——罚得重玩家才肯挤进眼里忍着贴身,罚得轻没人怕环带。带宽二十六步是眼和外界的缓冲——带宽太窄怪一脚跨进眼沿,太宽风暴占了半屏。常见坑三个:眼判定的圆心在战斗中漂移,净土跟着晃;环带的粒子遮住了角色的血条;怪进眼后仍执行环带外的寻路,怪在眼内鬼畜抖动。

商业服里风暴眼是"协作副本"的终极考题:眼内位置有限,谁站眼里、谁出去拉怪、谁卡眼沿输出,团队分工的考卷每八秒就重发一次考卷。攻城战里它是阵地战的天然分界:风暴眼落在城门正中,攻守双方抢眼的八秒就是攻城战的心跳段。变体做成双风暴眼:两个眼眼心相距一百步,两队各占一眼隔带对射——净土的争夺升级成净土的炮台。变体做成双风暴眼:两个眼眼心相距一百步,两队各占一眼隔带对射——净土的争夺升级成净土的炮台。眼的半径还能做成缩的:眼从五十缩到二十,净土的缩小就是节奏的收紧,缩到最后的贴身决战就在所难免、无可回避。这一篇的演示和整套模块照抄能跑,改的就是 CONST 那张表。

写完留一句给做风暴系的同学:风暴眼卖的是"危险的中心"——全场都在赶你走,唯独圆心这块五十步,值得你和它撕一场。

作者履历与出处

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

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

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

LATEST ARTICLES

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

进阶实战游戏功能

影锁:影锁绑你绑它,锁期拼刀

【游戏功能】 控制技能的镜像问题是:锁住了怪,自己是不是也闲着?影锁把这道两难摆上台面:影锁四秒,它动不了、你也动不了——它…

2026-10-01 10:00 996 技术组
进阶实战游戏功能

震魂:震魂入体叠加出招自损

【游戏功能】 打断为什么只有"清读条"一种?震魂偏要反过来——不清招、不断势,打在它读条半途的那一劲顺着招震进腑——中劲十秒…

2026-10-01 10:00 996 技术组
进阶实战游戏功能

蜂王:喂小怪蜂王进化,三阶蜂王蜇翻全场

【游戏功能】 蜂群为什么总是一只一只出?蜂王偏要让它们进化:放一只蜂王出来,杀掉小怪喂它——喂三只升一阶,三阶蜂王的毒刺一秒…

2026-10-01 10:00 996 技术组
进阶实战游戏功能

血刃:每刀扣血换吸血,血刀双刃

【游戏功能】 技能为什么总是不扣自己的血?血刃偏要反着来:开着血刃的每一刀先扣自己二血、命中回三血——净赚一但要打得中,空刀…

2026-10-01 10:00 996 技术组
进阶实战游戏功能

破军旗:旗立三秒圈内加攻,旗倒攻散

【游戏功能】 bn.t = bn.t - dt ——一行旗面倒计时的代码,撑起了破军旗全部的紧迫感:旗立三秒、圈内加攻三成,…

2026-10-01 10:00 996 技术组
进阶实战游戏功能

淬毒刃:淬毒三刀叠一层,一层毒过一秒六

【游戏功能】 淬毒为什么要一毒到底?淬毒刃偏要一层一层叠:一刀淬一层毒、毒层一秒六地烂,六层封顶之后一刀下去毒伤三十六——叠…

2026-10-01 10:00 996 技术组