How to setup multiplayer with netcode

I’m trying to create a multiplayer game, but I want to use netcode instead of a third-party solution.

I’ve tried using the manual, but it seems it isn’t up to date?
I’ve downloaded the sample project but personally, it’s way to large and complicated.
I’d love to get my hands on a simple sample project, like just some players that can move.

If there is such a project, or a good tutorial I’d love to hear about it.

Did you already see the Getting Started part of the manual? Getting started with NetCode | Unity NetCode | 0.1.0-preview.6 That helped me get going :slight_smile:

I keep running into the same error:

I’ve checked that everything is up to date, I used the latest editor and updated all the listed packages mentioned in the manual (Hybrid Renderer, Entities, Unity NetCode, Unity Transport + Burst + Jobs)

It’s possible your Entities package version is too advanced: error after updating to Entities 0.1.0 m_systemsToUpdate does not exist Iirc, the latest Entities version requirement is 0.6.0 - I found that 0.8.0 works for me.

1 Like

I’m now using the correct versions of all the packages and all the errors are gone.
But now I’m getting errors with the code

This is my Game.cs:

using System;
using UnityEngine;
using Unity.Burst;
using Unity.Entities;
using Unity.NetCode;
using Unity.Networking.Transport;

//update in default world
[UpdateInWorld(UpdateInWorld.TargetWorld.Default)]
public class Game : ComponentSystem
{
  //singleton trigger connections once from control system
  struct InitGameComponent : IComponentData
  {
  }

  protected override void OnCreate()
  {
    RequireSingletonForUpdate<InitGameComponent>();
    //run singleton once
    EntityManager.CreateEntity(typeof(InitGameComponent));
  }

  protected override void OnUpdate()
  {
    //Destroy singleton, prevent it 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 automaticlly connect to localhost
        NetworkEndPoint ep = NetworkEndPoint.LoopbackIpv4;
        ep.Port = 7979;
        network.Connect(ep);
      }

      #if UNITY_EDITOR
      else if (world.GetExistingSystem<ServerSimulationSystemGroup>() != null)
      {
        // Server worldd automatically listens for connections from any host
        NetworkEndPoint ep = NetworkEndPoint.AnyIpv4;
        ep.Port = 7979;
        network.Listen(ep);
      }
      #endif

    }
  }

}

[BurstCompile]
public struct GoInGameRequest : IRpcCommand
{
  public void Deserialize(DataStreamReader reader, ref DataStreamReader.Context ctx)
  {
  }

  public void Serialize(DataStreamWriter writer)
  {
  }

  [BurstCompile]
  private static void InvokeExecute(ref RpcExecutor.Parameters parameters)
  {
    RpcExecutor.ExecuteCreateRequestComponent<GoInGameRequest>(ref parameters);
  }

  static PortableFunctionPointer<RpcExecutor.ExecuteDelegate> InvokeExecuteFunctionPointer = new PortableFunctionPointer<RpcExecutor.ExecuteDelegate>(InvokeExecute);

  public PortableFunctionPointer<RpcExecutor.ExecuteDelegate> CompileExecute()
  {
    return InvokeExecuteFunctionPointer;
  }
}

// The system that makes the RPC request component transfer
public class GoInGameRequestSystem : RpcCommandRequestSystem<GoInGameRequest>
{
}

I’ve seen this problem before, but I have no idea what to do.

Errors 2 and 3 are telling you exactly what the issue is. Implement the two methods and remove (if not in the interface) the others and you should be fine :slight_smile:

How do I implement this? I thought that this is the way to do it.

[BurstCompile]
public struct GoInGameRequest : IRpcCommand
{
  public void Deserialize(DataStreamReader reader, ref DataStreamReader.Context ctx)
  {
  }
  public void Serialize(DataStreamWriter writer)
  {
  }
}

I can’t access my code right now, but I think it is:

