云脚本功能
功能简介
云脚本功能是y3编辑器的新增功能,允许作者将一部分敏感逻辑移动到服务端,从而显著提高作弊等违规操作的成本。
地图工程文件夹中的cloud_script文件夹为云脚本文件夹,在上传地图时,这些代码会被放入隔离的云脚本运行环境。运行时,客户端需要使用API向云脚本发送事件,同时,云脚本也可以读取一些玩家数据,并向客户端发送消息。
你可以点击这里下载云脚本的示例地图。注意,需要使用本地多开模式运行才能正常启用云脚本。
建议保留示例地图中cloud_script文件夹中的meta.lua文件,该文件可以提供云脚本接口的的代码提示。
快速开始(使用ECA)
在ECA中使用云脚本功能时,你需要使用【游戏 - 发送云脚本自定义消息】来将数据发送到云脚本;该ECA接收两个参数:字符串类型的事件名,和表类型的数据表。
需要注意的是,表结构中必须仅使用基础数据类型——对于类似单位、玩家等y3的复杂类型,在发送时会由于序列化失败而导致发送错误。
如果想使用ECA接收云脚本的信息,需要触发器监听【游戏 - 收到云脚本自定义消息】事件来处理。该事件带有三个参数:表类型的云脚本数据、字符串类型的事件名、和玩家类型的目标玩家。
额外需要注意的是,接收云脚本消息时,消息并非定向发送给指定的玩家,而是所有监听了这个事件的客户端都能够收到这个消息。对于一些仅希望对特定玩家生效的事件,你需要在这之后编写额外的逻辑进行处理。
快速开始(使用Lua)
在地图中导入y3库后,云脚本相关的代码提示已经被包含进来。
lua客户端逻辑的编写方法与ECA类似,限制也相同。
想要发送一条数据到云脚本,使用:
y3.game.send_mlscript_msg(Custom_Event_Name, Custom_Event_Data)想要接收云脚本的数据,使用:
y3.game:event("玩家-收到云脚本消息", function (trg, data)
-- 此处编写处理逻辑
end)云脚本逻辑编写
在地图工程文件的cloud_script文件夹内,main.lua作为云脚本的运行入口,会在运行时被调用。云脚本会将该文件夹作为根目录处理,使用require即可获取文件夹下的其他文件。
如果地图工程中没有cloud_script文件夹,则云脚本功能不启用。 云脚本部署在服务器上,目前没有热更功能。如果你想要查看修改后的云脚本逻辑的效果,必须重启游戏。
事件注册与取消注册
RegisterEvent
注册事件,当云脚本触发内置的事件,或从客户端发送事件时,会触发注册的回调函数。
function RegisterEvent(event_name, callback) -> integer参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
event_name | string | 事件名称;注意以下划线_开头的名称为内置的事件,自定义事件不要使用下划线开头 |
callback | function | 回调函数 |
返回值:
| 类型 | 描述 |
|---|---|
integer | 事件唯一 ID,用于后续注销事件 |
支持的事件类型:
_roomloaded - 房间加载完成事件
RegisterEvent("_roomloaded", function(event_idx, role_id, event_name, player_guids)
-- event_idx: integer - 事件索引
-- role_id: integer - 玩家角色 ID
-- event_name: string - 事件名称
-- player_guids: userdata - 玩家 ID 数组,可以通过遍历或下标访问获得其中的数据
end)_roomover - 房间结束事件
RegisterEvent("_roomover", function(event_idx, role_id, event_name, reason)
-- event_idx: integer - 事件索引
-- role_id: integer - 玩家角色 ID
-- event_name: string - 事件名称
-- reason: string - 结束原因
end)_playerexit - 玩家退出事件,一般不能重连回来
RegisterEvent("_playerexit", function(event_idx, role_id, event_name, reason)
-- event_idx: integer - 事件索引
-- role_id: integer - 玩家角色 ID
-- event_name: string - 事件名称
-- reason: string - 退出原因
end)_playerleave - 玩家离开事件
RegisterEvent("_playerleave", function(event_idx, role_id, event_name, reason)
-- event_idx: integer - 事件索引
-- role_id: integer - 玩家角色 ID
-- event_name: string - 事件名称
-- reason: string - 离开原因
end)_playerjoin - 玩家重连事件
RegisterEvent("_playerjoin", function(event_idx, role_id, event_name, reason)
-- event_idx: integer - 事件索引
-- role_id: integer - 玩家角色 ID
-- event_name: string - 事件名称
-- reason: string - 加入原因
end)_item_info_changed - 道具信息变更事件
RegisterEvent("_item_info_changed", function(event_idx, role_id, event_name, store_id, before_cnt, after_cnt)
-- event_idx: integer - 事件索引
-- role_id: integer - 玩家角色 ID
-- event_name: string - 事件名称
-- store_id: integer - 道具存储 ID
-- before_cnt: integer - 变更前数量
-- after_cnt: integer - 变更后数量
end)自定义事件
注意这里的evalue是字符串类型,且格式为json,云脚本环境下,你可以使用cjson库来完成json字符串的解析和序列化。
-- custom_event_name可替换为任意字符串
RegisterEvent("custom_event_name", function(event_idx, role_id, event_name, evalue)
-- event_idx: integer - 事件索引
-- role_id: integer - 玩家角色 ID
-- event_name: string - 事件名称
-- evalue: string - 事件参数
end)UnRegisterEvent
注销已注册的事件。注销后的事件不会再触发。
function UnRegisterEvent(event_idx)参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
event_idx | integer | 事件唯一 ID(由 RegisterEvent 返回) |
玩家相关 API
MsGetPlayerName
获取玩家名字。
function MsGetPlayerName(role_id) -> string|nil参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家序号 |
返回值:
| 类型 | 描述 |
|---|---|
string|nil | 玩家名字;玩家或房间不存在时返回 nil |
MsGetPlayerMapLevel
获取玩家的地图等级。
function MsGetPlayerMapLevel(role_id) -> integer|nil参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家序号 |
返回值:
| 类型 | 描述 |
|---|---|
integer|nil | 地图等级;玩家或房间不存在时返回 nil |
MsGetPlayedTime
获取玩家在此地图的游玩时长。
function MsGetPlayedTime(role_id) -> integer|nil参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家序号 |
返回值:
| 类型 | 描述 |
|---|---|
integer|nil | 游玩时长(秒);玩家不存在时返回 nil |
MsGetPlayedCount
获取玩家游玩此地图的次数。
function MsGetPlayedCount(role_id) -> integer|nil参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家序号 |
返回值:
| 类型 | 描述 |
|---|---|
integer|nil | 游玩次数;玩家不存在时返回 nil |
MsGetScriptArchive
获取玩家的脚本存档数据。这个存档数据保存在云脚本环境中。
function MsGetScriptArchive(role_id) -> string|nil参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家序号 |
返回值:
| 类型 | 描述 |
|---|---|
string|nil | 存档内容,以字符串类型返回,需要自行解析;玩家不存在时返回 nil |
MsSaveScriptArchive
保存玩家的脚本存档数据。这个存档数据保存在云脚本环境中。
保存会立刻生效,后续获取存档时会改为获取保存的值,但必须在房间结束后才会落盘,变为持久化数据。
function MsSaveScriptArchive(role_id, script_save_data) -> boolean参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家序号 |
script_save_data | string | 存档数据字符串 |
返回值:
| 类型 | 描述 |
|---|---|
boolean | 是否保存成功 |
道具相关 API
MsGetPlayerItem
获取玩家的道具信息。
function MsGetPlayerItem(role_id, goods_id) -> ItemInfo|nil参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家角色 ID |
goods_id | integer | 道具 ID(对应编辑器中的道具 ID) |
返回值:
| 类型 | 描述 |
|---|---|
ItemInfo|nil | 道具信息对象;不存在时返回 nil |
ItemInfo
道具信息数据结构。
---@class ItemInfo
---@field stack_cnt integer -- 当前堆叠数量
---@field can_use boolean -- 是否可使用
---@field expired_time integer -- 过期时间
---@field max_stack_cnt integer -- 最大堆叠数量| 字段名 | 类型 | 描述 |
|---|---|---|
stack_cnt | integer | 当前堆叠数量 |
can_use | boolean | 是否可使用 |
expired_time | integer | 过期时间戳,单位为秒 |
max_stack_cnt | integer | 最大堆叠数量 |
MsTryConsumeItem
尝试消耗玩家的道具。
function MsTryConsumeItem(role_id, item_store_id, cnt) -> MlRoomErrorCode参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家角色 ID |
item_store_id | integer | 道具 ID(对应编辑器中的道具 ID) |
cnt | integer | 消耗数量 |
返回值:
| 类型 | 描述 |
|---|---|
integer | 错误码(0 表示成功) |
MlRoomErrorCode
道具消耗操作错误码枚举。
| 错误码含义 | 值 | 描述 |
|---|---|---|
OK | 0 | 操作成功 |
RoomNotExist | 2 | 房间不存在 |
PlayerNotExist | 3 | 玩家不存在 |
ItemNotExisted | 12 | 道具不存在 |
ItemNotEnough | 13 | 道具数量不足 |
ItemNotUse | 14 | 道具不可使用 |
房间相关 API
MsGetRoomLoadedTs
获取房间加载完成的时间戳。
function MsGetRoomLoadedTs() -> integer|nil返回值:
| 类型 | 描述 |
|---|---|
integer|nil | 房间加载完成时间戳(秒);房间不存在时返回 nil |
MsGetRoomGameTime
获取房间当前的游戏时长。从加载完成时开始计时。
function MsGetRoomGameTime() -> integer|nil返回值:
| 类型 | 描述 |
|---|---|
integer|nil | 游戏时长(秒);房间不存在时返回 nil |
MsGetRoomPlayerCount
获取房间的玩家数量。
function MsGetRoomPlayerCount() -> integer|nil返回值:
| 类型 | 描述 |
|---|---|
integer|nil | 玩家数量;房间不存在时返回 nil |
通信
MsSendMlEvent
向玩家客户端发送自定义事件。所有客户端都会接到这次发送,通常需要客户端做进一步处理。
注意:event_msg必须为json字符串,这样客户端才能正确解析为table类型。
function MsSendMlEvent(role_id, event_name, event_msg) -> integer参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家角色 ID |
event_name | string | 事件名(最长 32 字节) |
event_msg | string | 事件消息内容(最长 900 字节) |
返回值:
| 类型 | 描述 |
|---|---|
integer | 错误码(0 表示成功) |
结束云脚本
MsEnd
结束玩家的脚本流程(触发房间结算)。
云脚本结束后,这个房间的云脚本不能再恢复,也不会再响应客户端事件或发送信息到客户端。
结算不会立刻发生,云端在收到结算事件后会进行额外的处理,因而存在数秒的延迟。这一点与war3的云脚本功能相同。
function MsEnd(role_id, reason) -> integer参数:
| 参数名 | 类型 | 描述 |
|---|---|---|
role_id | integer | 玩家角色 ID |
reason | string | 结束原因(最长 128 字节) |
返回值:
| 类型 | 描述 |
|---|---|
integer | 错误码(0 表示成功) |
计时器
Timer.new_timer
根据指定的时间间隔,定期调用回调函数。返回一个整数类型的计时器ID。
注意:此处的时间间隔单位为毫秒(ms)而非秒(s)。
单次的计时器与循环调用的多次计时器都使用这个函数声明,区别在于:回调函数返回nil或false时,计时器不会继续调用回调函数;其他情况下,会在给定的间隔之后再次调用回调函数。
-- 创建一个每秒执行一次的定时器
local timer_id = Timer.new_timer(1000, function(id)
Log.info("定时器触发: " .. id)
return true -- 继续执行
end)Timer.cancel_timer
取消ID指定的计时器
-- 取消定时器
Timer.cancel_timer(timer_id)特殊限制
为了合理分配云服务器,防止单一房间占用过多资源,当满足以下任意情况时,当前房间的游戏会被强制关闭,在编写代码时需要注意类似死循环等可能的风险:
- 云脚本单次执行时间超过5秒(UnsafeCostTime)
- 云脚本执行指令数超过10M
- 云脚本执行的超时次数达到20次(UnsafeTimeout)
- 云脚本的内存占用超过10M(UnsafeMemory)
另外,云脚本与客户端通信时,对于事件名长度和参数长度均有额外限制:
- 事件名的最大长度限制为32字节
- 事件数据的最大长度限制为900字节
- 单个玩家的存档容量上限为16M

