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

火油阵:先泼油再点灯,两步烧一条线

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

【游戏功能】

先泼油还是先点火?这道题答错的代价是一地白烧的火——灯点在空地上,怪还没踩上来,四秒的火一点没沾着它的毛。火油阵把答案写成了两段代码:油是布局的手,火是收账的刀,两步分开按,一步都不能急。核心代码照引擎技能脚本的全规格写:配置表、油面与火线两套构件、点燃状态机、定时器、调试按钮。

一、效果演示:一步铺路,一步收账

演示场有两颗按钮。「泼油!」掷出一团油——深褐色的油面在地上摊开一条黑亮的椭圆,头顶挂"油 八秒",油在等着。「点灯!」掷出一簇火苗——落点若在油上,油面轰地起火,橙色的火线一秒十五地烧,烧四秒;落点若在空地,火苗落地就灭,弹一行灰字"空烧"。大怪追着你跑,你要做的是把油泼在它要走的路上——它踩进油面的那一刻点火,十五一秒的账连烧四秒。演示里先乱点一次灯看空烧的浪费,再把油泼在怪的要路上算准点灯——两步的时序差就是一整场仗的输出差——油是三秒前的你,火是现在的你。

demo
 fx-oilline

二、底层原理:一份油面表加一次点燃的状态迁移

模块的账本是一张油面表。每摊油是三个字段的对象:位置、寿命、点燃态——未燃的油八秒自然挥发,点燃的油转为四秒火线、一秒十五地烧。点燃是一次单向的状态迁移:油从"待燃"迁到"燃烧",迁移的条件是火苗落点命中油面椭圆——迁移不可逆,烧着的油不能扑灭,烧完即出表。两颗按钮各管各的冷却:泼油四秒、点灯两秒——点灯的冷却比泼油短,是刻意让玩家"点得起灯、省得起油",错误的成本落在空烧上而不是落在按钮上——空烧的灰字要写得轻,轻到像自言自语,玩家的面子才挂得住。油面与火线是共用一个对象的不同形态,视觉上却是截然的两套构件——深褐的油面和橙红的火线,一眼分得出哪步是布、哪步是收。

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

lua
--------------------------------------------------------------------------------
-- @file        OilLine.lua
-- @brief       火油阵 —— 先泼油再点灯,两步烧一条线
--              客户端表现模块:配置表 / 油面构件 / 火线构件 / 定时器
--------------------------------------------------------------------------------

local OilLine = {}

--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
    OIL_LIFE      = 8.0,        -- 油面八秒挥发
    OIL_CD        = 4.0,        -- 泼油冷却
    LIT_CD        = 2.0,        -- 点灯冷却
    BURN_LIFE     = 4.0,        -- 火线烧四秒
    BURN_GAP      = 1.0,        -- 一秒一跳
    BURN_DMG      = 15,         -- 一跳十五
    BURN_WIDTH    = 70,         -- 油面横宽

    -- 颜色
    COLOR_FIRE    = "#f0883e",  -- 烈火橙
    COLOR_DEEP    = "#7a3a0e",  -- 深橙
    COLOR_OIL     = "#4a4428",  -- 油褐
    COLOR_GHOST   = "#ffd8a8",  -- 火白

    ZORDER_GROUND = 118,
    ZORDER_FIRE   = 126,
    ZORDER_EFFECT = 135,

    AUTOINC_BASE  = 935000,
}

--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _oils       = {}            -- 油面表:未燃与燃烧共用
local _oilCd      = 0
local _litCd      = 0
local _scheduler  = nil
local _autoInc    = 0

local m_sin  = math.sin
local m_rand = math.random