[BurstCompile]
public struct GoInGameRequest : IRpcCommand
{
  public void Deserialize(ref DataStreamReader reader)
  {
  }
  public void Serialize(ref DataStreamWriter writer)
  {
  }
}

(your IDE should help you as well)

Now I’m struggling with the CubeInput.cs

using System;
using UnityEngine;
using Unity.Burst;
using Unity.Entities;
using Unity.NetCode;
using Unity.Networking.Transport;

public struct CubeInput : ICommandData<CubeInput>
{
    public uint Tick => tick;
    public uint tick;
    public int horizontal;
    public int vertical;

    public void Deserialize(uint tick, DataStreamReader reader, ref DataStreamReader.Context ctx)
    {
        this.tick = tick;
        horizontal = reader.ReadInt(ref ctx);
        vertical = reader.ReadInt(ref ctx);
    }

    public void Serialize(ref DataStreamWriter writer)
    {
        writer.Write(horizontal);
        writer.Write(vertical);
    }

    public void Deserialize(uint tick, DataStreamReader reader, ref DataStreamReader.Context ctx, CubeInput baseline,
        NetworkCompressionModel compressionModel)
    {
        Deserialize(tick, reader, ref ctx);
    }

    public void Serialize(ref DataStreamWriter writer, CubeInput baseline, NetworkCompressionModel compressionModel)
    {
        Serialize(writer);
    }
}

public class NetCubeSendCommandSystem : CommandSendSystem<CubeInput>
{
}
public class NetCubeReceiveCommandSystem : CommandReceiveSystem<CubeInput>
{
}

Isn’t there a way to give access to DataStreamReader.Context

It’s frustrating that the documentation doesn’t work like it says, even if it’s the same version.

(PS: My IDE doesn’t support intellect for preview packages, since I’m running linux and using a custom version of unity)

Again, just match the method signatures to what the error wants. The context has been removed from the interface, afaics. Good luck!

P.S: If you have issues in the generated code, you best delete/comment it and regenerate the code :slight_smile:

If I cannot use context, how would I be able to get my code to work? I’d have to replace it with something.

You mainly need to update the code to the NetCode version you have.

For example, error 3 tells you that the reader is now passed by ref and that the context is not passed anymore. You can then assume that ReadInt does not take a context anymore either:

    public void Deserialize(uint tick, ref DataStreamReader reader)
    {
        this.tick = tick;
        horizontal = reader.ReadInt();
        vertical = reader.ReadInt();
    }

Continue like that and soon you’ll be finished.

Sorry to be a bit mysterious, but here you’ll just have to let the compiler tell you what to fix until the example is updated.

Okay, thanks for the great support!
I hope I’ll get it to work soon, this really is something for me.
But I like trying new things :wink:

1 Like

Would you be able to help me a bit further?


I tried specifying some things but nothing is really working out :confused:

    public void Deserialize(uint tick, ref DataStreamReader reader)
    {
        this.tick = tick;
        horizontal = reader.ReadInt();
        vertical = reader.ReadInt();
    }

    public void Serialize(ref DataStreamWriter writer)
    {
        writer.Write(horizontal);
        writer.Write(vertical);
    }

    public void Deserialize(uint tick,ref DataStreamReader reader, CubeInput baseline,
        NetworkCompressionModel compressionModel)
    {
        Deserialize(tick, ref reader);
    }

    public void Serialize(ref DataStreamWriter writer, CubeInput baseline, NetworkCompressionModel compressionModel)
    {
        Serialize(ref writer);
    }

It does not know what you want to write (byte? int? …?). In general, the Serialize/Deserialize methods need to correspond internally to one another, since what one serializes, the other deserializes again on the other end. So if you write an int, you need to read an int, in the same order.

So here:

public void Serialize(ref DataStreamWriter writer)
{
    writer.WriteInt(horizontal);
    writer.WriteInt(vertical);
}

I got a bit further and added SampleCubeInput. I’ve fixed some problems and marked them in the code.
But now I get this error


