Modified Netcode sample gives errors when several clients connect.

I am trying to modify the NetCube sample into pong game but I am having some problems. It works fine with only 1 client, but increasing num clients to 2 gives me errors:

InvalidOperationException: Ghost receive system only supports a single connection.

InvalidOperationException: GetSingleton<Unity.NetCode.CommandTargetComponent>() requires that exactly one Unity.NetCode.CommandTargetComponent exists but there are 2.

The parts dealing with CommandTargetComponent have not changed much. In the GoInGameRPC i just changed the name of the input component.

[UpdateInGroup(typeof(ServerSimulationSystemGroup))]
public class ServerGoInGameRequestSystem : ComponentSystem {

    protected override void OnCreate() {
        RequireSingletonForUpdate<EnablePongGhostSendSystemComponent>();
    }

    protected override void OnUpdate() {

        Entities.WithNone<SendRpcCommandRequestComponent>().ForEach((Entity reqEnt, ref GoInGameRequest req, ref ReceiveRpcCommandRequestComponent reqSrc) => {
            PostUpdateCommands.AddComponent<NetworkStreamInGame>(reqSrc.SourceConnection);
            UnityEngine.Debug.Log(String.Format("Server setting connection {0} to in game", EntityManager.GetComponentData<NetworkIdComponent>(reqSrc.SourceConnection).Value));

            var ghostCollection = GetSingleton<GhostPrefabCollectionComponent>();
            var ghostId = PongGhostSerializerCollection.FindGhostType<PlayerPaddleSnapshotData>();
            var prefab = EntityManager.GetBuffer<GhostPrefabBuffer>(ghostCollection.serverPrefabs)[ghostId].Value;
            var player = EntityManager.Instantiate(prefab);
            EntityManager.SetComponentData(player, new ClientMovableComponent { PlayerId = EntityManager.GetComponentData<NetworkIdComponent>(reqSrc.SourceConnection).Value });

            PostUpdateCommands.AddBuffer<ClientPlayerInput>(player);
            PostUpdateCommands.SetComponent(reqSrc.SourceConnection, new CommandTargetComponent { targetEntity = player });

            PostUpdateCommands.DestroyEntity(reqEnt);
        });
    }
}

In the system that deals with input i removed the horizontal component. I cannot really figure out whats wrong here.

[UpdateInGroup(typeof(ClientSimulationSystemGroup))]
public class ClientPaddleInputSystem : ComponentSystem {
    protected override void OnCreate() {
        RequireSingletonForUpdate<NetworkIdComponent>();
        RequireSingletonForUpdate<EnablePongGhostReceiveSystemComponent>();
    }

    protected override void OnUpdate() {
      
        /* Check if it already exists otherwise create it */
        var localInput = GetSingleton<CommandTargetComponent>().targetEntity;
        if (localInput == Entity.Null) {
            var localPlayerId = GetSingleton<NetworkIdComponent>().Value;
            Entities.WithNone<ClientPlayerInput>().ForEach((Entity ent, ref ClientMovableComponent paddle) => {
                if (paddle.PlayerId == localPlayerId) {
                    PostUpdateCommands.AddBuffer<ClientPlayerInput>(ent);
                    PostUpdateCommands.SetComponent(GetSingletonEntity<CommandTargetComponent>(), new CommandTargetComponent { targetEntity = ent });
                }
            });
            return;
        }

        var input = default(ClientPlayerInput);
        input.tick = World.GetExistingSystem<ClientSimulationSystemGroup>().ServerTick;

        if (Input.GetKey("s")) {
            input.vertical -= 1;
        }  
        if (Input.GetKey("w")) {
            input.vertical += 1;
        }
          
        var inputBuffer = EntityManager.GetBuffer<ClientPlayerInput>(localInput);
        inputBuffer.AddCommandData(input);
    }
}

Any pointers would be appreciated.

1 Like

I can only see two ways you would get that exception, either you have multiple connections on the client - presumably because you called NetworkStreamReceiveSystem.Connect multiple times, or you are calling GetSingleton in a system running on the server. You should be able to figure out which one of them is happening by looking at the entity debugger.

Have you correctly updated and generated the scripts from the GhostAuthoringComponent on your GameObjects? Perhaps the Predicting player network id’s are getting mixed up which leads the client to create new CommandTargetComponents.

I found the error, the sample did the connecting and listening in a system which operated on the default world.

I split this into 2 systems, one which operated on the client world for connecting and one which operated on the server world for listening. This caused there to be 2 connections, one for each simulated client.

2 Likes

Did you find a solution?

Just in case someone else needs the modified code:

using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.NetCode;
using Unity.Networking.Transport;
using UnityEngine;

[UpdateInWorld(UpdateInWorld.TargetWorld.Server)]
[UpdateInGroup(typeof(ServerSimulationSystemGroup))]
public class ServerGameSystem : ComponentSystem
{
    // Singleton component to trigger connections once from a control system
    struct InitGameComponent : IComponentData
    {
    }
    protected override void OnCreate()
    {
        RequireSingletonForUpdate<InitGameComponent>();
        // Create singleton, require singleton for update so system runs once
        EntityManager.CreateEntity(typeof(InitGameComponent));
    }

    protected override void OnUpdate()
    {
        // Destroy singleton to prevent system from running again
        EntityManager.DestroyEntity(GetSingletonEntity<InitGameComponent>());
        foreach (var world in World.AllWorlds)
        {
            var network = world.GetExistingSystem<NetworkStreamReceiveSystem>();
#if UNITY_EDITOR
            if (world.GetExistingSystem<ServerSimulationSystemGroup>() != null)
            {
                // Server world automatically listens for connections from any host
                NetworkEndPoint ep = NetworkEndPoint.AnyIpv4;
                ep.Port = 7979;
                network.Listen(ep);
            }
#endif
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.NetCode;
using Unity.Networking.Transport;
using UnityEngine;

[UpdateInWorld(UpdateInWorld.TargetWorld.Client)]
[UpdateInGroup(typeof(ClientSimulationSystemGroup))]
public class ClientGameSystem : ComponentSystem
{
    // Singleton component to trigger connections once from a control system
    struct InitGameComponent : IComponentData
    {
    }
    protected override void OnCreate()
    {
        RequireSingletonForUpdate<InitGameComponent>();
        // Create singleton, require singleton for update so system runs once
        EntityManager.CreateEntity(typeof(InitGameComponent));
    }

    protected override void OnUpdate()
    {
        // Destroy singleton to prevent system from running again
        EntityManager.DestroyEntity(GetSingletonEntity<InitGameComponent>());
        foreach (var world in World.AllWorlds)
        {
            var network = world.GetExistingSystem<NetworkStreamReceiveSystem>();
            if (world.GetExistingSystem<ClientSimulationSystemGroup>() != null)
            {
                // Client worlds automatically connect to localhost
                NetworkEndPoint ep = NetworkEndPoint.LoopbackIpv4;
                ep.Port = 7979;
                network.Connect(ep);
            }
        }
    }
}

Also don’t forget to install the Hybrid Renderer which is now hidden by Unity because it is a preview package, simply use the git button and enter ‘com.unity.rendering.hybrid’. This is not mentioned in the Documentation and it took me quite a while to figure out why the entities were not being rendered.