{"id":13676379,"url":"https://github.com/actionk/ECSPowerNetcode","last_synced_at":"2025-04-29T07:32:06.337Z","repository":{"id":73364867,"uuid":"264990737","full_name":"actionk/ECSPowerNetcode","owner":"actionk","description":"Library to power up your experience with the DOTS Unity Netcode.","archived":false,"fork":false,"pushed_at":"2020-12-27T14:01:31.000Z","size":445,"stargazers_count":37,"open_issues_count":0,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-11-11T18:41:10.938Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/actionk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-05-18T16:02:43.000Z","updated_at":"2024-08-13T14:59:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"76f81aad-e198-4ab4-a76b-979955ac2c95","html_url":"https://github.com/actionk/ECSPowerNetcode","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionk%2FECSPowerNetcode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionk%2FECSPowerNetcode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionk%2FECSPowerNetcode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionk%2FECSPowerNetcode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/actionk","download_url":"https://codeload.github.com/actionk/ECSPowerNetcode/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251455887,"owners_count":21592256,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T13:00:24.529Z","updated_at":"2025-04-29T07:32:05.833Z","avatar_url":"https://github.com/actionk.png","language":"C#","readme":"# ECSPowerNetcode\n\nThe library is made on top of the [Unity Netcode](https://docs.unity3d.com/Packages/com.unity.netcode@0.4/manual/index.html) package and saves you some time on configuring it / provides tools for client-server communication.\n\n[![](./.static/demo.png)](https://github.com/actionk/ECSPowerNetcodeDemo)\n\n# Table of Contents\n\n* [Install](#install)\n  * [Dependencies](#dependencies)\n* [Getting started](#getting-started)\n* * [Starting a server and connecting to it locally](#starting-a-server-and-connecting-to-it-locally)\n* * [Connecting to a remote server](#connecting-to-a-remote-server)\n* * [Accessing connection's entities](#accessing-connections-entities)\n* * [Handlers](#handlers)\n* [Groups](#groups)\n* [Command builders](#command-builders)\n* [Command handlers](#command-handlers)\n* [Synchronizing entities](#synchronizing-entities)\n* * [Creating an entity on server side](#creating-an-entity-on-server-side)\n* * [Transferring the entity](#transferring-the-entity)\n* * [Creating the entity on the client side](#creating-the-entity-on-the-client-side)\n* * [Synchronizing the entity](#synchronizing-the-entity)\n* * [Destroying the entity](#destroying-the-entity)\n* * [Accessing the entities](#accessing-the-entities)\n* * [Customization](#customization)\n* [Synchronizing components](#synchronizing-components)\n* [Managed RPC commands](#managed-rpc-commands)\n\n# Demo\n\nI like to have this repo as a module without a unity project, so please go to another repo which I created just for a demo: [ECSPowerNetcodeDemo](https://github.com/actionk/ECSPowerNetcodeDemo).\n\n# Install\n\nYou can either just put the files into `Assets/Plugins/ECSEntityBuilder` or use it as a submodule:\n```sh\ngit submodule add https://github.com/actionk/ECSPowerNetcode.git Assets/Plugins/ECSPowerNetcode\n```\n\n**Important!**\nAfter adding a plugin, please add the `Network` prefab into your scene from `ECSPowerNetcode/Prefabs` folder. This will enable networking.\n\n## Dependencies\n\nThe library depends on:\n* [UnityECSEntityBuilder](https://github.com/actionk/UnityECSEntityBuilder)\n* [Unity Netcode 0.3](https://docs.unity3d.com/Packages/com.unity.netcode@0.2/manual/index.html)\n\nThese are required dependencies\n\n# Getting started\n\n### Starting a server and connecting to it locally\n\n```cs\nServerManager.Instance.StartServer(7979);\nClientManager.Instance.ConnectToServer(7979);\n```\n\nAfter doing so, the library will automatically establish a connection and create command handlers for each connection in both client \u0026 server world.\n\n### Connecting to a remote server\n\n```cs\nClientManager.Instance.ConnectToServer(7979, \"remote ip address\");\n```\n\n### Accessing connection's entities\n\nEach connection is described by these parameters:\n\n```cs\npublic struct ConnectionDescription\n{\n    public int networkId; // unique network id value for client-server connection\n    public Entity connectionEntity;\n    public Entity commandHandlerEntity;\n}\n```\n\n#### Client side\n\n`ClientManager.Instance.IsConnected`\n\n`ClientManager.Instance.ConnectionToServer` -\u003e `ConnectionDescription` struct\n\n#### Server side\n\n`ServerManager.Instance.AllConnections` - getting list of all connected clients of `ConnectionDescription` struct\n\n`ServerManager.Instance.GetClientConnectionByNetworkId`\n\n### Handlers\n\n#### Client\n\nYou can set your handlers for `OnConnected` \u0026 `OnDisconnected` events:\n\n```cs\nClientManager.Instance.OnConnectedHandler += MyHandler;\nClientManager.Instance.OnDisconnectedHandler += MyHandler;\n```\n\n#### Server\n\nYou can set your handlers for `OnConnected` \u0026 `OnDisconnected` events for each player:\n\n```cs\npublic delegate void OnPlayerDisconnected(int networkConnectionId);\n\nServerManager.Instance.OnPlayerConnectedHandler += MyHandler;\nServerManager.Instance.OnPlayerDisconnectedHandler += MyHandler;\n```\n\n# Groups\n\n### Client\n\n```cs\nClientConnectionSystemGroup\nClientRequestProcessingSystemGroup\nClientNetworkEntitySystemGroup\nClientGameSimulationSystemGroup\n```\n\n| Group | Description |\n| --- | --- |\n| ClientConnectionSystemGroup | Connection/Disconnection from server |\n| ClientRequestProcessingSystemGroup | Processing requests from server |\n| ClientNetworkEntitySystemGroup | Processing network entities |\n| ClientGameSimulationSystemGroup | All your game simulations on client side |\n\n\n### Server\n\n```cs\nServerConnectionSystemGroup\nServerRequestProcessingSystemGroup\nServerNetworkEntitySystemGroup\nServerGameSimulationSystemGroup\n```\n\n| Group | Description |\n| --- | --- |\n| ServerConnectionSystemGroup | Connection/Disconnection from clients |\n| ServerRequestProcessingSystemGroup | Processing requests from clients |\n| ServerNetworkEntitySystemGroup | Processing network entities |\n| ServerGameSimulationSystemGroup | All your game simulations on server side |\n\n# Command builders\n\nFor making your life easier, there are command builders for both client \u0026 server commands.\nFirst of all, you have to create an [IRpcCommand](https://docs.unity3d.com/Packages/com.unity.netcode@0.1/manual/getting-started.html) yourself.\n\n### Client\n\n```cs\nClientToServerRpcCommandBuilder\n    .Send(new ClientPlayerLoginCommand {localPlayerSide = PlayerManager.LocalPlayerSide.LEFT})\n    .Build(PostUpdateCommands);\n```\n\nWhere `ClientPlayerLoginCommand` implements `IRpcCommand`\n\n### Server\n\n\nYou can specify which client to send the command to:\n\n```cs\nServerToClientRpcCommandBuilder\n    .SendTo(clientConnectionEntity, command)\n    .Build(PostUpdateCommands);\n    \nServerToClientRpcCommandBuilder\n    .SendTo(networkConnectionId, command)\n    .Build(PostUpdateCommands);\n```\n\nOr you can simply broadcast:\n\n```cs\nServerToClientRpcCommandBuilder\n    .Broadcast(command)\n    .Build(PostUpdateCommands);\n```\n\n# Command handlers\n\n### Client\n\nSimply inherit from `AClientReceiveRpcCommandSystem` to implement a client command handler for RPC command of type `ServerPlayerLoginResponseCommand` (for example):\n\n```cs\npublic class ClientPlayerLoginResponseSystem : AClientReceiveRpcCommandSystem\u003cServerPlayerLoginResponseCommand\u003e\n{\n    protected override void OnCommand(ref ServerPlayerLoginResponseCommand command, ConnectionDescription clientConnection)\n    {\n        // process your command\n    }\n}\n```\n\n### Server\n\nSimply inherit from `AServerReceiveRpcCommandSystem` to implement a server command handler for RPC command of type `ClientDropItemCommand` (for example):\n\n```cs\npublic class ServerDropItemSystem : AServerReceiveRpcCommandSystem\u003cClientDropItemCommand\u003e\n{\n    protected override void OnCommand(ref ClientDropItemCommand command, ConnectionDescription clientConnection)\n    {\n        // process your command\n    }\n}\n```\n\n# Synchronizing entities\n\nUsually your way of organizing entities in client-server architecture with ECS would look like that:\n\n![](./.static/organizing_entities.png)\n\nThat's for, the library provides you with a way of synchronizing entities without using ghost components:\n\n![](./.static/synchronizing_entities.png)\n\n### Creating an entity on server side\n\nYou start with creating an entity builder by inheriting your builder from `ServerNetworkEntityBuilder`:\n\n```cs\npublic class ServerPlayerBuilder : ServerNetworkEntityBuilder\u003cServerPlayerBuilder\u003e\n{\n    protected override ServerPlayerBuilder Self =\u003e this;\n\n    public static ServerPlayerBuilder Create(int networkId, Entity connection, uint playerId, PlayerManager.LocalPlayerSide localPlayerSide)\n    {\n        return new ServerPlayerBuilder(networkId, connection, playerId, localPlayerSide);\n    }\n\n    private ServerPlayerBuilder(int networkId, Entity connection, uint playerId, PlayerManager.LocalPlayerSide localPlayerSide) : base()\n    {\n        CreateFromArchetype\u003cPlayerArchetype\u003e(WorldType.SERVER);\n        SetComponentData(new Scale {Value = 1});\n        AddComponentData(new ServerPlayer\n        {\n            networkId = networkId,\n            connection = connection,\n            playerId = playerId,\n            localPlayerSide = localPlayerSide\n        });\n    }\n}\n```\n\n### Transferring the entity\n\nThen, you create an RPC command to send the entity to the clients:\n\n```cs\n[BurstCompile]\npublic struct PlayerTransferCommand : INetworkEntityCopyRpcCommand, IRpcCommand\n{\n    public ulong NetworkEntityId =\u003e networkEntityId;\n\n    public int networkId;\n    public ulong networkEntityId;\n    public uint playerId;\n    public PlayerManager.LocalPlayerSide localPlayerSide;\n    public float3 position;\n}\n```\n\nThen, for creating the command, you create a system inherited from `AServerNetworkEntityTransferSystem`:\n\n```cs\n[UpdateInGroup(typeof(ServerNetworkEntitySystemGroup))]\npublic class ServerPlayerTransferSystem : AServerNetworkEntityTransferSystem\u003cServerPlayer, PlayerTransferCommand\u003e\n{\n    protected override PlayerTransferCommand CreateTransferCommandForEntity(Entity entity, NetworkEntity networkEntity, ServerPlayer selectorComponent)\n    {\n        return new PlayerTransferCommand\n        {\n            networkId = selectorComponent.networkId,\n            networkEntityId = networkEntity.networkEntityId,\n            playerId = selectorComponent.playerId,\n            localPlayerSide = selectorComponent.localPlayerSide,\n            position = EntityManager.GetComponentData\u003cTranslation\u003e(entity).Value\n        };\n    }\n}\n```\n\nThere are also options for selection more components (2 and 3), such as `AServerNetworkEntityTransferSystemT2` and `AServerNetworkEntityTransferSystemT3`.\n\n### Creating the entity on the client side\n\nAnd the system for consuming this command on the client side:\n\n```cs\n[UpdateInGroup(typeof(ClientEarlyUpdateSystemGroup))]\npublic class ClientPlayerTransferSystem : AClientNetworkEntityTransferSystem\u003cPlayerTransferCommand\u003e\n{\n    protected override void CreateNetworkEntity(ulong networkEntityId, PlayerTransferCommand command)\n    {\n        ClientPlayerBuilder\n            .Create(command)\n            .Build(EntityManager);\n    }\n\n    protected override void SynchronizeNetworkEntity(Entity entity, PlayerTransferCommand command)\n    {\n        EntityWrapper.Wrap(entity, PostUpdateCommands)\n            .SetComponentData(new Translation {Value = command.position});\n    }\n}\n```\n\nThat's it! When you server entity is created, it will be automatically transferred to the client side by using `TransferNetworkEntityToAllClients`, which is described below\n\n### Synchronizing the entity\n\nYou have two possibilities of controlling that:\n\n1. By adding `TransferNetworkEntityToAllClients` component to your network entity. This will automatically send the transfer command to all the clients connected\n\n2. By adding a buffer of `TransferNetworkEntityToClient` and specifing the client connection to send the entity to. You can use EntityWrapper to use an existing buffer or create one if it doesn't exist:\n\n```cs\nEntityWrapper.Wrap(entity, EntityManager)\n    .AddElementToBuffer(new TransferNetworkEntityToClient(reqSrcSourceConnection));\n```\n\n### Destroying the entity\n\nWhen you want to destroy the entity on the server and all the clients at the same time, you can just add a `ServerDestroy` component to server entity and it will be automatically destroyed on all the clients:\n\n```cs\nPostUpdateCommands.AddComponent\u003cServerDestroy\u003e(myServerEntity);\n```\n\n### Accessing the entities\n\nAll:\n\n```cs\nClientManager.Instance.NetworkEntityManager.All\nServerManager.Instance.NetworkEntityManager.All\n```\n\nBy ID:\n\n```cs\nClientManager.Instance.NetworkEntityManager[networkEntityId]\nServerManager.Instance.NetworkEntityManager[networkEntityId]\n```\n\n### Customization\n\nYou have two ways of customizing your network entities:\n\n#### Custom network entity manager\n\nThe default network entity manager is `DefaultNetworkEntityManager` which implements `INetworkEntityManager`. You can just implement `INetworkEntityManager` on your own and set the entity manager for server/client:\n\n```cs\nClientManager.Instance.NetworkEntityManager = myEntityManager;\nServerManager.Instance.NetworkEntityManager = myEntityManager;\n```\n\n#### Custom network entity id factory\n\nThe default factory implementation is `DefaultNetworkEntityIdFactory`. However, you can also implement your own factory by implementing `INetworkEntityIdFactory` and replacing the default one:\n\n```cs\nServerManager.Instance.NetworkEntityIdFactory = myEntityManager;\n```\n\nAs you can see, it only works for server-side as the server is the one who assign the IDs.\n\n# Synchronizing components\n\nAs an alternatives to Unity Netcode's Ghosts, the lib provides a way of synchronizing components automatically from server to all clients.\n\n### Defining a component\n\nHere is the example:\n\n```cs\n[assembly: RegisterGenericComponentType(typeof(CopyEntityComponentRpcCommand\u003cVelocity, VelocityConverter\u003e))]\n\nnamespace Entities.Players.Packets\n{\n    public struct Velocity : IComponentData\n    {\n        public float3 value;\n\n        public bool IsZero =\u003e math.lengthsq(value) \u003c= 0.001f;\n    }\n\n    public struct VelocityConverter : ISyncEntityConverter\u003cVelocity\u003e\n    {\n        public Velocity velocity;\n        public Velocity Value =\u003e velocity;\n\n        public void Convert(Velocity value)\n        {\n            velocity = value;\n        }\n\n        public void Serialize(ref DataStreamWriter writer)\n        {\n            writer.WriteFloat(velocity.value.x);\n            writer.WriteFloat(velocity.value.y);\n            writer.WriteFloat(velocity.value.z);\n        }\n\n        public void Deserialize(ref DataStreamReader reader)\n        {\n            velocity.value = new float3(\n                reader.ReadFloat(),\n                reader.ReadFloat(),\n                reader.ReadFloat()\n            );\n        }\n    }\n\n    public class VelocityRpcCommandSender : RpcCommandSendSystem\u003c\n        CopyEntityComponentRpcCommand\u003cVelocity, VelocityConverter\u003e,\n        CopyEntityComponentRpcCommand\u003cVelocity, VelocityConverter\u003e\u003e\n    {\n    }\n}\n```\n\nThis way when you add a `Velocity` component to an entity which also has `NetworkEntity` component, the `Velocity` component will be automatically synchronized.\n\n### Triggering synchronization\n\nFor triggering synchronization process for all the components on the entities, add `Synchronize` component to the entity.\n\n### Transform synchronization\n\nYou can also synchronize all transform components in one command by just adding `SyncTransformFromServerToClient` to any entity in server world which has `NetworkEntity` component as well.\n\nIf you don't want to recieve such updates on the client side (for example, when you're controlling your character, you don't want to receive updates about his/her position), you can just add `IgnoreTransformCopyingFromServer` component to this entity in client world.\n\n# Managed RPC commands\n\nSometimes you want to have control over the requests you send to server. For example, when you send a request to perform an action and you wait until the server confirms it. In this case, you need to get a response to that exact request you sent and react to that response. You can do that with so-called managed rpc commands.\n\nFirst of all, you should create your command which should implement `IManagedRpcCommand` instead of `IRPCCommand`. This interface adds a `PacketId` field that will be filled automatically.\n\n### Client\n\nThen, you send a message to a server using `ClientToServerManagedRpcCommandBuilder`:\n\n```cs\nClientToServerManagedRpcCommandBuilder\n    .Send(new MyManagedCommand())\n    .AddEntityWaitingForResult(myEntity)\n    .Build(PostUpdateCommands)\n```\n\n### Server\n\nTo recieve the command on the server side and process it, you should create a system that implements `AServerReceiveManagedRpcCommandSystem`:\n\n```cs\nclass MyCommandRecieveSystem : AServerReceiveManagedRpcCommandSystem\u003cMyManagedCommand\u003e {\n    protected int OnCommand(ref T command, ref ReceiveRpcCommandRequestComponent requestComponent) {\n        // process command\n        return (int)MyStatusEnum.SUCCESS;\n    }\n}\n```\n\n`OnCommand` expects you to return a status int that will be sent back to the client.\n\nOnce you respond, the response will be sent back to the client and added to the entity which is waiting for it: `.AddEntityWaitingForResult(myEntity)`.\nThe added component is described below:\n\n```cs\npublic struct ManagedRpcCommandResponse : IComponentData\n{\n    public ulong packetId;\n    public int result;\n}\n```\n","funding_links":[],"categories":["NetWork","Open Source Repositories"],"sub_categories":["DOTS"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factionk%2FECSPowerNetcode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Factionk%2FECSPowerNetcode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factionk%2FECSPowerNetcode/lists"}