【游戏功能】
范围技能的镜像问题是:怪站在范围中心怎么办?风暴眼把范围技能翻了个面——满场风暴、环带一秒二十五谁站谁挨,唯独圆心五十步的"风暴眼"是净土。你可以站进眼里避险,但怪也会走进眼里和你抢净土——一个圈,两种人的求生。这篇的核心代码照引擎技能脚本的全规格写:配置表、风暴环构件、眼判定状态机、定时器、调试按钮。
一、效果演示:满场风暴,一眼净土
演示场点「风暴眼!」:风暴从两座之间拔地而起——外环一圈、内眼一圈,环带里紫芒翻滚,一秒二十五的扣血谁站谁挨。玩家往眼里一站:环带的雷在眼沿外炸,眼内纹丝不动——净土二字写在脚下。但怪不认这圈逻辑:它绕着环带外沿转了半圈,一头扎进眼里和你抢地盘——眼内近身战开打,你出圈是送、它出圈也是送,两个都在眼里的对手只能贴身分胜负。八秒风停,一切归位。演示里看三处:环带的旋转紫芒、眼沿的清晰边界、怪进眼时那一格身位的挤压——净土的净与挤,都在这五十步里。净土的净与挤,都在这五十步里。进眼的时机也是题:风暴刚起就进眼,怪随后跟进,净土变斗兽场;等它在外面转够半圈再进眼,独享的净土才叫净土。
fx-stormeye
二、底层原理:一份环带判定加一份眼的豁免
模块的机关是一对同心圆。环带判定:单位到风暴心的距离落在"眼半径"到"眼半径加带宽"之间,就算站在风暴里——一秒二十五地扣。眼的豁免:距离小于眼半径的单位完全豁免,眼是净土不是减速区。怪的走位写两条规矩:它在眼内就贴着你的身位挤压,它在带外就向眼沿推进——怪"想进眼"的倾向就是这场风暴的戏剧冲突:净土只有一份,进眼的双方必有一场贴身战。玩家的最优解同样是眼里——眼内打、带外躲、圈沿卡位,三个动作全在一对同心圆的半径上。眼内打、带外躲、圈沿卡位,三个动作全在一对同心圆的半径上。眼沿的挤压也要演——怪在眼沿外沿探进半个身位又被风压回去,净土的边界在挤压里一遍一遍地反复确认。
三、核心代码:完整模块(上·骨架)
--------------------------------------------------------------------------------
-- @file StormEye.lua
-- @brief 风暴眼 —— 满场风暴,只有眼里是净土
-- 客户端表现模块:配置表 / 风暴环构件 / 眼判定状态机 / 定时器
--------------------------------------------------------------------------------
local StormEye = {}
--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
STORM_LIFE = 8.0, -- 风暴八秒
STORM_CD = 14.0, -- 冷却十四秒
EYE_R = 50, -- 眼半径五十
BAND_W = 26, -- 环带宽二十六
TICK_GAP = 1.0, -- 一秒一跳
TICK_DMG = 25, -- 一跳二十五
-- 颜色
COLOR_STORM = "#8a9fe8", -- 风暴蓝紫
COLOR_DEEP = "#2a2a5e", -- 深暴
COLOR_GHOST = "#e8ecff", -- 眼白
ZORDER_GROUND = 118,
ZORDER_STORM = 126,
ZORDER_EFFECT = 135,
AUTOINC_BASE = 955000,
}
--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _stormT = 0 -- 风暴余时
local _stormX = 0 -- 风暴心
local _cdLeft = 0
local _tickT = 0
local _scheduler = nil
local _autoInc = 0
local m_cos = math.cos
local m_sin = math.sin
local m_abs = math.abs
--------------------------------------------------------------------------------
-- 辅助函数
--------------------------------------------------------------------------------
local function GenID(prefix)
_autoInc = _autoInc + 1
return string.format("%s_%d", prefix or "SME", 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 CreateStormNode(x)
local node = GUI:Layout_Create(GUI:Attach_SceneF(), GenID("Storm"), x, 0, 0, 0, false)
GUI:setLocalZOrder(node, CONST.ZORDER_STORM)
for i = 1, 36 do
local a1 = (i - 1) / 36 * 6.28
local a2 = i / 36 * 6.28
GUI:DrawLine_Create(node, string.format("e_%d", i), 0, 0, 2,
m_cos(a1) * CONST.EYE_R, m_sin(a1) * CONST.EYE_R * 0.38,
m_cos(a2) * CONST.EYE_R, m_sin(a2) * CONST.EYE_R * 0.38,
CONST.COLOR_GHOST, 210)
GUI:DrawLine_Create(node, string.format("o_%d", i), 0, 0, 2,
m_cos(a1) * (CONST.EYE_R + CONST.BAND_W), m_sin(a1) * (CONST.EYE_R + CONST.BAND_W) * 0.38,
m_cos(a2) * (CONST.EYE_R + CONST.BAND_W), m_sin(a2) * (CONST.EYE_R + CONST.BAND_W) * 0.38,
CONST.COLOR_STORM, 200)
end
return node
end
骨架的三处立规。风暴是双圈构件不是渐变糊——眼沿和外沿两条实线圈把"净土"和"绞肉机"的边界画死,模糊的边界等于没有边界。纵轴零点三八压扁——贴地的风暴才有地面的压迫感。TICK_DMG 二十五一跳是重罚——环带的痛要大到"没人愿意多站半秒",轻了风暴就是背景板。
四、核心代码:完整模块(下·定时器与眼判定)
--------------------------------------------------------------------------------
-- 风暴开与眼判定
--------------------------------------------------------------------------------
function StormEye.Cast(x)
if _cdLeft > 0 or _stormT > 0 then return end
_cdLeft = CONST.STORM_CD
_stormT = CONST.STORM_LIFE
_stormX = x
CreateStormNode(x)
ShowTip("风暴起了——环带一秒二十五,眼里是净土")
end
local function InBand(x)
if _stormT <= 0 then return false end
local d = m_abs(x - _stormX)
return d > CONST.EYE_R and d <= CONST.EYE_R + CONST.BAND_W
end
local function InEye(x)
if _stormT <= 0 then return false end
return m_abs(x - _stormX) <= CONST.EYE_R
end
--------------------------------------------------------------------------------
-- 定时器:跳表与怪的抢眼走位
--------------------------------------------------------------------------------
if not _scheduler then
_scheduler = SL:Schedule(function(dt)
_cdLeft = math.max(0, _cdLeft - dt)
if _stormT <= 0 then return end
_stormT = _stormT - dt
if _stormT <= 0 then
ShowTip("风停了——这一仗的净土没了")
return
end
_tickT = _tickT + dt
if _tickT >= CONST.TICK_GAP then
_tickT = _tickT - CONST.TICK_GAP
if InBand(SL:GetValue("USER_ID") and select(1, GetActorPos(SL:GetValue("USER_ID"))).x) then
SL:PlayerHurt(CONST.TICK_DMG)
end
local tid = SL:GetValue("MARK_TARGET_ID")
local tp = tid and GetActorPos(tid)
if tp and InBand(tp.x) then
SL:ActorHurt(tid, CONST.TICK_DMG)
end
end
local tid2 = SL:GetValue("MARK_TARGET_ID")
local tp2 = tid2 and GetActorPos(tid2)
if tp2 then
local d = m_abs(tp2.x - _stormX)
if d < CONST.EYE_R - 10 then
SL:WalkStep(tid2, (tp2.x > _stormX) and 1 or -1)
elseif d > CONST.EYE_R + CONST.BAND_W then
SL:WalkStep(tid2, (tp2.x > _stormX) and -1 or 1)
end
end
end, 0.02)
end
SL:RegisterLUAEvent(LUA_EVENT_SCENE_CLEAR, "SME_Clear", function()
_stormT = 0
end)
SL:ScheduleOnce(function()
ShowTip("技能已加载: 风暴眼 (调试按钮触发)")
end, 1.0)
return StormEye
下半场的抢眼走位是全模块的戏眼:怪的三段走位——眼内压向玩家、带外推进眼沿、带内横着游走——三条规矩让怪的行为从"追人"变成"抢地",风暴的戏剧性全在这一份走位上。跳表的双判定一次查玩家一次查怪——环带不认阵营,谁的脚在带里谁的账就扣,净土的公平写在同一段判定里。
五、调参指南与商业服怎么用
调参三笔账。眼半径五十对怪的体积是"能站两人"的净土——眼小到三十步净土变独木桥,大到八十步眼比战场大,风暴失去意义。环带一跳二十五是重罚——罚得重玩家才肯挤进眼里忍着贴身,罚得轻没人怕环带。带宽二十六步是眼和外界的缓冲——带宽太窄怪一脚跨进眼沿,太宽风暴占了半屏。常见坑三个:眼判定的圆心在战斗中漂移,净土跟着晃;环带的粒子遮住了角色的血条;怪进眼后仍执行环带外的寻路,怪在眼内鬼畜抖动。
商业服里风暴眼是"协作副本"的终极考题:眼内位置有限,谁站眼里、谁出去拉怪、谁卡眼沿输出,团队分工的考卷每八秒就重发一次考卷。攻城战里它是阵地战的天然分界:风暴眼落在城门正中,攻守双方抢眼的八秒就是攻城战的心跳段。变体做成双风暴眼:两个眼眼心相距一百步,两队各占一眼隔带对射——净土的争夺升级成净土的炮台。变体做成双风暴眼:两个眼眼心相距一百步,两队各占一眼隔带对射——净土的争夺升级成净土的炮台。眼的半径还能做成缩的:眼从五十缩到二十,净土的缩小就是节奏的收紧,缩到最后的贴身决战就在所难免、无可回避。这一篇的演示和整套模块照抄能跑,改的就是 CONST 那张表。
写完留一句给做风暴系的同学:风暴眼卖的是"危险的中心"——全场都在赶你走,唯独圆心这块五十步,值得你和它撕一场。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 蓄力技为什么总是单发?影炮偏要贯穿全屏:蓄一秒射一道暗影弹道穿全屏——线上所有目标各挨五十五,弹道从你脚下贯穿…
【游戏功能】 灼烧为什么总烧在皮肤上?灼心偏要烧进心里:心被灼了六秒——六秒里怪每出一招就自损八,出得越多损得越多。这篇的核…
【游戏功能】 魂卫为什么只有三盏就不能多了?魂蜂偏要做成可以积累的:杀掉小怪化一盏魂蜂跟着你走,至多三盏——三盏魂蜂各自两秒…
【游戏功能】 镜墙为什么只能反弹不能冰冻?霜镜偏要双管齐下:弹道撞上霜镜原路画回去——回程的弹道还带着冰冻效果,扎中怪怪也冻…
【游戏功能】 单体射击为什么打不动厚血怪?穿心刺给的答案是:箭伤八加穿心伤,穿心伤等于最大血量百分之六——血越厚的怪越疼。这…
【游戏功能】 影刺的全机制就一句话:化影绕到它身后,从背刺出的那一刀双倍二十四——正面十二、背后二十四,它一转身就变白板。这…