【游戏功能】
怪的仇恨为什么永远追着你?因为它看得见你。迷魂香把"看得见"这三个字没收了:一炉香落在它脚下,烟圈六秒——圈里的怪当场迷失方向,转着磨磨乱走乱拍,你在圈外一刀一刀点它,它连你在哪都分不清。这篇的核心代码照引擎技能脚本的全规格写:配置表、香烟构件、迷向状态机、定时器、调试按钮。
一、效果演示:一炉香,一场没有方向的仗
演示场点「焚香!」:一尊小香炉落在怪脚下,一圈绿烟的边界慢慢转——怪踩进烟圈的瞬间头顶蹦出三个问号,行动全乱:半秒一换方向地乱走,抬爪乱拍空气,走两步撞回圈心。你站在烟圈外,普攻一刀一刀点它——它够不着你,因为你在它的世界里不存在。六秒香烬,烟散的那一帧它猛地回神,第一个动作就是朝你冲——散香前的最后两秒是玩家收尾的窗口。演示里可以看两处:它在烟里的转向没有一次是朝你的,烟散的第一眼它必找你——迷与醒之间隔的就是那圈烟沿。迷与醒之间隔的就是那圈烟沿。烟圈的飘法也有讲究——绿烟一缕一缕从炉口斜着往上飘,烟的慵懒就是香的性格。
fx-incense
二、底层原理:一份迷向标记加一套乱走状态机
模块的机关是一对搭档。迷向标记:怪在烟圈内时挂上 confused,挂上即与玩家的仇恨解绑——寻路目标清空、出手目标清空,怪的每个行动都改从乱数表里抽:半秒抽一次方向、半秒抽一次抬爪。烟圈判定每帧跑:进圈挂标、出圈摘标——摘标的那一帧仇恨重建,怪直奔玩家的旧账新账一起算。乱走状态机与仇恨解绑的意义在"彻底"两个字:迷向的怪还保留一半目标信息的版本,玩家站着不动它迟早又摸过来——迷向要迷得干净,转磨磨才转得理直气壮。
三、核心代码:完整模块(上·骨架)
--------------------------------------------------------------------------------
-- @file Incense.lua
-- @brief 迷魂香 —— 一炉香点着,圈里的怪掉头打转
-- 客户端表现模块:配置表 / 香炉构件 / 迷向状态机 / 定时器
--------------------------------------------------------------------------------
local Incense = {}
--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
SMOKE_LIFE = 6.0, -- 烟圈六秒
SMOKE_CD = 10.0, -- 焚香冷却
SMOKE_R = 90, -- 烟圈半径九十
WANDER_GAP = 0.5, -- 半秒抽一次行动
WANDER_SPEED = 20, -- 乱走的步速
-- 颜色
COLOR_SMOKE = "#b8c89a", -- 香绿
COLOR_DEEP = "#5a6a3a", -- 深绿
COLOR_GHOST = "#e0ecd0", -- 香白
ZORDER_GROUND = 118,
ZORDER_SMOKE = 127,
ZORDER_EFFECT = 135,
AUTOINC_BASE = 949000,
}
--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _smokeT = 0 -- 烟圈余时
local _smokeX = 0 -- 炉心横坐标
local _cdLeft = 0
local _wanderT = 0 -- 乱走抽签计时
local _wanderDir = 1 -- 乱走方向
local _confused = false -- 迷向标记
local _scheduler = nil
local _autoInc = 0
local m_rand = math.random
local m_cos = math.cos
local m_sin = math.sin
--------------------------------------------------------------------------------
-- 辅助函数
--------------------------------------------------------------------------------
local function GenID(prefix)
_autoInc = _autoInc + 1
return string.format("%s_%d", prefix or "INC", 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 CreateBurnerNode(x, y)
local node = GUI:Layout_Create(GUI:Attach_SceneF(), GenID("Burner"), x, y, 0, 0, false)
GUI:setLocalZOrder(node, CONST.ZORDER_SMOKE)
GUI:DrawLine_Create(node, "pot", 0, 0, 16, -8, 0, 8, -12, CONST.COLOR_DEEP, 240)
GUI:DrawLine_Create(node, "lid", 0, 0, 10, -5, -12, 5, -16, CONST.COLOR_GHOST, 240)
return node
end
骨架的三处立规。烟圈的判定用横坐标对炉心——纵深方向的宽容和荡魂波的带状判定同理,圈是圆柱的投影不是纸片。_confused 是怪的属性标记不是烟圈的属性——烟圈只在"进圈挂标、出圈摘标"两件事上说话,怪的行为怎么乱是怪自己的状态机的事。香炉构件炉身加炉盖两件套——炉是香的根,炉不在烟就没有来处。
四、核心代码:完整模块(下·定时器与迷向状态机)
--------------------------------------------------------------------------------
-- 焚香与迷向
--------------------------------------------------------------------------------
function Incense.Cast(x)
if _cdLeft > 0 or _smokeT > 0 then return end
_cdLeft = CONST.SMOKE_CD
_smokeT = CONST.SMOKE_LIFE
_smokeX = x
CreateBurnerNode(x, 0)
ShowTip("香点着了——烟圈六秒,它在里面转磨磨")
end
--------------------------------------------------------------------------------
-- 定时器:烟圈、迷向与乱走
--------------------------------------------------------------------------------
if not _scheduler then
_scheduler = SL:Schedule(function(dt)
_cdLeft = math.max(0, _cdLeft - dt)
if _smokeT <= 0 then return end
_smokeT = _smokeT - dt
if _smokeT <= 0 then
_confused = false
ShowTip("香烬了——它回过神,第一眼就找你")
return
end
local tid = SL:GetValue("MARK_TARGET_ID")
local tp = tid and GetActorPos(tid)
if not tp then return end
local inSmoke = math.abs(tp.x - _smokeX) <= CONST.SMOKE_R
if inSmoke and not _confused then
_confused = true
SL:ClearHate(tid) -- 仇恨解绑,方向清零
SL:TipAbove(tid, "迷目")
elseif not inSmoke and _confused then
_confused = false
ShowTip("它走出烟了——下一炉跟上")
end
if _confused then
_wanderT = _wanderT - dt
if _wanderT <= 0 then
_wanderT = CONST.WANDER_GAP
_wanderDir = m_rand() > 0.5 and 1 or -1
SL:WanderStep(tid, _wanderDir * CONST.WANDER_SPEED * 0.5)
end
end
end, 0.02)
end
SL:RegisterLUAEvent(LUA_EVENT_SCENE_CLEAR, "INC_Clear", function()
_smokeT = 0
_confused = false
end)
SL:ScheduleOnce(function()
ShowTip("技能已加载: 迷魂香 (调试按钮触发)")
end, 1.0)
return Incense
下半场的迷向是一条三步链:进圈挂标、挂标解绑、解绑后乱走——三步的顺序不能动,先乱走后解绑的版本怪会一边迷向一边继续追人,圈的威严全无。出圈的摘标只做一件事:摘标不追击——追击交给怪自己的仇恨重建,香圈的角色到"解绑"为止。乱走的半步距离 WanderStep 每次半秒二十步——步子太大走出烟圈、太小原地打摆,二十步的磨磨转刚刚好。
五、调参指南与商业服怎么用
调参三笔账。烟圈九十步对怪的攻距三十步——怪在圈里够不着圈外的你,圈的宽度就是安全感的宽度;圈缩到四十步,怪站在烟里也能拍到你,迷向就白迷了。烟圈六秒对散香后的仇恨重建是收尾的窗口——六秒里前四秒转磨磨、后两秒收尾,节奏刚好;八秒的烟圈会让收尾变成磨洋工。乱走的半秒抽签是迷向的"醉态"——抽签间隔加长到一秒,怪的乱走变成匀速平移,醉态尽失。常见坑三个:迷向只清了移动目标没清出手目标,怪在圈里乱走却朝玩家的方向放招;烟圈的粒子在香烬后还飘,无香之烟满屏乱窜;散烟的第一帧没有醒目提示,玩家不知道随时会被反扑。
商业服里迷魂香是"控场流派"的气质单品:香的品级从线香到盘香到檀香,烟圈的纹样一圈比一圈讲究,控场职业的排面第一次不靠伤害撑。控场职业的排面第一次不靠伤害撑。迷向的怪连仇恨的重建都要慢半拍——散烟后它愣一下才想起你,这一愣是控场玩家的奖赏。这一愣是控场玩家的奖赏。香炉的余烬也要留两秒——炉还立着烬还亮着,玩家知道下一炉该落在哪。副本里它是救场技的顶配:全队被追时一炉香落地,追兵原地转磨磨,全队从烟边绕行的画面就是最好的副本宣传片。变体做成香阵叠加:两炉香圈的交叠区产生"深睡",迷向升级成定身——香与香的几何又是一层玩法。香与香的几何又是一层玩法。烟圈的内圈还可以做深浅两段——外圈迷向、内圈降攻,圈的层次多一层,站位的学问厚一页。这一篇的演示和整套模块照抄能跑,改的就是 CONST 那张表。
写完留一句给做迷向系的同学:迷魂卖的是"存在的消失"——在它的世界里你被整个抹掉的那一刻,控场就成了叙事。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 怪的仇恨为什么永远追着你?因为它看得见你。迷魂香把"看得见"这三个字没收了:一炉香落在它脚下,烟圈六秒——圈里…
【游戏功能】 连击值攒着有什么用?多数战斗的答案是攒个伤害加成——气势给了第二个答案:攒的是一支军队的士气。每一刀涨十二点、…
【游戏功能】 一道割裂挂上去,一秒一跳——它站着跳四,追你的时候崩成八:伤口一动就崩,跑得越快掉得越凶。崩血把"放风筝"从走…
【游戏功能】 突进出去了,回不回得来?多数突进的答案是"不回"——人留在怪堆里听天由命。燕回式给突进装了归途:穿身而过去时一…
【游戏功能】 读条变红的那一秒,全世界都在等一个决定。破招式把这一秒做成整场战斗的心跳:怪交替出红劈和蓝刺,红劈读条时按下破…
【游戏功能】 出招的顺序算不算伤害?大多数战斗里不算——三招乱按和按谱出,账面一个样。连招谱把顺序变成了伤害本身:「劈→撩→…