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

双形武:一念换形,刀快锤沉

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

【游戏功能】

双形武的全机制就一句:一念换形,刀快锤沉——刀形一秒一拍一口十一,锤形两秒一拍一口二十四还震得怪半拍出不了手,换形冷却两秒,拍子自己掐。这篇的核心代码照引擎技能脚本的全规格来:配置表、双形态视觉构件、姿态状态机、定时器、调试按钮,一个模块一根骨头都不少。

一、效果演示:两套拍子,一把武器

演示场点「换形」按钮就出发:默认刀形——金色细弧一秒一甩,十一的数字一拍一跳,整个节奏像织布;再点一下换锤形——按钮当场换成橙色,玩家身后甩出一记锤形残影,之后每两秒一记重锤:二十四的大数字、更宽的橙色弧光、震屏,怪头上蹦出"踉跄"——它下一次出手被硬生生拖慢半拍。两种形各自打十秒,总伤害几乎一样,节奏差的不是快慢是性格:刀形十口一百一,锤形五口一百二外加五次踉跄——差别不在伤害在节奏,什么时候换形,就是把两种节奏缝起来的针法——针脚落准了,两种形是一把武器。

demo
 fx-twostance

二、底层原理:一张姿态表加一次攻击循环重写

模块的账本是一张姿态表。每个姿态描述三件事:攻击间隔、单口伤害、命中附加——刀形是快而轻、无附加;锤形是慢而重、附加踉跄。换形动作只做一件事:把当前姿态指针切到另一行——攻击循环的下一次读表自然用上新系数,进行中的攻击不中断、已挥出的刀不撤销,形态切换永远从"下一拍"开始生效。踉跄是锤形的附加件:命中的瞬间给怪的攻击计时器加零点五秒、头顶亮半拍踉跄标记——注意是加在计时器上不是直接跳过怪的出手,加法的踉跄可叠加可预判,跳过式的打断就是另一门控制系了。换形冷却两秒卡在刀形的一个拍子和锤形的半个拍子之间——没有冷却的双形会被玩家当成交响乐指挥棒乱挥。

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

lua
--------------------------------------------------------------------------------
-- @file        TwoStance.lua
-- @brief       双形武 —— 一念换形,刀快锤沉
--              客户端表现模块:配置表 / 姿态表 / 攻击循环 / 定时器
--------------------------------------------------------------------------------

local TwoStance = {}

--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
    -- 刀形
    BLADE_GAP     = 1.1,        -- 一秒一拍
    BLADE_DMG     = 11,         -- 一口十一
    -- 锤形
    HAMMER_GAP    = 2.0,        -- 两秒一拍
    HAMMER_DMG    = 24,         -- 一口二十四
    HAMMER_DAZE   = 0.5,        -- 踉跄加时
    -- 换形
    SWAP_CD       = 2.0,        -- 换形冷却

    -- 颜色
    COLOR_BLADE   = "#e3b341",  -- 刀金
    COLOR_HAMMER  = "#f0883e",  -- 锤橙
    COLOR_GHOST   = "#ffd866",  -- 残影金

    ZORDER_GROUND = 118,
    ZORDER_ARC    = 134,
    ZORDER_EFFECT = 135,

    AUTOINC_BASE  = 924000,
}

--------------------------------------------------------------------------------
-- 姿态表:两种形各一行
--------------------------------------------------------------------------------
local STANCES = {
    blade  = { gap = CONST.BLADE_GAP,  dmg = CONST.BLADE_DMG,
               daze = 0, arc = 28, lw = 4 },
    hammer = { gap = CONST.HAMMER_GAP, dmg = CONST.HAMMER_DMG,
               daze = CONST.HAMMER_DAZE, arc = 36, lw = 7 },
}

--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _stance   = STANCES.blade
local _atkT     = 0.6
local _swapCd   = 0
local _scheduler = nil
local _autoInc  = 0

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

