Every client has a different gameManager?

I have an gamemanager that has a script attached to it. This script fill 2 multidimensional arrays with classes. I need these classes to check for objects in specific positions, each position in the array is equivalent to a position in my game.

When Im filling these arrays I can do 2 ways.

Using send to everyone

    [Rpc(SendTo.Everyone)]
    public void CreateTilesServerRpc()
    {

        //Create the tiles for all the grid, and set the isUsable variable to be true or false
        for (int i = 0; i < _size.x - 1; i++)
            for (int j = 0; j < _size.y; j++)
            {
                Vector3Int _pos = new Vector3Int(i + origin.x, j + origin.y, origin.z);
                _walls.gridArray[i, j] = new BackgroundTile(UsableTile(_pos), false, null, null, _pos.x, _pos.y);
                _bombs.gridArray[i, j] = new BackgroundBomb(UsableTile(_pos), false, null, _pos.x, _pos.y);

                DebugGrid.CreateWorldText(_isUsableParent, UsableTile(_pos) ? "1" : "0", _pos, 10, Color.white, TextAnchor.MiddleCenter);

                //Debug.Log(i + "," + j);
            }
    }

or using send to server

    [Rpc(SendTo.Server)]
    public void CreateTilesServerRpc()
    {

        //Create the tiles for all the grid, and set the isUsable variable to be true or false
        for (int i = 0; i < _size.x - 1; i++)
            for (int j = 0; j < _size.y; j++)
            {
                Vector3Int _pos = new Vector3Int(i + origin.x, j + origin.y, origin.z);
                _walls.gridArray[i, j] = new BackgroundTile(UsableTile(_pos), false, null, null, _pos.x, _pos.y);
                _bombs.gridArray[i, j] = new BackgroundBomb(UsableTile(_pos), false, null, _pos.x, _pos.y);

                DebugGrid.CreateWorldText(_isUsableParent, UsableTile(_pos) ? "1" : "0", _pos, 10, Color.white, TextAnchor.MiddleCenter);

                //Debug.Log(i + "," + j);
            }
    }

The first one seems weird but at the same time it works. Sometimes the client need to check for these arrays, and it works fine.
The second one looks more correct though. It makes sense to only the server create this thing, but at the same time I always need to call serverRpcs to look at the information that It should exist to the client too.

if I do a debug.Log asking to the client to print the components to the array it says “null” to the server it returns “backgroundBomb”, so only the server created and only the server have the information, how could I create something in the server but that shares to everyone?

Its weird because I think the client and the host should have access to the same GameManager, but if I say to the server to create an array only the server has access to this array. If I say to everyone to create an array looks like everyone has a reference to a different array. Idk maybe Im over complicating things.

I think you’d be saving yourself a lot of trouble if you went with a NetworkList, two if need be. The server handles the populating and updating of the list and these changes are sync’ed to all clients. The clients can pick up any changes in the form of events.

I’ve attached a package giving a brief idea of setting up and sync’ing a network list. There is also a network list example here (you’ll need ParrelSync for both).
bomberman.unitypackage (5.6 KB)

I really appreciate you for the effort of creating a demo just to show an example, but the networkList or even the networkVariable in this case isnt that good. Being able to just put value types to serialize everything is very limiting, because I need to hold the gameObject on that position.
I actually tried to create a INetworkSerializable struct to create an networkvariable, this struct would hold a multiDim array of another struct (this one similar to backgroundBomb class without the gameObj) but even then i doesnt seem to work…

public struct BackgroundBomb : INetworkSerializable
{
    public bool isUsable;
    public bool hasBomb;
    public int X;
    public int Y;

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref isUsable);
        serializer.SerializeValue(ref hasBomb);
        serializer.SerializeValue(ref X);
        serializer.SerializeValue(ref Y);
    }
    public BackgroundBomb(bool isUsable, bool hasBomb, int x, int y)
    {
        this.isUsable = isUsable;
        this.hasBomb = hasBomb;
        this.X = x;
        this.Y = y;
    }
}

public struct GridStruct<BackgroundBomb> : INetworkSerializable
{
    public int width;
    public int height;

    public BackgroundBomb[,] gridArray;

    public GridStruct(int width, int height)
    {
        this.width = width;
        this.height = height;

        gridArray = new BackgroundBomb[width, height];

    }

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref width);
        serializer.SerializeValue(ref height);
        serializer.SerializeValue(ref gridArray);
    }
}

