我希望能够有一大块 Lua 代码(一个“脚本”),可以在游戏中的敌人类型之间共享,但是脚本的每个实例都有一个独特的执行环境。为了说明我的问题,这是我第一次尝试脚本的外观:
time_since_last_shoot = 0
tick = function(entity_id, dt)
time_since_last_shoot = time_since_last_shoot + dt
if time_since_last_shoot > 10 then
enemy = find_closest_enemy(entity_id)
shoot(entity_id, enemy)
time_since_last_shoot = 0
end
end
Run Code Online (Sandbox Code Playgroud)
但这失败了,因为我将在所有敌人之间共享全局 time_since_last_shoot 变量。然后我尝试了这个:
spawn = function(entity)
entity.time_since_last_shoot = 0;
end
tick = function(entity, dt)
entity.time_since_last_shoot = entity.time_since_last_shoot + dt
if entity.time_since_last_shoot > 10 then
enemy = find_closest_enemy(entity)
shoot(entity, enemy)
entity.time_since_last_shoot = 0
end
end
Run Code Online (Sandbox Code Playgroud)
然后为每个实体创建一个唯一的表,然后在调用 spawn 和 tick 函数时将其作为第一个参数传递。然后在运行时以某种方式将该表映射回 id。这可以工作,但我有几个问题。
首先,它很容易出错。脚本仍然可能意外地创建全局状态,这可能导致以后在同一脚本甚至其他脚本中难以调试问题。
其次,由于 update 和 tick 函数本身是全局的,当我去创建尝试使用相同界面的第二种类型的敌人时,我仍然会遇到问题。我想我可以通过某种命名约定来解决这个问题,但肯定有更好的方法来处理它。
我确实发现这个问题似乎在问同样的事情,但接受的答案是关于具体细节的,并且指的是 Lua 5.3 中不存在的 lua_setfenv 函数。似乎它被 _ENV 取代了,不幸的是我对 Lua 不够熟悉,无法完全理解和/或翻译这个概念。
[编辑] 基于@hugomg 的建议的第三次尝试:
-- baddie.lua
baddie.spawn = function(self)
self.time_since_last_shoot = 0
end
baddie.tick = function(self, dt)
entity.time_since_last_shoot = entity.time_since_last_shoot + dt
if entity.time_since_last_shoot > 10 then
enemy = find_closest_enemy(entity)
shoot(entity, enemy)
entity.time_since_last_shoot = 0
end
end
Run Code Online (Sandbox Code Playgroud)
在 C++ 中(使用sol2):
// In game startup
sol::state lua;
sol::table global_entities = lua.create_named_table("global_entities");
// For each type of entity
sol::table baddie_prototype = lua.create_named_table("baddie_prototype");
lua.script_file("baddie.lua")
std::function<void(table, float)> tick = baddie_prototype.get<sol::function>("tick");
// When spawning a new instance of the enemy type
sol::table baddie_instance = all_entities.create("baddie_instance");
baddie_instance["entity_handle"] = new_unique_handle();
// During update
tick(baddie_instance, 0.1f);`
Run Code Online (Sandbox Code Playgroud)
这符合我的预期,我喜欢这个界面,但我不确定对于比我更熟悉 Lua 的人来说,它是否遵循最不令人惊讶的路径。也就是说,我使用隐式 self 参数和我的原型之间的区别/实例。我有正确的想法还是我做了一些奇怪的事情?
对于您的第一个问题(意外创建全局变量),您可以依赖像luacheck这样的linter或阻止您从Penlight创建像strict.lua这样的全局变量的模块。
然后,为什么不把事情本地化呢?我的意思是两者time_since_last_shoot和tick。这利用了闭包,这是 Lua 最有用的特性之一。如果你想要不同的刻度函数,每个都有自己的变量,你可以这样做:
local function new_tick()
local time_since_last_shoot = 0
return function(entity_id, dt)
time_since_last_shoot = time_since_last_shoot + dt
if time_since_last_shoot > 10 then
local enemy = find_closest_enemy(entity_id)
shoot(entity_id, enemy)
time_since_last_shoot = 0
end
end
end
local tick_1 = new_tick()
local tick_2 = new_tick()
Run Code Online (Sandbox Code Playgroud)
当然,你也可以为此使用环境,但在这里我认为局部变量和闭包是一个更好的解决方案。