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

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

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

【游戏功能】

if _mirrorT > 0 then SL:TipAboveMe("免疫") return end——一道写在扣血最前面的闸门,就是弹反镜的全部骨架:举镜半秒,镜面撞上箭就原路弹回去咬它三十五。半秒的窗口、两秒的冷却,举早了空镜、举晚了挨箭——这是给手速党的镜子。核心代码照引擎技能脚本的全规格写:配置表、镜面构件、弹道反转、定时器、调试按钮。

一、效果演示:半秒镜面,一支回旋箭

演示场里大怪两秒射一箭、一箭二十五。点「弹反!」:一面椭圆的镜在玩家身前举起,半秒里泛着白光——箭飞到脸上撞上镜面,叮的一声原路折返,扎进怪身上就是三十五,比它的箭还疼十点;镜举晚了半秒,箭扎你二十五,镜子白收。演示里故意空举一次镜、再掐着箭的拍子弹一次——空镜的尴尬和弹中的爽快摆在一起,这道题的答案就两个字:掐拍。

demo
 fx-mirrorparry

二、底层原理:一道半秒闸门加一次弹道反转

模块的机关是三个部件。镜面态:举镜的半秒里 _mirrorT 大于零,玩家周身挂镜光——镜面态不改移动不改攻击,它只是"举着一面镜"。镜面态不改移动不改攻击,它只是"举着一面镜"。举镜期间照常走位——镜随人走,挡箭的判定跟着玩家的实时位置算,站着不动才是唯一挨箭的姿势。闸门态:箭的命中判定先查镜面态——举着,箭的行程反转、伤害易主:二十五的箭变成三十五咬回怪身上,多出来的十是弹反的利息。空窗态:镜收了、箭来了,闸门不存在,伤害原路落下。三个部件的时序全在两秒冷却和半秒窗口的对比里:冷却四倍于窗口,玩家每一次举镜都是一次押注。三个部件的时序全在两秒冷却和半秒窗口的对比里:冷却四倍于窗口,玩家每一次举镜都是一次押注。押注的赔率也写在数字上——弹回的箭比原箭疼十点,接住一箭等于白赚一次普攻,这笔账让每次举镜都有正期望的冲动。弹回的箭比原箭疼十点,接住一箭等于白赚一次普攻,这笔账让每次举镜都有正期望的冲动。镜面态里的粒子也别浪费——镜沿飘着的白屑是"还开着"的常驻提示,提示断镜的时机全靠它。

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

lua
--------------------------------------------------------------------------------
-- @file        MirrorParry.lua
-- @brief       弹反镜 —— 架起一面镜,箭从哪来回哪去
--              客户端表现模块:配置表 / 镜面构件 / 弹道反转 / 定时器
--------------------------------------------------------------------------------

local MirrorParry = {}

--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
    MIRROR_LIFE   = 0.5,        -- 镜面半秒
    MIRROR_CD     = 2.0,        -- 冷却两秒
    REFLECT_DMG   = 35,         -- 弹回三十五
    BOLT_SPEED    = 250,        -- 箭速

    -- 颜色
    COLOR_MIRROR  = "#a8c8f0",  -- 镜蓝
    COLOR_DEEP    = "#2a4a7a",  -- 深蓝
    COLOR_GHOST   = "#e0ecff",  -- 镜白

    ZORDER_GROUND = 118,
    ZORDER_BOLT   = 133,
    ZORDER_EFFECT = 135,

    AUTOINC_BASE  = 939000,
}