Then the Idea would be to create an networkVar in the gameManager

private NetworkVariable<GridStruct> _bombs = new networkVariable<GridStruct>(width, height); 

and then

//Fill the array inside the netwokVar 
        for (int i = 0; i < _size.x - 1; i++)
            for (int j = 0; j < _size.y; j++)
              _bombs.gridArray[i,j] = new BackgroundBomb(true, false, i, j);

pretty much the same idea but instead of using classes I would use struct so that I could serialize this values, but it seems not possible to craete an array of another struct and serialize this…

CS8377 The type ‘T’ must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter in the generic type or method.

IF theres no other solution besides using an NetworkList as an 2d array Im probably going to make only the server be able to create this class and everytime I client needs to check call the server to do so…

There’s some info in this thread that should help with what you’re after. For the Grid class itself you might find it easier network wise to have it as an in-scene network object.

You might also want to look at spawning network objects per location or for bombs at least. I attached some code I was playing with, the way it sets initial network variable values is very dubious but it might give you some ideas.
bomberobjects.unitypackage (7.6 KB)

The last thing you wrote about creating the array only on the server and then clients reading it somehow is what you should do. This represents the state of the game, which should be owned by the server and synced to clients some way, through network variables or rpcs (probably better through network variables).

I never needed to serialize a matrix, but if that’s a challenge, there’s a different way you could structure this that might work. You could make every wall and every tile a NetworkBehaviour and let NGO sync their position through NetworkTransform and internal data through network variables.

1 Like

Sorry for the late replies guys, i havent be able to work much.
Anyways I find a solution that is pretty much what I was looking to do, the server creates an multidimensional array that hold the position of the bombs to make the clients not be able to place bombs in the same position.

This can be helpful to some people that may want to serialize a matrix.

This is the component that hold the information about the position of my bomb. All of its values are serialized as well, and each one is a simple value.

public struct BackgroundBomb : INetworkSerializable
{
    public bool isUsable;
    public bool hasBomb;
    public int X;
    public int Y;

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref isUsable);
        serializer.SerializeValue(ref hasBomb);
        serializer.SerializeValue(ref X);
        serializer.SerializeValue(ref Y);
    }
    public BackgroundBomb(bool isUsable, bool hasBomb, int x, int y)
    {
        this.isUsable = isUsable;
        this.hasBomb = hasBomb;
        this.X = x;
        this.Y = y;
    }
}

This is the struct that have a multidimensional array of the component BackgroundBomb

public struct GridStruct : INetworkSerializable
{
    public int width;
    public int height;

    public BackgroundBomb[,] gridArray;

    public GridStruct(int width, int height)
    {
        this.width = width;
        this.height = height;
        gridArray = new BackgroundBomb[width, height];
    }

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref width);
        serializer.SerializeValue(ref height);

        if (serializer.IsReader)
        {
            gridArray = new BackgroundBomb[width, height];
        }
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            { 
                // Serialize each item in the array
                serializer.SerializeValue(ref gridArray[i, j]);
            }
        }
    }
}

Notice that I just needed to do 2 for loops and serialize each value in my matrix.
The only information that I lost using this approach is the gameObject variable in the specific location, but I can work around in the future.

And then in the gameManager script I have a method that is send to the server to create the matrix.

[Rpc(SendTo.Server)]
public void CreateMatrix()
{
    for(int i = 0; i < size.x; i++)
          for(int j = 0; j < size.y; j++)
               _bombs.gridArray[i, j] = new BackgroundBomb(UsableTile(_pos), false, _pos.x, _pos.y);
}

Another thing that is important to notice is that everytime the client ask to place a bomb, the client send this method to the server to check if the position has a bomb.

I dont know if I should make the client have direct information about the matrix. (but writing this now didn’t seem like a good idea… XD)

    [Rpc(SendTo.Server)]
    private void CanPlaceBombServerRpc(int bombsRemaining, bool hasPressedAttack, Vector2 position)
    {
        (int x, int y) = ConvertPositionToGrid(position);
        bool hasBomb = HasBombInPosition(x, y);

        if(bombsRemaining > 0 && hasPressedAttack && !hasBomb)
            StartCoroutine(WaitBomb(position));
    }

Anyways its working as intended, so for now we’re better than before, thank you for taking the time to help me.