Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Issue getting Server to receive Client input when using IInputComponentData and GhostPrefabCreation

Discussion in 'NetCode for ECS' started by StayThirsty, Mar 6, 2023.

  1. StayThirsty

    StayThirsty

    Joined:
    Jul 6, 2013
    Posts:
    33
    Hello,

    I have a client ghost entity that "shakes" when it's trying to move but it returns back to the server's ghost entity position of (0,0,0). However, I can move the client ghost by manually changing the input of the server ghost in the inspector.
    What could cause this?

    I'm not using sub-scenes or baking because they are unsupported on WebGL.
    I'm using the Ghost instantiation without Prefabs method provided by @timjohansson here: https://forum.unity.com/threads/creating-ghosts-without-prefabs.1370169/#post-8638827

    I have a ghost instantiated on a client and server exactly the same.
    • My inputs on the client ghost are being updated
    • My inputs on the server ghost are not being updated.


    This is my Input component for IInputComponentData
    Code (CSharp):
    1. [Serializable]
    2. [GhostComponent(PrefabType = GhostPrefabType.AllPredicted)]
    3. public struct ThirdPersonPlayerInputs : IInputComponentData {
    4.     public float2 MoveInput;
    5.     public float2 CameraLookInput;
    6.     public float CameraZoomInput;
    7. }
    I am adding the AutoCommandTarget to my ghost(s) too

    em.AddComponentData(e, new AutoCommandTarget { Enabled = true });


    My InputSystem runs in GhostInputSystemGroup
    Code (CSharp):
    1.  
    2. [UpdateInGroup(typeof(GhostInputSystemGroup))]
    3. public partial class SampleCubeInput : SystemBase {
    4.     protected override void OnCreate() {
    5.         RequireForUpdate<ThirdPersonPlayerInputs>();
    6.         RequireForUpdate<NetworkStreamInGame>();
    7.     }
    8.  
    9.     protected override void OnUpdate() {
    10.         bool w = Input.GetKey(KeyCode.W);
    11.         bool s = Input.GetKey(KeyCode.S);
    12.         bool d = Input.GetKey(KeyCode.D);
    13.         bool a = Input.GetKey(KeyCode.A);
    14.         bool space = Input.GetKeyDown(KeyCode.Space);
    15.         var CameraLookInput = new float2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
    16.         var mouseScrollDeltaY = -Input.mouseScrollDelta.y;
    17.        
    18.        Entities.WithAll<GhostOwnerIsLocal>().ForEach((ref ThirdPersonPlayerInputs inputs) => {
    19.             inputs = default;
    20.             inputs.MoveInput = new float2();
    21.             inputs.MoveInput.y += w ? 1f : 0f;
    22.             inputs.MoveInput.y += s ? -1f : 0f;
    23.             inputs.MoveInput.x += d ? 1f : 0f;
    24.             inputs.MoveInput.x += a ? -1f : 0f;
    25.  
    26.             inputs.CameraLookInput = CameraLookInput;
    27.             inputs.CameraZoomInput = mouseScrollDeltaY;
    28.         }).ScheduleParallel();
    29.     }
    30. }
    31.  

    My MoveSystem runs in PredictedSimulationSystemGroup
    Code (CSharp):
    1. [UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
    2. public partial class CubeMovementSystem : SystemBase {
    3.  
    4.     protected override void OnCreate() {
    5.         RequireForUpdate<ThirdPersonPlayerInputs>();
    6.     }
    7.  
    8.     protected override void OnUpdate() {
    9.         var movementSpeed = SystemAPI.Time.DeltaTime * 4;
    10.         Entities.WithAll<Simulate>().ForEach((ref ThirdPersonPlayerInputs input, ref LocalTransform trans) => {
    11.             var moveInput = new float2(input.MoveInput.x, input.MoveInput.y);
    12.             moveInput = math.normalizesafe(moveInput) * movementSpeed;
    13.             trans.Position += new float3(moveInput.x, 0, moveInput.y);
    14.         }).ScheduleParallel();
    15.     }
    16. }

    I have an image of the ghost components
    upload_2023-3-6_15-51-56.png

    using Unity 2022.2.5f1 - Entities/Netcode 1.0.0-pre.15

    Is there something that I am missing? Thanks
    @CMarastoni
     
    Last edited: Mar 7, 2023
  2. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    774
    The documentation is telling you what to do..
    https://docs.unity3d.com/Packages/com.unity.netcode@1.0/manual/command-stream.html

    But long story short. If you are not using the auto command target (need to be enable on the prefab), you need to assign the entity as target for the command by setting the entity reference on the CommandTarget when you:
    - Spawn the entity on the server
    - On the client as well, by looping through the entity you own (you can use the GhostIsLocal for that)

    Unless you have some particular requirement, we suggest to use the AutoCommandTarget.
     
  3. StayThirsty

    StayThirsty

    Joined:
    Jul 6, 2013
    Posts:
    33
    Hi, I appreciate your response.

    I read that documentation several times as its confusing to me, especially since it's geared towards using authoring components, sub-scenes and baking (can't use them with WebGL :()

    When I spawn my entity I
    - add the AutoCommandTarget (enabled)
    - add the GhostOwnerComponent and set it to the NetworkIdComponent.Value
    - set the ghost as OwnerPredicted

    Doesn't that mean I am using AutoCommandTarget or am I missing something?

    The documentation refers to ICommandData for these parts, whereas the code examples and netcode projects don't use it, instead they use IInputComponentData...which means the command data usage is to be handled automatically via code generated systems, isn't it?

    By the way, it would be great if the documentation could address, more clearly, how to wire things up when not using authoring components, preferably with code samples that are up to date. And there is no code example actually demonstrating the usage of CommantTargetComponent, it just says:
    Again, no ICommandData is being used in any of the netcode entities sample projects.

    Thanks
     
    Last edited: Mar 11, 2023
  4. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    774
    It doesn't work like that. You MUST use sub-scene and authoring component as principal workflow. The reason is because baking does not work at runtime and we generate a lot of necessary metadata to make things work
    Both server and clients needs to have prefab to process and collected by GhostCollectionSystem in order to be able to exchange and receive ghost data.

    If you really can't use or don't want to use the sub-scene flow, then you need to use the GhostPrefabCreation for that purpose.
    You need to create first the prefabs on both client and server.
    For example:
    Code (csharp):
    1.  
    2. private Entity BuildPrefab(EntityManager entityManager, string prefabName)
    3. {
    4.     var prefab = entityManager.CreateEntity();
    5.     entityManager.AddComponentData(entity, new LocalTransform.FromPosition(float3.zero));
    6.     entityManager.AddComponent<GhostOwnerComponent>(entity);
    7.     entityManager.AddComponentData(entity, new SomeData());
    8.     GhostPrefabCreation.ConvertToGhostPrefab(entityManager, prefab, new GhostPrefabCreation.Config
    9.     {
    10.         Name = prefabName,
    11.         Importance = 1,
    12.         SupportedGhostModes = GhostModeMask.All,
    13.         DefaultGhostMode = GhostMode.OwnerPredicted,
    14.         OptimizationMode = GhostOptimizationMode.Dynamic,
    15.         UsePreSerialization = false
    16.     });
    17.     return prefab;
    18. }
    19.  
    And then the server can instantiate this at runtime when necessary.

    The client don't need to spawn entities in general. When a ghost's received from the server it is instantiate based on its prefab type.
    If and when the client need to spawn an entity (i.e bullet) it should use the predicted spawning version of that entity prefab, that have a specific component, the PredictedGhostSpawnRequestComponent.

    On the client it is necessary to create two prefabs: one with and one without the PredictedGhostSpawnRequestComponent.

    When the player need to spawn an entity, it MUST use the entity with the PredictedGhostSpawnRequestComponent. When it receive data from the server, the entity is spawned using the other one.
    It look complicated but it is quite simple as:

    Code (csharp):
    1.  
    2. BuildPrefab(world.EntityManager, "NormalPrefab");
    3. var entityThatCanBeSpawned = BuildPrefab(world.EntityManager, "PredictedVersion");
    4. world.EntityManager.AddComponent<PredictedGhostSpawnRequestComponent>(entity);
    5.  
    Of course this is meta code but should give you an idea.
     
    vildauget likes this.
  5. StayThirsty

    StayThirsty

    Joined:
    Jul 6, 2013
    Posts:
    33
    First of all I really appreciate your in-depth reply and for bearing with me, but I have to ask you to bear with me still :)

    I am using the GhostPrefabCreation and it's essentially identical to your example code.
    But I am confused about the whole PredictedGhostSpawnRequestComponent part.
    I am not looking to instantiate anything on the client. I only want a character controller, 1 per player. no bullets or such.

    I had my setup working with ICommandData. However we are looking to build a webgl framework, and packages such as the Rival DOTS character controller use IInputComponentData. I don't want to have to rewrite or maintain anything to make it work with ICommandData.

    It seems I have "dropped" something that I need to get IInputComponentData to work with GhostPrefabCreation.
    Below you will see how I use GhostPrefabCreation and how I create the entities. The method InitializeGhostPrefab() gets called on the client and the server.
    Code (CSharp):
    1.  
    2.     public GameObject Prefab;
    3.  
    4.     public Entity InitializeGhostPrefab(EntityManager em, string ghostname) {
    5.         var e = CreateGhostEntity(em);
    6.         em.SetName(e, "MyGhost");
    7.        
    8.         if(GetViewPrefab() != null) {
    9.             var goEntity = em.CreateEntity();
    10.             em.AddComponentObject(goEntity, new GhostPresentationGameObjectPrefab { Client = GetViewPrefab() });
    11.             em.SetName(goEntity, $"{goEntity}-GameObject");
    12.             em.AddComponentData(e, new GhostPresentationGameObjectPrefabReference { Prefab = goEntity });
    13.         }
    14.  
    15.         GhostPrefabCreation.ConvertToGhostPrefab(em, e, new GhostPrefabCreation.Config {
    16.             Name = ghostname,
    17.             Importance = 10,
    18.             SupportedGhostModes = GhostModeMask.All,
    19.             DefaultGhostMode = GhostMode.OwnerPredicted,
    20.             OptimizationMode = GhostOptimizationMode.Dynamic,
    21.             UsePreSerialization = false
    22.         });
    23.         return e;
    24.     }
    25.  
    26.     public Entity CreateGhostEntity(EntityManager em) {
    27.         var e = em.CreateEntity();
    28.         em.AddComponent<GhostOwnerComponent>(e);
    29.         em.AddComponent<GhostOwnerIsLocal>(e);
    30.         em.AddComponentData(e, new AutoCommandTarget { Enabled = true });
    31.         em.AddComponent<BasicTag>(e);
    32.         em.AddComponentData(e, new LocalTransform {...});
    33.         em.AddComponentData(e, new LocalToWorld());
    34.         em.AddComponentData(e, new ThirdPersonPlayerInputs());
    35.         return e;
    36.     }
    37.  
    38.     public GameObject GetViewPrefab() {
    39.         return Prefab;
    40.     }

    My EnterClientSystem and EnterServerSystem are essentially the same from the examples/netcode projects. I am not sure what else I need to do
     
    Last edited: Mar 11, 2023
  6. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    774
    Ah! fantastic. So you were actually perfectly on the right approach.

    The missing piece/problem is that you need to add manually the underlying buffer used to transmit the IInputComponentData (that is added by baking).
    We show that in the HelloNetcode sample (look for ThinClient).

    I think you just found a bug/limitation
    of that API. We aren't adding the buffer when we are converting the prefab. We technically can.

    Please add in your CreateGhostEntity this component, Sorry, we don't provide a public, easy api for that yet.

    Code (csharp):
    1.  
    2. // NOTE: The buffer type might not be recognized in your IDE but it will be generated and Unity will recognize it
    3. // the syntax is : YourNamespace.Generated.InputNameInputBufferData. So in your case should be something like
    4. em.AddComponent<XXX.Generated.ThirdPersonPlayerInputsInputBufferData>(e);
    5.  
    That should fix everything
     
    StayThirsty likes this.
  7. StayThirsty

    StayThirsty

    Joined:
    Jul 6, 2013
    Posts:
    33
    Awesome, that did the trick!
    I look forward to having that be part of the API, if that's the plan :)

    Thank you
     
  8. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    774
    It is somewhat the plan!