--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _mirrorT   = 0              -- 镜面余时
local _cdLeft    = 0
local _bolts     = {}             -- 弹道表:{ x, vx, back }
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 "MPP", 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 CreateMirrorNode()
    local mp = GetActorPos(SL:GetValue("USER_ID"))
    if not mp then return end
    local node = GUI:Layout_Create(GUI:Attach_SceneF(), GenID("Mir"),
        mp.x + 20, mp.y - 58, 0, 0, false)
    GUI:setLocalZOrder(node, CONST.ZORDER_EFFECT)
    for i = 1, 24 do
        local a1 = (i - 1) / 24 * 6.28
        local a2 = i / 24 * 6.28
        GUI:DrawLine_Create(node, string.format("m_%d", i), 0, 0, 2.5,
            m_cos(a1) * 8, m_sin(a1) * 26, m_cos(a2) * 8, m_sin(a2) * 26,
            CONST.COLOR_GHOST, 220)
    end
    return node
end

骨架的两处立规。弹道表 _bolts 的元素只有横坐标、速度、方向三个字段——箭是一维的,飞矢只有正反两向,二维弹道的复杂度留给别的机制。镜面构件竖椭圆贴在玩家身前二十步——镜要立在人和箭之间,贴身画框的镜子挡不了远来的箭。镜要立在人和箭之间,贴身画框的镜子挡不了远来的箭。举镜的那半秒里玩家的移动照旧——举镜跑位两不误,镜是随手的一抬手,不是站定的仪式。

四、核心代码:完整模块(下·定时器与弹道反转)

lua
--------------------------------------------------------------------------------
-- 举镜与箭的入账
--------------------------------------------------------------------------------
function MirrorParry.Cast()
    if _cdLeft > 0 or _mirrorT > 0 then return end
    _cdLeft = CONST.MIRROR_CD
    _mirrorT = CONST.MIRROR_LIFE
    CreateMirrorNode()
end

local function Bolt_OnHitPlayer(bolt)
    if _mirrorT > 0 then
        bolt.vx = -bolt.vx
        bolt.back = true
        SL:TipAboveMe("弹回去了")
        return false
    end
    SL:PlayerHurt(25)
    return true
end

local function Bolt_OnHitMonster(bolt)
    SL:ActorHurt(SL:GetValue("MARK_TARGET_ID"), CONST.REFLECT_DMG)
    SL:TipAbove(SL:GetValue("MARK_TARGET_ID"), "-" .. CONST.REFLECT_DMG)
end

--------------------------------------------------------------------------------
-- 定时器:弹道推进与两面判定
--------------------------------------------------------------------------------
if not _scheduler then
    _scheduler = SL:Schedule(function(dt)
        _cdLeft = math.max(0, _cdLeft - dt)
        if _mirrorT > 0 then
            _mirrorT = _mirrorT - dt
            if _mirrorT <= 0 then
                ShowTip("镜收了——没接到箭就白举")
            end
        end
        local mp = GetActorPos(SL:GetValue("USER_ID"))
        local tp = GetActorPos(SL:GetValue("MARK_TARGET_ID"))
        for i = #_bolts, 1, -1 do
            local b = _bolts[i]
            b.x = b.x + b.vx * dt
            if not b.back and mp and (b.x - mp.x) * b.vx <= 0 then
                if not Bolt_OnHitPlayer(b) then
                    b.vx = math.abs(b.vx)
                    b.back = true
                    SL:ActorHurt(SL:GetValue("MARK_TARGET_ID"), 0)
                end
            end
            if b.back and tp and math.abs(b.x - tp.x) < 20 then
                Bolt_OnHitMonster(b)
                SafeRemove(b.node)
                table.remove(_bolts, i)
            elseif b.x < -20 or b.x > 700 then
                SafeRemove(b.node)
                table.remove(_bolts, i)
            end
        end
    end, 0.02)
end

SL:RegisterLUAEvent(LUA_EVENT_MONSTER_SHOOT, "MPP_Shoot", function(data)
    if data and data.fromX then
        table.insert(_bolts, { x = data.fromX, vx = -CONST.BOLT_SPEED, back = false })
    end
end)

SL:ScheduleOnce(function()
    ShowTip("技能已加载: 弹反镜 (调试按钮触发)")
end, 1.0)

return MirrorParry

