快速入门
本教程通过简单示例介绍 Lua 在蛋仔编辑器中的常见用法。
加载 Lua 模块
创建 helper.lua
:
local helper = {}
function helper.add(a, b)
return a + b
end
return helper
在 main.lua
中使用:
local helper = require("helper")
print("add result: " .. helper.add(1, 2))
蛋码交互
您可以在蛋码中调用Lua函数。
可被蛋码调用的Lua函数需要使用emmylua风格的注释,对函数参数和返回值的格式进行描述。
---@export
---@desc 示例函数
---@param a integer
---@param b Fixed
---@param c string[]
---@return string
function foo(a, b, c)
return string.format("A: %d B: %s C: %s", a, b, table.concat(c))
end
在蛋码中调用 Lua 函数:
- 在蛋码中添加事件(如"玩家1的蛋仔 跳跃")
- 选择动作(有返回值时需要先创建利用返回值的动作)
- 填入参数
单位操作
操作 | 函数 | 示例 |
---|---|---|
创建单位 | GameAPI.create_obstacle() GameAPI.create_creature() GameAPI.create_triggerspace ... | local obstacle = GameAPI.create_obstacle(100099, math.Vector3(0, 0, 0), math.Quaternion(0, 0, 0), math.Vector3(1, 1, 1), nil) |
销毁单位 | GameAPI.destroy_unit() | GameAPI.destroy_unit(obstacle) |
查询单位 | LuaAPI.query_unit() | local unit = LuaAPI.query_unit("单位名称") |
获取单位 ID | unit.get_id() | local id = unit.get_id() |
通过 ID 获取单位 | GameAPI.get_unit() | local unit = GameAPI.get_unit(id) |
注意: 查询单位操作较慢,建议缓存结果。
单位ID
某些API的接口需要传入单位ID,您可以使用unit上的方法get_id()来获取它。
local id = unit.get_id()
相对应地,使用GameAPI.get_unit()可以通过id获取单位:
local unit = GameAPI.get_unit(id)
玩家角色与控制单位
在蛋仔派对中,玩家角色(Role)作为游戏内行为输入的主体,与其控制的单位(Character)并不是同一个概念。我们可以使用get_role()来获取单位目前所属于的玩家角色:
local role = unit.get_role()
相对应地,使用get_ctrl_unit()可以从角色上查询到其所操控的单位:
local unit = role.get_ctrl_unit()
通过GameAPI.get_all_valid_roles()可以获取游戏内的所有玩家角色。下面的代码展示了获得所有玩家所操作蛋仔的方式:
local allRoles = GameAPI.get_all_valid_roles()
for _, role in ipairs(allRoles) do
local eggy = role.get_ctrl_unit()
print(tostring(eggy))
end
操作单位
单位支持许多通用操作,比如当你想设置它的位置和旋转的时候,就可以使用set_position()/set_orientation():
unit.set_position(math.Vector3(0.0, 5.0, 0.0))
unit.set_orientation(math.Quaternion(0.0, 0.0, 0.0))
类似地,我们也可以添加受力,使用apply_force()即可:
-- 瞬间受力
unit.apply_force(math.Vector3(0.0, 2.0, 0.0))
UI 操作
local UITaskName, UITaskDesc = table.unpack(LuaAPI.query_ui_nodes({"任务名称", "任务描述"}))
role.set_label_text(UITaskName, "任务1")
role.set_node_visible(UITaskDesc, true)
提示: 每个玩家角色有独立的 UI,操作 UI 的 API 位于角色对象上。 提示:运行时使用query_ui_nodes存在性能问题。您可以使用蛋仔开发助手导出UI节点,从而避免使用query_ui_nodes查询。
接收触发器事件
蛋仔中的触发器分为全局触发器和局部触发器两类。全局触发器不需要指定事件单位,可直接使用LuaAPI.global_register_trigger_event()函数来注册其回调函数:
LuaAPI.global_register_trigger_event({ EVENT.GAME_INIT }, function ()
print("GAME INIT!")
end)
其中第一个参数是待注册事件的描述,注意这个描述是一个lua table,不同的事件会有不同的字段需求。对于游戏初始化(EVENT.GAME_INIT)而言,它没有额外的参数,只需要写 { EVENT.GAME_INIT } 即可。
另一种常见的全局触发器就是自定义事件,这些自定义事件一般是蛋码或者其他Lua代码触发的。对于自定义事件,描述就需要填两个值,第一个是EVENT.CUSTOM_EVENT,第二个是自定义事件的名称。
LuaAPI.global_register_trigger_event({ EVENT.CUSTOM_EVENT, "测试自定义事件名称" }, function (name, unit, data)
print("Custom Event!")
end)
对于局部触发器,我们就需要指定其事件单位。使用LuaAPI.unit_register_trigger_event来注册事件回调。
比如现在我想要实现一个跳扑二连击的效果:玩家先操作蛋仔跳起后,按下扑键,在脚下播放某个特效,那么修改上面的on_jump函数如下:
function on_jump()
local unit = LuaAPI.get_current_unit()
if unit then
print("unit type: " .. LuaAPI.get_value_type(unit))
local id = nil
id = LuaAPI.unit_register_trigger_event(unit, { EVENT.SPEC_LIFEENTITY_RUSH }, function (name, event_unit, data)
GameAPI.play_sfx_by_key(691, unit.get_position(), math.Quaternion(0.0, 0.0, 0.0), 1.0, 1.0, 1.0, true)
LuaAPI.unit_unregister_trigger_event(unit, id)
end)
end
end
其中LuaAPI.unit_register_trigger_event的第一个参数传入目标unit,其余参数格式与全局触发器相同。返回值为触发器id,之后可用于LuaAPI.unit_unregister_trigger_event()来取消此触发器。
这个事件触发器的回调函数中有三个参数name, event_unit, data,在大部分情况下name与注册时使用的名称相同。event_unit往往为触发器本体所在的unit,而data则是自定义数据。此外,如果一个触发器是由某个对象的行为触发的(比如某个对象进入本触发器的区域),那么这个对象就通过data.event_unit来传递。对于其他触发器事件,参数的结构会有所不同,请在文档所附带的API手册中查询。
在上面的示例中,我们使用GameAPI.play_sfx_by_key()来播放一个特效。
播放之后,使用LuaAPI.unit_unregister_trigger_event()取消此触发器。
发送自定义事件
在Lua中,还可以发送自定义事件,这些事件可以被蛋码接收。
LuaAPI.global_send_custom_event("MyEvent", nil)
在蛋码中,编辑自定义事件触发器:
运行后可以看到输出:
对于单位上局部事件,也可使用LuaAPI.unit_send_custom_event()API来发送:
LuaAPI.unit_send_custom_event(unit, "事件2", nil)
触发区域
在游戏开发中,一个常用的功能是当玩家走进某个区域时,触发一些动作。在lua里可以通过监听触发区域的事件来实现。
首先在场景中拖出一个触发区域,名称为“通用触发区域1”:
接着编写代码:
local region = LuaAPI.query_unit("通用触发区域1")
LuaAPI.global_register_trigger_event({ EVENT.ANY_LIFEENTITY_TRIGGER_SPACE, Enums.TriggerSpaceEventType.ENTER, region.get_id() }, function (name, actor, data)
print("进入触发区域!")
end)
触发区域的事件是全局事件,假设我们需要监听生命体的进入,那么事件名就选择ANY_LIFEENTITY_TRIGGER_SPACE,附加参数选择TriggerSpaceEventType.ENTER。
定时器
在lua中,我们可以使用LuaAPI.call_delay_time/LuaAPI.call_delay_frame函数来延迟执行代码。
函数 | 描述 | 示例 |
---|---|---|
LuaAPI.call_delay_frame() | 延迟指定帧数 | LuaAPI.call_delay_frame(30, callback) |
LuaAPI.call_delay_time() | 延迟指定秒数 | LuaAPI.call_delay_time(1.0, callback) |
提示: 这些函数是一次性的,需要在回调中再次调用以实现循环定时器。
提示:在游戏中1秒等价于30帧。call_delay_time()函数内部仍然是以帧为单位进行驱动的。比如,
call_delay_time(0.01, callback1)
不能保证其回调会早于call_delay_time(0.02, callback2)
。
如果您有时序上的需求,可以考虑使用
luaAPI.call_delay_frame(1, callback1)
和luaAPI.call_delay_frame(2, callback2)
为不同的回调显式指定不同的延迟帧数。
随机数
local result = LuaAPI.rand() -- 返回 [0, 1] 区间的定点数
提示: 如需固定随机序列,请自行实现随机算法并使用固定种子。
自定义属性
编辑器内可为预设添加自定义属性,绑定到每个单位上。
然后使用以下API读写:
类型 | 获取 | 设置 |
---|---|---|
整数 | unit.get_kv_by_type(Enums.ValueType.Int, "Key") | unit.set_kv_by_type(Enums.ValueType.Int, "Key", value) |
字符串 | unit.get_kv_by_type(Enums.ValueType.Str, "Key") | unit.set_kv_by_type(Enums.ValueType.Str, "Key", "value") |
提示: 如果您需要给游戏中的某些单位打上自定义的标记,推荐使用自定义属性。这样就不必在lua中手动维护这些标记了。
参数检查
警告: API 参数错误会在控制台报错,但不会阻止代码运行。后续代码可能因此产生连锁错误。
最佳实践:
- 仔细检查 API 文档中的参数要求
- 使用类型注解和静态检查工具(如 EmmyLua)提前发现类型错误
- 在关键位置添加断言(assert)进行运行时检查