Lua 5.3 中每个脚本的独特环境

Cha*_*ton 3 lua game-engine

我希望能够有一大块 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 参数和我的原型之间的区别/实例。我有正确的想法还是我做了一些奇怪的事情?

cat*_*ell 5

对于您的第一个问题(意外创建全局变量),您可以依赖像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)

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