簡(jiǎn)介
“時(shí)間停止”是曉美焰的標(biāo)志性技能,其效果顧名思義,凍結(jié)時(shí)間,控制除自己以外的所有物體,暫停其一切行為。當(dāng)這個(gè)技能發(fā)動(dòng)的時(shí)候,怪物無法移動(dòng)和攻擊,花草樹木不會(huì)生長(zhǎng),查理不會(huì)傷害黑暗中的玩家,飛行物將被定在原地,就連雨滴、雪和火山灰也會(huì)暫時(shí)懸浮在半空中。

時(shí)停技能可通過點(diǎn)按技能圖標(biāo)(在饑餓值圖標(biāo)左側(cè))來主動(dòng)觸發(fā),持續(xù)8秒,技能結(jié)束后會(huì)進(jìn)入冷卻,時(shí)間為25秒(單機(jī)版)/30秒(聯(lián)機(jī)版)。
在單機(jī)版中,世界時(shí)鐘也會(huì)因?yàn)檫@個(gè)技能停擺。換言之,如果你頻繁發(fā)動(dòng)時(shí)間停止,那么一天的時(shí)間可能會(huì)延長(zhǎng)到10分鐘,而非默認(rèn)的8分鐘。這拖延了獵犬、蝙蝠和四季boss的來襲時(shí)間,但也意味著更多的饑餓值消耗和魔力的流失。
在聯(lián)機(jī)版中,這個(gè)技能不會(huì)真正地停止游戲時(shí)間,并且它只會(huì)作用于曉美焰附近40單位距離內(nèi)的物體,所以離曉美焰太遠(yuǎn)的玩家可能并不會(huì)察覺到這個(gè)技能被發(fā)動(dòng),但一旦玩家踏入魔法的控制范圍,則ta也同樣會(huì)被凍結(jié)。作為發(fā)動(dòng)者,曉美焰自己顯然不會(huì)受時(shí)間停止的影響,而其他玩家也可以在魔法欄位利用1個(gè)金塊,1個(gè)齒輪和15點(diǎn)理智值制作一個(gè)精致的懷表,防止自己遭受時(shí)間魔法控制。
這些實(shí)體可以免疫時(shí)停:
- 帶有homuraTag_ignoretimemagic標(biāo)簽(如熔爐模式下的傷害數(shù)字)
- 帶有INLIMBO標(biāo)簽(放在物品欄和容器內(nèi)的所有物體)
- 攜帶精致的懷表(包括玩家、豬人、切斯特等)
- 曉美焰
- 本身不符合上述條件,但母體免疫時(shí)停(如玩家釋放符咒時(shí)的光)
這些實(shí)體可以暫時(shí)免疫時(shí)停:
- 剛剛被扔出的物體(如回力標(biāo)、水球)
- 剛剛被丟在地上的物體
代碼實(shí)現(xiàn)
1.計(jì)時(shí)機(jī)制的暫停
暫停實(shí)體需要注意很多細(xì)節(jié)。比如,將一個(gè)物體的動(dòng)畫(AnimState)和運(yùn)動(dòng)(Physics)暫停以后,還需要阻止它內(nèi)部的計(jì)時(shí)機(jī)制。以草的燃燒為例,一根草被點(diǎn)燃后,經(jīng)過7.5秒的燃燒,熄滅變?yōu)榛覡a,這個(gè)過程的計(jì)時(shí)是基于延時(shí)任務(wù)函數(shù)(DoTaskInTime)。對(duì)于這個(gè)計(jì)時(shí)器,官方提供了添加和取消它的方法,但沒有提供暫停它的方法,所以得自己寫一個(gè)。
--這個(gè)函數(shù)可以讓延時(shí)任務(wù)的完成時(shí)間往后推1幀
local function PeriodicPostInit(self)
function self:AddTick()
local thislist = scheduler.attime[self.nexttick]
self.nexttick = self.nexttick + 1
if not thislist then
return
end
if not scheduler.attime[self.nexttick] then
scheduler.attime[self.nexttick] = {}
end
local nextlist = scheduler.attime[self.nexttick]
thislist[self] = nil
nextlist[self] = true
self.list = nextlist
end
end
AddGlobalClassPostConstruct('scheduler', 'Periodic', PeriodicPostInit)
如果想要暫停一個(gè)實(shí)體的全部延時(shí)任務(wù),可以用以下這段代碼。
--遍歷實(shí)體的所有計(jì)時(shí)器,并讓它們的完成時(shí)間往后推1幀
--配合組件的OnUpdate函數(shù)就能實(shí)現(xiàn)計(jì)時(shí)器暫停
for k in pairs(inst.pendingtasks or {})do
k:AddTick()
end
這樣一來,物體內(nèi)部的計(jì)時(shí)器就被控制住了。這有什么作用呢?舉兩個(gè)例子。一個(gè)干草已經(jīng)燃燒了5秒,此時(shí)發(fā)動(dòng)時(shí)停技能,則這個(gè)燃燒的草不會(huì)在技能持續(xù)期間突然變成灰燼,而是在技能結(jié)束后的2.5秒才熄滅;反之,先在地上擺5個(gè)干草,發(fā)動(dòng)技能,趁技能結(jié)束前依次點(diǎn)燃這5個(gè)干草,那么這些干草會(huì)在技能結(jié)束后才會(huì)熄滅,并且是同時(shí)熄滅。(因?yàn)樗鼈兪潜弧巴瑫r(shí)”點(diǎn)燃)
2.阻止組件刷新
colourtweener(漸變),grue(查理攻擊),projectile(子彈)等組件中存在著一個(gè)刷新機(jī)制,例如海象射出的吹箭,會(huì)以每秒30次的頻率計(jì)算自己和目標(biāo)的距離,如果距離小于1,就判定為擊中目標(biāo),否則繼續(xù)往前飛行。我們并不希望在撞上停止運(yùn)動(dòng)的飛行物后會(huì)受到傷害,所以要阻止組件的刷新活動(dòng)。
有少部分組件的刷新是不應(yīng)該被暫停的,如container(容器),如果暫停的話,會(huì)導(dǎo)致界面無法正常關(guān)閉。
local skip_components = {
'container', --保證容器界面正常關(guān)閉
--[[......]]--
}
local function CanHook(name)
for _,v in pairs(skip_components)do
if v == name then
return false
end
end
return true
end
local function ComponentPostInit(self)
local old_add = self.AddComponent
function self:AddComponent(name, ...)
old_add(self, name, ...)
if CanHook(name) and self.components[name].OnUpdate then
local cmp = self.components[name]
local old_up = cmp.OnUpdate
cmp.OnUpdate = function(self, ...)
if self.inst:HasTag('homuraTag_pause') then
return
else
old_up(self, ...)
end
end
end
end
end
AddGlobalClassPostConstruct('entityscript','EntityScript', ComponentPostInit)
3.范圍搜索
為了游戲的流暢性,時(shí)間停止技能顯然不應(yīng)該將饑荒世界里的所有物體都控制住,它只影響以玩家為中心,40半徑范圍內(nèi)的物體——這個(gè)范圍已經(jīng)大大超出了玩家的視野范圍。
在何時(shí)搜索附近實(shí)體呢?一般的思路是,在技能發(fā)動(dòng)時(shí)搜索一次,把附近所有物體暫停,然后在技能結(jié)束時(shí)解除暫停狀態(tài)。但略作思考就能發(fā)現(xiàn)問題:假如在技能持續(xù)期間,出現(xiàn)了一個(gè)新的物體(比如落下來一只鳥),則這個(gè)物體是無法受到控制的。因此,我們應(yīng)該每隔一段時(shí)間就進(jìn)行一次搜索。
--以每秒30次的頻率搜索附近實(shí)體
local function OnUpdate(inst, dt)
--[[......]]--
--再次遍歷周圍實(shí)體,加入新的,移除出范圍的
local ignoretags = {"INLIMBO", "homuraTag_ignoretimemagic"}
local searchlocal = not TheNet:IsDedicated()
for player in pairs(inst.centers)do
local x,y,z = player:GetPosition():Get()
local ents = TheSim:FindEntities(x,y,z, MAGIC_RADIUS, nil, ignoretags)
for _, item in pairs(ents)do
if not IsMagicImmue(item) and (item.Network or searchlocal) then
if not inst.items[item] then
inst:AddItem(item)
inst.items[item] = 2
else
inst.items[item] = 2
end
end
--[[......]]--
end
end
--如果對(duì)應(yīng)值仍然為1,則移除
--如果對(duì)應(yīng)值為2,不移除,并重置為1,然后對(duì)這些實(shí)體進(jìn)行更新
for k,v in pairs(inst.items)do
if v == 1 then
inst:RemoveItem(k)
end
if v == 2 then
inst.items[k] = 1
ItemTimeMagicUpdate(k, dt)
end
end
--[[......]]--
end