Skip to content

快速入门

本教程通过简单示例介绍 Lua 在蛋仔编辑器中的常见用法。

加载 Lua 模块

创建 helper.lua:

lua
local helper = {}

function helper.add(a, b)
    return a + b
end

return helper

main.lua 中使用:

lua
local helper = require("helper")
print("add result: " .. helper.add(1, 2))

蛋码交互

您可以在蛋码中调用Lua函数。

可被蛋码调用的Lua函数需要使用emmylua风格的注释,对函数参数和返回值的格式进行描述。

lua
---@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. 在蛋码中添加事件(如"玩家1的蛋仔 跳跃")
  2. 选择动作(有返回值时需要先创建利用返回值的动作)
  3. 填入参数

蛋码调用 Lua 示例

单位操作

操作函数示例
创建单位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("单位名称")
获取单位 IDunit.get_id()local id = unit.get_id()
通过 ID 获取单位GameAPI.get_unit()local unit = GameAPI.get_unit(id)

注意: 查询单位操作较慢,建议缓存结果。

单位ID

某些API的接口需要传入单位ID,您可以使用unit上的方法get_id()来获取它。

lua
local id = unit.get_id()

相对应地,使用GameAPI.get_unit()可以通过id获取单位:

lua
local unit = GameAPI.get_unit(id)

玩家角色与控制单位

在蛋仔派对中,玩家角色(Role)作为游戏内行为输入的主体,与其控制的单位(Character)并不是同一个概念。我们可以使用get_role()来获取单位目前所属于的玩家角色:

lua
local role = unit.get_role()

相对应地,使用get_ctrl_unit()可以从角色上查询到其所操控的单位:

lua
local unit = role.get_ctrl_unit()

通过GameAPI.get_all_valid_roles()可以获取游戏内的所有玩家角色。下面的代码展示了获得所有玩家所操作蛋仔的方式:

lua
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():

lua
unit.set_position(math.Vector3(0.0, 5.0, 0.0))
unit.set_orientation(math.Quaternion(0.0, 0.0, 0.0))

类似地,我们也可以添加受力,使用apply_force()即可:

lua
 -- 瞬间受力
unit.apply_force(math.Vector3(0.0, 2.0, 0.0))

UI 操作

lua
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()函数来注册其回调函数:

lua
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,第二个是自定义事件的名称。

lua
LuaAPI.global_register_trigger_event({ EVENT.CUSTOM_EVENT, "测试自定义事件名称" }, function (name, unit, data)
    print("Custom Event!")
end)

对于局部触发器,我们就需要指定其事件单位。使用LuaAPI.unit_register_trigger_event来注册事件回调。

比如现在我想要实现一个跳扑二连击的效果:玩家先操作蛋仔跳起后,按下扑键,在脚下播放某个特效,那么修改上面的on_jump函数如下:

lua
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中,还可以发送自定义事件,这些事件可以被蛋码接收。

lua
LuaAPI.global_send_custom_event("MyEvent", nil)

在蛋码中,编辑自定义事件触发器:

运行后可以看到输出:

对于单位上局部事件,也可使用LuaAPI.unit_send_custom_event()API来发送:

lua
LuaAPI.unit_send_custom_event(unit, "事件2", nil)

触发区域

在游戏开发中,一个常用的功能是当玩家走进某个区域时,触发一些动作。在lua里可以通过监听触发区域的事件来实现。

首先在场景中拖出一个触发区域,名称为“通用触发区域1”:

接着编写代码:

lua
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) 为不同的回调显式指定不同的延迟帧数。

随机数

lua
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 参数错误会在控制台报错,但不会阻止代码运行。后续代码可能因此产生连锁错误。

最佳实践:

  1. 仔细检查 API 文档中的参数要求
  2. 使用类型注解和静态检查工具(如 EmmyLua)提前发现类型错误
  3. 在关键位置添加断言(assert)进行运行时检查