【游戏功能】
灵兽的出手为什么总是乱拍?因为它的节拍和你无关。协战把节拍焊在一起:按「号令!」,灵兽追你的出手拍——你的刀和它的爪落在半秒内,合击那下翻倍;对不齐就各打各的,白给。这篇的核心代码照引擎技能脚本的全规格写:配置表、灵兽构件、合击判定、定时器、调试按钮。
一、效果演示:号令一下,两把刀一个拍
演示场点「号令!」:灵兽的出手计时器当场对齐你的——你一刀它一爪,落在半秒内就是合击:绿色的"-24 合击"大字、双闪光、震屏全套伺候。对不齐的时候你十二它十,各自灰字各自跳。号令冷却六秒,六秒里你随时可以重新对拍。号令冷却六秒,六秒里你随时可以重新对拍——对拍的节奏感是协战的手感,对不上是灵兽的脾气。演示里看两处细节:合击的双数字是一大一小并排飘的——你的二十四在上它的十在下;合击的弧光也是双色的——你的白它的绿,两道光弧交叠的那一帧就是"同拍"的定义。合击的双数字是一大一小并排飘的——你的二十四在上它的十在下;合击的弧光也是双色的——你的白它的绿,两道光弧交叠的那一帧就是"同拍"的定义。拍子错开的时候灵兽也要有表情——错拍的爪虚挥一下,灵兽的"没对上"写在动作上。
fx-cofight
二、底层原理:一次节拍对齐加一份合击判定
模块的机关是一对搭档。节拍对齐:号令把灵兽的出手计时器压到和玩家的当前出手计时器一样低——灵兽的下一拍会追上玩家的下一拍,对齐之后的每一拍都自然趋同。合击判定:双方各自的落刀时间戳互相记——你的刀落时查灵兽上一爪的时间戳,差值半秒内就算合击,合击翻倍。时间戳的半秒窗口是合击的宽容度:窗口太窄纯手速、太宽变常驻,半秒卡在"顺手的默契"和"刻意配合"的交界。灵兽的节拍对齐只持续一拍——号令不是永动挂件,是每次主动花冷却买的一次同拍机会。
三、核心代码:完整模块(上·骨架)
--------------------------------------------------------------------------------
-- @file CoFight.lua
-- @brief 协战 —— 灵兽和你同拍出手,同拍那一击翻倍
-- 客户端表现模块:配置表 / 灵兽构件 / 合击判定 / 定时器
--------------------------------------------------------------------------------
local CoFight = {}
--------------------------------------------------------------------------------
-- 配置与常量
--------------------------------------------------------------------------------
local CONST = {
SYNC_WINDOW = 0.5, -- 半秒合击窗
CALL_CD = 6.0, -- 号令冷却
MY_GAP = 1.5, -- 你一点五一刀
PET_GAP = 1.5, -- 灵兽一点五一爪
MY_DMG = 12, -- 你单打十二
PET_DMG = 10, -- 灵兽单打十
SYNC_DMG = 24, -- 合击二十四
-- 颜色
COLOR_SYNC = "#7ec8a0", -- 协战青
COLOR_DEEP = "#1f5a3a", -- 深青
COLOR_GHOST = "#e2f5e8", -- 协白
ZORDER_GROUND = 118,
ZORDER_PET = 130,
ZORDER_EFFECT = 135,
AUTOINC_BASE = 963000,
}
--------------------------------------------------------------------------------
-- 模块内部状态
--------------------------------------------------------------------------------
local _myLast = -9 -- 我上一刀的时间戳
local _petLast = -9 -- 灵兽上一爪的时间戳
local _petAtkT = 0 -- 灵兽出手计时
local _callCd = 0 -- 号令冷却
local _sysT = 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 "CFH", 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 IsSync(lastA, lastB, now)
if lastA < 0 or lastB < 0 then return false end
return math.abs(lastA - lastB) <= CONST.SYNC_WINDOW
end
骨架的三处立规。时间戳用模块自己的时钟 _sysT 记——借用全局时间的版本页面切走再切回来,时间戳全是负数。合击判定查双戳——只查自己的戳不查对方的,等于没查;合击的定义是"两个戳同时近"。号令冷却六秒写在 CONST 里和灵兽的出手间隔分开——两张表互不干涉,调参的时候不会按下葫芦浮起瓢。
四、核心代码:完整模块(下·定时器与合击结算)
--------------------------------------------------------------------------------
-- 双方的出手结算
--------------------------------------------------------------------------------
local function My_Strike(tid)
local sync = IsSync(_myLast, _petLast, _sysT)
local dmg = sync and CONST.SYNC_DMG or CONST.MY_DMG
SL:ActorHurt(tid, dmg)
SL:TipAbove(tid, "-" .. dmg .. (sync and " 合击" or ""))
if sync then
SL:PlayComboFx()
SL:ScreenShake(2.6, 0.12)
end
end
local function Pet_Strike(tid)
local sync = IsSync(_petLast, _myLast, _sysT)
local dmg = sync and CONST.SYNC_DMG or CONST.PET_DMG
SL:ActorHurt(tid, dmg)
SL:TipAbove(tid, "-" .. dmg .. (sync and " 合击" or ""))
end
--------------------------------------------------------------------------------
-- 号令:节拍对齐
--------------------------------------------------------------------------------
function CoFight.Call()
if _callCd > 0 then return end
_callCd = CONST.CALL_CD
_petAtkT = CONST.MY_GAP -- 追玩家的拍
ShowTip("号令下了——灵兽会追你的拍,同拍那下翻倍")
end
--------------------------------------------------------------------------------
-- 定时器:双拍推进
--------------------------------------------------------------------------------
if not _scheduler then
_scheduler = SL:Schedule(function(dt)
_sysT = _sysT + dt
_callCd = math.max(0, _callCd - dt)
_petAtkT = _petAtkT - dt
if _petAtkT <= 0 then
_petAtkT = CONST.PET_GAP
_petLast = _sysT
local tid = SL:GetValue("MARK_TARGET_ID")
if tid then Pet_Strike(tid) end
end
end, 0.02)
end
SL:RegisterLUAEvent(LUA_EVENT_LOCAL_HIT, "CFH_Hit", function()
_myLast = _sysT
local tid = SL:GetValue("MARK_TARGET_ID")
if tid then My_Strike(tid) end
end)
SL:ScheduleOnce(function()
ShowTip("技能已加载: 协战 (调试按钮触发)")
end, 1.0)
return CoFight
下半场的双拍推进是全模块的呼吸:玩家的拍走事件钩子——出手时间戳在 LUA_EVENT_LOCAL_HIT 里记;灵兽的拍走定时器——一秒五一爪的节奏在 _petAtkT 里递减。合击的双向判定写在两边的结算函数里——玩家查灵兽的戳、灵兽查玩家的戳,互查才能保证"谁先落谁后"都算合击。号令的对齐是把灵兽的计时器压到和玩家的间隔一样——压完之后两拍自然趋同,趋同之后每一拍都是合击的机会。
五、调参指南与商业服怎么用
调参三笔账。合击二十四对单打加单打二十二——合击只比分开打多两点,加号的甜头在特效不在数值,玩家的动力是"一起出手"的爽感不是伤害差。号令冷却六秒对拍长一点五秒是四拍一对齐——四拍一轮的对齐频率让协战成为节奏循环而不是一次性爆发。合击窗半秒对出手间隔一点五秒是三分之一的命中率——三分之一的手感刚好卡在"会打就中、乱按就丢"的线上。常见坑三个:合击判定的窗口太大,两秒前的出手也算同拍;灵兽的出手和玩家的出手同帧,双数字叠在一起看不清;号令的对齐覆盖了灵兽自身的出手节奏,号令之后灵兽变成玩家的复读机。
商业服里协战是"宠物交互"的深度课:灵兽的品级决定它的拍能不能追上你的——高阶灵兽出手间隔和玩家一致,低阶的慢半拍永远合不上击,宠物的付费点从属性长到了节奏。副本里它是输出循环的放大器:号令的对齐窗口和爆发技能的冷却对上,合击那一拍就是全队的起手令。变体做成三兽协战:三盏灵兽的拍轮流追你——三拍叠一拍的合击矩阵,配合的复杂度翻着翻地涨。变体做成三兽协战:三盏灵兽的拍轮流追你——三拍叠一拍的合击矩阵,配合的复杂度翻着翻地涨。协战的节奏还能和连招谱交叉——按谱出招的每一招都算一次拍,谱和拍合流就是协战流的高级课。这一篇的演示和整套模块照抄能跑,改的就是 CONST 那张表。
写完留一句给做协战系的同学:协战卖的是"两把刀一个拍"——号令按下去的那一刻,你和灵兽不再是谁带谁,是一支队伍。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 控制技能的镜像问题是:锁住了怪,自己是不是也闲着?影锁把这道两难摆上台面:影锁四秒,它动不了、你也动不了——它…
【游戏功能】 打断为什么只有"清读条"一种?震魂偏要反过来——不清招、不断势,打在它读条半途的那一劲顺着招震进腑——中劲十秒…
【游戏功能】 蜂群为什么总是一只一只出?蜂王偏要让它们进化:放一只蜂王出来,杀掉小怪喂它——喂三只升一阶,三阶蜂王的毒刺一秒…
【游戏功能】 技能为什么总是不扣自己的血?血刃偏要反着来:开着血刃的每一刀先扣自己二血、命中回三血——净赚一但要打得中,空刀…
【游戏功能】 bn.t = bn.t - dt ——一行旗面倒计时的代码,撑起了破军旗全部的紧迫感:旗立三秒、圈内加攻三成,…
【游戏功能】 淬毒为什么要一毒到底?淬毒刃偏要一层一层叠:一刀淬一层毒、毒层一秒六地烂,六层封顶之后一刀下去毒伤三十六——叠…