diff --git a/Netick/Editor/Netick.CodeGen.dll b/Netick/Editor/Netick.CodeGen.dll index d813046..6f385ff 100644 Binary files a/Netick/Editor/Netick.CodeGen.dll and b/Netick/Editor/Netick.CodeGen.dll differ diff --git a/Netick/Editor/Netick.Unity.Editor.dll b/Netick/Editor/Netick.Unity.Editor.dll index a38cc42..9953eeb 100644 Binary files a/Netick/Editor/Netick.Unity.Editor.dll and b/Netick/Editor/Netick.Unity.Editor.dll differ diff --git a/Netick/Editor/Unity.Netick.CodeGen.dll b/Netick/Editor/Unity.Netick.CodeGen.dll index a18dc6b..dbde6d1 100644 Binary files a/Netick/Editor/Unity.Netick.CodeGen.dll and b/Netick/Editor/Unity.Netick.CodeGen.dll differ diff --git a/Netick/Runtime/Netick.Unity.dll b/Netick/Runtime/Netick.Unity.dll index 7bb160e..11b4d16 100644 Binary files a/Netick/Runtime/Netick.Unity.dll and b/Netick/Runtime/Netick.Unity.dll differ diff --git a/Netick/Runtime/Netick.dll b/Netick/Runtime/Netick.dll index a306644..0919bb6 100644 Binary files a/Netick/Runtime/Netick.dll and b/Netick/Runtime/Netick.dll differ diff --git a/README.md b/README.md index 03f3b0a..4d47079 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Netick Pic](https://i.ibb.co/QbCLKD3/Netick-Github.png) +![Netick Pic](https://i.ibb.co/C56h3dcT/Netick-Github-Main.png)

High-Performance Networking Engine for Unity

@@ -16,7 +16,7 @@ Netick is a free server-authoritative state-sync networking solution for Unity, and, in fact, for all C#-supporting game engines, it's entirely engine-agnostic. Netick is the state-of-the-art networking solution, it's the highest-performing networking solution for Unity, **achieving the lowest bandwidth and CPU usage, shown by** [**open-source benchmarks.**](#technology-and-performance) Built with an architecture that’s different from other solutions, Netick makes writing cheat-free networked games as close to writing single-player games as possible. Whether it's a competitive CS-like shooter, a 200-player battle royal, or a survival game, Netick can do it. -![Showcase](https://i.ibb.co/JRJvkCr/netick-games3.png) +![Showcase](https://i.ibb.co/9kNSY07z/Netick-Github-Showcase.png) What makes Netick vastly superior to other networking solutions are four core pillars: @@ -34,7 +34,7 @@ Netick uses an innovative Delta Snapshots algorithm that achieves the lowest ban ### Radical CPU Performance -Netick is written with high-performance unmanaged C# code in a data-oriented fashion, achieving an unseen level of performance. Netick is designed to not use any CPU time when something does not move or change. You can have 10K synced objects (non-empty) and use no additional CPU time. Again, Netick is the only solution in the market that does this. This has been referred to as "Dark Magic" by one of our users. +Netick is written with high-performance unmanaged C# code in a data-oriented fashion, achieving an unseen level of performance. Netick is designed to not use any CPU time when something does not move or change. You can have 10K synced objects (non-empty) and use no additional CPU time. Again, Netick is the only solution in the market that does this. This has been referred to as "Dark Magic" by one of our users. In addition, according to simulated testing, **Netick 2 can compress and write 250 continually moving objects (syncing position and rotation) to 250 clients in less than 0.35ms**, all in a single core (using any modern Intel CPU), giving you the best server performance ever seen in the market. ## Installation @@ -80,7 +80,7 @@ Netick lets you fully sync, predict, and interpolate anything in the game: netwo ### CPU -Netick 2 core is written with unsafe C# code, achieving the highest level of performance. According to simulated testing, it's able to write 1 packet (for 100 moving objects) to 200 clients in less than 1.4ms, all in a single core (using any modern Intel CPU). This means the networking CPU cost of your game is as little as possible, leaving you room to do other things in the game. It also means you can have more players than before, with higher tickrate, while requiring less demanding server specs. +Netick 2 core is written with unmanaged C# code, utilizing novel algorithms and achieving the highest level of performance. According to simulated testing, **Netick 2 can compress and write 250 continually moving objects (syncing position and rotation) to 250 clients in less than 0.35ms**, all in a single core (using any modern Intel CPU), giving you the best server performance ever seen in the market. This means the networking CPU cost of your game is as little as possible, leaving you room to do other things in the game. It also means you can have more players than before, with a higher tickrate, while requiring less demanding server specs. ## Helpful links: diff --git a/Samples~/First Person Shooter/Scripts/FPSEventsHandler.cs b/Samples~/First Person Shooter/Scripts/FPSEventsHandler.cs index 9de95e3..6f1fc4c 100644 --- a/Samples~/First Person Shooter/Scripts/FPSEventsHandler.cs +++ b/Samples~/First Person Shooter/Scripts/FPSEventsHandler.cs @@ -12,7 +12,7 @@ public class FPSEventsHandler : NetworkEventsListener // This is called on the server when a player has connected. public override void OnPlayerConnected(NetworkSandbox sandbox, NetworkPlayer networkPlayer) { - var spawnPos = SpawnPos.position + Vector3.left * (1 + sandbox.ConnectedPlayers.Count); + var spawnPos = SpawnPos.position + (Vector3.left * sandbox.ConnectedPlayers.Count); var player = sandbox.NetworkInstantiate(PlayerPrefab, spawnPos, Quaternion.identity, networkPlayer).GetComponent(); networkPlayer.PlayerObject = player.gameObject; } diff --git a/Scripts/AddressableSceneHandler.cs b/Scripts/AddressableSceneHandler.cs new file mode 100644 index 0000000..c84a2ce --- /dev/null +++ b/Scripts/AddressableSceneHandler.cs @@ -0,0 +1,166 @@ +using System.Collections.Generic; +using UnityEngine.SceneManagement; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; +using UnityEngine.ResourceManagement.ResourceProviders; +using UnityEngine; +using Netick.Unity; + +namespace Netick.Samples +{ + [AddComponentMenu("Netick/Addressable Scene Handler")] + public class AddressableSceneHandler : NetworkSceneHandler + { + private class AddressableSceneOperation : ISceneOperation + { + AsyncOperationHandle Handle; + bool ISceneOperation.IsDone => Handle.IsDone; + float ISceneOperation.Progress => Handle.PercentComplete; + public AddressableSceneOperation(AsyncOperationHandle handle) + { + Handle = handle; + } + } + + public string[] AddressableScenes = new string[0]; + public override int CustomScenesCount => AddressableScenes != null ? AddressableScenes.Length : 0; + + private Dictionary _keyToIndex; + private Dictionary _indexToKey; + private Dictionary _loadedScenes; + + private void Awake() + { + _keyToIndex = new(AddressableScenes.Length); + _indexToKey = new(AddressableScenes.Length); + _loadedScenes = new(AddressableScenes.Length); + + for (int i = 0; i < AddressableScenes.Length; i++) + { + _keyToIndex.Add(AddressableScenes[i], i); + _indexToKey.Add(i, AddressableScenes[i]); + } + } + + protected override ISceneOperation LoadCustomSceneAsync(int index, LoadSceneParameters loadSceneParameters, out string sceneName) + { + var key = _indexToKey[index]; + sceneName = key; + + var handle = Addressables.LoadSceneAsync(key, loadSceneParameters); + handle.Completed += handle => + { + if (handle.Status == AsyncOperationStatus.Succeeded) + _loadedScenes.Add(handle.Result.Scene, handle.Result); + else + Sandbox.LogError($"Addressables.LoadSceneAsync: failed to load an addressable scene {handle.DebugName}"); + }; + + return new AddressableSceneOperation(handle); + } + + protected override ISceneOperation UnloadCustomSceneAsync(Scene scene) + { + var didGetSceneInstance = _loadedScenes.TryGetValue(scene, out var sceneInstance); + + if (!didGetSceneInstance || !scene.IsValid()) + { + Sandbox.LogError($"Unloading scene: couldn't find a scene to unload {scene.name}"); + return null; + } + + var handle = Addressables.UnloadSceneAsync(sceneInstance); + handle.Completed += handle => + { + if (handle.Status == AsyncOperationStatus.Succeeded) + _loadedScenes.Remove(handle.Result.Scene); + else + Sandbox.LogError($"Addressables.UnloadSceneAsync: failed to unload scene {scene.name}"); + }; + + return new AddressableSceneOperation(handle); + } + + // -- Addressable Scenes + public void LoadAddressableSceneAsync(string key, LoadSceneMode loadSceneMode) + { + if (_keyToIndex.TryGetValue(key, out int customIndex)) + Sandbox.LoadCustomSceneAsync(customIndex, new LoadSceneParameters(loadSceneMode, Sandbox.GetDefaultPhysicsMode())); + else + Sandbox.LogError("Loading scene: failed to find the addressable scene key in the AddressableScenes array. Make sure to add all scenes keys to the array."); + } + + public void LoadAddressableSceneAsync(string key, LoadSceneParameters loadSceneParameters) + { + if (_keyToIndex.TryGetValue(key, out int customIndex)) + Sandbox.LoadCustomSceneAsync(customIndex, loadSceneParameters); + else + Sandbox.LogError("Loading scene: failed to find the addressable scene key in the AddressableScenes array. Make sure to add all scenes keys to the array."); + } + + public void UnloadAddressableSceneAsync(string key) + { + if (_keyToIndex.TryGetValue(key, out int customIndex)) + Sandbox.UnloadSceneAsync(customIndex); + else + Sandbox.LogError("Unloading scene: failed to find the addressable scene key in the AddressableScenes array. Make sure to add all scenes keys to the array."); + } + + public void UnloadAddressableSceneAsync(Scene scene) + { + Sandbox.UnloadSceneAsync(scene); + } + + // -- Build Scenes + // NetworkSceneHandler already implements exactly the code shown here, the reason we still included it in here is for demonstration purposes. + protected override ISceneOperation LoadBuildSceneAsync (int buildIndex, LoadSceneParameters loadSceneParameters) => new BuildSceneOperation(SceneManager.LoadSceneAsync(buildIndex, loadSceneParameters)); + protected override ISceneOperation UnloadBuildSceneAsync (Scene scene) => new BuildSceneOperation(SceneManager.UnloadSceneAsync(scene)); + } + + public static class AddressableSceneHandlerSandboxExtensions + { + /// + /// [Server Only] Loads an addressable scene asynchronously using a key. + /// + public static void LoadAddressableSceneAsync(this NetworkSandbox sandbox, string key, LoadSceneMode loadSceneMode) + { + if (sandbox.TryGetComponent(out var defaultSceneHandler)) + defaultSceneHandler.LoadAddressableSceneAsync(key, loadSceneMode); + else + sandbox.LogError($"{nameof(AddressableSceneHandler)} is not added to the sandbox. Make sure to add {nameof(AddressableSceneHandler)} to your sandbox prefab."); + } + + /// + /// [Server Only] Loads an addressable scene asynchronously using a key. + /// + public static void LoadAddressableSceneAsync(this NetworkSandbox sandbox, string key, LoadSceneParameters loadSceneParameters) + { + if (sandbox.TryGetComponent(out var defaultSceneHandler)) + defaultSceneHandler.LoadAddressableSceneAsync(key, loadSceneParameters); + else + sandbox.LogError($"{nameof(AddressableSceneHandler)} is not added to the sandbox. Make sure to add {nameof(AddressableSceneHandler)} to your sandbox prefab."); + } + + /// + /// [Server Only] Unloads an addressable scene asynchronously using a key. + /// + public static void UnloadAddressableSceneAsync(this NetworkSandbox sandbox, string key) + { + if (sandbox.TryGetComponent(out var defaultSceneHandler)) + defaultSceneHandler.UnloadAddressableSceneAsync(key); + else + sandbox.LogError($"{nameof(AddressableSceneHandler)} is not added to the sandbox. Make sure to add {nameof(AddressableSceneHandler)} to your sandbox prefab."); + } + + /// + /// [Server Only] Unloads an addressable scene asynchronously using a Scene struct. + /// + public static void UnloadAddressableSceneAsync(this NetworkSandbox sandbox, Scene scene) + { + if (sandbox.TryGetComponent(out var defaultSceneHandler)) + defaultSceneHandler.UnloadAddressableSceneAsync(scene); + else + sandbox.LogError($"{nameof(AddressableSceneHandler)} is not added to the sandbox. Make sure to add {nameof(AddressableSceneHandler)} to your sandbox prefab."); + } + } +} \ No newline at end of file diff --git a/Scripts/AddressableSceneHandler.cs.meta b/Scripts/AddressableSceneHandler.cs.meta new file mode 100644 index 0000000..c2ceb97 --- /dev/null +++ b/Scripts/AddressableSceneHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02be3a9e2ac6d2c45a70578c62ae81d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/GameStarter.cs b/Scripts/GameStarter.cs index 57e1327..f899eef 100644 --- a/Scripts/GameStarter.cs +++ b/Scripts/GameStarter.cs @@ -5,138 +5,141 @@ namespace Netick.Samples { - [AddComponentMenu("Netick/Game Starter")] - public class GameStarter : NetworkEventsListener + /// + /// This is a helper script for quick prototyping, used to start Netick. + /// + [AddComponentMenu("Netick/Game Starter")] + public class GameStarter : NetworkEventsListener + { + public GameObject SandboxPrefab; + public NetworkTransportProvider Transport; + public StartMode Mode = StartMode.MultiplePeers; + [Range(1, 5)] + public int Clients = 1; + public bool StartServerInMultiplePeersMode = true; + + public bool AutoStart; + public bool AutoConnect; + + [Header("Network")] + [Range(0, 65535)] + public int Port; + public string ServerIPAddress = "127.0.0.1"; + + [Header("Headless Server FPS")] + public bool Cap = true; + public int FPS = 450; + + [Header("UI")] + public bool ShowDisconnectButton = true; + public bool ShowConnectButton = true; + public Vector2 Offset = new Vector2(36, 0); + + private void Reset() { - public GameObject SandboxPrefab; - public NetworkTransportProvider Transport; - public StartMode Mode = StartMode.MultiplePeers; - [Range(1, 5)] - public int Clients = 1; - public bool StartServerInMultiplePeersMode = true; - - public bool AutoStart; - public bool AutoConnect; - - [Header("Network")] - [Range(0, 65535)] - public int Port; - public string ServerIPAddress = "127.0.0.1"; - - [Header("Headless Server FPS")] - public bool Cap = true; - public int FPS = 450; - - [Header("UI")] - public bool ShowDisconnectButton = true; - public bool ShowConnectButton = true; - public Vector2 Offset = new Vector2(36, 0); - - private void Reset() + if (Port == 0) + Port = Random.Range(4000, 65535); + } + + private void Awake() + { + if (Application.isBatchMode) + { + Application.targetFrameRate = FPS; + Network.StartAsServer(Transport, Port, SandboxPrefab); + } + + else if (AutoStart) + { + if (Network.Instance == null) { - if (Port == 0) - Port = Random.Range(4000, 65535); + switch (Mode) + { + case StartMode.Server: + Network.StartAsServer(Transport, Port, SandboxPrefab); + break; + case StartMode.Client: + Network.StartAsClient(Transport, Port, SandboxPrefab).Connect(Port, ServerIPAddress); + break; + case StartMode.MultiplePeers: + var sandboxes = Network.StartAsMultiplePeers(Transport, Port, SandboxPrefab, StartServerInMultiplePeersMode, true, Clients); + + if (AutoConnect) + { + for (int i = 0; i < sandboxes.Clients.Length; i++) + sandboxes.Clients[i].Connect(Port, ServerIPAddress); + } + + + break; + } } + } + } - private void Awake() + private void OnGUI() + { + if (Network.IsRunning) + { + if (Sandbox != null && Sandbox.IsClient) { - if (Application.isBatchMode) - { - Application.targetFrameRate = FPS; - Network.StartAsServer(Transport, Port, SandboxPrefab); - } + if (!Sandbox.IsVisible) + return; - else if (AutoStart) + if (Sandbox.IsConnected) + { + if (ShowDisconnectButton) { - if (Network.Instance == null) - { - switch (Mode) - { - case StartMode.Server: - Network.StartAsServer(Transport, Port, SandboxPrefab); - break; - case StartMode.Client: - Network.StartAsClient(Transport, Port, SandboxPrefab).Connect(Port, ServerIPAddress); - break; - case StartMode.MultiplePeers: - var sandboxes = Network.StartAsMultiplePeers(Transport, Port, SandboxPrefab, StartServerInMultiplePeersMode, true, Clients); - - if (AutoConnect) - { - for (int i = 0; i < sandboxes.Clients.Length; i++) - sandboxes.Clients[i].Connect(Port, ServerIPAddress); - } - - - break; - } - } - } - } + GUI.Label(new Rect(Offset.x, Offset.y + 170, 200, 50), $"Connected to {Sandbox.ServerEndPoint}"); - private void OnGUI() - { - if (Network.IsRunning) - { - if (Sandbox != null && Sandbox.IsClient) - { - if (!Sandbox.IsVisible) - return; - - if (Sandbox.IsConnected) - { - if (ShowDisconnectButton) - { - GUI.Label(new Rect(Offset.x, Offset.y + 170, 200, 50), $"Connected to {Sandbox.ServerEndPoint}"); - - if (GUI.Button(new Rect(Offset.x, Offset.y + 220, 200, 50), "Disconnect")) - Sandbox.DisconnectFromServer(); - } - - } - else if (ShowConnectButton) - { - if (GUI.Button(new Rect(Offset.x, Offset.y + 40, 200, 50), "Connect")) - Sandbox.Connect(Port, ServerIPAddress); - - ServerIPAddress = GUI.TextField(new Rect(Offset.x, Offset.y + 100, 200, 50), ServerIPAddress); - Port = int.Parse(GUI.TextField(new Rect(Offset.x, Offset.y+ 160, 200, 50), Port.ToString())); - } - } - - - return; + if (GUI.Button(new Rect(Offset.x, Offset.y + 220, 200, 50), "Disconnect")) + Sandbox.DisconnectFromServer(); } - if (GUI.Button(new Rect(Offset.x, Offset.y + 40, 200, 50), "Run Host")) - { - Network.StartAsHost(Transport, Port, SandboxPrefab); - } + } + else if (ShowConnectButton) + { + if (GUI.Button(new Rect(Offset.x, Offset.y + 40, 200, 50), "Connect")) + Sandbox.Connect(Port, ServerIPAddress); - if (GUI.Button(new Rect(Offset.x, Offset.y + 100, 200, 50), "Run Client")) - { - var sandbox = Network.StartAsClient(Transport, Port, SandboxPrefab); - sandbox.Connect(Port, ServerIPAddress); - } + ServerIPAddress = GUI.TextField(new Rect(Offset.x, Offset.y + 100, 200, 50), ServerIPAddress); + Port = int.Parse(GUI.TextField(new Rect(Offset.x, Offset.y + 160, 200, 50), Port.ToString())); + } + } - if (GUI.Button(new Rect(Offset.x, Offset.y + 160, 200, 50), "Run Server")) - { - Network.StartAsServer(Transport, Port, SandboxPrefab); - } - if (GUI.Button(new Rect(Offset.x, Offset.y + 220, 200, 50), "Run Host + Client")) - { - var sandboxes = Network.StartAsMultiplePeers(Transport, Port, SandboxPrefab, StartServerInMultiplePeersMode, true, Clients); + return; + } - if (AutoConnect) - { - for (int i = 0; i < Clients; i++) - sandboxes.Clients[i].Connect(Port, ServerIPAddress); - } - } + if (GUI.Button(new Rect(Offset.x, Offset.y + 40, 200, 50), "Run Host")) + { + Network.StartAsHost(Transport, Port, SandboxPrefab); + } + + if (GUI.Button(new Rect(Offset.x, Offset.y + 100, 200, 50), "Run Client")) + { + var sandbox = Network.StartAsClient(Transport, Port, SandboxPrefab); + sandbox.Connect(Port, ServerIPAddress); + } + + if (GUI.Button(new Rect(Offset.x, Offset.y + 160, 200, 50), "Run Server")) + { + Network.StartAsServer(Transport, Port, SandboxPrefab); + } - ServerIPAddress = GUI.TextField(new Rect(Offset.x, Offset.y + 280, 200, 50), ServerIPAddress); + if (GUI.Button(new Rect(Offset.x, Offset.y + 220, 200, 50), "Run Host + Client")) + { + var sandboxes = Network.StartAsMultiplePeers(Transport, Port, SandboxPrefab, StartServerInMultiplePeersMode, true, Clients); + if (AutoConnect) + { + for (int i = 0; i < Clients; i++) + sandboxes.Clients[i].Connect(Port, ServerIPAddress); } + } + + ServerIPAddress = GUI.TextField(new Rect(Offset.x, Offset.y + 280, 200, 50), ServerIPAddress); + } + } } diff --git a/Scripts/Netick.Samples.asmdef b/Scripts/Netick.Samples.asmdef index 7708f29..0d79ac5 100644 --- a/Scripts/Netick.Samples.asmdef +++ b/Scripts/Netick.Samples.asmdef @@ -1,7 +1,9 @@ { "name": "Netick.Samples", + "rootNamespace": "", "references": [ - "GUID:32b718c5820ccce438606ca82358f1da" + "GUID:84651a3751eca9349aac36a66bba901b", + "GUID:9e24947de15b9834991c9d8411ea37cf" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Scripts/NetworkInfo.cs b/Scripts/NetworkInfo.cs index 50f7be3..c25cec3 100644 --- a/Scripts/NetworkInfo.cs +++ b/Scripts/NetworkInfo.cs @@ -5,88 +5,91 @@ namespace Netick.Samples { - [AddComponentMenu("Netick/Network Info")] - public class NetworkInfo : NetworkEventsListener - { - [Header("Network Stats")] - public Vector2 Offset = new Vector2(27, 20); + /// + /// This is a helper script for quick prototyping, used to show useful network information of Netick. + /// + [AddComponentMenu("Netick/Network Info")] + public class NetworkInfo : NetworkEventsListener + { + [Header("Network Stats")] + public Vector2 Offset = new Vector2(27, 20); - [Header("Network Conditions Icons")] - public float MediumLatencyThreshold = 150; - public float HighLatencyThreshold = 250; - public float MediumPacketLossThreshold = 1; - public float HighPacketLossThreshold = 10; + [Header("Network Conditions Icons")] + public float MediumLatencyThreshold = 150; + public float HighLatencyThreshold = 250; + public float MediumPacketLossThreshold = 1; + public float HighPacketLossThreshold = 10; - public Vector2 PacketLossIconOffset = new Vector2(-80, 30); - public Vector2 LatencyIconOffset = new Vector2(-80, 70); - public Vector2 ServerLagIconOffset = new Vector2(-80, 110); - public float IconSize = 30; + public Vector2 PacketLossIconOffset = new Vector2(-80, 30); + public Vector2 LatencyIconOffset = new Vector2(-80, 70); + public Vector2 ServerLagIconOffset = new Vector2(-80, 110); + public float IconSize = 30; - private Texture _packetLossIcon; - private Texture _latencyIcon; - private Texture _serverLagIcon; + private Texture _packetLossIcon; + private Texture _latencyIcon; + private Texture _serverLagIcon; - private void Awake() - { - _packetLossIcon = Resources.Load("Network Icons/PacketLoss"); - _latencyIcon = Resources.Load("Network Icons/Latency"); - _serverLagIcon = Resources.Load("Network Icons/ServerLag"); - } + private void Awake() + { + _packetLossIcon = Resources.Load("Network Icons/PacketLoss"); + _latencyIcon = Resources.Load("Network Icons/Latency"); + _serverLagIcon = Resources.Load("Network Icons/ServerLag"); + } - private void OnGUI() + private void OnGUI() + { + if (Network.IsRunning) + { + if (Sandbox != null && Sandbox.IsConnected && Sandbox.IsVisible) { - if (Network.IsRunning) - { - if (Sandbox != null && Sandbox.IsConnected && Sandbox.IsVisible) - { - DrawText(0, "RTT", (Sandbox.RTT * 1000f).ToString(), "ms"); - DrawText(1, "In", Sandbox.InKBps.ToString(), "KB/s"); - DrawText(2, "Out", Sandbox.OutKBps.ToString(), "KB/s"); - DrawText(3, "In Loss", (Sandbox.InPacketLoss * 100f).ToString(), "%"); - DrawText(4, "Out Loss", (Sandbox.OutPacketLoss * 100f).ToString(), "%"); - DrawText(5, "Interp Delay", (Sandbox.InterpolationDelay * 1000f).ToString(), "ms"); - DrawText(6, "Resims", Sandbox.Monitor.Resimulations.Average.ToString(), "Ticks"); - DrawText(7, "Srv Tick Time", (Sandbox.Monitor.ServerTickTime.Max * 1000f).ToString(), "ms"); - DrawText(8, "Delta time", (Time.deltaTime * 1000f).ToString(), "ms"); + DrawText(0, "RTT", (Sandbox.RTT * 1000f).ToString(), "ms"); + DrawText(1, "In", Sandbox.InKBps.ToString(), "KB/s"); + DrawText(2, "Out", Sandbox.OutKBps.ToString(), "KB/s"); + DrawText(3, "In Loss", (Sandbox.InPacketLoss * 100f).ToString(), "%"); + DrawText(4, "Out Loss", (Sandbox.OutPacketLoss * 100f).ToString(), "%"); + DrawText(5, "Interp Delay", (Sandbox.InterpolationDelay * 1000f).ToString(), "ms"); + DrawText(6, "Resims", Sandbox.Monitor.Resimulations.Average.ToString(), "ticks"); + DrawText(7, "Srv Tick Time", (Sandbox.Monitor.ServerTickTime.Max * 1000f).ToString(), "ms"); + DrawText(8, "Delta time", (Time.deltaTime * 1000f).ToString(), "ms"); - DrawIcons(); - } - } + DrawIcons(); } + } + } - private void DrawText(int offset, string title, string content, string unit) - { - GUI.Label(new Rect(Offset.x + 10, Offset.y + 10 + (15 * offset), 200, 50), $"{title}: "); - GUI.Label(new Rect(Offset.x + 130, Offset.y + 10 + (15 * offset), 200, 50), $"{content} {unit}"); - } + private void DrawText(int offset, string title, string content, string unit) + { + GUI.Label(new Rect(Offset.x + 10, Offset.y + 10 + (15 * offset), 200, 50), $"{title}: "); + GUI.Label(new Rect(Offset.x + 130, Offset.y + 10 + (15 * offset), 200, 50), $"{content} {unit}"); + } - public void DrawIcons() - { - var pktLossIconPos = PacketLossIconOffset + (Screen.width * Vector2.right); - var latencyIconPos = LatencyIconOffset + (Screen.width * Vector2.right); - var serverLagIconPos = ServerLagIconOffset + (Screen.width * Vector2.right); + public void DrawIcons() + { + var pktLossIconPos = PacketLossIconOffset + (Screen.width * Vector2.right); + var latencyIconPos = LatencyIconOffset + (Screen.width * Vector2.right); + var serverLagIconPos = ServerLagIconOffset + (Screen.width * Vector2.right); - var pktLoss = Mathf.Max(Sandbox.InPacketLoss, Sandbox.OutPacketLoss) * 100; // multiplying by 100 to convert from a decimal to a percentage. - var rtt = Sandbox.RTT * 1000f; // multiplying by 1000 to convert from seconds to milliseconds. + var pktLoss = Mathf.Max(Sandbox.InPacketLoss, Sandbox.OutPacketLoss) * 100; // multiplying by 100 to convert from a decimal to a percentage. + var rtt = Sandbox.RTT * 1000f; // multiplying by 1000 to convert from seconds to milliseconds. - if (pktLoss >= MediumPacketLossThreshold) - { - Color color = pktLoss < HighPacketLossThreshold ? Color.yellow : Color.red; - GUI.DrawTexture(new Rect(pktLossIconPos, Vector2.one * IconSize), _packetLossIcon, ScaleMode.ScaleToFit, true, 1f, color, 0f, 0f); - } + if (pktLoss >= MediumPacketLossThreshold) + { + Color color = pktLoss < HighPacketLossThreshold ? Color.yellow : Color.red; + GUI.DrawTexture(new Rect(pktLossIconPos, Vector2.one * IconSize), _packetLossIcon, ScaleMode.ScaleToFit, true, 1f, color, 0f, 0f); + } - if (rtt >= MediumLatencyThreshold) - { - Color color = rtt >= HighLatencyThreshold ? Color.red : Color.yellow; - GUI.DrawTexture(new Rect(latencyIconPos, Vector2.one * IconSize), _latencyIcon, ScaleMode.ScaleToFit, true, 1f, color, 0f, 0f); - } + if (rtt >= MediumLatencyThreshold) + { + Color color = rtt >= HighLatencyThreshold ? Color.red : Color.yellow; + GUI.DrawTexture(new Rect(latencyIconPos, Vector2.one * IconSize), _latencyIcon, ScaleMode.ScaleToFit, true, 1f, color, 0f, 0f); + } - if (Sandbox.Monitor.ServerTickTime.Max >= Sandbox.FixedDeltaTime) - { - Color color = Sandbox.Monitor.ServerTickTime.Average >= Sandbox.FixedDeltaTime ? Color.red : Color.yellow; - GUI.DrawTexture(new Rect(serverLagIconPos, Vector2.one * IconSize), _serverLagIcon, ScaleMode.ScaleToFit, true, 1f, color, 0f, 0f); - } + if (Sandbox.Monitor.ServerTickTime.Max >= Sandbox.FixedDeltaTime) + { + Color color = Sandbox.Monitor.ServerTickTime.Average >= Sandbox.FixedDeltaTime ? Color.red : Color.yellow; + GUI.DrawTexture(new Rect(serverLagIconPos, Vector2.one * IconSize), _serverLagIcon, ScaleMode.ScaleToFit, true, 1f, color, 0f, 0f); + } - } } + } } diff --git a/Scripts/PlayerSpawner.cs b/Scripts/PlayerSpawner.cs new file mode 100644 index 0000000..e20f2a5 --- /dev/null +++ b/Scripts/PlayerSpawner.cs @@ -0,0 +1,41 @@ +using UnityEngine; +using Netick; +using Netick.Unity; +using Network = Netick.Unity.Network; + +namespace Netick.Samples +{ + /// + /// This is a helper script for quick prototyping, used to spawn/despawn a player prefab when a player (client or host) has connected/disconnected. + /// + [AddComponentMenu("Netick/Player Spawner")] + public class PlayerSpawner : NetworkEventsListener + { + public GameObject PlayerPrefab; + public Transform SpawnPosition; + public float HorizontalOffset = 5f; + public bool StaggerSpawns = true; + public bool DestroyPlayerObjectWhenLeaving = true; + + // This is called on the server when a player has connected. + public override void OnPlayerConnected(NetworkSandbox sandbox, Netick.NetworkPlayer client) + { + var spawnPos = SpawnPosition.position; + if (StaggerSpawns) + spawnPos += (HorizontalOffset * Vector3.left) * (sandbox.ConnectedPlayers.Count - 1); + var player = sandbox.NetworkInstantiate(PlayerPrefab, spawnPos, SpawnPosition.rotation, client); + client.PlayerObject = player; + } + + // This is called on the server when a player has disconnected. + public override void OnPlayerDisconnected(NetworkSandbox sandbox, Netick.NetworkPlayer client, TransportDisconnectReason transportDisconnectReason) + { + if (!DestroyPlayerObjectWhenLeaving) + return; + + var netObj = client.PlayerObject as NetworkObject; + if (netObj != null) + Sandbox.Destroy(netObj); + } + } +} diff --git a/Scripts/PlayerSpawner.cs.meta b/Scripts/PlayerSpawner.cs.meta new file mode 100644 index 0000000..ea14840 --- /dev/null +++ b/Scripts/PlayerSpawner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 13d95c28de1bac54f8b26fc8dd960077 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Third-Party-Notices.txt b/Third-Party-Notices.txt index 0dacd26..c15932f 100644 --- a/Third-Party-Notices.txt +++ b/Third-Party-Notices.txt @@ -48,4 +48,20 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. +- TLSF + https://github.com/mattconte/tlsf + Copyright (c) 2006-2016, Matthew Conte + All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Transport/LiteNetLib Transport/LiteNetLibTransportProvider.cs b/Transport/LiteNetLib Transport/LiteNetLibTransportProvider.cs index 4f8efd5..c0ad5db 100644 --- a/Transport/LiteNetLib Transport/LiteNetLibTransportProvider.cs +++ b/Transport/LiteNetLib Transport/LiteNetLibTransportProvider.cs @@ -120,8 +120,9 @@ void INetEventListener.OnConnectionRequest(ConnectionRequest request) return; } - request.Data.GetBytes(_connectionBytes, 0, request.Data.AvailableBytes); - bool accepted = NetworkPeer.OnConnectRequest(_connectionBytes, request.Data.AvailableBytes, request.RemoteEndPoint.ToNetickEndPoint()); + int len = request.Data.AvailableBytes; + request.Data.GetBytes(_connectionBytes, 0, len); + bool accepted = NetworkPeer.OnConnectRequest(_connectionBytes, len, request.RemoteEndPoint.ToNetickEndPoint()); if (accepted) request.Accept(); @@ -194,5 +195,4 @@ void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErr void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { } void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) { } } -} - +} \ No newline at end of file diff --git a/package.json b/package.json index b135426..c1bcff7 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "com.karrar.netick", - "version": "0.12.72", + "version": "0.13.63", "displayName": "Netick", "description": "A networking solution for Unity", "unity": "2021.3", "dependencies": { - "com.unity.nuget.mono-cecil": "1.11.4" + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.addressables": "1.22.3" }, "documentationUrl": "https://www.netick.net/docs.html", "samples": [