Basic Unity Multiplayer Game With Networker – Part 4

Sending and Receiving Packets

Minor note: If you’re starting part 4 after 23rd March 2019 you may have to look at the previous parts and update your project names, and Networker versions to 3.0.0

We’re ready to start sending and receiving packets! For this example we are going to send a basic chat message.

Packet Library

Let’s go ahead and add a new project. This should be a .NET Standard Class Library. Call it Tutorial.Common.

Edit your .csproj to add the NuGet references for the packet serialiser.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Networker.Extensions.ProtobufNet" Version="3.0.0" />
  </ItemGroup>

</Project>

Create a new class called ChatPacket, and give it a few properties.

using ProtoBuf;

namespace Tutorial.Common
{
	[ProtoContract]
	public class ChatPacket
	{
		[ProtoMember(1)] 
		public virtual string Name { get; set; }

		[ProtoMember(2)] 
		public virtual string Message { get; set; }
	}
}

Handling Packets

Then in Tutorial.Server make sure you have added a reference to the Tutorial.Common project, and add a new class called ChatPacketHandler.

This class should derive from PacketHandlerBase<T> where T is type of the packet we have just created. The PacketHandlerBase class will handle deserialisation for us, we just need to tell it what type we are expecting. Implement the Process method as shown below.

public class ChatPacketHandler : PacketHandlerBase<ChatPacket>
{
	public override async Task Process(ChatPacket packet, IPacketContext packetContext)
	{
	}
}

One of the most important features of Networker is dependency injection, and it’s built-in logging. We can really easily create a logger to see exactly what’s happening in our packet handler.

public class ChatPacketHandler : PacketHandlerBase<ChatPacket>
{
	private readonly ILogger<ChatPacketHandler> _logger;

	public ChatPacketHandler(ILogger<ChatPacketHandler> logger)
	{
		_logger = logger;
	}

	public override async Task Process(ChatPacket packet, IPacketContext packetContext)
	{
		_logger.LogDebug("I received the chat message: " + packet.Message);
	}
}

We need to tell the server about this packet type, and which handler it should use when it receives it. This is done in our builder class in Program.

var server = new ServerBuilder()
	.UseTcp(networkerSettings.GetValue<int>("TcpPort"))
	.UseUdp(networkerSettings.GetValue<int>("UdpPort"))
	.ConfigureLogging(loggingBuilder =>
	{
		loggingBuilder.AddConfiguration(config.GetSection("Logging"));
		loggingBuilder.AddConsole();
	})
	.UseProtobufNet()
	.RegisterPacketHandler<ChatPacket, ChatPacketHandler>()
	.Build();

Sending Packets

In our Program class we have access to the Client. Let’s try sending some packets to the server, populating the Messsage property with the current date and time.

var gameClient = new GameClient();
gameClient.Client.Connect();
 
while (server.Information.IsRunning)
{
	gameClient.Client.Send(new ChatPacket
	{
		Message = DateTime.Now.ToString()
	});

	Thread.Sleep(10000);
}

If everything is set up correctly, you’ll start seeing some new debug logs popping up in the console.

Responding to packets

In the Tutorial.Client project add a new class ClientChatPacketHandler.

This class is going to log that it received a packet in response.

public class ClientChatPacketHandler : PacketHandlerBase<ChatPacket>
{
	private readonly ILogger<ClientChatPacketHandler> _logger;

	public ClientChatPacketHandler(ILogger<ClientChatPacketHandler> logger)
	{
		_logger = logger;
	}

	public override async Task Process(ChatPacket packet, IPacketContext packetContext)
	{
		_logger.LogDebug("Client received the response packet: " + packet.Message);
	}
}

Then in our existing ChatPacketHandler in the server add the following line.

public class ChatPacketHandler : PacketHandlerBase<ChatPacket>
{
	private readonly ILogger<ChatPacketHandler> _logger;

	public ChatPacketHandler(ILogger<ChatPacketHandler> logger)
	{
		_logger = logger;
	}

	public override async Task Process(ChatPacket packet, IPacketContext packetContext)
	{
		_logger.LogDebug("I received the chat message: " + packet.Message);
		packetContext.Sender.Send(packet);
	}
}

Then in our GameClient class we need to register the packet type and it’s handler.

