Plugin Lua 集成指南
Plugin Lua 集成指南
目前支持 Lua 5.1 版本
Lua 插件生命周期方法
1.创建房间成功后
-- 创建房间成功后
function OnCreateRoom(room)
-- 输入是Table类型Room
-- muninn.LogInfo("OnCreateRoom")
end
-- 输入: 房间类型Room (Table)
room = {
UosAppId = "", // uos的appid
Id = "", // 房间id
Name = "", // 房间名称
OwnerId = "", // 开发者侧的userId,房主
Namespace = "", // 命令空间
Visibility = "", // 可见性
JoinCode = "", // 邀请码
MaxPlayers = 10, // 房间的最大用户数
Properties = {}, // 房间的自定义属性, 是一个table类型
}
-- 输出: 无返回值2.关闭房间前
-- 关闭房间前
function BeforeCloseRoom(room)
-- muninn.LogInfo("BeforeCloseRoom")
end
-- 输入: 房间类型Room (Table)
room = {
UosAppId = "", // uos的appid
Id = "", // 房间id
Name = "", // 房间名称
OwnerId = "", // 开发者侧的userId,房主
Namespace = "", // 命令空间
Visibility = "", // 可见性
JoinCode = "", // 邀请码
MaxPlayers = 10, // 房间的最大用户数
Properties = {}, // 房间的自定义属性, 是一个table类型
}
-- 输出: 无返回值3.关闭房间后
-- 关闭房间响应
function OnClose(room)
-- muninn.LogInfo("OnCloseRoom")
end
-- 输入: 房间类型Room (Table)
room = {
UosAppId = "", // uos的appid
Id = "", // 房间id
Name = "", // 房间名称
OwnerId = "", // 开发者侧的userId,房主
Namespace = "", // 命令空间
Visibility = "", // 可见性
JoinCode = "", // 邀请码
MaxPlayers = 10, // 房间的最大用户数
Properties = {}, // 房间的自定义属性, 是一个table类型
}
-- 输出: 无返回值4.玩家加入房间前
-- 玩家加入房间前
function BeforeJoin(player)
-- 输入是Table类型Player
-- muninn.LogInfo("before Join")
-- 返回ReturnType
-- 没有返回值,按CONTINUE处理
return muninn.ReturnType.CANCEL
end
-- 输入: 玩家类型Player (Table)
player = {
UniqueId = "", // 开发者侧的唯一用户id
Id = 1, // 房间内部分配的id
Name = "", // 用户名称
JoinCode = "", // 用户带过来的邀请码
Properties = {}, // 用户自定义属性,是table类型
}
-- 输出: 返回值类型 ReturnType
local muninn = require("MuninnPlugin")
[[
ReturnType = {
CONTINUE = 0, // 按房间默认行为
CANCEL = 1 // logic plugin 拦截,表示拦截这消息的后续处理逻辑,
// 比如一款游戏,只允许服务端处理,并下发,不允许各客户端之间发送消息
}
]]
-- CANCEL会把后续操作拦截。
-- 如玩家发起RaiseEvent,但Plugin逻辑经处理后判定不需要发送消息给其他玩家,则返回 CANCEL 即可
-- return muninn.ReturnType.CONTINUE
-- return muninn.ReturnType.CANCEL5.玩家加入房间后
-- 玩家加入房间
function OnJoin(player)
-- 输入是Table类型Player
-- muninn.LogInfo("On Join")
end
-- 输入: 玩家类型Player (Table)
player = {
UniqueId = "", // 开发者侧的唯一用户id
Id = 1, // 房间内部分配的id
Name = "", // 用户名称
JoinCode = "", // 用户带过来的邀请码
Properties = {}, // 用户自定义属性,是table类型
}
-- 输出: 无返回值6.玩家离开房间
-- 玩家离开房间
function OnLeave(player)
-- 输入是Table类型Player
end
-- 输入: 玩家类型Player (Table)
player = {
UniqueId = "", // 开发者侧的唯一用户id
Id = 1, // 房间内部分配的id
Name = "", // 用户名称
JoinCode = "", // 用户带过来的邀请码
Properties = {}, // 用户自定义属性,是table类型
}
-- 输出: 无返回值7.玩家发起的事件
-- 玩家的事件响应
function OnMessage(msg)
-- msg 是Table类型Message
-- msg.Data 表示消息的内容,[]byte类型
-- msg.SenderId 表示消息发送方的senderId,int类型
-- msg.MessageType 获取消息类型,详见下方枚举类型定义
-- msg.TargetReceiverIds 当 MessageType 为 TARGET_TO_PLAYERS 时,此参数为 receiver ID 数组
-- msg.TargetGroups 当 MessageType 为 TARGET_TO_GROUPS 时,此参数为 group ID 数组
-- 返回ReturnType, 见核心枚举类型说明
-- 没有返回值,按CONTINUE处理
return muninn.ReturnType.CONTINUE
end
-- 消息枚举类型,主要用于OnMessage中
MessageType = munin.MessageType
[[
MessageType = { // 根据消息接收方分类
TARGET_TO_ALL = 0, // 发送给所有玩家
TARGET_TO_GROUPS = 1, // 发送给 TargetGroups 指定的群组
TARGET_TO_ALL_BUT_ME = 2, // 发送给除了发送者以外的所有玩家
TARGET_TO_PLAYERS = 3, // 发送给 TargetReceiverIds 指定的玩家
TARGET_TO_MASTER = 4, // 发送给房主
TARGET_TO_PLUGIN = 5 // 发送给 Plugin 使用
}
]]
-- 输出: 返回值类型 ReturnType
ReturnType = muninn.ReturnType
[[
ReturnType = {
CONTINUE = 0, // 按房间默认行为
CANCEL = 1 // logic plugin 拦截,表示拦截这消息的后续处理逻辑,
// 比如一款游戏,只允许服务端处理,并下发,不允许各客户端之间发送消息
}
]]
-- CANCEL会把后续操作拦截。
-- 如玩家发起RaiseEvent,但Plugin逻辑经处理后判定不需要发送消息给其他玩家,则返回 CANCEL 即可插件脚本支持方法缺省。对于不需要实现的某些插件方法,可以不写入脚本。
8.房主切换前
function OnMasterClientChanged(players)
-- 输入为 Table 类型的 player 列表
-- 输出为 uint32 类型的 newMasterClientId
-- for i = 1, #players do
-- end
-- 返回 0 即走默认逻辑选择新房主
return 0
end
-- 输入: 玩家列表players (Table)
players = [{
UniqueId = "", // 开发者侧的唯一用户id
Id = 1, // 房间内部分配的id
Name = "", // 用户名称
JoinCode = "", // 用户带过来的邀请码
Properties = {}, // 用户自定义属性,是table类型
}]功能函数簇
房间服务提供的功能,以lua库的形式动态传入
local muninn = require("MuninnPlugin")1.打印日志
muninn.LogInfo("OnCreateRoom")2.发送消息
发送消息至某个玩家
-- 第一个参数,接收者的id
-- 第二个参数,消息内容
muninn.SendMessage(2, "你好,unity")广播消息
-- 第一个参数,消息内容
muninn.BroadcastMessage("各位好,welcome to unity")3.管理房间或玩家
通过id来获取玩家的信息
player, err = muninn.GetPlayer(2)
多返回值
player 玩家对象
err 0表示调用成功, 非0表示对应的错误码获取在线的玩家列表
players, err = muninn.GetOnlinePlayers()
多返回值
players 玩家对象列表
err 0表示调用成功, 非0表示对应的错误码获取房间信息
roomInfo, err = muninn.GetRoom()
多返回值
roomInfo 表示房间信息
err 0表示调用成功, 非0表示对应的错误码踢人操作
muninn.KickPlayer(2)
-- 参数
-- playerId int 是房间分配给用户的短id设置房间属性
muninn.SetRoomProperty(id, "key", "value")
-- id 为房间分配给玩家的短 id
-- 该操作仅会更新指定key的属性值
-- 操作成功会通知当前房间内的所有玩家设置房间级别的 sticky event
muninn.SetRoomStickyEvent("key", data)
-- data 为[]byte类型的数据4.调度定时器
重复的定时器
-- 示例,调用repeating定时任务,单位为ms
-- 返回值
-- taskId 任务id
taskId = muninn.ScheduleRepeat(EventLoop, 200)
function EventLoop()
-- 名字可以随意取
local t = os.time()
str = os.date("%Y-%m-%d %H:%M:%S",t)
muninn.LogInfo(str .. " call a event loop in lua ")
end一次性定时器
-- 返回值
-- taskId 任务id
muninn.ScheduleOnce(
funcName, -- 函数名称, string 函数指针
interval, -- 调度时间,单位为ms, int
table -- 统一为 Lua Table类型, 该参数可以缺省,缺省表示无参数
)不带参数的定时任务
-- 返回值
-- taskId 任务id
muninn.ScheduleOnce(OnceAd, 2000) -- 2秒后触发,调用 OnceAd 方法
function OnceAd()
muninn.LogInfo("welcome to ad service")
end带参数的一次性任务
muninn.ScheduleOnce(
OnceAdWithParam,
10 * 1000,
{SenderId = msg.senderId)}
)
-- 被定义的函数
function OnceAdWithParam(params)
muninn.SendMessage(params.SenderId, "Alarm")
end任务取消
-- 调度拿到taskId
-- taskId = muninn.ScheduleOnce(OnceAd, 2000) -- 2秒后触发,调用 OnceAd 方法
muninn.CancelTask(taskId)5.调用 Webhook
支持脚本访问外部服务的功能。
- 支持同步/异步的访问。其中同步方法会阻塞房间主事件的逻辑。
- 支持HTTP GET 与 POST 方法。
同步接口 (GET, POST)
req = {
Method = "GET", // GET, POST
Url = "",
Headers = {}, // http 的 headers的参数key/value
Params = {}, // 参数
Data = "", // body,lua字符串和二进制串是一致的
Timeout = 1000, // http操作的超时值, 单位为毫秒,默认是5秒, 可以缺省
}
rsp, err = muninn.SyncHttp(req)
-- 返回结果
-- rsp 是table类型
-- rsp.HttpStatusCode
-- rsp.Data异步接口 (GET, POST)
req = {
Method = "GET", // GET, POST
Url = "",
Headers = {}, // http 的 headers的参数key/value
Params = {}, // 参数
Data = "", // body,lua字符串和二进制串是一致的
Timeout = 1000, // http操作的超时值, 单位为毫秒,默认是5秒, 可以缺省
}
// 第2个参数是回调参数OnService的方法名
// 第3个参数是回调函数的context参数,数据类型为table
muninn.AsyncHttp(req, "OnService", {})
function OnService(context, rsp, err)
-- 具体的实现
end
-- rsp, table类型
-- rsp.HttpStatusCode
-- rsp.Data
-- context, 上下文对象, AsyncHttp里指定6. 错误处理
自定义插件应尽量处理错误,如Webhook超时、空指针、系统栈溢出等。
任何未处理的插件运行时错误,都会导致插件函数执行失败;这种情况下,房间会按照没有该插件函数的默认逻辑继续运行。
-- 常见的错误码说明 枚举类型
ErrorCode = {
OK = 0 // 操作成功
ParamInvalid = 10001 // 参数不合法
OperationFail = 10002 // 操作失败
VMStatusError = 10003 // 脚本的虚拟机状态出错
HttpError = 10004 // http请求出错
HttpIoError = 10005 // http请求io出错
HttpTimeoutError = 10006 // http请求超时
UserNotFound = 20001 // 用户不存在
TaskNotFound = 20002 // 该定时任务不存在
}Demo展示
1. 问答式服务
被动响应, 一问一答的模式,可以借于此实现,比如常规的rpc服务
-- 引入go的函数库
local muninn = require("MuninnPlugin")
-- 定义muninn-plugin约定好的回调函数
function OnCreateRoom(room)
muninn.LogInfo("Create Echo Service Successfully")
end
function OnMessage(msg)
-- msg 是 Table, 需要和Plugin供应商约定
-- msg.Data 获取数据
-- msg.SenderId 获取发送者的senderId
muninn.LogInfo("receive a message, content => " .. msg.Data)
muninn.SendMessage(msg.SenderId, "echo " .. msg.Data)
end
function OnCloseRoom(room)
-- muninn.LogInfo("Close Echo Service Successfully")
end2. 定时闹钟
每隔1分钟,群发一条消息,每个用户可以设定自己的定时推送消息(闹钟消息)。
local muninn = require("MuninnPlugin")
local step = 60 -- 1分钟
local nextTimestamp = math.floor(os.time() / step) * step + step
-- lua 5.3 以后才支持 '//' 取整, 所以这边用了math.floor
--muninn.LogInfo(nextTimestamp)
function OnCreateRoom(room)
muninn.LogInfo("Create Clock Service Successfully")
-- 这边进行事件循环的注册(EventLoop函数,每隔200ms跑一次)
taskId = muninn.ScheduleRepeat(EventLoop, 200)
end
function OnMessage(msg)
-- msg 是 Table
-- msg.Data 获取数据
-- msg.SenderId 获取发送者的senderId
-- muninn.LogInfo("receive a message, content => " .. msg.Data)
-- muninn.SendMessage(msg.SenderId, "echo " .. msg.Data)
-- 触发一个10秒的定时任务
muninn.ScheduleOnce(OnceAdWithParam, 10 * 1000,
{Id = msg.SenderId})
end
function OnCloseRoom(room)
-- muninn.LogInfo("Close Clock Service Successfully")
end
function EventLoop()
-- 名字可以随意取
local now = os.time()
if now >= nextTimestamp then
muninn.BroadcastMessage("[Broadcast] Alarm")
nextTimestamp = math.floor(now / step) * step + step
end
end
function OnceAdWithParam(params)
muninn.SendMessage(params.Id, "[Direct] Alarm")
end3. Webhook调用
local muninn = require("MuninnPlugin")
function OnCreateRoom(e)
-- 建议这样打印日志
muninn.LogInfo("OnCreateRoom")
end
function OnMessage(e)
muninn.LogInfo("OnMessage")
req = {
Url = "https://uos.unity.cn",
Method = "GET",
Timeout = 2000, -- 2秒
}
senderId = e.SenderId
muninn.AsyncHttp(req, Callback, {senderId = senderId})
end
function Callback(context, resp, err)
muninn.LogInfo("" .. tostring(err))
if err == 0 then
if resp.HttpStatusCode == 200 then
muninn.SendMessage(context.senderId, resp.Data)
end
end
end4. 石头剪刀布 (使用所有功能函数)
-- 石头剪刀布的游戏
-- 只允许2个玩家参与,多余的玩家进来,会被BeforeJoin拦截
-- 游戏在自驱主循环中,维护状态机
-- 有2个玩家在线后,立马进入游戏态
-- 等2个玩家都猜拳后,公布结果
-- 并借助webhook,上报结果给服务器端
local muninn = require("MuninnPlugin")
local MessageType = muninn.MessageType
local ReturnType = muninn.ReturnType
local GameStatusType = {
INIT = 0,
RUN = 1, -- 游戏开始
OVER = 2, -- 游戏结束
}
local gameStatus = GameStatusType.INIT
local gameData = {
slot1 = nil,
slot2 = nil,
}
function Judge()
t1 = string.lower(gameData.slot1.result)
t2 = string.lower(gameData.slot2.result)
if t1 == "rock" then
if t2 == "paper" then
return gameData.slot2.player.Name .. " win"
elseif t2 == "scissors" then
return gameData.slot1.player.Name .. "win"
end
end
if t1 == "paper" then
if t2 == "rock" then
return gameData.slot1.player.Name .. " win"
elseif t2 == "scissors" then
return gameData.slot2.player.Name .. "win"
end
end
if t1 == "scissors" then
if t2 == "rock" then
return gameData.slot2.player.Name .. " win"
elseif t2 == "paper" then
return gameData.slot1.player.Name .. "win"
end
end
return "draw"
end
function OnCreateRoom(e)
-- 建议这样打印日志
muninn.LogInfo("Rock Paper Scissors Game Start")
-- 每200毫秒回调一次,作为游戏的主循环
muninn.ScheduleRepeat(EventLoop, 200)
end
function BeforeCloseRoom(e)
-- print("BeforeCloseRoom")
muninn.LogInfo("BeforeCloseRoom")
end
function OnCloseRoom(e)
-- print("OnCloseGame")
muninn.LogInfo("OnCloseGame")
end
function BeforeJoin(e)
players = muninn.GetOnlinePlayers()
if #players >= 2 then
-- 房间里已经有2人,则直接拒绝后面的人进来
muninn.LogInfo("reject player:" .. e.Name .. " enter room")
return ReturnType.CANCEL
end
return ReturnType.CONTINUE
end
function OnJoin(e)
muninn.LogInfo("Welcome to room, player: " .. e.Name)
end
function OnLeave(e)
muninn.LogInfo("OnLeave")
end
function OnMessage(e)
player = muninn.GetPlayer(e.SenderId)
muninn.LogInfo("receive a message " .. e.Data .. " messageType" .. tostring(e.Target) .. ", from player: " .. player.Name)
if e.MessageType ~= MessageType.TARGET_TO_PLUGIN then
-- 阻断客户端所有非发送给plugin的消息
return ReturnType.CANCEL
end
-- 游戏开始后, 收集用户的猜拳动作
if gameStatus == GameStatusType.RUN then
-- 存储结果
if gameData.slot1 == nil then
gameData.slot1 = {
player = player,
result = e.Data
}
return ReturnType.CONTINUE
elseif gameData.slot1.player.Id == player.Id then
gameData.slot1.result = e.Data
return ReturnType.CONTINUE
end
if gameData.slot2 == nil then
gameData.slot2 = {
player = player,
result = e.Data
}
else
gameData.slot2.result = e.Data
end
end
return ReturnType.CONTINUE
end
function EventLoop()
players = muninn.GetOnlinePlayers()
if gameStatus == GameStatusType.INIT then
-- 只要满2人,立即进入战斗模式
if #players == 2 then
gameStatus = GameStatusType.RUN
muninn.BroadcastMessage("Start New Round")
end
end
if gameStatus == GameStatusType.RUN then
-- 如果双方已经出了结果,则进行判定
if gameData.slot1 ~= nil and gameData.slot2 ~= nil then
res = Judge()
muninn.BroadcastMessage(res)
-- 上报结果至Webhook
--req = {
-- Url = "http://game.cn",
-- Method = "POST",
--}
--muninn.AsyncHttp(req, function(context, rsp, err)
-- if err == 0 then
-- muninn.LogInfo("receive http server ack, report result successfully" .. rsp.Data)
-- end
--end)
gameData.slot1 = nil
gameData.slot2 = nil
-- 新的一轮猜拳开始了
muninn.BroadcastMessage("Start New Round")
end
end
muninn.LogInfo("game status: " .. tostring(gameStatus) .. " online players number: " .. tostring(#players))
end在线体验 Lua Plugin
1. 添加 Plugin
在逻辑插件界面,选择添加一个新的插件。在上传窗口处,您可以使用我们的示例插件程序,其中包含基本的日志打印。
2. 为在线体验的配置关联 Plugin
在线体验会选择房间配置列表中的第一个房间配置。因此,您可以在房间配置页面,为第一个房间配置关联创建好的逻辑插件。
3. 接受 Plugin 发送的定时消息
如果您的插件程序使用定时器发送消息,您将于在线体验的示例内收到这些消息。
4. 查看 Plugin 日志
您可以在房间管理页面,针对使用逻辑插件的房间,查看 Plugin 内打印的日志。
限制
1. 文件名与文件格式
无论单文件或是多文件,都需要上传zip的压缩包。
- 对于单文件脚本,压缩包内的文件需命名为main.lua;
- 对于多文件脚本,入口文件为main.lua,这个入口文件需包括实现的插件方法。
2. 文件大小
每个UOS应用可以上传不超过 200 MB 的插件,每个插件文件大小不超过 5 MB,如您需要上传更大尺寸的插件,请 联系我们 。
3. 插件内置库
目前支持 Lua 5.1 版本以及以下常用Lua库
- string: 字符串处理库
- table: 表处理库
- math: 数学库
- os: 操作系统库
- package: 模块管理库,提供了模块加载、模块路径配置等功能
如您需要更多Lua库,请 联系我们 。