This commit is contained in:
yaoyanwei
2025-08-04 16:45:48 +08:00
parent 565aa16389
commit 2f2a601227
2296 changed files with 522745 additions and 93 deletions

View File

@@ -0,0 +1,151 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
namespace TcgEngine
{
/// <summary>
/// Base class for all Authenticators, must be inherited
/// </summary>
public abstract class Authenticator
{
protected string user_id = null;
protected string username = null;
protected bool logged_in = false;
protected bool inited = false;
public virtual async Task Initialize()
{
inited = true;
await Task.Yield(); //Do nothing
}
public virtual async Task<bool> Login(string username)
{
await Task.Yield(); //Do nothing
return false;
}
public virtual async Task<bool> Login(string username, string token)
{
return await Login(username); //Some authenticator dont define this function
}
public virtual async Task<bool> RefreshLogin()
{
return await Login(username); //Same as Login if not defined
}
//Bypass login system by just assigning your own values, for testing
public virtual void LoginTest(string username)
{
this.user_id = username;
this.username = username;
logged_in = true;
}
public virtual async Task<bool> Register(string username, string email, string token)
{
return await Login(username, token); //Some authenticator dont define this function
}
public virtual async Task<UserData> LoadUserData()
{
await Task.Yield(); //Do nothing
return null;
}
public virtual async Task<bool> SaveUserData()
{
await Task.Yield(); //Do nothing
return false;
}
public virtual void Logout()
{
logged_in = false;
user_id = null;
username = null;
}
public virtual bool IsInited()
{
return inited;
}
public virtual bool IsConnected()
{
return IsSignedIn() && !IsExpired();
}
public virtual bool IsSignedIn()
{
return logged_in; //IsSignedIn will still be true if the login expires
}
public virtual bool IsExpired()
{
return false;
}
public virtual string GetUserId()
{
return user_id;
}
public virtual string GetUsername()
{
return username;
}
public virtual int GetPermission()
{
return logged_in ? 1 : 0;
}
public virtual UserData GetUserData()
{
return null;
}
public virtual string GetError()
{
return ""; //Should return the latest error
}
public bool IsTest()
{
return NetworkData.Get().auth_type == AuthenticatorType.LocalSave;
}
public bool IsApi()
{
return NetworkData.Get().auth_type == AuthenticatorType.Api;
}
public string UserID{ get{ return GetUserId(); }}
public string Username{ get { return GetUsername(); } }
public UserData UserData{ get { return GetUserData(); } }
public static Authenticator Create(AuthenticatorType type)
{
if (type == AuthenticatorType.Api)
return new AuthenticatorApi();
else
return new AuthenticatorLocal();
}
public static Authenticator Get()
{
return TcgNetwork.Get().Auth; //Access authenticator
}
}
public enum AuthenticatorType
{
LocalSave = 0, //Test Mode, Fake login for quick testing without the need to login each time
Api = 10, //Actual online login
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a56e07537e90ef46b7f66b6f65dd361
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,105 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
namespace TcgEngine
{
/// <summary>
/// This authenticator require external UserLogin API asset
/// It works with an actual web API and database containing all user info
/// </summary>
public class AuthenticatorApi : Authenticator
{
private int permission = 0;
public override async Task Initialize()
{
await base.Initialize();
}
public override async Task<bool> Login(string username, string password)
{
LoginResponse res = await Client.Login(username, password);
if (res.success)
{
this.logged_in = true;
this.user_id = res.id;
this.username = res.username;
permission = res.permission_level;
}
return res.success;
}
public override async Task<bool> RefreshLogin()
{
LoginResponse res = await Client.RefreshLogin();
if (res.success)
{
this.logged_in = true;
this.user_id = res.id;
this.username = res.username;
}
return res.success;
}
public override async Task<bool> Register(string username, string email, string password)
{
RegisterResponse res = await Client.Register(username, email, password);
if (res.success)
await Login(username, password);
return res.success;
}
public override async Task<UserData> LoadUserData()
{
UserData res = await Client.LoadUserData();
return res;
}
public override async Task<bool> SaveUserData()
{
//Do nothing, saved on each api request, no need to save to disk
await Task.Yield();
return false;
}
public override void Logout()
{
base.Logout();
Client.Logout();
permission = 0;
}
public override UserData GetUserData()
{
return Client.UserData;
}
public override bool IsSignedIn()
{
return Client.IsLoggedIn();
}
public override bool IsExpired()
{
return Client.IsExpired();
}
public override int GetPermission()
{
return permission;
}
public override string GetError()
{
return Client.GetLastError();
}
public ApiClient Client { get { return ApiClient.Get(); } }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0522f2da9e4192e4cb5d83f4dfb2e2b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
namespace TcgEngine
{
/// <summary>
/// Test authenticator just generates a random ID to use as user id
/// This is very useful to test the game in multiplayer without needing to login each time
/// Unity Services features won't work in test mode (Relay, Cloud Saves...)
/// Use Anonymous mode to test those features (after connecting your project ID in services window)
/// </summary>
public class AuthenticatorLocal : Authenticator
{
private UserData udata = null;
public override async Task<bool> Login(string username)
{
this.user_id = username; //User username as ID for save file consistency when testing
this.username = username;
logged_in = true;
await Task.Yield(); //Do nothing
PlayerPrefs.SetString("tcg_user", username); //Save last user
return true;
}
public override async Task<bool> RefreshLogin()
{
string username = PlayerPrefs.GetString("tcg_user", "");
if (!string.IsNullOrEmpty(username))
{
bool success = await Login(username);
return success;
}
return false;
}
public override async Task<UserData> LoadUserData()
{
string user = PlayerPrefs.GetString("tcg_user", "");
string file = username + ".user";
if (!string.IsNullOrEmpty(user) && SaveTool.DoesFileExist(file))
{
udata = SaveTool.LoadFile<UserData>(file);
}
if(udata == null)
{
udata = new UserData();
udata.username = username;
udata.id = username;
}
await Task.Yield(); //Do nothing
return udata;
}
public override async Task<bool> SaveUserData()
{
if (udata != null && SaveTool.IsValidFilename(username))
{
string file = username + ".user";
SaveTool.SaveFile<UserData>(file, udata);
await Task.Yield(); //Do nothing
return true;
}
return false;
}
public override void Logout()
{
base.Logout();
udata = null;
PlayerPrefs.DeleteKey("tcg_user");
}
public override UserData GetUserData()
{
return udata;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4def3c2352754d840b1f4c9c558100b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TcgEngine
{
/// <summary>
/// Main config file for all network-related things
/// Server API password is not in this file (and is in the Server scene instead) to prevent exposing it to client build
/// </summary>
[CreateAssetMenu(fileName = "NetworkData", menuName = "TcgEngine/NetworkData", order = 0)]
public class NetworkData : ScriptableObject
{
[Header("Game Server")]
public string url; //Url of your Game Server
public ushort port; //Port to connect/listen on your game server
[Header("API")]
public string api_url; //Url of your Nodejs API (can be same as Game Server)
public bool api_https; //Http or Https ? Http will use port 80, https will use port 443
[Header("Settings")]
public SoloType solo_type; //Wether to use Netcode or not in Solo mode (multiplayer always use netcode), using Netcode means more consistency between the 2 modes
public AuthenticatorType auth_type; //Test Mode (local mode) or API mode
public static NetworkData Get()
{
return TcgNetwork.Get().data;
}
}
public enum SoloType
{
UseNetcode = 0, //Use Netcode network messages in solo to have more similar behavior on both multiplayer and solo, Recommended for consistency between multi/solo
Offline = 10 //Make solo totally offline (no netcode) but may behave differently than multiplayer, required for WebGL since StartHost don't work on webgl.
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70dc9fcaed7d5274a8381edb4e206a92
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,405 @@
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
namespace TcgEngine
{
/// <summary>
/// Base class for sending and receiving network messages
/// </summary>
public class NetworkMessaging
{
private TcgNetwork network;
private Dictionary<string, System.Action<ulong, FastBufferReader>> msg_dict = new Dictionary<string, System.Action<ulong, FastBufferReader>>();
public NetworkMessaging(TcgNetwork network)
{
this.network = network;
network.onConnect += OnConnect;
}
private void OnConnect()
{
foreach (KeyValuePair<string, System.Action<ulong, FastBufferReader>> pair in msg_dict)
{
RegisterNetMsg(pair.Key, pair.Value);
}
}
public void ListenMsg(string type, System.Action<ulong, FastBufferReader> callback)
{
msg_dict[type] = callback;
RegisterNetMsg(type, callback);
}
public void UnListenMsg(string type)
{
msg_dict.Remove(type);
if (network.NetworkManager.CustomMessagingManager != null)
network.NetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler(type);
}
private void RegisterNetMsg(string type, System.Action<ulong, FastBufferReader> callback)
{
if (IsOnline)
{
network.NetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(type, (ulong client_id, FastBufferReader reader) =>
{
ReceiveNetMessage(type, client_id, reader);
});
}
}
private void ReceiveNetMessage(string type, ulong client_id, FastBufferReader reader)
{
bool valid = msg_dict.TryGetValue(type, out System.Action<ulong, FastBufferReader> callback);
if (valid && IsOnline)
{
callback(client_id, reader);
}
}
//--------- Send Single ----------
public void SendEmpty(string type, ulong target, NetworkDelivery delivery)
{
FastBufferWriter writer = new FastBufferWriter(0, Allocator.Temp);
Send(type, target, writer, delivery);
writer.Dispose();
}
public void SendBytes(string type, ulong target, byte[] msg, NetworkDelivery delivery)
{
FastBufferWriter writer = new FastBufferWriter(msg.Length, Allocator.Temp);
writer.WriteBytesSafe(msg, msg.Length);
Send(type, target, writer, delivery);
writer.Dispose();
}
public void SendString(string type, ulong target, string msg, NetworkDelivery delivery)
{
FastBufferWriter writer = new FastBufferWriter(msg.Length, Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteValueSafe(msg);
Send(type, target, writer, delivery);
writer.Dispose();
}
public void SendInt(string type, ulong target, int data, NetworkDelivery delivery)
{
FastBufferWriter writer = new FastBufferWriter(4, Allocator.Temp);
writer.WriteValueSafe(data);
Send(type, target, writer, delivery);
writer.Dispose();
}
public void SendUInt64(string type, ulong target, ulong data, NetworkDelivery delivery)
{
FastBufferWriter writer = new FastBufferWriter(8, Allocator.Temp);
writer.WriteValueSafe(data);
Send(type, target, writer, delivery);
writer.Dispose();
}
public void SendFloat(string type, ulong target, float data, NetworkDelivery delivery)
{
FastBufferWriter writer = new FastBufferWriter(4, Allocator.Temp);
writer.WriteValueSafe(data);
Send(type, target, writer, delivery);
writer.Dispose();
}
public void SendObject<T>(string type, ulong target, T data, NetworkDelivery delivery) where T : INetworkSerializable
{
FastBufferWriter writer = new FastBufferWriter(256, Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteNetworkSerializable(data);
Send(type, target, writer, delivery);
writer.Dispose();
}
//--------- Send Multi ----------
public void SendEmpty(string type, IReadOnlyList<ulong> targets, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(0, Allocator.Temp);
Send(type, targets, writer, delivery);
writer.Dispose();
}
}
public void SendBytes(string type, IReadOnlyList<ulong> targets, byte[] msg, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(msg.Length, Allocator.Temp);
writer.WriteBytesSafe(msg, msg.Length);
Send(type, targets, writer, delivery);
writer.Dispose();
}
}
public void SendString(string type, IReadOnlyList<ulong> targets, string msg, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(msg.Length, Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteValueSafe(msg);
Send(type, targets, writer, delivery);
writer.Dispose();
}
}
public void SendInt(string type, IReadOnlyList<ulong> targets, int data, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(4, Allocator.Temp);
writer.WriteValueSafe(data);
Send(type, targets, writer, delivery);
writer.Dispose();
}
}
public void SendUInt64(string type, IReadOnlyList<ulong> targets, ulong data, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(8, Allocator.Temp);
writer.WriteValueSafe(data);
Send(type, targets, writer, delivery);
writer.Dispose();
}
}
public void SendFloat(string type, IReadOnlyList<ulong> targets, float data, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(4, Allocator.Temp);
writer.WriteValueSafe(data);
Send(type, targets, writer, delivery);
writer.Dispose();
}
}
public void SendObject<T>(string type, IReadOnlyList<ulong> targets, T data, NetworkDelivery delivery) where T : INetworkSerializable
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(256, Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteNetworkSerializable(data);
Send(type, targets, writer, delivery);
writer.Dispose();
}
}
//--------- Send All ----------
public void SendEmptyAll(string type, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(0, Allocator.Temp);
SendAll(type, writer, delivery);
writer.Dispose();
}
}
public void SendStringAll(string type, string msg, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(msg.Length, Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteValueSafe(msg);
SendAll(type, writer, delivery);
writer.Dispose();
}
}
public void SendIntAll(string type, int data, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(4, Allocator.Temp);
writer.WriteValueSafe(data);
SendAll(type, writer, delivery);
writer.Dispose();
}
}
public void SendUInt64All(string type, ulong data, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(8, Allocator.Temp);
writer.WriteValueSafe(data);
SendAll(type, writer, delivery);
writer.Dispose();
}
}
public void SendFloatAll(string type, float data, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(4, Allocator.Temp);
writer.WriteValueSafe(data);
SendAll(type, writer, delivery);
writer.Dispose();
}
}
public void SendBytesAll(string type, byte[] msg, NetworkDelivery delivery)
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(msg.Length, Allocator.Temp);
writer.WriteBytesSafe(msg, msg.Length);
SendAll(type, writer, delivery);
writer.Dispose();
}
}
public void SendObjectAll<T>(string type, T data, NetworkDelivery delivery) where T : INetworkSerializable
{
if (IsServer)
{
FastBufferWriter writer = new FastBufferWriter(256, Allocator.Temp, TcgNetwork.MsgSizeMax);
writer.WriteNetworkSerializable(data);
SendAll(type, writer, delivery);
writer.Dispose();
}
}
//-------- Generic Send ----------
public void Send(string type, ulong target, FastBufferWriter writer, NetworkDelivery delivery)
{
if (IsOnline)
SendOnline(type, target, writer, delivery);
else if(target == ClientID)
SendOffline(type, writer);
}
public void Send(string type, IReadOnlyList<ulong> targets, FastBufferWriter writer, NetworkDelivery delivery)
{
if (IsOnline)
SendOnline(type, targets, writer, delivery);
else if (Contains(targets, ClientID))
SendOffline(type, writer);
}
public void SendAll(string type, FastBufferWriter writer, NetworkDelivery delivery)
{
Send(type, ClientList, writer, delivery);
}
private void SendOnline(string type, ulong target, FastBufferWriter writer, NetworkDelivery delivery)
{
network.NetworkManager.CustomMessagingManager.SendNamedMessage(type, target, writer, delivery);
}
private void SendOnline(string type, IReadOnlyList<ulong> targets, FastBufferWriter writer, NetworkDelivery delivery)
{
network.NetworkManager.CustomMessagingManager.SendNamedMessage(type, targets, writer, delivery);
}
//Just copy the message from writer to reader locally and call the callback immediately
private void SendOffline(string type, FastBufferWriter writer)
{
bool found = msg_dict.TryGetValue(type, out System.Action<ulong, FastBufferReader> callback);
if (found)
{
FastBufferReader reader = new FastBufferReader(writer, Allocator.Temp);
callback?.Invoke(ClientID, reader);
reader.Dispose();
}
}
//--------- Forward msgs ----------
//Forward a client message to one client
//Make sure you finished reading the reader before forwarding
public void Forward(string type, ulong target, FastBufferReader reader, NetworkDelivery delivery)
{
if (IsServer && IsOnline)
{
reader.Seek(0); //Reset reader
reader.ReadValueSafe(out ulong header); //Ignore header
byte[] bytes = new byte[reader.Length - reader.Position];
reader.ReadBytesSafe(ref bytes, reader.Length - reader.Position);
FastBufferWriter writer = new FastBufferWriter(bytes.Length, Allocator.Temp);
writer.WriteBytesSafe(bytes, bytes.Length);
network.NetworkManager.CustomMessagingManager.SendNamedMessage(type, target, writer, delivery);
writer.Dispose();
}
}
//Forward a client message to all target clients
//Make sure you finished reading the reader before forwarding
public void Forward(string type, IReadOnlyList<ulong> targets, FastBufferReader reader, NetworkDelivery delivery)
{
if (IsServer && IsOnline)
{
reader.Seek(0); //Reset reader
reader.ReadValueSafe(out ulong header); //Ignore header
byte[] bytes = new byte[reader.Length - reader.Position];
reader.ReadBytesSafe(ref bytes, reader.Length - reader.Position);
FastBufferWriter writer = new FastBufferWriter(bytes.Length, Allocator.Temp);
writer.WriteBytesSafe(bytes, bytes.Length);
network.NetworkManager.CustomMessagingManager.SendNamedMessage(type, targets, writer, delivery);
writer.Dispose();
}
}
//Forward a client message to all other clients (other than the source)
//Make sure you finished reading the reader before forwarding
public void ForwardAll(string type, ulong source_client, FastBufferReader reader, NetworkDelivery delivery)
{
if (IsServer && IsOnline)
{
reader.Seek(0); //Reset reader
reader.ReadValueSafe(out ulong header); //Ignore header
byte[] bytes = new byte[reader.Length - reader.Position];
reader.ReadBytesSafe(ref bytes, reader.Length - reader.Position);
FastBufferWriter writer = new FastBufferWriter(bytes.Length, Allocator.Temp);
writer.WriteBytesSafe(bytes, bytes.Length);
foreach (ulong client in ClientList)
{
if(client != source_client && client != ClientID)
network.NetworkManager.CustomMessagingManager.SendNamedMessage(type, client, writer, delivery);
}
writer.Dispose();
}
}
private bool Contains(IReadOnlyList<ulong> list, ulong client_id)
{
foreach (ulong cid in list)
{
if (cid == client_id)
return true;
}
return false;
}
public IReadOnlyList<ulong> ClientList { get { return network.GetClientsIds(); } }
public bool IsOnline { get { return network.IsOnline; } }
public bool IsServer { get { return network.IsServer; } }
public ulong ServerID { get { return network.ServerID; } }
public ulong ClientID { get { return network.ClientID; } }
public static NetworkMessaging Get()
{
return TcgNetwork.Get().Messaging;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d92360b2bb9a0be4daa5a012f0b9b63c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,366 @@
using Unity.Netcode;
using UnityEngine.Events;
namespace TcgEngine
{
//-------- Connection --------
public class MsgPlayerConnect : INetworkSerializable
{
public string user_id;
public string username;
public string game_uid;
public int nb_players;
public bool observer; //join as observer
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref user_id);
serializer.SerializeValue(ref username);
serializer.SerializeValue(ref game_uid);
serializer.SerializeValue(ref nb_players);
serializer.SerializeValue(ref observer);
}
}
public class MsgAfterConnected : INetworkSerializable
{
public bool success;
public int player_id;
public Game game_data;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref success);
serializer.SerializeValue(ref player_id);
if (serializer.IsReader)
{
int size = 0;
serializer.SerializeValue(ref size);
if (size > 0)
{
byte[] bytes = new byte[size];
serializer.SerializeValue(ref bytes);
game_data = NetworkTool.Deserialize<Game>(bytes);
}
}
if (serializer.IsWriter)
{
byte[] bytes = NetworkTool.Serialize(game_data);
int size = bytes.Length;
serializer.SerializeValue(ref size);
if(size > 0)
serializer.SerializeValue(ref bytes);
}
}
}
//-------- Matchmaking --------
public class MsgMatchmaking : INetworkSerializable
{
public string user_id;
public string username;
public string group;
public int players;
public int elo;
public bool refresh;
public float time;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref user_id);
serializer.SerializeValue(ref username);
serializer.SerializeValue(ref group);
serializer.SerializeValue(ref players);
serializer.SerializeValue(ref elo);
serializer.SerializeValue(ref refresh);
serializer.SerializeValue(ref time);
}
}
public class MatchmakingResult : INetworkSerializable
{
public bool success;
public int players;
public string group;
public string server_url;
public string game_uid;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref success);
serializer.SerializeValue(ref players);
serializer.SerializeValue(ref group);
serializer.SerializeValue(ref server_url);
serializer.SerializeValue(ref game_uid);
}
}
public class MsgMatchmakingList : INetworkSerializable
{
public string username;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref username);
}
}
[System.Serializable]
public struct MatchmakingListItem : INetworkSerializable
{
public string group;
public string user_id;
public string username;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref group);
serializer.SerializeValue(ref user_id);
serializer.SerializeValue(ref username);
}
}
public class MatchmakingList : INetworkSerializable
{
public MatchmakingListItem[] items;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
NetworkTool.NetSerializeArray(serializer, ref items);
}
}
[System.Serializable]
public class MatchListItem : INetworkSerializable
{
public string group;
public string username;
public string game_uid;
public string game_url;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref group);
serializer.SerializeValue(ref username);
serializer.SerializeValue(ref game_uid);
serializer.SerializeValue(ref game_url);
}
}
public class MatchList : INetworkSerializable
{
public MatchListItem[] items;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
NetworkTool.NetSerializeArray(serializer, ref items);
}
}
//-------- In Game --------
public class MsgPlayCard : INetworkSerializable
{
public string card_uid;
public Slot slot;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref card_uid);
serializer.SerializeNetworkSerializable(ref slot);
}
}
public class MsgCard : INetworkSerializable
{
public string card_uid;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref card_uid);
}
}
public class MsgCardValue : INetworkSerializable
{
public string card_uid;
public int value;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref card_uid);
serializer.SerializeValue(ref value);
}
}
public class MsgPlayer : INetworkSerializable
{
public int player_id;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref player_id);
}
}
public class MsgPlayerValue : INetworkSerializable
{
public int player_id;
public int value;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref player_id);
serializer.SerializeValue(ref value);
}
}
public class MsgAttack : INetworkSerializable
{
public string attacker_uid;
public string target_uid;
public int damage;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref attacker_uid);
serializer.SerializeValue(ref target_uid);
serializer.SerializeValue(ref damage);
}
}
public class MsgAttackPlayer : INetworkSerializable
{
public string attacker_uid;
public int target_id;
public int damage;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref attacker_uid);
serializer.SerializeValue(ref target_id);
serializer.SerializeValue(ref damage);
}
}
public class MsgCastAbility : INetworkSerializable
{
public string ability_id;
public string caster_uid;
public string target_uid;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref ability_id);
serializer.SerializeValue(ref caster_uid);
serializer.SerializeValue(ref target_uid);
}
}
public class MsgCastAbilityPlayer : INetworkSerializable
{
public string ability_id;
public string caster_uid;
public int target_id;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref ability_id);
serializer.SerializeValue(ref caster_uid);
serializer.SerializeValue(ref target_id);
}
}
public class MsgCastAbilitySlot : INetworkSerializable
{
public string ability_id;
public string caster_uid;
public Slot slot;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref ability_id);
serializer.SerializeValue(ref caster_uid);
serializer.SerializeNetworkSerializable(ref slot);
}
}
public class MsgSecret : INetworkSerializable
{
public string secret_uid;
public string triggerer_uid;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref secret_uid);
serializer.SerializeValue(ref triggerer_uid);
}
}
public class MsgMulligan : INetworkSerializable
{
public string[] cards;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
NetworkTool.NetSerializeArray(serializer, ref cards);
}
}
public class MsgInt : INetworkSerializable
{
public int value;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref value);
}
}
public class MsgChat : INetworkSerializable
{
public int player_id;
public string msg;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref player_id);
serializer.SerializeValue(ref msg);
}
}
public class MsgRefreshAll : INetworkSerializable
{
public Game game_data;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
if (serializer.IsReader)
{
int size = 0;
serializer.SerializeValue(ref size);
if (size > 0)
{
byte[] bytes = new byte[size];
serializer.SerializeValue(ref bytes);
game_data = NetworkTool.Deserialize<Game>(bytes);
}
}
if (serializer.IsWriter)
{
byte[] bytes = NetworkTool.Serialize(game_data);
int size = bytes.Length;
serializer.SerializeValue(ref size);
if (size > 0)
serializer.SerializeValue(ref bytes);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc2dfed09963fd54eafcfe01672fdb4b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,387 @@
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Events;
namespace TcgEngine
{
/// <summary>
/// Main script handling network connection betweeen server and client
/// It's one of the few scripts in this asset that needs to be on a DontDestroyOnLoad object
/// </summary>
[DefaultExecutionOrder(-10)]
[RequireComponent(typeof(NetworkManager))]
[RequireComponent(typeof(TcgTransport))]
public class TcgNetwork : MonoBehaviour
{
public NetworkData data;
//Server & Client events
public UnityAction onTick; //Every network tick
public UnityAction onConnect; //Event when self connect, happens before onReady, before sending any data
public UnityAction onDisconnect; //Event when self disconnect
//Server only events
public UnityAction<ulong> onClientJoin; //Server event when any client connect
public UnityAction<ulong> onClientQuit; //Server event when any client disconnect
public UnityAction<ulong> onClientReady; //Server event when any client become ready
public delegate bool ApprovalEvent(ulong client_id, ConnectionData connect_data);
public ApprovalEvent checkApproval; //Additional approval validations for when a client connects
//---------
private NetworkManager network;
private TcgTransport transport;
private NetworkMessaging messaging;
private Authenticator auth;
private ConnectionData connection;
[System.NonSerialized]
private static bool inited = false;
private static TcgNetwork instance;
private const int msg_size = 1024 * 1024;
private bool offline_mode = false;
private bool connected = false;
void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
return; //Manager already exists, destroy this one
}
Init();
DontDestroyOnLoad(gameObject);
}
public void Init()
{
if (!inited || transport == null)
{
instance = this;
inited = true;
network = GetComponent<NetworkManager>();
transport = GetComponent<TcgTransport>();
messaging = new NetworkMessaging(this);
connection = new ConnectionData();
transport.Init();
network.ConnectionApprovalCallback += ApprovalCheck;
network.OnClientConnectedCallback += OnClientConnect;
network.OnClientDisconnectCallback += OnClientDisconnect;
InitAuth();
}
}
void Update()
{
}
//Start a host (client + server)
public void StartHost(ushort port)
{
Debug.Log("Host Server Port " + port);
transport.SetServer(port);
connection.user_id = auth.UserID;
connection.username = auth.Username;
network.NetworkConfig.ConnectionData = NetworkTool.NetSerialize(connection);
offline_mode = false;
network.StartHost();
AfterConnected();
}
//Start a dedicated server
public void StartServer(ushort port)
{
Debug.Log("Start Server Port " + port);
transport.SetServer(port);
connection.user_id = "";
connection.username = "";
network.NetworkConfig.ConnectionData = NetworkTool.NetSerialize(connection);
offline_mode = false;
network.StartServer();
AfterConnected();
}
//If is_host is set to true, it means this player created the game on a dedicated server
//so its still a client (not server) but is the one who selected game settings
public void StartClient(string server_url, ushort port)
{
Debug.Log("Join Server: " + server_url + " " + port);
transport.SetClient(server_url, port);
connection.user_id = auth.UserID;
connection.username = auth.Username;
network.NetworkConfig.ConnectionData = NetworkTool.NetSerialize(connection);
offline_mode = false;
network.StartClient();
}
//Start simulated host with all networking turned off
public void StartHostOffline()
{
Debug.Log("Host Offline");
Disconnect();
offline_mode = true;
AfterConnected();
}
public void Disconnect()
{
if (!IsClient && !IsServer)
return;
Debug.Log("Disconnect");
network.Shutdown();
AfterDisconnected();
}
public void SetConnectionExtraData(byte[] bytes)
{
connection.extra = bytes;
}
public void SetConnectionExtraData(string data)
{
connection.extra = NetworkTool.SerializeString(data);
}
public void SetConnectionExtraData<T>(T data) where T : INetworkSerializable, new()
{
connection.extra = NetworkTool.NetSerialize(data);
}
private async void InitAuth()
{
auth = Authenticator.Create(data.auth_type);
await auth.Initialize();
}
private void AfterConnected()
{
if (connected)
return;
if (network.NetworkTickSystem != null)
network.NetworkTickSystem.Tick += OnTick;
connected = true;
onConnect?.Invoke();
}
private void AfterDisconnected()
{
if (!connected)
return;
if (network.NetworkTickSystem != null)
network.NetworkTickSystem.Tick -= OnTick;
offline_mode = false;
connected = false;
onDisconnect?.Invoke();
}
private void OnClientConnect(ulong client_id)
{
if (IsServer && client_id != ServerID)
{
Debug.Log("Client Connected: " + client_id);
onClientJoin?.Invoke(client_id);
}
if (!IsServer)
AfterConnected(); //AfterConnected wasn't called yet for client
}
private void OnClientDisconnect(ulong client_id)
{
if (IsServer && client_id != ServerID)
{
Debug.Log("Client Disconnected: " + client_id);
onClientQuit?.Invoke(client_id);
}
if (ClientID == client_id || client_id == ServerID)
AfterDisconnected();
}
private void OnTick()
{
onTick?.Invoke();
}
private void ApprovalCheck(NetworkManager.ConnectionApprovalRequest req, NetworkManager.ConnectionApprovalResponse res)
{
ConnectionData connect = NetworkTool.NetDeserialize<ConnectionData>(req.Payload);
bool approved = ApproveClient(req.ClientNetworkId, connect);
res.Approved = approved;
}
private bool ApproveClient(ulong client_id, ConnectionData connect)
{
if (client_id == ServerID)
return true; //Server always approve itself
if (offline_mode)
return false;
if (connect == null)
return false; //Invalid data
if (string.IsNullOrEmpty(connect.username) || string.IsNullOrEmpty(connect.user_id))
return false; //Invalid username
if (checkApproval != null && !checkApproval.Invoke(client_id, connect))
return false; //Custom approval condition
return true; //New Client approved
}
public IReadOnlyList<ulong> GetClientsIds()
{
return network.ConnectedClientsIds;
}
public int CountClients()
{
if (offline_mode)
return 1;
if (IsServer && IsConnected())
return network.ConnectedClientsIds.Count;
return 0;
}
public bool IsConnecting()
{
return IsActive() && !IsConnected(); //Trying to connect but not yet
}
public bool IsConnected()
{
return offline_mode || network.IsServer || network.IsConnectedClient;
}
public bool IsActive()
{
return offline_mode || network.IsServer || network.IsClient;
}
public string Address
{
get { return transport.GetAddress(); }
}
public ushort Port
{
get { return transport.GetPort(); }
}
public ulong ClientID { get { return offline_mode ? ServerID : network.LocalClientId; } } //ID of this client (if host, will be same than ServerID), changes for every reconnection, assigned by Netcode
public ulong ServerID { get { return NetworkManager.ServerClientId; } } //ID of the server
public bool IsServer { get { return offline_mode || network.IsServer; } }
public bool IsClient { get { return offline_mode || network.IsClient; } }
public bool IsHost { get { return IsClient && IsServer; } } //Host is both a client and server
public bool IsOnline { get { return !offline_mode && IsActive(); } }
public NetworkTime LocalTime { get { return network.LocalTime; } }
public NetworkTime ServerTime { get { return network.ServerTime; } }
public float DeltaTick { get { return 1f / network.NetworkTickSystem.TickRate; } }
public NetworkManager NetworkManager { get { return network; } }
public TcgTransport Transport { get { return transport; } }
public NetworkMessaging Messaging { get { return messaging; } }
public Authenticator Auth { get { return auth; } }
public static int MsgSizeMax { get { return msg_size; } }
public static int MsgSize => MsgSizeMax; //Old name
public static TcgNetwork Get()
{
if (instance == null)
{
TcgNetwork net = FindObjectOfType<TcgNetwork>();
net?.Init();
}
return instance;
}
}
[System.Serializable]
public class ConnectionData : INetworkSerializable
{
public string user_id = "";
public string username = "";
public byte[] extra = new byte[0];
//If you add extra data, make sure the total size of ConnectionData doesn't exceed Netcode max unfragmented msg (1400 bytes)
//Fragmented msg are not possible for connection data, since connection is done in a single request
public string GetExtraString()
{
return NetworkTool.DeserializeString(extra);
}
public T GetExtraData<T>() where T : INetworkSerializable, new()
{
return NetworkTool.NetDeserialize<T>(extra);
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref user_id);
serializer.SerializeValue(ref username);
serializer.SerializeValue(ref extra);
}
}
public class SerializedData
{
private FastBufferReader reader;
private INetworkSerializable data;
private byte[] bytes;
public SerializedData(FastBufferReader r) { reader = r; data = null; }
public SerializedData(INetworkSerializable d) { data = d; }
public string GetString()
{
reader.ReadValueSafe(out string msg);
return msg;
}
public T Get<T>() where T : INetworkSerializable, new()
{
if (data != null)
{
return (T)data;
}
else if (bytes != null)
{
data = NetworkTool.NetDeserialize<T>(bytes);
return (T)data;
}
else
{
reader.ReadNetworkSerializable(out T val);
data = val;
return val;
}
}
//PreRead in advance without knowing the object type, since FastBufferReader will get disposed by netcode
public void PreRead()
{
int size = reader.Length - reader.Position;
bytes = new byte[size];
reader.ReadBytesSafe(ref bytes, size);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bbe5bb65445eda14794157ae42758a1f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -10
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
namespace TcgEngine
{
//Just a wrapper of UnityTransport to make it easier to replace with WebSocketTransport if planning to build for WebGL
public class TcgTransport : MonoBehaviour
{
//[Header("Client")]
//[TextArea] public string chain;
//[Header("Server")]
//[TextArea] public string cert;
//[TextArea] public string key; //Set this on server only
private UnityTransport transport;
private const string listen_all = "0.0.0.0";
public virtual void Init()
{
transport = GetComponent<UnityTransport>();
}
public virtual void SetServer(ushort port)
{
transport.ConnectionData.ServerListenAddress = listen_all;
transport.SetConnectionData(listen_all, port);
//transport.SetServerSecrets(cert, key);
}
public virtual void SetClient(string address, ushort port)
{
string ip = NetworkTool.HostToIP(address);
transport.SetConnectionData(ip, port);
//transport.SetClientSecrets(address, chain);
}
public virtual string GetAddress() { return transport.ConnectionData.Address; }
public virtual ushort GetPort() { return transport.ConnectionData.Port; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d354098f233ef3c4e82b6136b46e83ef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: