脚本组件使用 Lua 编程语言 编程. 脚本像其他 组件 一样附加到游戏对象上, Defold 会在引擎声明循环周期中运行这些 Lua 代码.
Defold 里有三种脚本, 每种脚本对应各自的 Defold 函数库.
Defold 把 Lua 脚本作为引擎生命周期的一部分来执行并且向脚本暴露了一些生命周期函数. 当你把脚本组件附加到游戏对象上时这个脚本就变成了游戏对象及其组件的生命周期的一部分. 脚本加载后先进行上下文评估, 然后引擎开始执行以下函数同时传递一个当前组件实例的引用作参数. 这个参数就是 self
, 可以用来保存组件实例上的各种状态.
self
是一个 userdata 对象, 可以用作 Lua 表但是不能使用 pairs()
或者 ipairs()
迭代, 而且也不能使用 pprint()
输出其内容.
init(self)
function init(self)
-- 这些变量会在组件生命周期中一直存在
self.my_var = "something"
self.age = 0
end
final(self)
function final(self)
if self.my_var == "something" then
-- 做一些清理工作
end
end
update(self, dt)
dt
是从上一帧到这一帧的时差.
function update(self, dt)
self.age = self.age + dt -- 把每帧时差加到 age 上
end
fixed_update(self, dt)
dt
是从上一帧到这一帧的时差. 当 engine.fixed_update_frequency
开启 (!= 0) 时该函数会被调用. 当 game.project 文件里开启了 physics.use_fixed_timestep
的时候, 该函数可以用于对物理对象进行一个稳定的模拟.
function fixed_update(self, dt)
msg.post("#co", "apply_force", {force = vmath.vector3(1, 0, 0), position = go.get_world_position()})
end
on_message(self, message_id, message, sender)
msg.post()
把消息发送到脚本组件上时, 接收方组件的脚本中此函数被调用.
function on_message(self, message_id, message, sender)
if message_id == hash("increase_score") then
self.total_score = self.total_score + message.score
end
end
on_input(self, action_id, action)
acquire_input_focus
) 那么当输入触发时此函数被引擎调用.
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
print("Touch", action.x, action.y)
end
end
on_reload(self)
function on_reload(self)
print(self.age) -- 输出对象的 age
end
带脚本的游戏对象可以实现一些逻辑. 通常, 逻辑是否触发取决于一些条件. 玩家走到敌人一定距离之内才会触发敌人AI; 关闭的们只有在玩家交互之后才能打开等等等等.
在 update()
函数中可以实现复杂的行为定义比如每帧运行的状态机—有时这是不错的用法. 但是每帧调用一个 update()
一点点计时有点浪费. 可以的话最好自己实现一个不依靠update 链式逻辑. 它不是被动等待时间累计而是主动设置触发时间目标. 此外, 链式逻辑的设计往往需要尽量简洁稳定易于实现.
来看一个具体实例. 假设你希望一个脚本初始化2秒后发出一个消息. 然后等待消息被收到, 之后过5秒再发一个消息. 非链式逻辑代码看起里像这样:
function init(self)
-- 计时器.
self.counter = 0
-- 状态.
self.state = "first"
end
function update(self, dt)
self.counter = self.counter + dt
if self.counter >= 2.0 and self.state == "first" then
-- 2 秒后发送消息
msg.post("some_object", "some_message")
self.state = "waiting"
end
if self.counter >= 5.0 and self.state == "second" then
-- 状态改变后 5 秒再发送一个消息
msg.post("another_object", "another_message")
--再次改变状态以避免再次进入这里的代码.
self.state = nil
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("response") then
-- 第一个消息收到, 改变状态
self.state = "second"
-- 清空计时器
self.counter = 0
end
end
本来逻辑挺简单, 代码却麻烦的一塌糊涂. 其实使用携程 (见下文) 就能大大简化代码复杂度, 这回先用内置计时器功能改写一下.
local function send_first()
msg.post("some_object", "some_message")
end
function init(self)
-- 等 2 秒后调用 send_first()
timer.delay(2, false, send_first)
end
local function send_second()
msg.post("another_object", "another_message")
end
function on_message(self, message_id, message, sender)
if message_id == hash("response") then
-- 等 5 秒后调用 send_second()
timer.delay(5, false, send_second)
end
end
这样就简单易懂多了. 不需要导出调整状态变量 — 不小心就出错. 我们还完全离开了 update()
功能. 这样就不必让引擎每秒白白调用 60 次函数, 尽管里面没代码.
可以使用 Lua 预处理器特殊标记, 用于有条件地包含基于编译变体的代码. 例如:
-- 使用关键字: RELEASE, DEBUG 或 HEADLESS
--#IF DEBUG
local lives_num = 999
--#ELSE
local lives_num = 3
--#ENDIF
预处理器作为编译扩展存在. 详情请参见 GitHub 上的扩展页面.
Defold 编辑器支持 Lua 脚本编辑, 还提供语法高亮和自动补全功能. 要让 Defold 补全函数名, 按 Ctrl+Space 会弹出相关函名数列表.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB