文章目录
- 分析
- 创建权威比赛
- 控制台使用
【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})
endfunction getmatchlist_rpc(content, payload)-- 列出当前所有比赛local limit = 10local authoritative = falselocal label = nillocal min_size = 1local max_size = 10local matches = nk.match_list(limit, authoritative, label, min_size, max_size)-- 构建返回的 JSON 结构local matchList = {}for _, m in ipairs(matches) dotable.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 })
endnk.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
endfunction M.match_join_attempt(context, dispatcher, tick, state, presence, metadata)local acceptuser = truenk.logger_warn("match_join_attempt")return state, acceptuser
endfunction M.match_join(context, dispatcher, tick, state, presences)nk.logger_warn("match_join")-- 先添加新玩家到状态for _, presence in ipairs(presences) dostate.presences[presence.session_id] = presenceend-- 发送玩家加入通知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) doif p.session_id ~= presence.session_id thentable.insert(recipients, p)endend-- 发送加入广播if #recipients > 0 thendispatcher.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) doif p.session_id ~= presence.session_id thentable.insert(existing_players, {user_id = p.user_id,username = p.username,session_id = p.session_id})endendif #existing_players > 0 thenlocal existing_msg = {op = 11,type = "EXISTING_PLAYERS",data = existing_players}dispatcher.broadcast_message(11, nk.json_encode(existing_msg), { presence })endend-- 原有存储数据逻辑(保持兼容)for _, presence in pairs(state.presences) dolocal storageObjectId = {{ collection = "test", key = "key1", user_id = presence.user_id }}local storageObject = nk.storage_read(storageObjectId)if storageObject thenlocal message = {op = 99,data = storageObject}-- 使用新通知系统dispatcher.broadcast_message(99, nk.json_encode(message))elsenk.logger_warn("storageObject为空,查询的user id为" .. presence.user_id)endendreturn 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) doif p.session_id ~= presence.session_id thentable.insert(recipients, p)endend-- 发送离开通知(使用op_code 6表示玩家离开)if #recipients > 0 thendispatcher.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] = nilendreturn state
end-- 处理比赛人员交互操作
function M.match_loop(context, dispatcher, tick, state, messages)if(messages ~= nil) thenfor _, m in ipairs(messages) dolocal recipients = {}for _, p in pairs(state.presences) doif p.user_id ~= m.sender.user_id thentable.insert(recipients, p)endendif #recipients > 0 thenif(m ~= nil and m.op_code ~= nil) thendispatcher.broadcast_message(m.op_code, m.data, recipients)endendendendreturn 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
endreturn M
对了,还有一点麻烦的是,vscode
里面没有代码提示,可以搞个前人写好的库,我找了下,在github
上能找到地址,能补充一下缺失的提示框(但是错误提示依旧没有)
控制台使用
- 控制台如果你想要其他的图形化的比如
Grafana
啥的,也能自己搭建,不过目前我看这个控制大多数都能满足,包括查看用户,查看聊天信息,查看比赛(房间)信息,然后主动调用接口,还是挺方便的,这个没什么好讲的
结语,
Nakama
相关的一些使用就到这,再深的讲就是一些服务器的布置,负载均衡,集群啥的了(集群需要Nakama
的企业版),Nakama
本身自带的一些功能比如排行榜,好友群组,连接x
啥的我都没试。再深入研究要等下次有机会了