我希望能够有一大块 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

但这失败了,因为我将在所有敌人之间共享全局 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

然后为每个实体创建一个唯一的表,然后在调用 spawn 和 tick 函数时将其作为第一个参数传递。然后在运行时以某种方式将该表映射回 id。这可以工作,但我有几个问题。

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

其次,由于 update 和 tick 函数本身是全局的,当我去创建尝试使用相同界面的第二种类型的敌人时,我仍然会遇到问题。我想我可以用某种命名约定来解决这个问题,但肯定有更好的方法来处理它。

我确实发现了 this 问题,它似乎在问同样的事情,但接受的答案是关于具体细节的,并且指的是 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

在 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);`

这符合我的预期并且我喜欢这个界面,但我不确定对于可能比我更熟悉 Lua 的人来说,它是否遵循最不令人惊讶的路径。即,我使用隐式 self 参数和我的原型(prototype)之间的区别/实例。我有正确的想法还是我做了一些奇怪的事情?

最佳答案

_ENV 在 5.3 中的工作方式是全局变量是从 _ENV 变量读取字段的“语法”糖。例如,一个程序执行

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

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

默认情况下,_ENV 是一个“全局表”,其工作方式与您期望的全局变量的行为类似。但是,如果您创建一个名为 _ENV 的局部变量(或函数参数),那么在该变量的作用域中,任何未绑定(bind)的变量都将指向这个新环境,而不是指向通常的全局作用域。例如,以下程序打印 10:
local _ENV = {
    x = 10,
    print=print
}
-- the following line is equivalent to
-- _ENV.print(_ENV.x)
print(x)

在您的程序中,使用此技术的一种方法是为环境的函数添加一个额外的参数:
tick = function(_ENV, entity, dt)
    -- ...
end

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

也就是说,我不确定 _ENV 是解决您问题的最佳工具。对于意外创建全局变量的第一个问题,一个更简单的解决方案是使用 linter 来警告您是否分配给未声明的全局变量。至于第二个问题,你可以将 update 和 tick 函数放在一个表中,而不是让它们是全局的。

关于lua - Lua 5.3 中每个脚本的独特环境,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39314043/

10-16 15:55
查看更多