【游戏功能】
if _goldT > 0 then return end——一行免伤闸门的写法,撑起了金身咒全部的取舍:金身亮起的两秒里,箭雨、重锤、毒火全部弹开,可你自己的刀也一并被钉在鞘里——买命的钱是输出窗口,两秒的账一笔不差。这篇的核心代码照引擎技能脚本的全规格写:配置表、佛光构件、免伤闸门、定时器、调试按钮。
一、效果演示:两秒佛光,三箭弹开
演示场里大怪每四秒张一轮箭雨:三支箭错着拍子飞来,一箭二十,不躲就是六十的账。点「金身!」:玩家的周身腾起一层金光蛋壳,头顶挂出"金身 两秒"——第一支箭撞在蛋壳上只蹦出"免疫"两个字,第二支、第三支一样;同一时间你的攻击键是灰的,两秒里一刀不出。演示里让箭雨撞一次金身、再走位躲一次箭雨,两种躲法的账摆在一起:走位不花钱但可能躲不干净,金身万无一失但要还两秒的输出债。
fx-goldbody
二、底层原理:一道免伤闸门加一份冻结代价
模块的机关是一对互锁的账,锁芯就是 _goldT 这一个字段。免伤侧:金身期是扣血入口最前面的一道闸,闸开着,一切伤害在预演之前就被弹回,连毒这样的持续账也进不来——金身免的是"所有",留一个后门的金身会被玩家找出万能解。代价侧:金身期攻击循环被冻结——不是减速是冻结,普攻计时器整个停摆、技能入口直接拦下——金身的两秒是纯支出,玩家花这两秒买的是下一轮箭雨的零账——买命的账要两页对读:这页是支出,下页是省下的血。两侧的互锁写在同一个开关上:_goldT 大于零就是免伤加冻结的同一个状态,不存在"能打的金身",那叫护盾,是另一种商品。
三、核心代码:完整模块(上·骨架)
--------------------------------------------------------------------------------
-- @file GoldBody.lua
-- @brief 金身咒 —— 两秒刀枪不入,但手脚也钉死
-- 客户端表现模块:配置表 / 佛光构件 / 免伤冻结 / 定时器
--------------------------------------------------------------------------------
local GoldBody = {}
--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
GOLD_LIFE = 2.0, -- 金身两秒
GOLD_CD = 9.0, -- 冷却九秒
FREEZE_ATK = true, -- 金身期冻结攻击
-- 颜色
COLOR_GOLD = "#e3b341", -- 身金
COLOR_DEEP = "#7a5a12", -- 佛金
COLOR_LIGHT = "#fff8e1", -- 光白
ZORDER_GROUND = 118,
ZORDER_SHELL = 134,
ZORDER_EFFECT = 135,
AUTOINC_BASE = 928000,
}
--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _goldT = 0 -- 金身余时
local _cdLeft = 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 "GBD", CONST.AUTOINC_BASE + _autoInc)
end
local function GetMyPos()
local x = SL:GetValue("ACTOR_POSITION_X", SL:GetValue("USER_ID"))
local y = SL:GetValue("ACTOR_POSITION_Y", SL:GetValue("USER_ID"))
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 CreateShellNode()
local mp = GetMyPos()
if not mp then return end
local node = GUI:Layout_Create(GUI:Attach_SceneF(), GenID("Shell"),
mp.x, mp.y - 48, 0, 0, false)
GUI:setLocalZOrder(node, CONST.ZORDER_SHELL)
for i = 1, 30 do
local a1 = (i - 1) / 30 * 6.28
local a2 = i / 30 * 6.28
GUI:DrawLine_Create(node, string.format("in_%d", i), 0, 0, 2,
m_cos(a1) * 30, m_sin(a1) * 58, m_cos(a2) * 30, m_sin(a2) * 58,
CONST.COLOR_LIGHT, 190)
end
for i = 1, 20 do
local a1 = (i - 1) / 20 * 6.28
local a2 = i / 20 * 6.28
GUI:DrawLine_Create(node, string.format("out_%d", i), 0, 0, 3,
m_cos(a1) * 34, m_sin(a1) * 64, m_cos(a2) * 34, m_sin(a2) * 64,
CONST.COLOR_GOLD, 150)
end
return node
end
骨架的定调在两层壳的画法:内层亮线是蛋壳的主体,外层淡线是金光的辉——两层错半圈的椭圆叠出来的才是"佛光",单层椭圆像玻璃罩。免伤的闸门写在扣血入口的最前面,这个位置是金身的生命线——写进扣血函数内部的版本,遇上有穿透词条的刀就得一处一处补丁。FREEZE_ATK 做成常量开关而不是写死——策划哪天要给金身开攻击的口子,改开关不改结构,这是配置表存在的意义。
四、核心代码:完整模块(下·定时器与免伤冻结)
--------------------------------------------------------------------------------
-- 金身开关
--------------------------------------------------------------------------------
local function Gold_Open(shellBag)
_goldT = CONST.GOLD_LIFE
_cdLeft = CONST.GOLD_CD
shellBag[#shellBag + 1] = CreateShellNode()
SL:TipAboveMe("金身!")
SL:ScreenShake(2.5, 0.18)
end
--------------------------------------------------------------------------------
-- 免伤闸门:扣血入口最前的一道关
--------------------------------------------------------------------------------
local function OnPlayerHurt(raw)
if _goldT > 0 then
SL:TipAboveMe("免疫")
return
end
SL:PlayerHurt(raw)
end
--------------------------------------------------------------------------------
-- 攻击冻结:攻击入口的对称闸门
--------------------------------------------------------------------------------
local function Attack_Gate()
if CONST.FREEZE_ATK and _goldT > 0 then
return false
end
return true
end
function GoldBody.CanAttack()
return Attack_Gate()
end
function GoldBody.Cast(shellBag)
if _cdLeft > 0 or _goldT > 0 then return end
Gold_Open(shellBag)
end
--------------------------------------------------------------------------------
-- 定时器与调试
--------------------------------------------------------------------------------
if not _scheduler then
_scheduler = SL:Schedule(function(dt)
_cdLeft = math.max(0, _cdLeft - dt)
if _goldT > 0 then
_goldT = _goldT - dt
if _goldT <= 0 then
ShowTip("金身散了——这两秒的输出账要补回来")
end
end
end, 0.02)
end
SL:RegisterLUAEvent(LUA_EVENT_LOCAL_HURT, "GBD_Hurt", function(data)
if data and data.raw then
OnPlayerHurt(data.raw)
end
end)
SL:ScheduleOnce(function()
ShowTip("技能已加载: 金身咒 (调试按钮触发)")
end, 1.0)
return GoldBody
下半场的对称结构是全模块的课:扣血入口有一道免伤闸,攻击入口有一道冻结闸,两道闸由同一个 _goldT 拉动——玩家的账两头同时停,谁也不欠谁。免伤写在 OnPlayerHurt 的最前面,连日志都不留地弹回;冻结的 CanAttack 做成查询接口,技能系统每帧来问一句"能打吗",比在三个技能里各写一遍金身判断干净十倍。金身到期的提示要落在散光的那一帧,晚一拍,玩家会再等一个不存在的免疫。
五、调参指南与商业服怎么用
调参三笔账。金身两秒对箭雨三箭的节奏是"一轮全免"的配置——箭雨密到四箭五箭,金身就要往三秒抬,全免变半免的手感立刻碎。冷却九秒对箭雨四秒是两轮一开——每轮都开金身的玩法会退化成站桩挨打流,空出一轮逼玩家在金身和走位之间做选择。冻结攻击是代价的骨线:这一条拿掉,金身从保命技变成无敌输出窗,全职业的平衡跟着塌。常见坑三个:持续伤害每帧扣血,金身期每帧弹"免疫",免疫的小字糊了半屏——持续类账要走静默弹回;金身期的位移没有被冻住,玩家顶着蛋壳走位,"钉死"名不副实;冷却从开启起算,金身两秒加九秒冷却实际十一秒一轮,界面却按九秒转。金身期的受击要留一层弹开的白光反馈——静默免疫像断线,弹光的免疫才像壳在替你挨。
商业服里金身咒是"反应速度"的商品化:金身的触发做成手动键就是操作流的尊严,做成自动挡就是月卡的卖点——同一道闸,两种卖法。副本里它是救场技的教科书:全队残血那一轮箭雨,一个金身换三秒的奶量窗口,指挥的麦克风里永远有它。变体做成金身共享:一人开金身全队免半秒,个人技长成团队技,行会战的价码翻着翻地涨。金身的判定顺序也要在文档里写死——免伤闸门排在减伤链之前还是之后,决定了它和石肤、修盾叠用时的结算先后,文档不写清,玩家与客服的战争就没完。这一篇的演示和整套模块照抄能跑,改的就是 CONST 那张表。
写完留一句给做免伤系的同学:金身卖的是"停下来的勇气"——两秒里你什么都做不了,所以这两秒免的一切都理直气壮。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 安全巡检扫出一笔可疑的挂账:玩家的"周围单位结算"列表里,同一只怪每零点八秒出现一次,可战斗记录里玩家根本没出…
【游戏功能】 困灵阵的全机制就一句:阵落在它脚下,八秒里它就是圈中之物——敢跨出圈沿,一步吃十五还被弹回去,你在圈外安安稳稳…
【游戏功能】 先泼油还是先点火?这道题答错的代价是一地白烧的火——灯点在空地上,怪还没踩上来,四秒的火一点没沾着它的毛。火油…
【游戏功能】 寻路组收到一笔诡异的工单:怪物巡逻到某个坐标段必卡三秒,像踩了看不见的门槛——排查两小时,发现那段路上有一个上…
【游戏功能】 local a, b = math.min(x1, x2), math.max(x1, x2) ——一行把两根…
【游戏功能】 测试组报过一口邪门的单:头目的读条明明被打断过,技能照样出了膛——查了半天不是打断失效,是打断只"清了"不"冻…