Lua 5.3 中每个脚本拥有唯一的环境

我想能够拥有一段 Lua 代码(一个“脚本”),该代码可以在游戏中的敌人类型之间共享,但是每个脚本实例都有一个唯一的执行环境。为了说明我的问题,这是我第一次尝试脚本的样子:

时间自上次射击以来 = 0

tick = function(entity_id, dt)
  时间自上次射击以来 = 时间自上次射击以来 + dt
  如果时间自上次射击以来大于10 then
    enemy = find_closest_enemy(entity_id)
    shoot(entity_id, enemy)
    时间自上次射击以来 = 0
  end
end

但是这会导致失败,因为我会将全局的时间_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

然后,为每个实例创建一个独特的表格,并在调用生成和tick函数时将其作为第一个参数传递。然后在运行时将该表格映射回一个ID。这可能有效,但我有一些担心。

首先,这容易出错。脚本仍然可以意外创建全局状态,这可能导致稍后在同一个脚本甚至其他脚本中难以调试的问题。

其次,由于更新和tick函数本身是全局变量,因此在创建尝试使用相同接口的第二种类型的敌人时仍会遇到问题。我想我可以通过某种命名约定来解决这个问题,但肯定有更好的处理方法。

我发现了这个问题,它似乎正在询问同样的事情,但是接受的答案缺乏具体细节,并引用了Lua_setfenv函数,在Lua 5.3中不存在。它似乎被_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

然后在C++(使用sol2)中:

//在游戏启动时
 sol::state lua;
 sol::table global_entities = lua.create_named_table("global_entities");

//对于每种类型的实体
 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");

//生成敌人类型的新实例时
sol::table baddie_instance = all_entities.create("baddie_instance");
baddie_instance["entity_handle"] = new_unique_handle();

//更新时
tick(baddie_instance, 0.1f);`

这符合我的预期,并且我喜欢这个接口,但我不确定对于更熟悉Lua的人是否会有最少惊喜的路径。主要是,我隐式的self参数使用以及原型/实例的区别。我对此理解得是否正确,或是否做了一些奇怪的事情?

点赞
用户90511
用户90511

在 Lua 5.3 中,_ENV 的工作方式是全局变量是从 _ENV 变量中读取字段的 "句法" 糖果。例如,一个执行以下代码的程序

local x = 10
y = 20
print(x + y)

等价于

local x = 10
_ENV.y = 20
_ENV.print(x + _ENV.y)

默认情况下,_ENV 是一个"全局表",其行为与您预期的全局变量的行为相同。但是,如果您创建了一个名为 _ENV 的局部变量(或函数参数),则在该变量的作用域中,任何未绑定的变量将指向该新环境,而不是指向通常的全局作用域。例如,下面的程序会打印 10:

local _ENV = {
    x = 10,
    print=print
}
-- 下面这行代码等价于 _ENV.print(_ENV.x)
print(x)

在您的程序中,使用这种技术的一种方法是为您的函数添加一个额外的参数以用作环境:

tick = function(_ENV, entity, dt)
    -- ...
end

然后,函数中的任何全局变量实际上只是访问 _ENV 参数中的字段,而不是实际的全局变量。


话虽如此,我不确定_ENV 是否是解决您问题的最佳工具。对于您遇到的第一个问题,“意外创建全局变量”,更简单的解决方案是使用一个代码检查器,在您赋值给未声明的全局变量时提醒您。至于第二个问题,您可以将更新和 tick 函数放在一个表中,而不是让它们成为全局变量。

2016-09-04 06:31:05
用户204011
用户204011

对于你的第一个问题(意外创建全局变量),你可以依靠像luacheck这样的linter或者像Penlight的模块来防止你创建全局变量。

然后,为什么不把这些变量都变成本地变量呢? 我是说time_since_last_shoottick这两个变量。 这充分利用了Lua中最有用的功能之一——闭包。 如果你想要不同的tick函数,每个函数都有自己的变量,你可以这样做:

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()

当然,你也可以使用环境变量解决这个问题,但是我认为局部变量和闭包是解决这个问题的更好的解决方案。

2016-09-04 13:03:58