当我们为一个可以长时间存在预制体写完基本的逻辑代码后,可能会发现,在我们关闭服务器后下一次进入游戏,这个实体原本的属性会消失,比如,我在关闭服务器之前将一个可以升级的武器费尽心思升到了99级,但一觉醒来,兴高采烈地打开了电脑,想象着自己拿着99级的武器玩着饥荒暖暖,但拿起武器后瞬间石化。
这是因为饥荒在每次服务器加载时,都会将世界中的所有预制体重新加载一遍,相当于初始化,原本保存在预制体及其组件中的数据也就被清空了。这时就需要我们对需要保存的数据进行保存与加载。
一、预制体数据的保存与加载
对于预制体数据的保存与加载,官方给出了函数接口,保存函数与加载函数通常在一起出现,函数接口如下:
-- 这两行一般加在预制体fn()的末尾
inst.OnSave = onsave -- inst.OnLoad为保存函数接口,onload为自定义的保存函数
inst.OnLoad = onload -- inst.OnLoad为加载函数接口,onload为自定义的加载函数
注意:lua语言是区分大小写的。
在onsave和onload中,分别用data来保存与加载数据。data会被保存在世界数据中而不会被清除或是初始化,所以在下次加载世界的时候,我们需要保存的数据被初始化后,通过把data中保存的值赋值给实体inst,就可以实现数据的保存与加载。
具体函数示例如下:
保存:
inst.OnSave会在每次世界保存(即右上角出现科学机器时)调用
-- 这里用stage作为示例,实际上data可以保存更多的数据
local function onsave(inst,data)
data.stage = inst.stage
end
加载:
inst.OnLoad会在每次世界加载时调用
local function onload(inst,data)
inst.stage = data and data.stage or 1 -- 注意如果是第一次加载,data为空,所以必须先判断是否为空
update(inst) -- 这里是用来更新网络同步数据的,在[网络同步数据]中会详细讲解
end
此时便完成了预制体数据的保存与加载。
二、组件数据的保存与加载
若组件不是纯方法,而是同样保存了数据,则组件中的数据也需要被保存,组件中保存的数据会被自动绑定到对应的预制体上。
关于组件的保存与加载,官方也给出了接口,即OnSave()与OnLoad()方法。原理同预制体的保存与加载相同,这两个方法分别会在世界保存时与世界加载时自动调用,不用人为干预。
具体代码示例如下:
保存:
只需要参考前三行即可,下面只是因组件需要,对需要保存的数据进行了判断。
function Pickable:OnLoad(data)
self.transplanted = data.transplanted or false
self.cycles_left = data.cycles_left or self.cycles_left
self.max_cycles = data.max_cycles or self.max_cycles
if data.picked or data.time ~= nil then
if self:IsBarren() and self.makebarrenfn ~= nil then
self.makebarrenfn(self.inst, true)
elseif self.makeemptyfn ~= nil then
self.makeemptyfn(self.inst)
end
self.canbepicked = false
else
if self.makefullfn ~= nil then
self.makefullfn(self.inst)
end
self.canbepicked = true
end
if data.caninteractwith then
self.caninteractwith = data.caninteractwith
end
if not self.useexternaltimer then
if data.paused then
self.paused = true
self.pause_time = data.pause_time
if self.task ~= nil then
self.task:Cancel()
self.task = nil
end
elseif data.time ~= nil then
if self.task ~= nil then
self.task:Cancel()
end
self.task = self.inst:DoTaskInTime(data.time, OnRegen)
self.targettime = GetTime() + data.time
end
end
if data.makealwaysbarren == 1 and self.makebarrenfn ~= nil then
self:MakeBarren()
end
self.protected_cycles = data.protected_cycles ~= nil and data.protected_cycles > 0 and data.protected_cycles or nil
if self.inst.components.witherable ~= nil then
self.inst.components.witherable:Enable(self.protected_cycles == nil)
end
end
加载:
同样也是只需要看前几行:
function Pickable:OnSave()
local data =
{
protected_cycles = self.protected_cycles,
picked = not self.canbepicked and true or nil,
transplanted = self.transplanted and true or nil,
paused = self.paused and true or nil,
caninteractwith = self.caninteractwith and true or nil,
}
if self.cycles_left ~= self.max_cycles then
data.cycles_left = self.cycles_left
data.max_cycles = self.max_cycles
end
if not self.useexternaltimer then
if self.pause_time ~= nil and self.pause_time > 0 then
data.pause_time = self.pause_time
end
if self.targettime ~= nil then
local time = GetTime()
if self.targettime > time then
data.time = math.floor(self.targettime - time)
end
end
end
return next(data) ~= nil and data or nil
end
最后将data表返回,插入到世界数据中加以保存。
return next(data) ~= nil and data or nil意为,若data表为空,则不进行操作;若有数据则保存
三、总结
原理很简单:
保存即为将需要保存的inst数据全部赋值给data表,此外不需要做任何处理。
加载即为将需要恢复的inst数据从data表中读取出来,此外不需要做任何处理。