Megacity demo game for UOS
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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;
}
}
}