您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
609 行
20 KiB
609 行
20 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Unity.NetCode.Extensions;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
|
|
namespace Unity.MegaCity.Gameplay
|
|
{
|
|
[Serializable]
|
|
public class CreateRoomReq
|
|
{
|
|
public string multiverseProfileId; // required
|
|
public string gameRegionId; // required
|
|
public string playerId; // required
|
|
public string @namespace; // default = "DEFAULT"
|
|
public string name; // default = "DEFAULT"
|
|
public int maxPlayers;
|
|
public string visibility; // default = "PUBLIC", options = ["PUBLIC", "PRIVATE"]
|
|
public string joinCode; // required if visibility == "PRIVATE"
|
|
public Dictionary<string, string> customProperties;
|
|
public int roomTTLInMinutes;
|
|
public Dictionary<string, string> allocationEnvs;
|
|
}
|
|
|
|
[Serializable]
|
|
public class JoinRoomReq
|
|
{
|
|
public string roomUUID; // required
|
|
public string playerId; // required
|
|
public string joinCode; // required if room is private
|
|
}
|
|
|
|
[Serializable]
|
|
public class LeaveRoomReq
|
|
{
|
|
public string roomUUID; // required
|
|
public string playerId; // required
|
|
}
|
|
|
|
[Serializable]
|
|
public class GetRoomInfoReq
|
|
{
|
|
public string roomUUID; // required
|
|
}
|
|
|
|
[Serializable]
|
|
public class GetRoomListReq
|
|
{
|
|
public string name;
|
|
public string status; // options = ["CLOSED", "SERVER_ALLOCATED", "CREATED", "ALLOCATION_FAILED"]
|
|
public string @namespace;
|
|
public string ownerId;
|
|
public string visibility;
|
|
public UInt32 start; // default = 0
|
|
public UInt32 count; // default = 20
|
|
}
|
|
|
|
[Serializable]
|
|
public class UpdateRoomReq
|
|
{
|
|
public string roomUUID; // required
|
|
public string externalUniqueId;
|
|
public Dictionary<string, string> customProperties;
|
|
}
|
|
|
|
[Serializable]
|
|
public class CreateRoomResp
|
|
{
|
|
public AllocationInfo allocationInfo;
|
|
public List<string> players;
|
|
public RoomInfo roomInfo;
|
|
}
|
|
|
|
[Serializable]
|
|
public class JoinRoomResp
|
|
{
|
|
public AllocationInfo allocationInfo;
|
|
public RoomInfo roomInfo;
|
|
}
|
|
|
|
[Serializable]
|
|
public class GetRoomInfoResp
|
|
{
|
|
public AllocationInfo allocationInfo;
|
|
public List<string> players;
|
|
public RoomInfo roomInfo;
|
|
}
|
|
|
|
[Serializable]
|
|
public class GetRoomListResp
|
|
{
|
|
public List<RoomInfo> rooms;
|
|
public int totalCount;
|
|
}
|
|
|
|
[Serializable]
|
|
public class UpdateRoomResp
|
|
{
|
|
public string externalUniqueId;
|
|
public RoomInfo roomInfo;
|
|
}
|
|
|
|
[Serializable]
|
|
public class GameServerPort
|
|
{
|
|
public ushort port;
|
|
public string protocol;
|
|
public string name;
|
|
}
|
|
|
|
[Serializable]
|
|
public class AllocationInfo
|
|
{
|
|
public string uuid;
|
|
public string gameId;
|
|
public string profileId;
|
|
public string regionId;
|
|
public string ip;
|
|
public List<GameServerPort> gameServerPorts;
|
|
public string gameServerName;
|
|
public TimeStamp createdAt;
|
|
public TimeStamp fulfilledAt;
|
|
public string status;
|
|
public string allocationTTL;
|
|
public string regionName;
|
|
public string profileName;
|
|
}
|
|
|
|
public class TimeStamp
|
|
{
|
|
public long seconds;
|
|
public long nanos;
|
|
}
|
|
|
|
[Serializable]
|
|
public class RoomInfo
|
|
{
|
|
public int id;
|
|
public string roomUUID;
|
|
public string multiverseProfileId;
|
|
public string uosAppId;
|
|
public string type;
|
|
public string ownerId;
|
|
public string status;
|
|
public string @namespace;
|
|
public string name;
|
|
public string joinCode;
|
|
public string visibility;
|
|
public int maxPlayers;
|
|
public int roomTTLInMinutes;
|
|
public Dictionary<string, string> customProperties;
|
|
public string expirationTime;
|
|
public string createdAt;
|
|
public string updatedAt;
|
|
public int playerCount; // exist only when status == "SERVER_ALLOCATED" && playerCount > 0
|
|
}
|
|
|
|
public class MultiverseRoomAPI
|
|
{
|
|
private static readonly MultiverseRoomAPI instance = new();
|
|
|
|
private MultiverseRoomAPI()
|
|
{
|
|
// Get name from BotNameGenerator to use as default name
|
|
var name = BotNameGenerator.GetRandomName();
|
|
Debug.Log($"MultiverseRoomAPI: PlayerName = {name}");
|
|
m_PlayerName = name;
|
|
}
|
|
|
|
public static MultiverseRoomAPI Instance
|
|
{
|
|
get
|
|
{
|
|
return instance;
|
|
}
|
|
}
|
|
|
|
private readonly string baseUrl = "https://s.unity.cn/service/rooms"; // room api url
|
|
|
|
// Please fill in the required fields
|
|
private readonly string appId = ""; // app id
|
|
private readonly string appSecret = ""; // app secret
|
|
private readonly string gameRegionId = ""; // game region id
|
|
private readonly string multiverseProfileId = ""; // multiverse profile id
|
|
|
|
public string IP { get; private set; } = "";
|
|
public ushort Port { get; private set; } = 0;
|
|
public string RoomUUID { get; private set; } = "";
|
|
public string PlayerID { get; private set; } = "";
|
|
|
|
private string m_PlayerName = "";
|
|
public string PlayerName => m_PlayerName;
|
|
|
|
public bool IsTryingToConnect { get; private set; } = false;
|
|
public bool ClientIsInGame { get; set; } = false;
|
|
|
|
public async Task<GetRoomListResp> GetRoomList(GetRoomListReq req)
|
|
{
|
|
try
|
|
{
|
|
if (req == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(req));
|
|
}
|
|
req.status ??= "SERVER_ALLOCATED";
|
|
var resp = await GetRoomListHttpRequest(req);
|
|
|
|
return resp;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"GetRoomList failed: {e.Message}");
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
// require: req.name
|
|
public async Task<CreateRoomResp> CreateRoom(CreateRoomReq req)
|
|
{
|
|
if (req == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(req));
|
|
}
|
|
|
|
if (req.name == null)
|
|
{
|
|
Debug.LogError("name is null, but can still continue");
|
|
}
|
|
|
|
PlayerID = Guid.NewGuid().ToString(); // new playerId
|
|
req.gameRegionId = gameRegionId;
|
|
req.multiverseProfileId = multiverseProfileId;
|
|
req.playerId = PlayerID;
|
|
req.maxPlayers = req.maxPlayers > 0 ? req.maxPlayers : 64;
|
|
req.roomTTLInMinutes = req.roomTTLInMinutes > 0 ? req.roomTTLInMinutes : 20;
|
|
|
|
try
|
|
{
|
|
var resp = await CreateRoomHttpRequest(req);
|
|
|
|
Debug.Log("CreateRoom success, now connecting to server...");
|
|
RoomUUID = resp.roomInfo.roomUUID;
|
|
IP = resp.allocationInfo.ip;
|
|
Port = resp.allocationInfo.gameServerPorts.FirstOrDefault(port => port.protocol == "UDP").port;
|
|
ConnectToServer();
|
|
return resp;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"CreateRoom failed: {e.Message}, PlayerID:{PlayerID}");
|
|
PlayerID = ""; // reset playerId
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
// require: req.roomUUID
|
|
public async Task JoinRoom(JoinRoomReq req)
|
|
{
|
|
if (req == null || req.roomUUID == null)
|
|
{
|
|
throw new ArgumentNullException("roomUUID is null");
|
|
}
|
|
|
|
RoomUUID = req.roomUUID;
|
|
PlayerID = Guid.NewGuid().ToString();
|
|
req.playerId = PlayerID;
|
|
|
|
try
|
|
{
|
|
var resp = await JoinRoomHttpRequest(req);
|
|
|
|
Debug.Log("JoinRoom success, now connecting to server...");
|
|
IP = resp.allocationInfo.ip;
|
|
Port = resp.allocationInfo.gameServerPorts.FirstOrDefault(port => port.protocol == "UDP").port;
|
|
ConnectToServer();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"JoinRoom failed: {e.Message}, PlayerID:{PlayerID}, RoomUUID:{RoomUUID}");
|
|
PlayerID = ""; // reset playerId
|
|
RoomUUID = ""; // reset roomUUID
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
public async Task LeaveRoom()
|
|
{
|
|
var req = new LeaveRoomReq
|
|
{
|
|
playerId = PlayerID,
|
|
roomUUID = RoomUUID
|
|
};
|
|
Debug.Log($"leavingroom: PlayerID:{PlayerID}, RoomUUID: {RoomUUID}");
|
|
|
|
try
|
|
{
|
|
await LeaveRoomHttpRequest(req);
|
|
Debug.Log("LeaveRoom success");
|
|
}
|
|
catch
|
|
{
|
|
Debug.Log("LeaveRoom Error");
|
|
}
|
|
|
|
PlayerID = ""; // reset playerId
|
|
RoomUUID = ""; // reset roomUUID
|
|
}
|
|
|
|
public void ConnectByIP(string ipString)
|
|
{
|
|
var ipSplit = ipString.Split(":");
|
|
if (ipSplit.Length < 2)
|
|
{
|
|
throw new ArgumentException("IP string is not in the correct format");
|
|
}
|
|
|
|
IP = ipSplit[0];
|
|
var portString = ipSplit[1];
|
|
if (!ushort.TryParse(portString, out var portShort))
|
|
{
|
|
throw new ArgumentException("Port is not correctly formatted. Expected a valid ushort value.");
|
|
}
|
|
Port = portShort;
|
|
ConnectToServer();
|
|
}
|
|
|
|
public void ConnectToServer()
|
|
{
|
|
// if (ClientIsInGame)
|
|
// return;
|
|
UpdateConnectionStatusLabel();
|
|
}
|
|
|
|
private void UpdateConnectionStatusLabel()
|
|
{
|
|
Debug.Log($"Attempting to Connect to {IP}:{Port}.");
|
|
IsTryingToConnect = true;
|
|
}
|
|
|
|
private async Task<CreateRoomResp> CreateRoomHttpRequest(CreateRoomReq req)
|
|
{
|
|
if (req == null||req.multiverseProfileId == null||req.gameRegionId == null || req.playerId == null)
|
|
{
|
|
throw new ArgumentNullException("CreateRoomHttpRequest Parameter Error");
|
|
}
|
|
string url = baseUrl;
|
|
var headers = SetAuthHeaders();
|
|
var bodyJson = JsonUtility.ToJson(req);
|
|
var bodyRaw = new UTF8Encoding().GetBytes(bodyJson);
|
|
|
|
UnityWebRequest www = new(url, "POST")
|
|
{
|
|
uploadHandler = new UploadHandlerRaw(bodyRaw),
|
|
downloadHandler = new DownloadHandlerBuffer()
|
|
};
|
|
www.SetRequestHeader("Content-Type", "application/json");
|
|
|
|
foreach (var header in headers)
|
|
{
|
|
www.SetRequestHeader(header.Key, header.Value);
|
|
}
|
|
|
|
// Http Request
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
www.SendWebRequest().completed += op => tcs.SetResult(true);
|
|
await tcs.Task;
|
|
|
|
if (www.result != UnityWebRequest.Result.Success)
|
|
{
|
|
throw new Exception($"CreateRoom Error: {www.error}");
|
|
}
|
|
|
|
return JsonUtility.FromJson<CreateRoomResp>(www.downloadHandler.text);
|
|
}
|
|
|
|
private async Task<JoinRoomResp> JoinRoomHttpRequest(JoinRoomReq req)
|
|
{
|
|
if (req == null || req.playerId == null || req.roomUUID == null)
|
|
{
|
|
throw new ArgumentNullException("JoinRoomHttpRequest Parameter Error");
|
|
}
|
|
|
|
string url = $"{baseUrl}/{req.roomUUID}/join";
|
|
var headers = SetAuthHeaders();
|
|
var bodyJson = JsonUtility.ToJson(req);
|
|
var bodyRaw = new UTF8Encoding().GetBytes(bodyJson);
|
|
|
|
UnityWebRequest www = new(url, "POST")
|
|
{
|
|
uploadHandler = new UploadHandlerRaw(bodyRaw),
|
|
downloadHandler = new DownloadHandlerBuffer()
|
|
};
|
|
www.SetRequestHeader("Content-Type", "application/json");
|
|
|
|
foreach (var header in headers)
|
|
{
|
|
www.SetRequestHeader(header.Key, header.Value);
|
|
}
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
www.SendWebRequest().completed += op => tcs.SetResult(true);
|
|
await tcs.Task;
|
|
|
|
if (www.result != UnityWebRequest.Result.Success)
|
|
{
|
|
throw new Exception($"JoinRoom Error: {www.error}");
|
|
}
|
|
|
|
return JsonUtility.FromJson<JoinRoomResp>(www.downloadHandler.text);
|
|
}
|
|
|
|
private async Task LeaveRoomHttpRequest(LeaveRoomReq req)
|
|
{
|
|
if (req == null || req.playerId == null || req.roomUUID == null)
|
|
{
|
|
throw new ArgumentNullException("LeaveRoomHttpRequest Parameter Error");
|
|
}
|
|
|
|
string url = $"{baseUrl}/{req.roomUUID}/leave";
|
|
var headers = SetAuthHeaders();
|
|
var bodyJson = JsonUtility.ToJson(req);
|
|
var bodyRaw = new UTF8Encoding().GetBytes(bodyJson);
|
|
|
|
UnityWebRequest www = new(url, "POST")
|
|
{
|
|
uploadHandler = new UploadHandlerRaw(bodyRaw),
|
|
downloadHandler = new DownloadHandlerBuffer()
|
|
};
|
|
www.SetRequestHeader("Content-Type", "application/json");
|
|
|
|
foreach (var header in headers)
|
|
{
|
|
www.SetRequestHeader(header.Key, header.Value);
|
|
}
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
www.SendWebRequest().completed += op => tcs.SetResult(true);
|
|
await tcs.Task;
|
|
|
|
if (www.result != UnityWebRequest.Result.Success)
|
|
{
|
|
throw new Exception($"LeaveRoom Error: {www.error}");
|
|
}
|
|
}
|
|
|
|
private async Task<GetRoomInfoResp> GetRoomInfoHttpRequest(GetRoomInfoReq req)
|
|
{
|
|
if (req == null || req.roomUUID == null)
|
|
{
|
|
throw new ArgumentNullException("GetRoomInfoHttpRequest Parameter Error");
|
|
}
|
|
|
|
string url = $"{baseUrl}/{req.roomUUID}";
|
|
var headers = SetAuthHeaders();
|
|
|
|
UnityWebRequest www = UnityWebRequest.Get(url);
|
|
www.downloadHandler = new DownloadHandlerBuffer();
|
|
|
|
foreach (var header in headers)
|
|
{
|
|
www.SetRequestHeader(header.Key, header.Value);
|
|
}
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
www.SendWebRequest().completed += op => tcs.SetResult(true);
|
|
await tcs.Task;
|
|
|
|
if (www.result != UnityWebRequest.Result.Success)
|
|
{
|
|
throw new Exception($"GetRoomInfo Error: {www.error}");
|
|
}
|
|
|
|
return JsonUtility.FromJson<GetRoomInfoResp>(www.downloadHandler.text);
|
|
}
|
|
|
|
private async Task<GetRoomListResp> GetRoomListHttpRequest(GetRoomListReq req)
|
|
{
|
|
if (req == null)
|
|
{
|
|
throw new ArgumentNullException("GetRoomListHttpRequest Parameter Error");
|
|
}
|
|
|
|
// Build the query parameters
|
|
List<string> queryParams = new();
|
|
if (!string.IsNullOrEmpty(req.name))
|
|
queryParams.Add("name=" + UnityWebRequest.EscapeURL(req.name));
|
|
if (!string.IsNullOrEmpty(req.status))
|
|
queryParams.Add("status=" + UnityWebRequest.EscapeURL(req.status));
|
|
if (!string.IsNullOrEmpty(req.@namespace))
|
|
queryParams.Add("namespace=" + UnityWebRequest.EscapeURL(req.@namespace));
|
|
if (!string.IsNullOrEmpty(req.ownerId))
|
|
queryParams.Add("ownerId=" + UnityWebRequest.EscapeURL(req.ownerId));
|
|
if (!string.IsNullOrEmpty(req.visibility))
|
|
queryParams.Add("visibility=" + UnityWebRequest.EscapeURL(req.visibility));
|
|
queryParams.Add("start=" + req.start.ToString());
|
|
queryParams.Add("count=" + req.count.ToString());
|
|
|
|
// Construct the full URL
|
|
string url = baseUrl;
|
|
if (queryParams.Count > 0)
|
|
url += "?" + string.Join("&", queryParams);
|
|
|
|
var headers = SetAuthHeaders();
|
|
|
|
UnityWebRequest www = UnityWebRequest.Get(url);
|
|
www.downloadHandler = new DownloadHandlerBuffer();
|
|
|
|
foreach (var header in headers)
|
|
{
|
|
www.SetRequestHeader(header.Key, header.Value);
|
|
}
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
www.SendWebRequest().completed += op => tcs.SetResult(true);
|
|
await tcs.Task;
|
|
|
|
if (www.result != UnityWebRequest.Result.Success)
|
|
{
|
|
throw new Exception($"GetRoomList Error: {www.error}");
|
|
}
|
|
|
|
return JsonUtility.FromJson<GetRoomListResp>(www.downloadHandler.text);
|
|
}
|
|
|
|
private async Task<UpdateRoomResp> UpdateRoomHttpRequest(UpdateRoomReq req)
|
|
{
|
|
if (req == null || req.roomUUID == null)
|
|
{
|
|
throw new ArgumentNullException("UpdateRoomHttpRequest Parameter Error");
|
|
}
|
|
|
|
string roomUUID = req.roomUUID;
|
|
string url = $"{baseUrl}/{roomUUID}";
|
|
var headers = SetAuthHeaders();
|
|
var bodyJson = JsonUtility.ToJson(req);
|
|
var bodyRaw = new UTF8Encoding().GetBytes(bodyJson);
|
|
|
|
UnityWebRequest www = new(url, "PUT")
|
|
{
|
|
uploadHandler = new UploadHandlerRaw(bodyRaw),
|
|
downloadHandler = new DownloadHandlerBuffer()
|
|
};
|
|
www.SetRequestHeader("Content-Type", "application/json");
|
|
|
|
foreach (var header in headers)
|
|
{
|
|
www.SetRequestHeader(header.Key, header.Value);
|
|
}
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
www.SendWebRequest().completed += op => tcs.SetResult(true);
|
|
await tcs.Task;
|
|
|
|
if (www.result != UnityWebRequest.Result.Success)
|
|
{
|
|
throw new Exception($"UpdateRoom Error: {www.error}");
|
|
}
|
|
|
|
return JsonUtility.FromJson<UpdateRoomResp>(www.downloadHandler.text);
|
|
}
|
|
|
|
private Dictionary<string, string> SetAuthHeaders()
|
|
{
|
|
if (appId == "" || appSecret == "" || gameRegionId == "" || multiverseProfileId == "")
|
|
{
|
|
Debug.LogError("MultiverseRoomAPI: Please fill in the required fields in Assets/Scripts/Gameplay/Lobby/MultiverseRoomAPI.cs");
|
|
Debug.LogError("MultiverseRoomAPI: appId, appSecret, gameRegionId, multiverseProfileId");
|
|
}
|
|
|
|
var headers = new Dictionary<string, string>();
|
|
var nonce = Guid.NewGuid().ToString();
|
|
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
|
|
|
|
headers.Add("X-TIMESTAMP", timestamp);
|
|
headers.Add("X-NONCE", nonce);
|
|
headers.Add("X-APPID", appId);
|
|
|
|
var hash = SHA256.Create();
|
|
var input = $"{appId}:{appSecret}:{timestamp}:{nonce}";
|
|
var hashBytes = hash.ComputeHash(Encoding.UTF8.GetBytes(input));
|
|
var hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
|
|
|
|
headers.Add("Authorization", $"Bearer {hashString}");
|
|
|
|
return headers;
|
|
}
|
|
|
|
internal void SetPlayerName(string newname)
|
|
{
|
|
m_PlayerName = newname;
|
|
}
|
|
|
|
internal void ConnectionFailed()
|
|
{
|
|
IsTryingToConnect = false;
|
|
ClientIsInGame = false;
|
|
}
|
|
|
|
internal void ConnectionSucceeded()
|
|
{
|
|
ClientIsInGame = true;
|
|
IsTryingToConnect = false;
|
|
}
|
|
}
|
|
}
|