下半场的三段判定是全模块的节拍器。箭的每帧推进后先问"过没过玩家的脸"——方向和位置一齐验,穿脸的那一帧查镜面态:举着,速度取反、方向标回、利息十点记账;没举,扣血出表。反程的箭只认怪的坐标,扎中就结算三十五——弹回去的箭走的是自己的结算口,和玩家的扣血再无瓜葛。射箭事件从 LUA_EVENT_MONSTER_SHOOT 订阅,怪的每一次出手都在模块的账上挂号。

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

调参三笔账。窗口半秒对箭速二百五十步是"看清箭再举也来得及"的底线——窗口压到零点三秒就是纯手速考试,受众从所有人缩成主播。弹回三十五对原箭二十五是利息四成——利息必须高于原伤,玩家弹中的那一刻要有"赚了"的手感;低于原伤的弹反是哑巴亏,没人会练。冷却两秒对箭周期两点二秒是"每一箭都有机会弹"的配置——冷却长过箭的周期,某些箭注定免弹,弹反从节奏题变成抽签题。常见坑三个:弹回的箭穿过玩家自己二次判定,回程箭要标向;镜面态的攻击没冻结,举镜同时照常输出,弹反成了零成本常驻;空举没有任何反馈,玩家不知道镜收了。镜面的朝向也要跟着怪的方位转——怪绕到背后射箭,镜还举在正面就是摆设。

商业服里弹反镜是"操作流"的门面:弹反成功的特写做成品级——铁镜银光、玉镜金光,主播集锦里每一帧弹反都是免费广告。比武场里它是心理战的筹码:镜的冷却看得见,对手掐你冷却的真空期倾泻弹雨,读冷却的博弈直接长在两秒的冷却上。变体做成镜面吸收:弹回的箭再举镜再弹,连弹三次翻三倍——极限手速的天花板明码标价。变体做成镜面吸收:弹回的箭再举镜再弹,连弹三次翻三倍——极限手速的天花板明码标价。再进一步的弹反成就是镜面的新卖法:百次弹反的称号、千次弹反的称号边框,手速党的荣誉墙从此有了第一块砖。这一篇的演示和整套模块照抄能跑,改的就是 CONST 那张表。

写完留一句给做格挡系的同学:弹反卖的是"半秒的胆量"——窗口越窄,撞上镜面的那一声叮越像勋章。

作者履历与出处

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

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

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

LATEST ARTICLES

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

进阶实战游戏功能

迷魂香:一炉香点着,圈里的怪掉头打转

【游戏功能】 怪的仇恨为什么永远追着你?因为它看得见你。迷魂香把"看得见"这三个字没收了:一炉香落在它脚下,烟圈六秒——圈里…

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

气势:连击涨势 挨打掉势 提气回血五十

【游戏功能】 连击值攒着有什么用?多数战斗的答案是攒个伤害加成——气势给了第二个答案:攒的是一支军队的士气。每一刀涨十二点、…

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

崩血:伤口一动就崩,跑得越快掉血越凶

【游戏功能】 一道割裂挂上去,一秒一跳——它站着跳四,追你的时候崩成八:伤口一动就崩,跑得越快掉得越凶。崩血把"放风筝"从走…

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

燕回式:突进穿它一遭,原路飞回起手点

【游戏功能】 突进出去了,回不回得来?多数突进的答案是"不回"——人留在怪堆里听天由命。燕回式给突进装了归途:穿身而过去时一…

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

破招式:它抡重刀你抢半拍,招克招

【游戏功能】 读条变红的那一秒,全世界都在等一个决定。破招式把这一秒做成整场战斗的心跳:怪交替出红劈和蓝刺,红劈读条时按下破…

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

连招谱:三招按谱出,谱尾一击翻倍

【游戏功能】 出招的顺序算不算伤害?大多数战斗里不算——三招乱按和按谱出,账面一个样。连招谱把顺序变成了伤害本身:「劈→撩→…

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