This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English

Lua 模块

Lua 模块使你的项目更具结构化还可以创建可重用库代码. 这是降低项目复杂度的好办法. Defold 可以使用 Lua 模块功能把脚本引入到其他脚本中去. 它可以封装函数 (和数据) 到专门的文件以便由游戏对象和 GUI 脚本重用.

引入 Lua 文件

Lua 代码保存在游戏项目中的 “.lua” 文件里, 可以由脚本和gui脚本引入. 创建Lua模块, 在 Assets 视图右键点击, 选择 New... ▸ Lua Module. 输入文件名点击 Ok:

new file

假设 “main/anim.lua” 有如下代码:

function direction_animation(direction, char)
    local d = ""
    if direction.x > 0 then
        d = "right"
    elseif direction.x < 0 then
        d = "left"
    elseif direction.y > 0 then
        d = "up"
    elseif direction.y < 0 then
        d = "down"
    end
    return hash(char .. "-" .. d)
end

这样其他脚本就能引入这个文件使用其中的函数:

require "main.anim"

function update(self, dt)
    -- 更新位置, 设置方向之类的
    ...

    -- 设置方向动画
    local anim = direction_animation(self.dir, "player")
    if anim ~= self.current_anim then
        sprite.play_flipbook("#sprite", anim)
        self.current_anim = anim
    end
end

关键字 require 引入了模块. 先从 package.loaded 表中查找模块是否已被加载. 找到后, require 返回 package.loaded[module_name] 的值. 否则, 使用加载其加载并处理模块文件.

require 接文件名的语法有点特别. Lua 把文件名中的 ‘.’ 替换为路径分隔符: 在 macOS 和 Linux 上是 ‘/’ , 在 Windows 上是 ‘\’ .

注意尽量不要像上例那样在全局范围保存数据和定义函数. 这样可能会造成命名冲突, 暴露模块数据或者增加模块调用者间的耦合.

模块

Lua 使用 模块 封装数据和函数. Lua 模块是用来保存函数和数据的普通表. 这个表被定义在本地而不是全局范围:

local M = {}

-- 私有
local message = "Hello world!"

function M.hello()
    print(message)
end

return M

这样就定义好了模块. 使用时也是, 最好把模块定义为本地变量:

local m = require "mymodule"
m.hello() --> "Hello world!"

模块热重载

假设有如下模块:

-- module.lua
local M = {} -- 本地表
M.value = 4711
return M

然后使用这个模块:

local m = require "module"
print(m.value) --> "4711" (如果 "module.lua" 更改了此值并且完成热重载这个值仍然不变)

如果 “module.lua” 更改了此值并且完成热重载 m.value 仍然不变. 为什么呢?

首先, “module.lua” 表建立在本地环境下作为 引用 返回. 重载 “module.lua” 模块时解析了代码但是新建了另一个本地表 m 却没有更新对它的引用.

其次, Lua 加载了模块. 文件第一次被加载时, 会被放入 package.loaded 表中便于后续快速访问. 如果把模块设置为 nil 可以强制它重新加载: package.loaded["my_module"] = nil.

要想正确热重载模块, 需要先加载模块, 重置缓存然后更新每个引用模块的文件. 这不利于优化.

一定需要的话可以考虑在 开发时 使用一个解决方法: 把模块表放入全局空间然后返回 M 的引用. 重载时会更新全局表:

--- module.lua

-- 测试完了还是替换成 local M = {}
uniquevariable12345 = uniquevariable12345 or {}
local M = uniquevariable12345

M.value = 4711
return M

模块和缓存

带缓存模块的数据表在所有调用者之间共享:

local M = {}

-- 所有调用者共享此表
local state = {}

function M.do_something(foobar)
    table.insert(state, foobar)
end

return M

无缓存模块内部不存储缓存数据. 但是它可以把实例缓存作为本地表暴露给调用者. 有若干种方式实现这样的功能:

使用数据表
构造函数返回包含一个值的缓存表. 每个缓存相关函数都需要把实例作为第一个参数传入.
local M = {}
  
function M.alter_state(the_state, v)
    the_state.value = the_state.value + v
end
  
function M.get_state(the_state)
    return the_state.value
end
  
function M.new(v)
    local state = {
        value = v
    }
    return state
end
  
return M

调用如下:

local m = require "main.mymodule"
local my_state = m.new(42)
m.alter_state(my_state, 1)
print(m.get_state(my_state)) --> 43
使用元数据表
构造函数返回缓存表以及所有缓存相关函数:
local M = {}
  
function M:alter_state(v)
    -- 使用 : 声明函数会自动添加第一个参数self
    self.value = self.value + v
end
  
function M:get_state()
    return self.value
end
  
function M.new(v)
    local state = {
        value = v
    }
    return setmetatable(state, { __index = M })
end
  
return M

调用如下:

local m = require "main.mymodule"
local my_state = m.new(42)
my_state:alter_state(1) -- 使用 : 调用函数会自动添加第一个参数"my_state"
print(my_state:get_state()) --> 43
使用闭包
构造函数返回缓存表及所有相关函数. 无需传入实例作为第一个参数 (无需显式传入也无需用冒号隐式传入). 这种方法运行较快因为不必从元数据 __index 查找函数, 但是每个闭包都包含全套功能所以占用内存稍多.
  local M = {}
  
  function M.new(v)
      local state = {
          value = v
      }
  
      state.alter_state = function(v)
          state.value = state.value + v
      end
  
      state.get_state = function()
          return state.value
      end
  
      return state
  end
  
  return M

调用如下:

  local m = require "main.mymodule"
  local my_state = m.new(42)
  my_state.alter_state(1)
  print(my_state.get_state())