Multiple Command Targets on same client

In the latest release (0.50.0) it is now possible to switch command targets.

But how can multiple target entities be used from the same client?

For example, if there are two local players (two game-controllers sending input), or in VR we have often 3 inputs (headset, 2 controllers), I would want to have a command stream for each of these inputs linked to an entity, but the connection needs to define the CommandTargetComponent (which can refer only to one entity)

Is there a good way to implement this currently?

The updated command stream supports multiple command targets and even multiple ICommandData on each command target. The simplest way to use it is to check “Support Auto Command Target” on the Ghost Authoring Component.

3 Likes

Really? That’s awesome!
The docs are a little confusing here because the legacy way of doing things is still supported and only has the single command target option via CommandTargetComponent on the connection entity.

If we really can have multiple command targets (hooray!) can I recommend it be mentioned explicitly in the docs.

1 Like

It’s mentioned in the changelog Changelog | Netcode for Entities | 0.50.0-preview.29 (unity3d.com)

1 Like

Nice! I shoulda read that first :confused:

Indeed it was mentioned, but I honestly didn’t think it would be that simple.

Having it as an example in the example repo would be great of course.

I agree we need some more work on the docs for this, we are planning to focus more on docs and samples but I can’t say exactly when that will be available.

All the samples in GitHub - Unity-Technologies/multiplayer: Unity multiplayer packages and samples are updated to use auto command target, but there is no sample for multiple command targets or switching of command target yet.

I can’t really point you to any code to look at since the point of auto command target is that it doesn’t require any code, but if you look at the Cube prefab in the NetCube sample you can see that it enables “Support Auto Command Target” and “Has Owner”, sets “Default Ghost Mode” = “Owner Predicted” and adds the input at authoring by adding the “Ghost Input Authoring” component - which is all you need to do.

Asteroids is a bit more complex as it is using the old CommandTargetComponent for thin clients but the new auto command target for real clients.

2 Likes

Here’s an example I made today.
The idea is that you can have some entity be “operable”, e.g. emplaced weapon, vehicle etc.
In your code you just set operable.NewOperator and the OperationSystem will handle switching the command targets.
I know this isn’t exactly your situation but hopefully the usage sheds light on how it works.
Also I am no expert, but this seems to work for me.

    [Serializable]
    [GhostComponent]
    public struct Operable : IComponentData
    {
        [GhostField] public Entity NewOperator;
        [GhostField] public Entity CurrentOperator;
    }
[UpdateInGroup(typeof(GhostSimulationSystemGroup))]
public partial class OperationSystem : SystemBase
{
    protected override void OnUpdate()
    {
        Entities
            .WithAll<GhostOwnerComponent>()
            .WithAll<AutoCommandTarget>()
            .ForEach((Entity operableEntity, ref Operable operable) =>
            {
                // Shortcut return if the operator hasn't changed.
                if (operable.NewOperator == operable.CurrentOperator) return;
                
                // Operator arrives.
                if (operable.NewOperator != Entity.Null && operable.CurrentOperator == Entity.Null)
                {
                    // Enable the operable commands.
                    var newOperatorGhostOwner = GetComponent<GhostOwnerComponent>(operable.NewOperator);
                    SetComponent(operableEntity, newOperatorGhostOwner);
                    SetComponent(operableEntity, new AutoCommandTarget { Enabled = true });
                    
                    // Disable the operator commands.
                    if (HasComponent<AutoCommandTarget>(operable.NewOperator))
                        SetComponent(operable.NewOperator, new AutoCommandTarget { Enabled = false });
                }
                
                // Operator leaves.
                if (operable.NewOperator == Entity.Null && operable.CurrentOperator != Entity.Null)
                {
                    // Disable the operable commands.
                    SetComponent(operableEntity, new GhostOwnerComponent { NetworkId = 0 });
                    SetComponent(operableEntity, new AutoCommandTarget { Enabled = false });
                    
                    // Enable the operator commands.
                    if (HasComponent<AutoCommandTarget>(operable.CurrentOperator))
                        SetComponent(operable.CurrentOperator, new AutoCommandTarget { Enabled = true });
                }

                operable.CurrentOperator = operable.NewOperator;
                this.Log(operable, "New Operator");
            }).WithoutBurst().Run();
    }