饥荒

时间停止

2019-07-10  本文已影响0人  能鸽善舞的小豪

简介

“时间停止”是晓美焰的标志性技能,其效果顾名思义,冻结时间,控制除自己以外的所有物体,暂停其一切行为。当这个技能发动的时候,怪物无法移动和攻击,花草树木不会生长,查理不会伤害黑暗中的玩家,飞行物将被定在原地,就连雨滴、雪和火山灰也会暂时悬浮在半空中。

时停技能可通过点按技能图标(在饥饿值图标左侧)来主动触发,持续8秒,技能结束后会进入冷却,时间为25秒(单机版)/30秒(联机版)。

在单机版中,世界时钟也会因为这个技能停摆。换言之,如果你频繁发动时间停止,那么一天的时间可能会延长到10分钟,而非默认的8分钟。这拖延了猎犬、蝙蝠和四季boss的来袭时间,但也意味着更多的饥饿值消耗和魔力的流失。

在联机版中,这个技能不会真正地停止游戏时间,并且它只会作用于晓美焰附近40单位距离内的物体,所以离晓美焰太远的玩家可能并不会察觉到这个技能被发动,但一旦玩家踏入魔法的控制范围,则ta也同样会被冻结。作为发动者,晓美焰自己显然不会受时间停止的影响,而其他玩家也可以在魔法栏位利用1个金块,1个齿轮和15点理智值制作一个精致的怀表,防止自己遭受时间魔法控制。


这些实体可以免疫时停:

这些实体可以暂时免疫时停:


代码实现

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
上一篇 下一篇

猜你喜欢

热点阅读