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

ops/2025/3/17 3:56:09/

文章目录

  • 【Unity网络同步框架 - Nakama研究(三)】
    • 准备工作
    • 前言
    • Unity部分
      • 连接服务器
      • 创建并进入房间
      • 创建人物
      • 人物移动和同步

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

以下部分需要有一定的Unity基础,在官方的案例Pirate Panic基础上进行修改而成。如果没有下载并熟悉过官方案例,最好先下载对应的工程查看。工程地址为:https://github.com/heroiclabs/unity-sampleproject,对应的案例文档为:https://heroiclabs.com/docs/zh/nakama/tutorials/unity/pirate-panic/,以下运行的Unity版本为2022LTS,一般关系不大

准备工作

  • Unity2022或者随便一个LTS版本
  • VS2022
  • Nakama Unity SDK(官网或者Unity商店都有,实在找不到把上面的案例的程序集偷出来)

前言

  • Nakama是一个网络同步库,兼容很多游戏引擎,名字取自于日语伙伴,底层由Go开发,所以性能上有保证(可以对比其他流行的网络框架)。并且拥有大量已经开发好而且经过检验的功能(聊天,排行榜,群组,房间,身份验证,存储,好友等等),但是之前在网络上,甚至官网上找到的博客或者文章要么是性质雷同,要么就是空谈。
  • 以下的改变主要是用于网上找不到,AI提供不准确,论坛全英文,翻找资料麻烦的基础上提供的。

Unity部分

连接服务器

  • 我喜欢尽量把逻辑精简,让程序能跑起来,再去研究里面的细节,就像钢铁侠里面的台词“有时候你得先跑起来,再学会走路”
	[SerializeField] private GameConnection _connection;public static string DeviceIdKey => "nakama.deviceId" + UserData.Id;public static string AuthTokenKey => "nakama.authToken" + UserData.Id;public static string RefreshTokenKey => "nakama.refreshToken" + UserData.Id;private Client client;private ISocket socket;private const string ServerIp = "xxx.xxx.xx.xx"; // 你的ip地址public async void RequireEnterRoom(){if (_connection.Session == null){string deviceId = GetDeviceId();if (!string.IsNullOrEmpty(deviceId)){PlayerPrefs.SetString(DeviceIdKey, deviceId);}await InitializeGame(deviceId);}}private async Task InitializeGame(string deviceId){client = new Client("http", ServerIp, 7350, "defaultkey", UnityWebRequestAdapter.Instance);client.Timeout = 5;socket = client.NewSocket(useMainThread: true);string authToken = PlayerPrefs.GetString(AuthTokenKey, null);bool isAuthToken = !string.IsNullOrEmpty(authToken);string refreshToken = PlayerPrefs.GetString(RefreshTokenKey, null);ISession session = null;// refresh token can be null/empty for initial migration of client to using refresh tokens.if (isAuthToken){session = Session.Restore(authToken, refreshToken);// Check whether a session is close to expiry.if (session.HasExpired(DateTime.UtcNow.AddDays(1))){try{// get a new access tokensession = await client.SessionRefreshAsync(session);}catch (ApiResponseException){// get a new refresh tokensession = await client.AuthenticateDeviceAsync(deviceId);PlayerPrefs.SetString(RefreshTokenKey, session.RefreshToken);}PlayerPrefs.SetString(AuthTokenKey, session.AuthToken);}}else{session = await client.AuthenticateDeviceAsync(deviceId);PlayerPrefs.SetString(AuthTokenKey, session.AuthToken);PlayerPrefs.SetString(RefreshTokenKey, session.RefreshToken);}Connect(socket, session);IApiAccount account = null;try{account = await client.GetAccountAsync(session);}catch (ApiResponseException e){Debug.LogError("Error getting user account: " + e.Message);}_connection.Init(client, socket, account, session);}private async void Connect(ISocket socket, ISession session){try{if (!socket.IsConnected){await socket.ConnectAsync(session);}}catch (Exception e){Debug.LogWarning("Error connecting socket: " + e.Message);}}private string GetDeviceId(){string deviceId = "";deviceId = PlayerPrefs.GetString(DeviceIdKey);if (string.IsNullOrWhiteSpace(deviceId)){deviceId = Guid.NewGuid().ToString();}return deviceId;}

上面的这部分就是连接的函数部分,其中的结构GameConnection如下:

