Search Unity

[UNET] Explain SyncListStruct to me like I am five years old

Discussion in 'UNet' started by Murgilod, Jun 23, 2017.

  1. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    10,157
    I'm working on a "simple" multiplayer FPS where the players can create temporary holes in the level geometry and I've run into some issues as I start moving away from my split-screen multiplayer prototype. Here's what I think I need to do:
    1. Have a custom struct. In this case, I have CSG_Brush, as seen here:
      Code (CSharp):
      1. using System.Collections;
      2. using System.Collections.Generic;
      3. using UnityEngine;
      4. using UnityEngine.Networking;
      5.  
      6. [System.Serializable]
      7. public struct CSG_Brush{
      8.  
      9.     public GameObject brush;
      10.     public float scale;
      11.     public Vector3 position;
      12.     public bool isActive;
      13.  
      14.     public CSG_Brush(GameObject brush, float scale, Vector3 position, bool isActive)
      15.     {
      16.         this.brush = brush;
      17.         this.scale = scale;
      18.         this.position = position;
      19.         this.isActive = isActive;
      20.     }
      21. }
      22.  
    2. I want each player to have their own entry in a CSG_Brush list. This list would be hosted on the server for the players to access so they can rebuild their level geometry from their end. I assume I want to use SyncListStruct for this, correct?
    3. ...Hooooooow? I mean, I know how to set up a SyncListStruct in the code, but I have no idea how to do even the most basic of things like add to the list or do... well, anything.
    4. Is there a way to serialize SyncListStructs in the inspector? It makes it a lot easier to do quick debugging when I can easily see what values are in the struct.
    Any help would be great, thanks.
     
  2. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    10,157
    Okay, I think I have it working after bashing my head against it for a while. However, for some reason I'm getting weird results when I start trying to loop through the list to get its results. Here's the code I'm using to initialise my players:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Networking;
    5.  
    6. public class PlayerInit : NetworkBehaviour {
    7.  
    8.     public HoleManager holeManager;
    9.     public GameObject brush;
    10.     public float scale = 3.0f;
    11.  
    12.     string NetworkIdentity()
    13.     {
    14.         return GetComponent<NetworkIdentity>().netId.ToString();
    15.     }
    16.  
    17.     public void AddToBrushList()
    18.     {
    19.         holeManager.brushes.Add(new CSG_Brush(brush, scale, Vector3.zero, false));
    20.         holeManager.players.Add(NetworkIdentity());
    21.     }
    22.    
    23.     public override void OnStartLocalPlayer()
    24.     {
    25.         holeManager = GameObject.FindObjectOfType<HoleManager>().GetComponent<HoleManager>();
    26.  
    27.         AddToBrushList();
    28.     }
    29. }
    30.  
    And here's the HoleManager script:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Networking;
    5.  
    6. public class HoleManager : NetworkBehaviour {
    7.  
    8.     public class SyncListCSG_Brush : SyncListStruct<CSG_Brush> { }
    9.     public SyncListCSG_Brush brushes = new SyncListCSG_Brush();
    10.  
    11.     public SyncListString players = new SyncListString();
    12.  
    13.     //public List<GameObject> players;
    14.     public GameObject[] levelGeo;
    15.     public List<GameObject> modifiedGeometry = new List<GameObject>();
    16.  
    17.     // Use this for initialization
    18.     void Start () {
    19.         DontDestroyOnLoad(this);
    20.         levelGeo = GameObject.FindGameObjectsWithTag("LevelGeo");
    21.     }
    22. }
    23.  
    However, whenever I loop through the list when there's more than one player connected, the second player to connect always has brushes.brush show up as null. This is obviously unexpected and kinda unwelcome behaviour for obvious reasons. Is there something I'm doing wrong here or is there something special about constructing SyncListStructs I've missed out on?
     
  3. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    10,157
    I've done some code shuffling and mostly have this working, but now I'm stuck with another question. Is there any way to get this to execute on clients? I can get it to work just fine on the server, but I really need the clients to be away of changes to the list.
     
  4. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    10,157
    Anyone? This last one is really throwing me for a loop and I can't figure out how to properly implement the necessary [Command]/[ClientRPC] setup.
     
  5. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Well, first of all SyncListStruct is used to synchronize data from Server->Client. You can't call directly an .Add method on a client and expect it to work. Since it won't. That's why you're getting nulls.

    This is about PlayerInit script.
    Instead of
    Code (CSharp):
    1. public void AddToBrushList()
    2.     {
    3.         holeManager.brushes.Add(new CSG_Brush(brush, scale, Vector3.zero, false));
    4.         holeManager.players.Add(NetworkIdentity());
    5.     }
    Try putting this method to HoleManager itself.
    And marking it as follows:

    Code (CSharp):
    1. [Command]
    2. public void CmdAddToBrushList(NetworkInstanceId id)
    3.     {
    4.         brushes.Add(new CSG_Brush(brush, scale, Vector3.zero, false));
    5.         players.Add(id);
    6.     }
    7.  
    And in:
    Code (CSharp):
    1.  public override void OnStartLocalPlayer()
    2.     {
    3.         ...
    4.         holeManager.CmdAddToBrushList(your_netId);
    5.         ...
    6.     }
    By the way. I'm pretty sure you don't need to serialize NetworkIdentity as a string. NetworkWriter/NetworkReader internal classes will automatically do that for you.
    In theory, by sending a command to the server you're asking server to add a brush and a player. Note here that used values (brush, scale, etc) will be server values. If you want something else to be passed - you can use base-types to pass it as a parameter, and then apply them instead.

    After list value changed event happens, SyncLists automatically resend changed data to all clients connected to the server. Which is Server -> Client interraction. So you won't be needing an Rpc call in this case.

    Edit: Also, I've forgot that you need an actual authority to send commands, so placing Cmd's and Rpc's is also a valid solution.

    Hope this helps you a bit.
     
    Last edited: Jun 29, 2017
    TwoTen likes this.