简介
“时间停止”是晓美焰的标志性技能,其效果顾名思义,冻结时间,控制除自己以外的所有物体,暂停其一切行为。当这个技能发动的时候,怪物无法移动和攻击,花草树木不会生长,查理不会伤害黑暗中的玩家,飞行物将被定在原地,就连雨滴、雪和火山灰也会暂时悬浮在半空中。
时停技能可通过点按技能图标(在饥饿值图标左侧)来主动触发,持续8秒,技能结束后会进入冷却,时间为25秒(单机版)/30秒(联机版)。
在单机版中,世界时钟也会因为这个技能停摆。换言之,如果你频繁发动时间停止,那么一天的时间可能会延长到10分钟,而非默认的8分钟。这拖延了猎犬、蝙蝠和四季boss的来袭时间,但也意味着更多的饥饿值消耗和魔力的流失。
在联机版中,这个技能不会真正地停止游戏时间,并且它只会作用于晓美焰附近40单位距离内的物体,所以离晓美焰太远的玩家可能并不会察觉到这个技能被发动,但一旦玩家踏入魔法的控制范围,则ta也同样会被冻结。作为发动者,晓美焰自己显然不会受时间停止的影响,而其他玩家也可以在魔法栏位利用1个金块,1个齿轮和15点理智值制作一个精致的怀表,防止自己遭受时间魔法控制。
这些实体可以免疫时停:
- 带有homuraTag_ignoretimemagic标签(如熔炉模式下的伤害数字)
- 带有INLIMBO标签(放在物品栏和容器内的所有物体)
- 携带精致的怀表(包括玩家、猪人、切斯特等)
- 晓美焰
- 本身不符合上述条件,但母体免疫时停(如玩家释放符咒时的光)
这些实体可以暂时免疫时停:
- 刚刚被扔出的物体(如回力标、水球)
- 刚刚被丢在地上的物体
代码实现
1.计时机制的暂停
暂停实体需要注意很多细节。比如,将一个物体的动画(AnimState)和运动(Physics)暂停以后,还需要阻止它内部的计时机制。以草的燃烧为例,一根草被点燃后,经过7.5秒的燃烧,熄灭变为灰烬,这个过程的计时是基于延时任务函数(DoTaskInTime)。对于这个计时器,官方提供了添加和取消它的方法,但没有提供暂停它的方法,所以得自己写一个。
--这个函数可以让延时任务的完成时间往后推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)
如果想要暂停一个实体的全部延时任务,可以用以下这段代码。
--遍历实体的所有计时器,并让它们的完成时间往后推1帧
--配合组件的OnUpdate函数就能实现计时器暂停
for k in pairs(inst.pendingtasks or {})do
k:AddTick()
end
这样一来,物体内部的计时器就被控制住了。这有什么作用呢?举两个例子。一个干草已经燃烧了5秒,此时发动时停技能,则这个燃烧的草不会在技能持续期间突然变成灰烬,而是在技能结束后的2.5秒才熄灭;反之,先在地上摆5个干草,发动技能,趁技能结束前依次点燃这5个干草,那么这些干草会在技能结束后才会熄灭,并且是同时熄灭。(因为它们是被“同时”点燃)
2.阻止组件刷新
colourtweener(渐变),grue(查理攻击),projectile(子弹)等组件中存在着一个刷新机制,例如海象射出的吹箭,会以每秒30次的频率计算自己和目标的距离,如果距离小于1,就判定为击中目标,否则继续往前飞行。我们并不希望在撞上停止运动的飞行物后会受到伤害,所以要阻止组件的刷新活动。
有少部分组件的刷新是不应该被暂停的,如container(容器),如果暂停的话,会导致界面无法正常关闭。
local skip_components = {
'container', --保证容器界面正常关闭
--[[......]]--
}
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.范围搜索
为了游戏的流畅性,时间停止技能显然不应该将饥荒世界里的所有物体都控制住,它只影响以玩家为中心,40半径范围内的物体——这个范围已经大大超出了玩家的视野范围。
在何时搜索附近实体呢?一般的思路是,在技能发动时搜索一次,把附近所有物体暂停,然后在技能结束时解除暂停状态。但略作思考就能发现问题:假如在技能持续期间,出现了一个新的物体(比如落下来一只鸟),则这个物体是无法受到控制的。因此,我们应该每隔一段时间就进行一次搜索。
--以每秒30次的频率搜索附近实体
local function OnUpdate(inst, dt)
--[[......]]--
--再次遍历周围实体,加入新的,移除出范围的
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
--如果对应值仍然为1,则移除
--如果对应值为2,不移除,并重置为1,然后对这些实体进行更新
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
网友评论