diff --git a/Game/Game.csproj b/Game/Game.csproj
index f15750f..6136642 100644
--- a/Game/Game.csproj
+++ b/Game/Game.csproj
@@ -21,6 +21,7 @@
+
diff --git a/Game/Network/Abstract/INetworkBehaviour.cs b/Game/Network/Abstract/INetworkBehaviour.cs
new file mode 100644
index 0000000..1f18e94
--- /dev/null
+++ b/Game/Network/Abstract/INetworkBehaviour.cs
@@ -0,0 +1,11 @@
+using Syntriax.Engine.Core.Abstract;
+
+namespace Syntriax.Engine.Network.Abstract;
+
+public interface INetworkBehaviour : IBehaviour, INetworkEntity
+{
+ bool IsServer { get; }
+ bool IsClient { get; }
+
+ INetworkCommunicator NetworkCommunicator { get; }
+}
diff --git a/Game/Network/Abstract/INetworkCommunicator.cs b/Game/Network/Abstract/INetworkCommunicator.cs
new file mode 100644
index 0000000..33e1819
--- /dev/null
+++ b/Game/Network/Abstract/INetworkCommunicator.cs
@@ -0,0 +1,19 @@
+using LiteNetLib;
+using LiteNetLib.Utils;
+
+namespace Syntriax.Engine.Network.Abstract;
+
+public interface INetworkCommunicator
+{
+ EventBasedNetListener Listener { get; }
+ NetManager Manager { get; }
+
+ void PollEvents();
+ void Stop();
+
+ void RegisterEntityPacketListener(INetworkEntity networkEntity, EntityPacketReceivedDelegate onPacketReceived) where T : INetSerializable;
+ void UnregisterEntityPacketListener(INetworkEntity networkEntity, EntityPacketReceivedDelegate onPacketReceived) where T : INetSerializable;
+ void SendEntityPacket(INetworkEntity networkEntity, T packet, params NetPeer[] netPeer) where T : INetSerializable;
+
+ delegate void EntityPacketReceivedDelegate(INetworkEntity networkEntity, object packet, NetPeer netPeer);
+}
diff --git a/Game/Network/Abstract/INetworkCommunicatorClient.cs b/Game/Network/Abstract/INetworkCommunicatorClient.cs
new file mode 100644
index 0000000..a195c5e
--- /dev/null
+++ b/Game/Network/Abstract/INetworkCommunicatorClient.cs
@@ -0,0 +1,6 @@
+namespace Syntriax.Engine.Network.Abstract;
+
+public interface INetworkCommunicatorClient : INetworkCommunicator
+{
+ void Connect(string address, int port, string? password = null);
+}
diff --git a/Game/Network/Abstract/INetworkCommunicatorServer.cs b/Game/Network/Abstract/INetworkCommunicatorServer.cs
new file mode 100644
index 0000000..4364238
--- /dev/null
+++ b/Game/Network/Abstract/INetworkCommunicatorServer.cs
@@ -0,0 +1,10 @@
+namespace Syntriax.Engine.Network.Abstract;
+
+public interface INetworkCommunicatorServer : INetworkCommunicator
+{
+ string Password { get; }
+ int MaxConnectionCount { get; }
+ int Port { get; }
+
+ void Start(int port, int maxConnectionCount, string? password = null);
+}
diff --git a/Game/Network/Abstract/INetworkEntity.cs b/Game/Network/Abstract/INetworkEntity.cs
new file mode 100644
index 0000000..45c8c81
--- /dev/null
+++ b/Game/Network/Abstract/INetworkEntity.cs
@@ -0,0 +1,17 @@
+using LiteNetLib.Utils;
+
+namespace Syntriax.Engine.Network.Abstract;
+
+public interface INetworkEntity
+{
+ event OnNetworkIdChangedDelegate? OnNetworkIdChanged;
+
+ uint NetworkId { get; set; }
+
+ delegate void OnNetworkIdChangedDelegate(INetworkEntity sender, uint previousId);
+ delegate void PacketReceivedDelegate(INetworkEntity entity, object packet);
+
+ void RegisterPacketListener(PacketReceivedDelegate onPacketReceived) where T : INetSerializable;
+ void UnregisterPacketListener(PacketReceivedDelegate onPacketReceived) where T : INetSerializable;
+ void SendPacket(T packet) where T : INetSerializable;
+}
diff --git a/Game/Network/Abstract/INetworkManager.cs b/Game/Network/Abstract/INetworkManager.cs
new file mode 100644
index 0000000..b42dc65
--- /dev/null
+++ b/Game/Network/Abstract/INetworkManager.cs
@@ -0,0 +1,10 @@
+namespace Syntriax.Engine.Network.Abstract;
+
+public interface INetworkManager
+{
+ // Action? OnNetworkGameObjectInstantiated { get; set; }
+
+ INetworkCommunicator NetworkCommunicator { get; }
+
+ // Task Instantiate(params object?[]? args) where T : class, IGameObject;
+}
diff --git a/Game/Network/Abstract/NetworkPacket.cs b/Game/Network/Abstract/NetworkPacket.cs
new file mode 100644
index 0000000..e1d9bc2
--- /dev/null
+++ b/Game/Network/Abstract/NetworkPacket.cs
@@ -0,0 +1,21 @@
+using LiteNetLib.Utils;
+
+namespace Syntriax.Engine.Network.Abstract;
+
+public class NetworkPacket()
+ : INetSerializable
+ where T : INetSerializable
+{
+ public uint NetworkId = 0;
+ public T Data = default!;
+
+ public void Deserialize(NetDataReader reader)
+ {
+ NetworkId = reader.GetUInt();
+ }
+
+ public void Serialize(NetDataWriter writer)
+ {
+ writer.Put(NetworkId);
+ }
+}
diff --git a/Game/Network/NetworkBase.cs b/Game/Network/NetworkBase.cs
new file mode 100644
index 0000000..aa9b028
--- /dev/null
+++ b/Game/Network/NetworkBase.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+using LiteNetLib;
+using LiteNetLib.Utils;
+
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Network.Abstract;
+
+namespace Syntriax.Engine.Network;
+
+public abstract class NetworkBase : BehaviourOverride, INetworkCommunicator
+{
+ private readonly NetPacketProcessor netPacketProcessor = new();
+
+ private readonly Dictionary networkEntities = [];
+ private readonly Dictionary> callbacks = [];
+
+ private BehaviourCollector networkEntityCollector = null!;
+
+ public EventBasedNetListener Listener { get; private set; } = null!;
+ public NetManager Manager { get; private set; } = null!;
+
+ public NetworkBase()
+ {
+ Priority = 10;
+
+ Listener = new EventBasedNetListener();
+ Manager = new NetManager(Listener);
+
+ Listener.NetworkReceiveEvent += NetworkReceiveEvent;
+ }
+
+ protected override void OnInitialize()
+ {
+ base.OnInitialize();
+
+ networkEntityCollector = new(GameObject.GameManager);
+ networkEntityCollector.OnCollected += OnNetworkEntityCollected;
+ networkEntityCollector.OnRemoved += OnNetworkEntityRemoved;
+ }
+ protected override void OnFinalize()
+ {
+ networkEntityCollector.OnCollected -= OnNetworkEntityCollected;
+ networkEntityCollector.OnRemoved -= OnNetworkEntityRemoved;
+ Stop();
+ }
+
+ private void OnNetworkEntityCollected(BehaviourCollector sender, INetworkEntity behaviourCollected)
+ => networkEntities.Add(behaviourCollected.NetworkId, behaviourCollected);
+
+ private void OnNetworkEntityRemoved(BehaviourCollector sender, INetworkEntity behaviourRemoved)
+ => networkEntities.Remove(behaviourRemoved.NetworkId);
+
+ private void NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod)
+ {
+ netPacketProcessor.ReadAllPackets(reader, peer);
+ }
+
+ public void PollEvents() => Manager.PollEvents();
+ public void Stop() => Manager.Stop();
+
+ protected override void OnUpdate() => PollEvents();
+
+ public void RegisterEntityPacketListener(INetworkEntity networkEntity, INetworkCommunicator.EntityPacketReceivedDelegate onPacketReceived) where T : INetSerializable
+ {
+ if (!callbacks.TryGetValue(typeof(T), out var list))
+ {
+ list = [];
+ callbacks.Add(typeof(T), list);
+ netPacketProcessor.SubscribeReusable, NetPeer>(OnPacketReceived);
+ }
+
+ if (list.Contains(onPacketReceived))
+ return;
+
+ list.Add(onPacketReceived);
+ }
+
+ public void UnregisterEntityPacketListener(INetworkEntity networkEntity, INetworkCommunicator.EntityPacketReceivedDelegate onPacketReceived) where T : INetSerializable
+ {
+ if (!callbacks.TryGetValue(typeof(T), out var list))
+ return;
+
+ list.Remove(onPacketReceived);
+ }
+
+ public void SendEntityPacket(INetworkEntity networkEntity, T packet, params NetPeer[] netPeers) where T : INetSerializable
+ {
+ NetworkPacket networkPacket = new() { NetworkId = networkEntity.NetworkId, Data = packet };
+ NetDataWriter netDataWriter = new();
+ netPacketProcessor.Write(netDataWriter, networkPacket);
+ foreach (var netPeer in netPeers)
+ netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered);
+ }
+
+ private void OnPacketReceived(NetworkPacket packet, NetPeer peer) where T : INetSerializable
+ {
+ Debug.WriteLine($"Packet Received: {packet.NetworkId} - {typeof(T)}");
+
+ if (!callbacks.TryGetValue(typeof(T), out var list))
+ return;
+
+ INetworkEntity networkEntity = networkEntities[packet.NetworkId];
+ foreach (INetworkCommunicator.EntityPacketReceivedDelegate callback in list)
+ callback.Invoke(networkEntity, packet, peer);
+ }
+}
diff --git a/Game/Network/NetworkBehaviour.cs b/Game/Network/NetworkBehaviour.cs
new file mode 100644
index 0000000..832b3de
--- /dev/null
+++ b/Game/Network/NetworkBehaviour.cs
@@ -0,0 +1,68 @@
+using System;
+using LiteNetLib;
+using LiteNetLib.Utils;
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Network.Abstract;
+
+namespace Syntriax.Engine.Network;
+
+public abstract class NetworkBehaviour : BehaviourOverride, INetworkBehaviour
+{
+ public event INetworkEntity.OnNetworkIdChangedDelegate? OnNetworkIdChanged = null;
+
+ private uint _networkId = 0;
+ public uint NetworkId
+ {
+ get => _networkId;
+ set
+ {
+ if (!IsServer)
+ return;
+
+ if (value == _networkId)
+ return;
+
+ uint previousNetworkId = _networkId;
+ _networkId = value;
+ OnNetworkIdChanged?.Invoke(this, previousNetworkId);
+ }
+ }
+
+ public bool IsServer { get; private set; } = false;
+ public bool IsClient { get; private set; } = false;
+
+ public INetworkCommunicator NetworkCommunicator { get; private set; } = null!;
+
+
+ protected override void OnInitialize()
+ {
+ NetworkCommunicator = BehaviourController.GetBehaviourInParent()
+ ?? GameObject.GameManager.FindBehaviour()
+ ?? throw new Exception($"Could not find an {nameof(INetworkCommunicator)}.");
+
+ if (NetworkCommunicator is INetworkCommunicatorClient client)
+ {
+ IsClient = true;
+ return;
+ }
+
+ IsServer = true;
+ }
+
+ public void RegisterPacketListener(INetworkEntity.PacketReceivedDelegate onPacketReceived) where T : INetSerializable
+
+ private void OnEntityPacketReceived(INetworkEntity networkEntity, object packet, NetPeer netPeer)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void UnregisterPacketListener(INetworkEntity.PacketReceivedDelegate onPacketReceived) where T : INetSerializable
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SendPacket(T packet) where T : INetSerializable
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/Game/Network/NetworkClient.cs b/Game/Network/NetworkClient.cs
new file mode 100644
index 0000000..24ef10f
--- /dev/null
+++ b/Game/Network/NetworkClient.cs
@@ -0,0 +1,12 @@
+using Syntriax.Engine.Network.Abstract;
+
+namespace Syntriax.Engine.Network;
+
+public class NetworkClient : NetworkBase, INetworkCommunicatorClient
+{
+ public void Connect(string address, int port, string? password = null)
+ {
+ Manager.Start();
+ Manager.Connect(address, port, password ?? string.Empty);
+ }
+}
diff --git a/Game/Network/NetworkExtensions.cs b/Game/Network/NetworkExtensions.cs
new file mode 100644
index 0000000..e98e479
--- /dev/null
+++ b/Game/Network/NetworkExtensions.cs
@@ -0,0 +1,21 @@
+using LiteNetLib;
+using LiteNetLib.Utils;
+
+using Syntriax.Engine.Core;
+
+namespace Syntriax.Engine.Network;
+
+public static class NetworkExtensions
+{
+ public static Vector2D GetVector2D(this NetPacketReader reader)
+ => new(reader.GetFloat(), reader.GetFloat());
+
+ public static void GetVector2D(this NetPacketReader reader, out Vector2D vector2D)
+ => vector2D = new(reader.GetFloat(), reader.GetFloat());
+
+ public static void Put(this NetDataWriter writer, Vector2D vector)
+ {
+ writer.Put(vector.X);
+ writer.Put(vector.Y);
+ }
+}
diff --git a/Game/Network/NetworkManager.cs b/Game/Network/NetworkManager.cs
new file mode 100644
index 0000000..9382abf
--- /dev/null
+++ b/Game/Network/NetworkManager.cs
@@ -0,0 +1,28 @@
+using Syntriax.Engine.Core;
+
+using Syntriax.Engine.Network.Abstract;
+
+namespace Syntriax.Engine.Network;
+
+public class NetworkManager : NetworkBehaviour, INetworkManager
+{
+ private BehaviourCollector entities = null!;
+
+ private static uint networkIdIndex = 0;
+
+ protected override void OnInitialize()
+ {
+ base.OnInitialize();
+
+ NetworkId = networkIdIndex++;
+
+ entities = new(GameObject.GameManager);
+ foreach (var entity in entities)
+ entity.NetworkId = networkIdIndex++;
+
+ entities.OnCollected += OnCollected;
+ }
+
+ private void OnCollected(BehaviourCollector collector, INetworkEntity entity)
+ => entity.NetworkId = networkIdIndex++;
+}
diff --git a/Game/Network/NetworkServer.cs b/Game/Network/NetworkServer.cs
new file mode 100644
index 0000000..a8e26ec
--- /dev/null
+++ b/Game/Network/NetworkServer.cs
@@ -0,0 +1,30 @@
+using Syntriax.Engine.Network.Abstract;
+
+namespace Syntriax.Engine.Network;
+
+public class NetworkServer : NetworkBase, INetworkCommunicatorServer
+{
+ public string Password { get; private set; } = string.Empty;
+ public int MaxConnectionCount { get; private set; } = 0;
+ public int Port { get; private set; } = 8888;
+
+ public NetworkServer() : base()
+ {
+ Listener.ConnectionRequestEvent += request =>
+ {
+ if (Manager.ConnectedPeersCount < MaxConnectionCount)
+ request.AcceptIfKey(Password);
+ else
+ request.Reject();
+ };
+ }
+
+ public void Start(int port, int maxConnectionCount, string? password = null)
+ {
+ Password = password ?? string.Empty;
+ MaxConnectionCount = maxConnectionCount;
+ Port = port;
+
+ Manager.Start(port);
+ }
+}