using Nakama;
using UnityEngine;public class GameConnection : ScriptableObject
{private IClient _client;public IClient Client => _client;public ISession Session { get; set; }public IApiAccount Account { get; set; }private ISocket _socket;public ISocket Socket => _socket;private IChannel _channel;public IChannel Channel => _channel;public string MatchID { get; set; }public void Init(IClient client, ISocket socket, IApiAccount account, ISession session){_client = client;_socket = socket;Account = account;Session = session;}
}

上面大部分的代码都能在案例中找到,有些小修改。需要注意的是,如果要在电脑上实现多开(非编辑器模式下,处于打完包的exe状态),需要修改DeviceIdKey等参数,不然服务器接收到的时候,这俩会识别成同一个帐号(因为传入的参数deviceId一致),会给后续操作带来麻烦。

创建并进入房间

  • 这一步开始就跟案例中的不一样了,案例使用的是AddMatchmakerAsync,这个方法在文档中说明是不会创建房间的,只是简单的匹配机制,所以如果这个时候你写了如下代码:
	private async void ListMatchesAndJoin(){var minPlayers = 0;var maxPlayers = 10;var limit = 10;var authoritative = true;var label = "";var query = "*";try{var result = await client.ListMatchesAsync(_connection.Session, minPlayers, maxPlayers, limit, authoritative, null, null);// 添加新的列表项foreach (var match in result.Matches){Debug.LogFormat("{0}: {1}/{2} players", match.MatchId, match.Size, maxPlayers);JoinMatch(match.MatchId);break;}}catch (System.Exception e){Debug.LogError("Error listing matches: " + e.Message);}}

到时候你就会发现怎么都拿不到房间信息,一直返回空,这里根据需求分为两步,一是你自己创建的房间(如果人数为0,会被销毁,而且走的是官方设定好的逻辑,叫非权威比赛),二是服务器创建的权威比赛,这个比赛即使房间内人数为0也不会解散(关服务器还是会解散的)

// 这是非权威比赛(权威比赛会在上述代码中直接返回对应的matchid)
var matchid = await _connection.Socket.CreateMatchAsync();// 通过返回的matchid加入
var match = await socket.JoinMatchAsync(matchId);

至于如何创建服务器的权威比赛,留到下次讲服务器扩展再说。

  • 走到这一步,其实我们已经在房间里了,看服务器的日志,日志
    第一条是连接socket,第二条是加入房间。

创建人物

  • 进入了房间,接下去做的一般是创建你所加入房间的那个摆设,或者新的场景,然后给服务器发送创建人物的信息,涉及到操作信息在房间内的传递。
  • 创建新的场景这一点,Unity自己就能做到
  • 发送操作信息要分开,因为Nakama有很多种渠道可以发送消息,这里采用正规一点的房间消息,需要注意的是,如果这个房间是非权威房间,那么房间信息Nakama给你写好了,如果是服务器自己创建的非权威房间,那么需要你自己写。
	public static async Task SpawnPlayer(){var matchMessagePlayerCreate = new MatchMessagePlayerCreate(BattleSceneController.Instance.Connection.Session.Username,BattleSceneController.Instance.Connection.Session.UserId,randomPos.x,randomPos.y,randomPos.z,0, 0, 0,selectCharacterId,selectCharacterData);BattleSceneController.Instance.StateManager.SendMatchStateMessage(MatchMessageType.UnitSpawned, matchMessagePlayerCreate);BattleSceneController.Instance.StateManager.SendMatchStateMessageSelf(MatchMessageType.UnitSpawned, matchMessagePlayerCreate);}

创建人物信息的方式跟案例里面的差不多,注意一下时序问题即可。然后在监听对应事件的地方GameStateManager处理服务器发送过来的消息即可。

private GameConnection _connection;_connection.Socket.ReceivedMatchState += ReceiveMatchStateMessage;private void ReceiveMatchStateMessage(IMatchState matchState){string messageJson = System.Text.Encoding.UTF8.GetString(matchState.State);if (string.IsNullOrEmpty(messageJson))return;ReceiveMatchStateHandle(matchState.OpCode, messageJson);}public void SendMatchStateMessageSelf<T>(MatchMessageType opCode, T message)where T : MatchMessage<T>{switch (opCode){case MatchMessageType.UnitSpawned:OnPlayerCreate?.Invoke(message as MatchMessagePlayerCreate);break;default:break;}}
public void ReceiveMatchStateHandle(long opCode, string messageJson){switch ((MatchMessageType)opCode){case MatchMessageType.UnitSpawned:MatchMessagePlayerCreate matchMessagePlayerCreate = MatchMessagePlayerCreate.Parse(messageJson);OnPlayerCreate?.Invoke(matchMessagePlayerCreate);break;default:break;}}

有一点需要注意的是,Nakama传递的消息结构字段是json,而且是Base64转义之后的,如果你在服务器的日志中看到错误信息,记得先转回正常的字符串。

  • 然后你的人物就能出现在场景中了。人物

人物移动和同步

  • 再往后面就是正常的人物之间的同步信息,比如人物的旋转,移动,动画等等,都可以在上面ReceiveMatchStateHandle方法里面进行监听和执行,涉及到CinemachineTimeline,动画状态机等等,就不在这里详细展开了。

下一章讲讲服务器的扩展相关和一些可能遇到的问题


http://www.ppmy.cn/ops/166391.html

相关文章

宇数科技激光雷达L2

使用的ubuntu18.04ROS-melodic 官网找到L2系列的产品 SDK下载&#xff1a;下载中心 L2 - 宇树科技 激光雷达使用 下载unilidar_sdk2-2.0.4.zip&#xff0c;解压只用到unitree_lidar_sdkunitree_lidar_ros&#xff08;ROS1&#xff09;。 L2有两个工作模式&#xff1a;网口…

Linux 下 MySQL 8 搭建教程

一、下载 你可以从 MySQL 官方下载地址 下载所需的 MySQL 安装包。 二、环境准备 1. 查看 MySQL 是否存在 使用以下命令查看系统中是否已经安装了 MySQL&#xff1a; rpm -qa|grep -i mysql2. 清空 /etc/ 目录下的 my.cnf 执行以下命令删除 my.cnf 文件&#xff1a; [roo…

Docker配置代理,以保证可以快速拉取镜像

序言 本来不想写了&#xff0c;然后记笔记了&#xff0c;但是今天遇到这个问题了再一次&#xff0c;还是写一写吧&#xff0c;加深一下印象 因为Docker被墙了&#xff0c;所以拉取Docker镜像的时候&#xff0c;需要通过代理的方式 xxxxxxxxxx,此处省略十几个字&#xff0c;然…

解决启动Vue项目时遇到的 error:0308010C:digital envelope routines::unsupported 错误

问题描述 最近&#xff0c;在启动一个遗留前端(Vue)项目时&#xff0c;遇到了error:0308010C:digital envelope routines::unsupported错误。 95% emitting CompressionPlugin ERROR Error: error:0308010C:digital envelope routines::unsupported Error: error:0308010C:d…

手势调控屏幕亮度:Python + OpenCV + Mediapipe 打造智能交互体验

前言 你有没有遇到过这样的情况? 夜晚玩电脑,屏幕亮得像个小太阳,晃得眼泪直流,想调暗一点,却在键盘上盲摸半天,结果误触关机键,直接黑屏;白天屏幕暗得像熄火的煤油灯,想调亮点,鼠标点来点去,调节条藏得像猫一样不见踪影。这年头,我们的设备都快能听懂人话了,怎…

系统分析师论文《论系统运维方法及其应用》

【摘要】 2022年4月&#xff0c;我公司承接了某大型国有企业"智能办公自动化系统"的运维优化项目&#xff0c;我担任系统分析师并负责运维体系建设工作。该系统涉及流程审批、数据报表、移动端接入等核心功能模块&#xff0c;支撑1300余名员工的日常办公。由于历史遗…

golang开发支持onlyoffice的token功能

一直都没去弄token这块&#xff0c;想着反正docker run的时候将jwt置为false即可。 看了好多文章&#xff0c;感觉可以试试&#xff0c;但是所有文件几乎都没说思路。 根据我的理解和成功的调试&#xff0c;思路是&#xff1a; 我们先定义2个概念&#xff0c;一个是文档下载…

vscode编译器的一些使用问题

目录 解决pip不可用问题 检查VSCode的终端配置 解决pip不可用问题 eg&#xff1a; C:\Users\student>pip pip 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 先找到系统环境变量 高级->环境变量 系统属性->Path 变量名随意&#xff0c;自己后续知道…