• 产品手册
  • 编辑器功能手册
  • 云脚本

云脚本功能

功能简介

云脚本功能是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_namestring事件名称;注意以下划线_开头的名称为内置的事件,自定义事件不要使用下划线开头
callbackfunction回调函数

返回值:

类型描述
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_idxinteger事件唯一 ID(由 RegisterEvent 返回)

玩家相关 API

MsGetPlayerName

获取玩家名字。

function MsGetPlayerName(role_id) -> string|nil

参数:

参数名类型描述
role_idinteger玩家序号

返回值:

类型描述
string|nil玩家名字;玩家或房间不存在时返回 nil

MsGetPlayerMapLevel

获取玩家的地图等级。

function MsGetPlayerMapLevel(role_id) -> integer|nil

参数:

参数名类型描述
role_idinteger玩家序号

返回值:

类型描述
integer|nil地图等级;玩家或房间不存在时返回 nil

MsGetPlayedTime

获取玩家在此地图的游玩时长。

function MsGetPlayedTime(role_id) -> integer|nil

参数:

参数名类型描述
role_idinteger玩家序号

返回值:

类型描述
integer|nil游玩时长(秒);玩家不存在时返回 nil

MsGetPlayedCount

获取玩家游玩此地图的次数。

function MsGetPlayedCount(role_id) -> integer|nil

参数:

参数名类型描述
role_idinteger玩家序号

返回值:

类型描述
integer|nil游玩次数;玩家不存在时返回 nil

MsGetScriptArchive

获取玩家的脚本存档数据。这个存档数据保存在云脚本环境中。

function MsGetScriptArchive(role_id) -> string|nil

参数:

参数名类型描述
role_idinteger玩家序号

返回值:

类型描述
string|nil存档内容,以字符串类型返回,需要自行解析;玩家不存在时返回 nil

MsSaveScriptArchive

保存玩家的脚本存档数据。这个存档数据保存在云脚本环境中。

保存会立刻生效,后续获取存档时会改为获取保存的值,但必须在房间结束后才会落盘,变为持久化数据。

function MsSaveScriptArchive(role_id, script_save_data) -> boolean

参数:

参数名类型描述
role_idinteger玩家序号
script_save_datastring存档数据字符串

返回值:

类型描述
boolean是否保存成功

道具相关 API

MsGetPlayerItem

获取玩家的道具信息。

function MsGetPlayerItem(role_id, goods_id) -> ItemInfo|nil

参数:

参数名类型描述
role_idinteger玩家角色 ID
goods_idinteger道具 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_cntinteger当前堆叠数量
can_useboolean是否可使用
expired_timeinteger过期时间戳,单位为秒
max_stack_cntinteger最大堆叠数量

MsTryConsumeItem

尝试消耗玩家的道具。

function MsTryConsumeItem(role_id, item_store_id, cnt) -> MlRoomErrorCode

参数:

参数名类型描述
role_idinteger玩家角色 ID
item_store_idinteger道具 ID(对应编辑器中的道具 ID)
cntinteger消耗数量

返回值:

类型描述
integer错误码(0 表示成功)
MlRoomErrorCode

道具消耗操作错误码枚举。

错误码含义描述
OK0操作成功
RoomNotExist2房间不存在
PlayerNotExist3玩家不存在
ItemNotExisted12道具不存在
ItemNotEnough13道具数量不足
ItemNotUse14道具不可使用

房间相关 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_idinteger玩家角色 ID
event_namestring事件名(最长 32 字节)
event_msgstring事件消息内容(最长 900 字节)

返回值:

类型描述
integer错误码(0 表示成功)

结束云脚本

MsEnd

结束玩家的脚本流程(触发房间结算)。

云脚本结束后,这个房间的云脚本不能再恢复,也不会再响应客户端事件或发送信息到客户端。

结算不会立刻发生,云端在收到结算事件后会进行额外的处理,因而存在数秒的延迟。这一点与war3的云脚本功能相同。

function MsEnd(role_id, reason) -> integer

参数:

参数名类型描述
role_idinteger玩家角色 ID
reasonstring结束原因(最长 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