【游戏功能】
魂卫的全机制就一句:杀掉小怪化一盏魂火跟着你走,每盏魂火两秒自动劈最近的大怪一口八点,至多三盏——「引魂!」按下,三盏魂火齐射一轮二十四。收割的价值第一次写在你的身后。这篇的核心代码照引擎技能脚本的全规格写:配置表、魂火构件、收割表、索敌开火、定时器、调试按钮。
一、效果演示:杀两只,多三口
演示场里大怪带着两只小蛛。你的普攻先点名蛛:一刀一只,蛛倒的瞬间化出一盏青蓝色的魂火,飘到你的轨道上——头顶"魂火 ×1"。两只蛛清完,你身后跟着两盏魂火,两秒一口地自动劈大怪,你普攻的每一拍之间都夹着魂火的八点。点「引魂!」:两盏魂火同时出刀,一轮十六——引魂是把被动收割变成主动齐射的扳机。大怪拍你的时候魂火还在劈——它的仇恨在你身上,魂火的刀在它背上。演示里试一次只杀一只就开打:一盏魂火的账和两盏的账,差的就是那一只你没杀的蛛。
fx-soulwisp
二、底层原理:一张收割表加一套自动索敌
模块的账本是一张收割表。小怪的死亡钩子往表里压一盏魂火:至多三盏,满了再杀是白给——白给的提示要直说,玩家的下一刀才知道该转向大怪。魂火各自带两秒的出手计时,出手时对最近的敌人劈一口八点——魂火的索敌独立于玩家的目标:玩家打蛛,魂火劈大怪,两线的输出互不占拍。两线的输出互不占拍——你打你的、它劈它的,魂卫从不动玩家的目标锁定。引魂是收割表的扳机:把所有魂火的出手计时清零,下一拍齐射——被动收割攒、主动齐射放,攒放之间就是这门机制的节奏。魂火的轨道是纯视觉:三盏魂火绕着玩家飘,位置只给眼睛看,伤害的账全在索敌的计时器里。
三、核心代码:完整模块(上·骨架)
--------------------------------------------------------------------------------
-- @file SoulWisp.lua
-- @brief 魂卫 —— 杀一葬一,魂火替你出手
-- 客户端表现模块:配置表 / 魂火构件 / 收割表 / 定时器
--------------------------------------------------------------------------------
local SoulWisp = {}
--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
WISP_CAP = 3, -- 至多三盏
WISP_GAP = 2.0, -- 每盏两秒一劈
WISP_DMG = 8, -- 一劈八点
ORBIT_R = 46, -- 轨道半径四十六
-- 颜色
COLOR_WISP = "#8fd4f0", -- 魂青
COLOR_DEEP = "#1f4f8f", -- 深青
COLOR_GHOST = "#d8f0ff", -- 魂白
ZORDER_GROUND = 118,
ZORDER_WISP = 133,
ZORDER_EFFECT = 135,
AUTOINC_BASE = 942000,
}
--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _wisps = {} -- 魂火表:每盏一个出手计时
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 "SLW", 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 CreateWispNode()
local node = GUI:Layout_Create(GUI:Attach_SceneF(), GenID("Wisp"), 0, 0, 0, 0, false)
GUI:setLocalZOrder(node, CONST.ZORDER_WISP)
for i = 1, 10 do
local a1 = (i - 1) / 10 * 6.28
local a2 = i / 10 * 6.28
GUI:DrawLine_Create(node, string.format("w_%d", i), 0, 0, 2,
m_cos(a1) * 10, m_sin(a1) * 10, m_cos(a2) * 10, m_sin(a2) * 10,
CONST.COLOR_WISP, 190)
end
return node
end
骨架的三处立规。魂火表按盏存对象、每盏自带头像节点和出手计时——魂火是独立单位不是共享读数,齐射的扳机才能逐盏触发。WISP_CAP 的封顶写在收割钩子里——满了之后的击杀不生成魂火,也不顶掉旧魂火,三盏是上限不是队列。魂火构件双层光珠加外圈光晕——魂火要"轻",画重了像灯笼,轻了才像一缕不肯散的魂。
四、核心代码:完整模块(下·定时器与索敌开火)
--------------------------------------------------------------------------------
-- 收割钩子与索敌开火
--------------------------------------------------------------------------------
local function Wisp_Spawn()
if #_wisps >= CONST.WISP_CAP then
ShowTip("三盏满了——这一杀白给")
return
end
local node = CreateWispNode()
table.insert(_wisps, { node = node, fireT = 1.0, seed = #_wisps * 2.1 })
SL:TipAboveMe("魂火+" .. #_wisps)
end
local function Wisp_Fire(w, mp, tid, tp)
SL:ActorHurt(tid, CONST.WISP_DMG)
SL:TipAbove(tid, "-" .. CONST.WISP_DMG)
SL:PlayWispBolt(mp.x, mp.y, tp.x, tp.y)
end
--------------------------------------------------------------------------------
-- 引魂齐射
--------------------------------------------------------------------------------
function SoulWisp.CallAll()
for i = 1, #_wisps do
_wisps[i].fireT = 0
end
if #_wisps > 0 then
ShowTip(#_wisps .. "盏魂火齐劈——" .. (#_wisps * CONST.WISP_DMG) .. "点一轮")
else
ShowTip("没有魂火——先杀小怪攒")
end
end
--------------------------------------------------------------------------------
-- 定时器:轨道绘制与索敌节拍
--------------------------------------------------------------------------------
if not _scheduler then
_scheduler = SL:Schedule(function(dt)
local mp = GetActorPos(SL:GetValue("USER_ID"))
if not mp then return end
local tid = SL:GetValue("MARK_TARGET_ID")
local tp = tid and GetActorPos(tid)
for i = 1, #_wisps do
local w = _wisps[i]
local a = SL:GetValue("SYS_TIME") * 1.6 + w.seed
local wx = mp.x + m_cos(a) * CONST.ORBIT_R
local wy = mp.y - 56 + m_sin(a * 1.3) * 20
SL:MoveNode(w.node, wx, wy)
if not tp then
w.fireT = math.min(w.fireT, 0.4)
else
w.fireT = w.fireT - dt
if w.fireT <= 0 then
w.fireT = CONST.WISP_GAP
Wisp_Fire(w, mp, tid, tp)
end
end
end
end, 0.02)
end
SL:RegisterLUAEvent(LUA_EVENT_MONSTER_DEAD, "SLW_Kill", function(data)
if data and data.isMinion then
Wisp_Spawn()
end
end)
SL:RegisterLUAEvent(LUA_EVENT_SCENE_CLEAR, "SLW_Clear", function()
for i = 1, #_wisps do SafeRemove(_wisps[i].node) end
_wisps = {}
end)
SL:ScheduleOnce(function()
ShowTip("技能已加载: 魂卫 (调试按钮触发)")
end, 1.0)
return SoulWisp
下半场的两处见功夫。魂火的轨道相位用 seed 错开——三盏魂火各转各的圈,重叠成一盏的版本就是没写 seed。索敌的目标无着落时把出手计时压回零点四——没有可打的目标时魂火憋着不空劈,目标一出现半秒内接上火力;劈完的出手计时重置回两秒,齐射的扳机就是把所有盏的计时器同时清零,清得越齐,那一轮越像一张弓。
五、调参指南与商业服怎么用
调参三笔账。三盏封顶对两秒一劈八点,满魂火的被动产值是每秒十二——贴着玩家普攻产值的八成走,魂卫是副刀,盖过普攻的魂卫会反客为主。收割上限三盏对两只小怪的场面意味着"有杀必满"——想让玩家珍惜每一杀的版本,把上限压到二、把怪提到三只,白给的击杀就有了取舍。引魂齐射是把被动转主动的扳机——齐射产值二十四不设冷却门槛的设计,齐射后所有盏从零计时,齐射的代价是两秒的火力真空。齐射后所有盏从零计时,齐射的代价是两秒的火力真空。魂火的照明也有讲究——魂火飘过的地方地面泛一层淡青,三盏齐亮的时候,玩家身后像提着三盏灯笼。魂火飘过的地方地面泛一层淡青,三盏齐亮的时候,玩家身后像提着三盏灯笼。引魂齐射的出手也要有先后——三盏错开十分之一拍齐劈,数字三连跳比同帧一笔好看得多。常见坑三个:小怪的死因不查,魂火被毒死的也算玩家收割;玩家死亡魂火不散,复活后带着上一世的魂火;魂火的轨道节点不随玩家瞬移更新,传送后魂火从半场外飘过来。
商业服里魂卫是"收割美学"的门面:魂火的颜色按赛季换——春樱、夏萤、秋枫,收割的每一杀都带着当季的限定色。副本里它是清怪速度的乘法:三盏魂火齐劈的清场效率把小怪关的通关时间砍半,效率榜上魂卫玩家的名字永远靠前。变体做成魂火继承:玩家倒下时魂火飞向队友,收割权随魂火移交——团队玩法里最浪漫的交接棒。这一篇的演示和整套模块照抄能跑,改的就是 CONST 那张表。
写完留一句给做收割系的同学:魂卫卖的是"每一杀都有回响"——身后飘着的三盏魂火,是把击杀清单挂在了玩家腰间。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 怪的仇恨为什么永远追着你?因为它看得见你。迷魂香把"看得见"这三个字没收了:一炉香落在它脚下,烟圈六秒——圈里…
【游戏功能】 连击值攒着有什么用?多数战斗的答案是攒个伤害加成——气势给了第二个答案:攒的是一支军队的士气。每一刀涨十二点、…
【游戏功能】 一道割裂挂上去,一秒一跳——它站着跳四,追你的时候崩成八:伤口一动就崩,跑得越快掉得越凶。崩血把"放风筝"从走…
【游戏功能】 突进出去了,回不回得来?多数突进的答案是"不回"——人留在怪堆里听天由命。燕回式给突进装了归途:穿身而过去时一…
【游戏功能】 读条变红的那一秒,全世界都在等一个决定。破招式把这一秒做成整场战斗的心跳:怪交替出红劈和蓝刺,红劈读条时按下破…
【游戏功能】 出招的顺序算不算伤害?大多数战斗里不算——三招乱按和按谱出,账面一个样。连招谱把顺序变成了伤害本身:「劈→撩→…