【Unity网络同步框架 - Nakama研究(四)】

server/2025/3/19 0:49:22/

文章目录

    • 分析
    • 创建权威比赛
    • 控制台使用

【Unity网络同步框架 - Nakama研究(四)】

关于Nakama的源码问题,Nakama的源码官方是不建议修改的,不建议重构以添加新的功能,推荐使用嵌入式运行库,也就是使用lua,gotypescript进行扩展。

分析

  1. 扩展使用的语言中Go作为源码语言,能进行断点调试,而且性能高效,适合原生的服务端用户书写。相对应的,tslua就更加适合比如cocos creator或者使用lua进行Unity开发的人员书写,各有对应,遗憾的是目前官方对这两种语言都不支持断点调试,反馈
  2. 官方的案例中使用了ts进行扩展(在PiratePanic中能看到),在此之前,我们需要做一些工作,就是让Nakama能够识别你的扩展目录,在服务器的Docker配置中,扩展这一行就是你指定你的扩展的文件所存放的位置,比如我放两个lua文件ltest.luamain.lua进去,Nakama会自动读取对应位置的文件信息,然后打印出来载入lua,载入的顺序是按照你的文件名来排序的,日志里面也显示了你在里面注册了哪些rpc方法,你也能在客户端直接调用。

创建权威比赛

  • 权威比赛的一个关键点就是房间内没人也依旧会存在,而且可以自定义房间内的比赛逻辑,包括如何开始如何结束等等。逻辑也很简单,就是那几个关键的函数方法:match_join,match_leave,match_loop(主要是这三个),下面是我写着测试的代码:main.lualtest.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本身自带的一些功能比如排行榜,好友群组,连接facebook,x啥的我都没试。再深入研究要等下次有机会了


http://www.ppmy.cn/server/176097.html

相关文章

【项目管理git】git学习

ps:所有东西都是个人理解 文章目录 一、git是什么,它用来做什么?二、相关知识库2.1 简单的linux指令2.2 git配置指令2.3 git常见的指令2.3.1 Git的上传原理2.3.2 版本回退相关内容 2.4 设置远程地址,本地上传到github2.4.1 ssh相…

QT编程之HTTP服务端与客户端技术

一、HTTP 服务器实现方案 ‌QtWebApp 集成‌ 将QtWebApp源码的 httpserver 目录导入项目,并在 .pro 文件中添加 include ($$PWD/httpserver/httpserver.pri)‌。配置 WebApp.ini 文件定义服务参数(IP、端口、线程池等),通过 HttpL…

Spring Cloud Stream - 构建高可靠消息驱动与事件溯源架构

一、引言 在分布式系统中,传统的 REST 调用模式往往导致耦合,难以满足高并发和异步解耦的需求。消息驱动架构(EDA, Event-Driven Architecture)通过异步通信、事件溯源等模式,提高了系统的扩展性与可观测性。 作为 S…

C++ primer plus 类和对象下

目录 前言 一 this指针 二 对象数组 三 类作用域 总结 前言 接着上一篇继续 一 this指针 我们可能看到这个this指针是不知道干什么的,但是我们可以通过一个问题来引入这个,就比如我们上一章的程序,我们知道是用来计算股票的&#xf…

前端(vue)学习笔记(CLASS 4):组件组成部分与通信

1、组件的三大组成部分(结构/样式/逻辑) 注意点: 1、结构只能有一个根元素 2、全局样式(默认),影响所有组件;局部样式,scoped下样式,只作用于当前组件 3、el根实例独…

平板作为笔记本副屏使用spacedesk

平板作为笔记本的一块副屏使用 软件 spacedesk 已上传,可自行下载。(上传需要审核且只能绑定一个资源,可在官网自行下载,或私聊我) PC版 移动版 spacedesk-2-1-17.apk 电脑版按照提示一步一步安装节即可移动端直接…

SpringBoot入门-(1) Maven【概念+流程】

SpringBoot入门-(1) Maven 动机 对于企业级大项目而言,需要手动导入很大Jar包,费时费力,且Jar包之间也可能存在依赖和冲突,这些关系导致Jar包之间想毛线团一样缠在一起,因此我们需要一个包管理系统帮我们自动下载导入…

06.Python基础4

目录 元组 tuple 1.概述 2.可变数据为什么可变 3.不可变数据如何变化 4.基础操作 4.1创建元组 4.2定位元素 4.3遍历元组 4.4序列拆包 5.字符串 str 5.1定义 5.2编码 5.2.1字符集 5.2.2编码方式 5.2.3不可变 5.3序列 5.4字面值 5.4.1单引和双引号的区别 5.4.2…