--------------------------------------------------------------------------------
-- 辅助函数
--------------------------------------------------------------------------------
local function GenID(prefix)
    _autoInc = _autoInc + 1
    return string.format("%s_%d", prefix or "TWS", 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 CreateArc(x, y, ang, r, color, lw)
    local node = GUI:Layout_Create(GUI:Attach_SceneF(), GenID("Arc"), x, y, 0, 0, false)
    GUI:setLocalZOrder(node, CONST.ZORDER_ARC)
    GUI:setRotation(node, -math.deg(ang))
    for i = -2, 2 do
        local a = i * 0.28
        GUI:DrawLine_Create(node, string.format("s_%d", i), 0, 0, lw,
            m_cos(a - 0.5) * r * 0.8, m_sin(a - 0.5) * r * 0.8,
            m_cos(a + 0.5) * r * 0.8, m_sin(a + 0.5) * r * 0.8,
            color, 230)
    end
    GUI:setScale(node, 0.5)
    GUI:runAction(node, GUI:ActionSequence(
        GUI:ActionSpawn(
            GUI:ActionScaleTo(0.14, 1.4, 1.4),
            GUI:ActionFadeOut(0.20)
        ),
        GUI:CallFunc(function() SafeRemove(node) end)
    ))
end

骨架的题眼是 STANCES 这张姿态表:两种形的全部差别浓缩成五行字段——攻击循环读表干活,姿态切换只换指针,新增第三种形态就是加一行表。弧光构件做成参数化的共用件:半径、线宽、颜色全部由姿态行传入,刀的细弧和锤的宽弧是同一个构件的两件衣裳。

四、核心代码:完整模块(下·定时器与攻击循环)

lua
--------------------------------------------------------------------------------
-- 攻击循环:读姿态表干活
--------------------------------------------------------------------------------
local function Stance_Strike(myID, targetID, st)
    local mp = GetActorPos(myID)
    local tp = GetActorPos(targetID)
    if not mp or not tp then return end
    local ang = (mp.x < tp.x) and 0.5 or 2.6
    CreateArc(mp.x + m_cos(ang) * 26, mp.y - 52, ang, st.arc, st == STANCES.hammer
        and CONST.COLOR_HAMMER or CONST.COLOR_BLADE, st.lw)
    SL:ActorHurt(targetID, st.dmg)
    SL:TipAbove(targetID, "-" .. st.dmg)
    if st.daze > 0 then
        SL:SlowAttack(targetID, 1.0, st.daze)   -- 加时踉跄
        SL:TipAbove(targetID, "踉跄")
    end
    if st == STANCES.hammer then
        SL:ScreenShake(4, 0.22)
    end
end

--------------------------------------------------------------------------------
-- 换形入口
--------------------------------------------------------------------------------
function TwoStance.Swap()
    if _swapCd > 0 then return end
    _swapCd = CONST.SWAP_CD
    _stance = (_stance == STANCES.blade) and STANCES.hammer or STANCES.blade
    ShowTip(_stance == STANCES.blade and "刀形——一秒一拍" or "锤形——两秒一拍")
end

function TwoStance.IsBlade()
    return _stance == STANCES.blade
end

--------------------------------------------------------------------------------
-- 定时器与调试
--------------------------------------------------------------------------------
if not _scheduler then
    _scheduler = SL:Schedule(function(dt)
        _swapCd = math.max(0, _swapCd - dt)
        _atkT = _atkT - dt
        if _atkT <= 0 then
            _atkT = _stance.gap
            local myID = SL:GetValue("USER_ID")
            local targetID = SL:GetValue("MARK_TARGET_ID")
            if targetID then
                Stance_Strike(myID, targetID, _stance)
            end
        end
    end, 0.02)
end

SL:ScheduleOnce(function()
    ShowTip("技能已加载: 双形武 (调试按钮触发)")
end, 1.0)

SL:RegisterLUAEvent(LUA_EVENT_TARGET_DEAD, "TWS_Dead", function()
    _atkT = 0.3
end)

return TwoStance

下半场的攻击循环是全模块最薄的一段——_atkT 每拍递减、归零就按姿态表打一口,打完用姿态行自己的间隔重置计时器:攻击循环里没有任何一行认识"刀"或"锤",它只认识表——表驱动写到底,加第三种形态也不碰循环一行。换形入口只改指针不动计时器——换形的瞬间正在走的攻击间隔保留原形节奏,下一拍才上新形,这个"从下一拍生效"的规矩避免了换形瞬间连发的刷拍漏洞。目标死亡钩子把攻击计时器压到零点三秒,下一只怪刷新立刻接上节奏,磨刀不误砍柴工。

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

调参的对手戏在两个形的期望伤害:刀形十除以一点一是每秒十,锤形二十四除以二是每秒十二——锤形比刀形贵两成,贵出来的部分用踉跄付账,这个不等式一破,双形就退化成"永远锤形"的单形武。踉跄零点五秒对怪两秒二的出手间隔是四分之一的拖延,五锤下去怪白丢一拍——拖延可叠不可跳,是双形武和打断系的国界。换形冷却两秒卡在刀形的一个拍子和锤形的半个拍子之间——冷却长于三秒,玩家换形前要先算账,手感就从"切歌"变成"填表"。常见坑三个:换形瞬间攻击计时器被重置,玩家卡冷却连拍;踉跄标记没有持续时间,永久慢半拍的怪被玩家当成残血;姿态图标不跟按钮色走,玩家不知道现在是什么形。攻击循环的落空也要有交代:目标已死的那一拍不重置计时器,下一只怪刷新立刻满拍接上,节奏不断电。

商业服里双形武是"武器大师"人设的发动机:一把武器两种形,每种形一套词条,商城的武器皮肤从一件卖成两件。副本里它是节奏关的钥匙——快拍小怪用刀形清、重甲门神用锤形磨,读怪换形的水平就是通关速度。变体做成三形:刀锤弓,远程形收钱,姿态表加一行的成本,付费深度加一档。这一篇的演示和整套模块照抄能跑,改的就是 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 技术组