Lets Make A Multiplayer Tank Game – Part 2 (Setup)

Setup And Connecting

For this project I am going to use the LTS version of Unity for 2020 (2020.3.x). My exact version is 2020.3.1f1 but I don’t think the minor version matters. What does matter is I know this version of Unity works. Feel free to follow along with 2021.x – I don’t see why that won’t work but I personally think projects should be on the most stable version you can use.

I have already covered the initial setup for getting up and running with a client and embedded server. If you don’t already know how to do this, you should read part 2 followed by part 3 up until the”HANDLING NEW CLIENTS” section. Once you have done that, come back here 🙂

ASIDE: In case you are wondering, I am using the embedded approach again to help keep things simple and to take advantage of what Unity offers. There is some debate in the DarkRift community about how performant a Unity server can be but if Unity ever is the bottleneck, it shouldn’t be for a game of this size. The other advantage this approach gives is the ability to see what the server is “seeing” – this can be great for debugging purposes.

After you have completed the initial setup and confirmed the client can connected, we are going to do a couple of things differently. I always try to write better code then I previously did, and experiment with different approaches. Here are the current changes I have made to this project vs the last one (by the end of part 3):

  1. Instead of ServerManager.cs containing a list of clients we are going to have a ClientManager.cs class who’s job it is to maintain that list. In an ideal world, a class is responsible for just the thing it does. I have also renamed ServerManager to Server. Server is going to have it’s own instance of ClientManager – I am trying to cut down on the use of singletons and will be building the server a little different this time.
  2. The ClientManager will take care of adding new client connections to the server and storing said connections.
  3. The ClientConnection class will no longer have a separate field for it’s ID, but instead just hold a reference to the Client itself.

At this point, your server scene should look like:

And you scripts should look as follows

Server.cs

using DarkRift;
using DarkRift.Server;
using DarkRift.Server.Unity;
using UnityEngine;

public class Server : MonoBehaviour
{
    public static Server Instance;

    private XmlUnityServer xmlServer;
    private DarkRiftServer server;

    private ClientManager clientManager = new ClientManager();
    void Awake()
    {
        if (Instance != null)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(this);
    }
    void Start()
    {
        xmlServer = GetComponent<XmlUnityServer>();
        server = xmlServer.Server;
        server.ClientManager.ClientConnected += OnClientConnected;
        server.ClientManager.ClientDisconnected += OnClientDisconnected;
    }

    void OnDestroy()
    {
        server.ClientManager.ClientConnected -= OnClientConnected;
        server.ClientManager.ClientDisconnected -= OnClientDisconnected;
    }

    private void OnClientDisconnected(object sender, ClientDisconnectedEventArgs e)
    {
        e.Client.MessageReceived -= OnMessage;
    }

    private void OnClientConnected(object sender, ClientConnectedEventArgs e)
    {
        e.Client.MessageReceived += OnMessage;
    }

    private void OnMessage(object sender, MessageReceivedEventArgs e)
    {
        IClient client = (IClient) sender;
        using (Message message = e.GetMessage())
        {
            switch ((Tags) message.Tag)
            {
                case Tags.JoinGameRequest:
                    OnPlayerJoinGameRequest(client);
                    break;
            }
        }
    }

    private void OnPlayerJoinGameRequest(IClient client)
    {
        clientManager.AddClient(client);
    }
}

ClientManager.cs

using System.Collections.Generic;
using DarkRift;
using DarkRift.Server;
public class ClientManager 
{
    private Dictionary<ushort, ClientConnection> ConnectedClients = new Dictionary<ushort, ClientConnection>();

    public Dictionary<ushort, ClientConnection> GetConnectedClients()
    {
        return ConnectedClients;
    }

    public void AddClient(IClient client)
    {
        JoinGameResponseData data = new JoinGameResponseData();
        
        if (ConnectedClients.ContainsKey(client.ID))
        {
            data.JoinGameRequestAccepted = false;

            using (Message message = Message.Create((ushort)Tags.JoinGameResponse, data))
            {
                client.SendMessage(message, SendMode.Reliable);
            }
        } 
        
        data.JoinGameRequestAccepted = true;

        ClientConnection c = new ClientConnection(client);
        ConnectedClients.Add(client.ID, c);

        using (Message message = Message.Create((ushort)Tags.JoinGameResponse, data))
        {
            client.SendMessage(message, SendMode.Reliable);
        }
    }
}

ClientConnection

using DarkRift.Server;
public class ClientConnection 
{
    public IClient Client;

    public ClientConnection(IClient client)
    {
        Client = client;
    }
}

And on the Client side of things:

I know some people like to follow along exactly so here are my (current) camera settings:

Great, now we are at the end of “part 3” from the previous series. I want to keep this post focused on the initial setup – in the next post we are going to work on creating a tank for the player to control, spawning it in, and verifying the movement is being sent correctly. See you there 🙂

Leave a Reply