开发技巧
Lua语言编码技巧
优先使用局部变量
在Lua中,访问局部变量要远要快于访问表属性或者全局变量。如果一个变量被频繁使用,可优先考虑将其存到局部变量中。如:
local Vector3 = math.Vector3
local RegEvent = LuaAPI.global_register_trigger_event
优先使用整数循环和整数下标
在Lua中,整数循环相比于泛型循环拥有更好的性能。在纯计算代码中,可考虑优先使用整数循环组织代码。
for i = 1, 10 do
foo(tab[i])
end
避免频繁取表长度
在Lua中,表的长度并未记录在表的内存中,每次取其长度都会重新计算,这会带来一定开销。如果可能,尽量避免频繁读取表长度:
local length = #tab
for i = 1, length do
foo(tab[i])
end
for j = 1, length do
bar(tab[i])
end
适情况减少表和复杂类型的数量
对于需要大量存储和访问的数据,可以考虑使用面向数据编程的一些策略。现在假设我们有海量的数据,每个数据是这样一个表:
local data = {
name = "foo",
pos = { x = 1.0, y = 2.0 },
size = 5.0,
}
local all_data = {
data, data1, data2,
}
则可以考虑存储为三个表:
local all_names = { "foo", ... }
local all_pos = { 1.0, 2.0, ... }
local all_size = { 5.0, ... }
这样可以较大程度地减少表的数量,减轻内存占用,缓解Lua垃圾回收的压力。不过这样写也有明显的缺点:使用和维护的复杂度都增加了。请在您真的需要对海量数据做优化时考虑此方案。
除此以外,在存储大量数据时,基础类型int/定点数的访存性能都要优于存储Vector3,Quaternion这样的高级类型。如果您的数据主要是用于存储,较少用于计算,可以考虑将:
local positions = { math.Vector3(1.0, 2.0, 3.0), math.Vector3(11.0, 22.0, 33.0), ... }
展开为:
local positions = { 1.0, 2.0, 3.0, 11.0, 22.0, 33.0 }
这样改造后,访问数据时需要自己处理下标的转换,但是可以带来一定的性能优化。
避免频繁转换数据类型
在Lua中,数据类型间的转换往往涉及到比较重的逻辑判断,如果可能,应该尽量复用转换的结果。
使用print和traceback获取运行信息
蛋仔的print支持直接打印表,traceback则可以获取当前的调用栈信息。这对于调试游戏逻辑非常有用。
游戏逻辑开发技巧
分帧执行重度逻辑
如果您在一帧内执行的逻辑过多,或者使用了某些较耗时的API(如创建单位),那么玩家可能会面临较长时间的卡顿,影响体验。在这种情况下,可以考虑通过将逻辑分在不同的帧中(通过LuaAPI.call_delay_frame)来缓解玩家侧的卡顿感。
缓存已创建的对象
游戏中创建单位/特效等操作都属于比较耗时的操作。如果可能,维护一个Lua管理的对象池,复用这些对象,会较大程度上缓解游玩时的顿卡。
使用触发器代替Tick轮询
许多时候,为了逻辑状态管理的方便,我们会过度依赖每帧Tick来开发游戏逻辑。比如每帧去检查某个物体是不是进入了特定范围,用来实现技能范围/光环类效果。如果需要检查的物体较多,这样做的性能开销会比较高。
使用触发器事件来监听单位进出某个范围就可以有效避免这个开销。只有当单位真的触发事件之后,我们才需要使用Lua代码来对它们进行处理。对于复杂的需求,可以通过触发器先筛一轮需要Tick的单位,也能起到不错的效果。