Unity网络同步框架-Nakama研究四
【Unity网络同步框架 - Nakama研究(四)】
【Unity网络同步框架 - Nakama研究(四)】
关于
Nakama
的源码问题,Nakama
的源码官方是不建议修改的,不建议重构以添加新的功能,推荐使用嵌入式运行库,也就是使用lua
,go
和typescript
进行扩展。
分析
- 扩展使用的语言中
Go
作为源码语言,能进行断点调试,而且性能高效,适合原生的服务端用户书写。相对应的,ts
和lua
就更加适合比如cocos creator
或者使用lua
进行Unity
开发的人员书写,各有对应,遗憾的是目前官方对这两种语言都不支持断点调试, - 官方的案例中使用了
ts
进行扩展(在PiratePanic
中能看到),在此之前,我们需要做一些工作,就是让Nakama
能够识别你的扩展目录,在服务器的Docker
配置中,这一行就是你指定你的扩展的文件所存放的位置,比如我放两个
lua
文件ltest.lua
和main.lua
进去,Nakama
会自动读取对应位置的文件信息,然后打印出来,载入的顺序是按照你的文件名来排序的,日志里面也显示了你在里面注册了哪些
rpc
方法,你也能在客户端直接调用。
创建权威比赛
- 权威比赛的一个关键点就是房间内没人也依旧会存在,而且可以自定义房间内的比赛逻辑,包括如何开始如何结束等等。逻辑也很简单,就是那几个关键的函数方法:
match_join
,match_leave
,match_loop
(主要是这三个),下面是我写着测试的代码:main.lua
和ltest.lua
local nk = require(“nakama”) nk.logger_info(“Lua模块进入——”) – 创建比赛 local state = { label = “比赛一” } local match_id = nk.match_create(“ltest”, state) local state_2 = { label = “比赛二” } local match_id_2 = nk.match_create(“ltest”, state_2) local state_3 = { label = “比赛三” } local match_id_3 = nk.match_create(“ltest”, state_3)
function healthcheck_rpc(content, payload)
nk.logger_info(“Healthcheck RPC called”)
return nk.json_encode({[“success”] = true})
end
function getmatchlist_rpc(content, payload)
– 列出当前所有比赛
local limit = 10
local authoritative = false
local label = nil
local min_size = 1
local max_size = 10
local matches = nk.match_list(limit, authoritative, label, min_size, max_size)
– 构建返回的 JSON 结构
local matchList = {}
for _, m in ipairs(matches) do
table.insert(matchList, {
match_id = m.match_id,
size = m.size,
max_size = m.max_size,
authoritative = m.is_authoritative
})
end
– 返回 JSON 结构
return nk.json_encode({ matches = matchList })
end
nk.register_rpc(healthcheck_rpc, “healthcheck_lua”)
nk.register_rpc(getmatchlist_rpc, “getmatchlist_lua”)
local M = {}
local nk = require(“nakama”)
function M.match_init(context, setupstate)
local gamestate = {
presences = {},
name = “”,
min_size = 1,
max_size = 10,
is_authoritative = false,
label = setupstate.label
}
– 设置 tick 率
local tickrate = 30
– 设置比赛标签
local label = gamestate.label or ""
nk.logger_warn(“match_init”)
return gamestate, tickrate, label
end
function M.match_join_attempt(context, dispatcher, tick, state, presence, metadata)
local acceptuser = true
nk.logger_warn(“match_join_attempt”)
return state, acceptuser
end
function M.match_join(context, dispatcher, tick, state, presences)
nk.logger_warn(“match_join”)
– 先添加新玩家到状态
for _, presence in ipairs(presences) do
state.presences[presence.session_id] = presence
end
– 发送玩家加入通知
for _, presence in ipairs(presences) do
– 构造加入消息
local join_message = {
op = 10, – 新操作码表示玩家加入
type = “PLAYER_JOINED”,
timestamp = os.time(),
data = {
user = {
id = presence.user_id,
name = presence.username
},
session = presence.session_id
}
}
– 构建收件人列表(排除自己)
local recipients = {}
for _, p in pairs(state.presences) do
if p.session_id ~= presence.session_id then
table.insert(recipients, p)
end
end
– 发送加入广播
if #recipients > 0 then
dispatcher.broadcast_message(10, nk.json_encode(join_message), recipients)
nk.logger_info(string.format(“Player %s(%s) joined the match”,
presence.user_id, presence.username))
end
– 给新玩家发送现有玩家列表
local existing_players = {}
for _, p in pairs(state.presences) do
if p.session_id ~= presence.session_id then
table.insert(existing_players, {
user_id = p.user_id,
username = p.username,
session_id = p.session_id
})
end
end
if #existing_players > 0 then
local existing_msg = {
op = 11,
type = “EXISTING_PLAYERS”,
data = existing_players
}
dispatcher.broadcast_message(11, nk.json_encode(existing_msg), { presence })
end
end
– 原有存储数据逻辑(保持兼容)
for _, presence in pairs(state.presences) do
local storageObjectId = {
{ collection = “test”, key = “key1”, user_id = presence.user_id }
}
local storageObject = nk.storage_read(storageObjectId)
if storageObject then
local message = {
op = 99,
data = storageObject
}
– 使用新通知系统
dispatcher.broadcast_message(99, nk.json_encode(message))
else
nk.logger_warn(“storageObject为空,查询的user id为” .. presence.user_id)
end
end
return state
end
– presences表示离开的人数
function M.match_leave(context, dispatcher, tick, state, presences)
– 遍历所有离开的玩家
for _, presence in ipairs(presences) do
– 构造离开消息
local leave_message = {
UserId = presence.user_id,
UserName = presence.username,
SessionId = presence.session_id,
Reason = “player_left”
}
– 构建收件人列表(排除离开的玩家)
local recipients = {}
for _, p in pairs(state.presences) do
if p.session_id ~= presence.session_id then
table.insert(recipients, p)
end
end
– 发送离开通知(使用op_code 6表示玩家离开)
if #recipients > 0 then
dispatcher.broadcast_message(6, nk.json_encode(leave_message), recipients)
nk.logger_info(string.format(“Player %s(%s) left the match”,
presence.user_id, presence.username))
end
– 从状态中移除玩家
state.presences[presence.session_id] = nil
end
return state
end
– 处理比赛人员交互操作
function M.match_loop(context, dispatcher, tick, state, messages)
if(messages ~= nil) then
for _, m in ipairs(messages) do
local recipients = {}
for _, p in pairs(state.presences) do
if p.user_id ~= m.sender.user_id then
table.insert(recipients, p)
end
end
if #recipients > 0 then
if(m ~= nil and m.op_code ~= nil) then
dispatcher.broadcast_message(m.op_code, m.data, recipients)
end
end
end
end
return state
end
– 用于在服务器关闭时,优雅地处理比赛状态,确保比赛能够正确结束并通知客户端
function M.match_terminate(context, dispatcher, tick, state, grace_seconds)
local message = “Server shutting down in " .. grace_seconds .. " seconds”
dispatcher.broadcast_message(2, message)
return nil
end
– 用于在用户正式加入比赛之前,提前预留位置或进行一些预处理
function M.match_signal(context, dispatcher, tick, state, data)
return state, “signal received: " .. data
end
return M
对了,还有一点麻烦的是,vscode
里面没有代码提示,可以搞个前人写好的库,我找了下,在github
上能找到[地址](
Intellisense-Module-for-Lua),能补充一下缺失的提示框(但是错误提示依旧没有)
控制台使用
- 控制台如果你想要其他的图形化的比如
Grafana
啥的,也能自己搭建,不过目前我看这个控制大多数都能满足,包括查看用户,查看聊天信息,查看比赛(房间)信息,然后主动调用接口,还是挺方便的,这个没什么好讲的
结语,
Nakama
相关的一些使用就到这,再深的讲就是一些服务器的布置,负载均衡,集群啥的了(集群需要Nakama
的企业版),Nakama
本身自带的一些功能比如排行榜,好友群组,连接x
啥的我都没试。再深入研究要等下次有机会了