--------------------------------------------------------------------------------
-- 辅助函数
--------------------------------------------------------------------------------
local function GenID(prefix)
    _autoInc = _autoInc + 1
    return string.format("%s_%d", prefix or "OIL", 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 CreateOilNode(x, y)
    local node = GUI:Layout_Create(GUI:Attach_SceneF(), GenID("Oil"), x, y, 0, 0, false)
    GUI:setLocalZOrder(node, CONST.ZORDER_GROUND)
    for i = 1, 20 do
        local a1 = (i - 1) / 20 * 6.28
        local a2 = i / 20 * 6.28
        GUI:DrawLine_Create(node, string.format("o_%d", i), 0, 0, 3,
            m_cos(a1) * CONST.BURN_WIDTH, m_sin(a1) * 16,
            m_cos(a2) * CONST.BURN_WIDTH, m_sin(a2) * 16,
            CONST.COLOR_OIL, 200)
    end
    return node
end

骨架的三处立规。油面表未燃与燃烧共用——一张表一套遍历,点燃只是改字段,两套表分开写的版本,点燃时要搬数据、熄火时要搬家,事故全在搬家的路上。油面构件二十段折线画椭圆,横宽七十纵深十六——扁的油才是摊在地上的油。BURN_LIFE 与油面的自然寿命各自独立——点燃的那一瞬火线按自己的四秒重算,两本寿命互不继承。

四、核心代码:完整模块(下·定时器与点燃状态机)

lua
--------------------------------------------------------------------------------
-- 泼油与点灯
--------------------------------------------------------------------------------
function OilLine.Spill(x)
    if _oilCd > 0 then return end
    _oilCd = CONST.OIL_CD
    local node = CreateOilNode(x, 0)
    table.insert(_oils, { x = x, t = CONST.OIL_LIFE, lit = false, tick = 0, node = node })
end

function OilLine.Light(x)
    if _litCd > 0 then return end
    _litCd = CONST.LIT_CD
    for i = 1, #_oils do
        local o = _oils[i]
        if not o.lit and math.abs(o.x - x) <= CONST.BURN_WIDTH then
            o.lit = true
            o.t = CONST.BURN_LIFE
            o.tick = 0
            SL:TipAboveAt(o.x, "着了!")
            return true
        end
    end
    ShowTip("空烧——地上没油")
    return false
end

--------------------------------------------------------------------------------
-- 定时器:挥发、燃烧与扣血
--------------------------------------------------------------------------------
if not _scheduler then
    _scheduler = SL:Schedule(function(dt)
        _oilCd = math.max(0, _oilCd - dt)
        _litCd = math.max(0, _litCd - dt)
        local myID = SL:GetValue("USER_ID")
        local mp = GetActorPos(myID)
        local tid = SL:GetValue("MARK_TARGET_ID")
        local tp = tid and GetActorPos(tid)
        for i = #_oils, 1, -1 do
            local o = _oils[i]
            o.t = o.t - dt
            if o.t <= 0 then
                SafeRemove(o.node)
                table.remove(_oils, i)
            elseif o.lit then
                o.tick = o.tick + dt
                if o.tick >= CONST.BURN_GAP then
                    o.tick = o.tick - CONST.BURN_GAP
                    if tp and math.abs(tp.x - o.x) <= CONST.BURN_WIDTH then
                        SL:ActorHurt(tid, CONST.BURN_DMG)
                        SL:TipAbove(tid, "-" .. CONST.BURN_DMG)
                    end
                end
                if m_rand() > 0.6 then
                    SL:PlayFlameParticle(o.x + (m_rand() - 0.5) * CONST.BURN_WIDTH * 2)
                end
            elseif mp and math.abs(mp.x - o.x) <= 20 then
                ShowTip("别站在油上——点了灯你也是柴")
            end
        end
    end, 0.02)
end

SL:RegisterLUAEvent(LUA_EVENT_SCENE_CLEAR, "OIL_Clear", function()
    for i = #_oils, 1, -1 do SafeRemove(_oils[i].node) end
    _oils = {}
end)

SL:ScheduleOnce(function()
    ShowTip("技能已加载: 火油阵 (调试按钮触发)")
end, 1.0)

return OilLine

下半场的咽喉在 Light 的命中循环:点灯查的是"落点是否落在任何一摊未燃油面内",命中即迁移、迁移即重置火线寿命——命中判定用横宽对齐而非点到点,泼得宽的油好点,泼得偏的油点不着,油面的地理就是点灯的手感。定时器里油面的三种命运写在同一段遍历里:挥发到期的出表、燃烧的按跳扣血、未燃的提醒玩家脚下有油——提醒那行是给自己的温柔,玩家站在自家油上点灯,烧的也是自己。

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

调参三笔账。火线一秒十五烧四秒是六十点的单摊产值——两步换六十,产值得压在玩家同期输出的一半以内,火油阵是布阵者的副业不是主业。油面八秒对火线四秒是布局的宽限——油比火长寿一倍,玩家有充足的时间等怪进圈;油比火短命的版本,点灯永远在追着挥发跑,布局布局,先有局才有点。泼油四秒对点灯两秒是节奏的错拍——点灯永远比泼油醒得快,玩家可以连续点几摊油,但不能连泼两摊,布局的珍贵,就藏在这四秒的错拍里。常见坑三个:点灯命中判定没查未燃状态,燃着的油再点一次重置火线无限烧;玩家站自家油里点灯烧到自己,提示却没写;油面出表时火线粒子还挂着,无火之油满地飘。泼油的落点预览要画成虚线椭圆——油摊在哪一寸,点灯的手就有底。

商业服里火油阵是"双人协作"的入门课:一人专职泼油、一人专职点灯,配合的默契第一次有了明确的分工表。攻城战里它是守城方的经济火:油泼在城门前的必经路,一波攻城一波火,守城的资源账从此多一页。变体做成油品升级:褐油烧四秒、蓝油冰冻、黑油烟盲——油罐子里装的不只是火,是整套的战术库。这一篇的演示和整套模块照抄能跑,改的就是 CONST 那张表。

写完留一句给做两段系的同学:两步机制卖的是"布局的远见"——灯点在油上的那一声轰,是玩家三秒前落下的那团油在替他说话。

作者履历与出处

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

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

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

LATEST ARTICLES

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

进阶实战游戏功能

偷影:偷走它的影子,没了影子的怪刀都虚

【游戏功能】 if _shadowT 0 then spd = noShadow and 12 or 24 end ——一行…

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

魂卫:杀一葬一,魂火替你出手

【游戏功能】 魂卫的全机制就一句:杀掉小怪化一盏魂火跟着你走,每盏魂火两秒自动劈最近的大怪一口八点,至多三盏——「引魂!」按…

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

纸人替灾:贴一个纸人,下一道咒它替你挨

【游戏功能】 一道咒飞过来,硬吃是四秒里一秒十的钝疼——有没有第三条路?纸人替灾给的就是第三条路:贴一个纸人在身后,八秒里头…

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

水镜式:挨的刀存进镜里,连本带利还回去

【游戏功能】 挨打还能赚钱?水镜式的答案是:挨的刀七成存进镜里不扣血、三成照常疼,六秒后点「还镜」,存量乘一点五放成水炮——…

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

弹反镜:架起一面镜,箭从哪来回哪去

【游戏功能】 if _mirrorT 0 then SL:TipAboveMe("免疫") return end ——一道写…

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

缩地阵:圈越缩越小,逼它和你贴身

【游戏功能】 远近战的矛盾怎么解?远战的答案是把怪挡在射程外,近战的答案是把人拖进射程内——缩地阵给的第三个答案最蛮横:圈只…

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