and I have no idea with what I should change it.

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

        //EDITED
        //RequireSingletonForUpdate<EnableNetCubeGhostReceiveSystemComponent>();
    }

    protected override void OnUpdate()
    {
        var localInput = GetSingleton<CommandTargetComponent>().targetEntity;
        if (localInput == Entity.Null)
        {
            var localPlayerId = GetSingleton<NetworkIdComponent>().Value;

            //EDITED
            //Entities.WithNone<CubeInput>().ForEach((Entity ent, ref MovableCubeComponent cube) =>
            Entities.WithNone<CubeInput>().ForEach((Entity ent) =>
            {
                //EDITED, really doubt it will work
                var cube = new MovableCubeComponent();
                if (cube.PlayerId == localPlayerId)
                {
                    PostUpdateCommands.AddBuffer<CubeInput>(ent);
                    PostUpdateCommands.SetComponent(GetSingletonEntity<CommandTargetComponent>(), new CommandTargetComponent {targetEntity = ent});
                }
            });
            return;
        }
        var input = default(CubeInput);
        input.tick = World.GetExistingSystem<ClientSimulationSystemGroup>().ServerTick;
        if (Input.GetKey("q"))
            input.horizontal -= 1;
        if (Input.GetKey("d"))
            input.horizontal += 1;
        if (Input.GetKey("s"))
            input.vertical -= 1;
        if (Input.GetKey("z"))
            input.vertical += 1;
        var inputBuffer = EntityManager.GetBuffer<CubeInput>(localInput);
        inputBuffer.AddCommandData(input);
    }
}

[UpdateInGroup(typeof(GhostPredictionSystemGroup))]
public class MoveCubeSystem : ComponentSystem
{
    protected override void OnUpdate()
    {
        var group = World.GetExistingSystem<GhostPredictionSystemGroup>();
        var tick = group.PredictingTick;
        var deltaTime = Time.DeltaTime;

        //what to use instead of "Translation"
        Entities.ForEach((DynamicBuffer<CubeInput> inputBuffer, ref Translation trans, ref PredictedGhostComponent prediction) =>
        {
            if (!GhostPredictionSystemGroup.ShouldPredict(tick, prediction))
                return;
            CubeInput input;
            inputBuffer.GetDataAtTick(tick, out input);
            if (input.horizontal > 0)
                trans.Value.x += deltaTime;
            if (input.horizontal < 0)
                trans.Value.x -= deltaTime;
            if (input.vertical > 0)
                trans.Value.z += deltaTime;
            if (input.vertical < 0)
                trans.Value.z -= deltaTime;
        });
    }
}

I’ve added the project to github
https://github.com/PerfMouse/netcode_sample

You’re missing a using Unity.Transforms;. I have to tell you that if you already have trouble with this error or how to handle it, you will most likely not get very far with the experimental package that is the current NetCode, since there are some hurdles hidden in there that require some knowledge on how to handle issues in code. Sorry to be so blunt :frowning:

I figured, I’m not really far in programming. I’ve gotten so far as creating games and being able to write decent code with documentation. But this really is new territory for me, I’ve only been programming for 2 years so I really do have a lot to learn. I just thought if I tried my best I’d be able to get it working, but it’s a real struggle for without documentation.
However I really thank you for your help! :slight_smile:

I want you to succeed! :slight_smile:
But I also did not want to paint too rosy of a picture since you’ll be using an experimental package with changing code and (as you also found) not up to date documentation and very few examples, videos, etc. I just wanted to provide a bit of a warning, since figuring it all out on one’s own can be nerve-wracking. The next version will be similar, but will require code changes again, I assume, and the one version after that will contain even bigger changes, if I understood the team correctly.
Anyway, you’ll only get better by trying, so all the best on your path! :slight_smile:

P.S: I can recommend an IDE such as Rider, which will automatically suggest fixes such as the one above with using.

Thank you! :slight_smile:

1 Like