RTS game. UNet. How to synchronize structs or classes (unit commands)?

Hi! I am trying to make simplest RTS game.

The question is: how to send the orders (classes or structures) to the server from a client and then back to all clients?

As it’s known, many of RTS games use so called command (order) based approach. Every unit has it’s own “order queue”, and we just put any given order for unit to it’s order queue and it starts to execute the orders in the queue.

In multiplayer game, I would like to send (synchronize) only the orders for units between server and players (instead of unit positions, health, etc). So a player sends an order to server and then server sends given order to other players and back to sender also. And then units start to execute given order on all clients simultaneously.

In my case an order is a class:

  • publicclassOrder
  • {
  • publicint orderType;
  • publicUnit orderUnit;
  • // etc…
  • }

Notice please, orderUnit is a Unit class which derives from MonoBehavior and it represents the target unit in order. For example the unit that will be attacked.

Could you give some tips on how to send to the server complex data which include “pointers” to gameObjects which are instances of MonoBehavior class?

Or in general, how to implement this approach? In simple words, how can server say to a client: "Hey! Take unit “A”, and give him order “O”, and say him to cast a spell to unit “B”. How it could be implemented in UNet? Any common or practical tips will be very welcome :slight_smile:

Thanks a lot.

Have you checked out SyncLists, in particular SyncListStruct? Maybe it is a solution.
http://docs.unity3d.com/Manual/UNetStateSync.html

Something that I do that is probably not the best practice is to combine serialization and SyncVars. I haven’t tested this approach with classes that have child class, but it might work.

All you need to do is serialize the object, send it through a [Command] method, assign it to a SyncVar variable on the server and then deserialize on the clients.

make a Command for each order.

Thanks for your answers, guys.

Yes, I have checked SyncListStruct. But unfortunately, it is not so easy to understand for me what is going on in that example. I am not so strong in network and C# programming. Could you give a bit more explanation how can I use that example. In particular, what does mean this string:

public class TestBufs : SyncListStruct {}

Is it a class that derived from a struct? For what reason we need this class? Also it is not so clear how to use Operation and ItemIndex parameters in BufChanged callback. And for what reason the we need the callback?

void BufChanged(Operation op, int itemIndex)
{
Debug.Log(“buf changed:” + op);
}

Sorry for noobish questions, but I cant understand that example (( Could you please give an example alike my case with sending orders? Say we have a simple struct:

struct Order
{
int unitId;
string orderType;
}

As for using Command for each order, if I remember right, it is impossible to send Command with parameters. But I can be wrong…

I’m also not aware of how SyncList works, but just to clarify my tip:

The Order class could be like this:

using UnityEngine;
using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
public class Order
{
//All fields must be of serializabe types (like int, float, string or another custom class with [Serializable])
int unitId;
string orderType;

public string DoSerialize()
    {
        MemoryStream ms = new MemoryStream();
        BinaryFormatter bf1 = new BinaryFormatter();
        bf1.Serialize(ms, this);
        return Convert.ToBase64String(ms.ToArray());
    }
    public static Order DoDeserialize(string order)
    {
        try
        {
            MemoryStream memoryStream = new MemoryStream(System.Convert.FromBase64String(order));
            BinaryFormatter bf = new BinaryFormatter();
            return (Order)bf.Deserialize(memoryStream);
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
            return null;
        }
    }
}

In the player object or a non-player object with client authority, you could have:

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;

public class CommandSender : NetworkBehaviour
{
[SyncVar(hook = "hookUpdateOrder")]
string orderString;
Order order;

void hookUpdateOrder(string orderString)
{
       //The hook will be triggered when the server updates the orderString and now we deserialize the Order
        this.orderString= orderString;
        order = Order.DoDeserialize(orderString);   
}
[Command]
public void CmdSendCommand(string orderString)
{
       //This will be called on the server and trigger the hook on all clients
       this.orderString= orderString;
}

public void SendCommand(Order order)
{
       //This should be called by a client sending the order to the server
       string orderToString = order.DoSerialize();
       CmdSendCommand(orderToString)
}
    
}

I kinda adapted this code from something that I’m working with, so it probably needs some tweaking to fit your needs, but I think you’ll get the idea.

[Update] You can pass GameObjects, but not components. so use m y example can be simplified to:

    public void SetCommandElementPlayer(CommandElement ce)
    {
        CmdSetCommandElementPlayer(ce.gameObject);
    }
    [Command]
    public void CmdSetCommandElementPlayer(GameObject value)
    {
        CommandElement ce = value.GetComponent<CommandElement>();
        ce.player = this;
        CombatManager cm = FindObjectOfType<CombatManager>();
        cm.OnChangeCommandElementOwner(ce);
        RpcSetCommandElementPlayer(value);
    }
    [ClientRpc]
    void RpcSetCommandElementPlayer(GameObject value)
    {
        if (isServer) return;
        Debug.Log("RpcSetCommandElementPlayer go=\"" + value + "\"");
        CommandElement ce  = value.GetComponent<CommandElement>();
        ce.player = this;
    }

I had implemented a similar system in the old Unity Networking, and it was quite cumbersome. I was hoping that I could now just [SyncVar] references between NetworkBehaviours, but that seems to crash the editor, or at least prevent it from compiling with no useful errors. This is what I have so far. It works, but It’s quite horrible, I hope someone has a better method.

    //[SyncVar] does not compile if uncommmented
    //public Order current;

    // Workaround
    Order _current; //Order subclasses NetworkBehaviour and must be  NetworkServer.Spawn(order.gameObject); before being set here.
    public Order current
    {
        get
        {
            return _current;
        }

        set
        {
          //should be called only on the server as  result of a [Command]
            if (_current == value ) return;
            _current = value;
            Debug.Log("Set current to \"" + _current+"\"");
            if (_current == null)
            {
                RpcSetCurrent(NetworkInstanceId.Invalid);
            }
            else
            {
                RpcSetCurrent(value.netId);
            }
        }
    }
    [ClientRpc]
    void RpcSetCurrent(NetworkInstanceId value)
    {
        if (isServer) return;
        if (value == NetworkInstanceId.Invalid)
        {
            _current = null;
            Debug.Log("RpcSetCurrent netId \"" + value + "\" as null");
        }

        Debug.Log("RpcSetCurrent netId \"" + value+"\"");
        GameObject go = ClientScene.FindLocalObject(value);
        Debug.Log("RpcSetCurrent netId \"" + value+"\" \""+go+"\"");

        if (go == null ) return;

        _current = go.GetComponent<Order>();
        Debug.Log("RpcSetCurrent netId \"" + value + "\" \"" + go + "\" \""+_current+"\"");
    }
2 Likes