Client = new ClientBuilder()
	.UseIp(networkerSettings.GetValue<string>("Address"))
	.UseTcp(networkerSettings.GetValue<int>("TcpPort"))
	.UseUdp(networkerSettings.GetValue<int>("UdpPort"))
	.ConfigureLogging(loggingBuilder =>
	{
		loggingBuilder.AddConfiguration(config.GetSection("Logging"));
		loggingBuilder.AddConsole();
	})
	.UseProtobufNet()
	.RegisterPacketHandler<ChatPacket, ClientChatPacketHandler>()
	.Build();

Hopefully you will now see log output like below!

That’s it for part 4!

A short note, please be patient in waiting for the next parts of the tutorial as I write this in my spare time and I have been busy moving house. We have a new community Discord ( https://discord.gg/NdEqhAe), you can join to ask questions or discuss new features.

Basic Unity Multiplayer Game With Networker – Part 3

In this part of the tutorial we will look at creating our client.

Let’s go ahead and add a new project. This should be a .NET Standard Class Library. The reason we pick this project type is so we can test it in our console application, but later we can import it into our Unity project. Call it Tutorial.Client.

This .csproj will also need the NuGet packages which we added to the server project in the previous parts of this tutorial. Edit your .csproj to add the NuGet references.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Networker" Version="3.0.0" />
    <PackageReference Include="Networker.Extensions.ProtobufNet" Version="3.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
  </ItemGroup>

</Project>

We will also need a new configuration file for the client. So in the same way we did in part 2, add a new file clientSettings.json to our client project. This configuration file looks the same as the server, except we have added a new entry for the Address.

{
  "Networker": {
    "TcpPort": 1000,
    "UdpPort": 1001,
    "Address": "localhost" 
  },
  "Logging": {
    "LogLevel": {
      "Default": "Debug"
    },
    "Console": {
      "LogLevel": {
        "Default": "Debug"
      }
    }
  }
}

We need a new class in this project called GameClient. Inside this class we must add a constructor to create our client. This is done in a similar way to the ServerBuilder but instead we are using the ClientBuilder.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Networker.Client;
using Networker.Client.Abstractions;
using Networker.Extentions.ProtobufNet;

namespace Tutorial.Client
{
    public class GameClient
    {
        public GameClient()
        {
            var config = new ConfigurationBuilder()
                .AddJsonFile("clientSettings.json", false, true)
                .Build();

            var networkerSettings = config.GetSection("Networker");

            Client = new ClientBuilder()
                .UseIp(networkerSettings.GetValue<string>("Address"))
                .UseTcp(networkerSettings.GetValue<int>("TcpPort"))
                .UseUdp(networkerSettings.GetValue<int>("UdpPort"))
                .ConfigureLogging(loggingBuilder =>
                {
                    loggingBuilder.AddConfiguration(config.GetSection("Logging"));
                    loggingBuilder.AddConsole();
                })
                .UseProtobufNet()
                .Build();
        }

        public IClient Client { get; set; }
    }
}

Go back to Program.cs in our server application and create an instance of the client. This is just for testing and we’ll remove it later.

static void Main(string[] args)
{
    IConfiguration config = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", false, true)
        .Build();

    var networkerSettings = config.GetSection("Networker");

    var server = new ServerBuilder()
        .UseTcp(networkerSettings.GetValue<int>("TcpPort"))
        .UseUdp(networkerSettings.GetValue<int>("UdpPort"))
        .ConfigureLogging(loggingBuilder =>
        {
            loggingBuilder.AddConfiguration(config.GetSection("Logging"));
            loggingBuilder.AddConsole();
        })
        .UseProtobufNet()
        .Build();

    server.Start();

    var gameClient = new GameClient();
    gameClient.Client.Connect();

    while (server.Information.IsRunning)
    {
        Thread.Sleep(10000);
    }
}

Go ahead and run the application, you should see some more logs showing a successful connection.


Basic Unity Multiplayer Game With Networker – Part 2

In part 2 of this tutorial we are going to look at loading in settings from a configuration file, and adding logging to our application.

Loading configuration

You can load your configuration any way you want, if you want to use a database for that or an .INI file then go nuts. The recommended way to do configuration is using Microsoft.Extensions.Configuration.

Let’s go ahead and add in the NuGet package which gives us functionality to read configuration from a JSON file.

<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />

Now we need to add our configuration file, so Right click on your .csproj > Add > New Item. Add a text file but call it appSettings.json

Add the following as our default configuration to the appSettings.config

{
  "Networker": {
    "TcpPort": 1000,
    "UdpPort": 1001 
  }
}

Make sure in the file properties you set the file to copy to output directory

Now we need to add the code to read from this file at the top of our Main method

IConfiguration config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", false, true)
    .Build();

Then modify our existing ServerBuilder code to read the TCP and UDP ports from the configuration

var networkerSettings = config.GetSection("Networker");

var server = new ServerBuilder()
    .UseTcp(networkerSettings.GetValue<int>("TcpPort"))
    .UseUdp(networkerSettings.GetValue<int>("UdpPort"))
    .UseProtobufNet()
    .Build();

Logging to console

One of the features of Networker is support for the Microsoft.Extensions.Logging ecosystem.

Let’s go ahead and add in the NuGet package which gives us the functionality to write logs to the console window. Add the following line to our .csproj file.

<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />

Great. So now we have the library imported, but how do we know what type of logs to write? How do we tell Networker to use the console logging?

The first step is to add some new entries into our appSettings.config file.

{
  "Networker": {
    "TcpPort": 1000,
    "UdpPort": 1001
  },
  "Logging": {
    "LogLevel": {
      "Default": "Debug"
    },
    "Console": {
      "LogLevel": {
        "Default": "Debug"
      }
    }
  }
}

Then we tell the ServerBuilder it needs to add logging to console, and we also need to tell it where it can find the settings for that.

var server = new ServerBuilder()
    .UseTcp(networkerSettings.GetValue<int>("TcpPort"))
    .UseUdp(networkerSettings.GetValue<int>("UdpPort"))
    .ConfigureLogging(loggingBuilder =>
    {
        loggingBuilder.AddConfiguration(config.GetSection("Logging"));
        loggingBuilder.AddConsole();
    })
    .UseProtobufNet()
    .Build();

Time to run the project – if you’ve done everything correctly you should see a console that looks something like this

Basic Unity Multiplayer Game With Networker – Part 1

Welcome to the first ever tutorial for Networker. I first started developing Networker because I wanted a simple to user networking library which supported my favourite development features such as Dependency Injection and .NET Core.

Before we get started I would like to make an important point. Networker is a general use networking library. This means it will not magically do things for your game like a standard game networking library might, such as player interpolation or object management. These concepts will need to be implemented yourself.

What you will learn

Part 1 – Create a server with Networker
Part 2 – Adding configuration and logging
Part 3 – Create a client with Networker
Part 4 – Sending packets
Part 5 – Add your client to a Unity project
Part 6 – Basic database access

What you will need

Visual Studio 2017+
Patience
Basic C# knowledge
Basic NuGet knowledge

Creating your server

Great – Let’s get started!

First you’ll need to open up Visual Studio and create a new project which should be a .NET Core Console Application. Call it Tutorial.Server.

Right click on your newly created project and edit the file. Add in the following line to pull in the latest Networker NuGet package.

<PackageReference Include="Networker" Version="3.0.0" />

We also need to make a choice at this point. Which packet formatter are we going to use? Right now there are two choices. ZeroFormatter or protobuf-net

For this tutorial we are going to use the protobuf-net formatter, so we need to add in this NuGet package too.

<PackageReference Include="Networker.Extensions.ProtobufNet" Version="3.0.0" />

Your project file should now look something like this

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Networker" Version="3.0.0" />
    <PackageReference Include="Networker.Extensions.ProtobufNet" Version="3.0.0" />
  </ItemGroup>

</Project>

Let’s code!

Now that our project is setup correctly we can start adding our server code. We are going to use the ServerBuilder class to help us create our server instance.

For our example we are going to use both TCP and UDP which are supported by Networker.

static void Main(string[] args)
{
    var server = new ServerBuilder()
        .UseTcp(1000)
        .UseUdp(1001)
        .UseProtobufNet()
        .Build();

    server.Start();

    while (server.Information.IsRunning)
    {
        Thread.Sleep(10000);
    }
}

There you have it, you’ve created your server instance and your application is now listening for TCP connections on port 1000 and UDP is listening on port 1001.

In the next part we will look at adding logging and using config files.