Unity - Networking. Host can't see client changes

Thomas Upson

Ok this is getting frustrating for me because I've never done this kind of thing before and I need a solution.

So my game is 2D and involves you going against another player to collect as many resources in the map as possible in the time limit. The world is generated by instantiating a sprite at every tile in the map with a random block and the player digs their way through and gets points depending on which resources they mine. The blocks are also saved to a public 2D array.

I have a script called LevelGenerator which runs when the player starts a game. In the script, the blocks are chosen at random, then spawned through instantiate and then by NetworkServer.Spawn. The whole generation is called as a [Command]

The gameobject that holds the levelgenerator script has a network identity with neither server only or local player authority.

Within the player script, I have some code that casts a raycast from the player to the mouse position, and it works fine, however the destroying of the gameobject is broken. When the player presses left mouse and the raycast is hitting a block, I destroy the hit object, add the same amount of points as the block's points value in the 2D array of blocks, and then set that block in the array to null. When the host breaks a block, the client can see that happening but when the client breaks a block, the client can obviously see it breaking but the host cannot see changes from that client. This may not be related, but an error is thrown saying that the array of blocks is null, even though I am directly finding the levelgenerator through findobjectoftype. This does not happen on the host however. The maps are the same so networkserver.spawn is working, but I am having an issue with syncing the blocks and whether or not they are broken or not.

The blocks are prefabs, and all have a network identity with neither box checked, and a network transform with the network send rate of 0 as they are not moving from when they are spawned.

So yeah I can't figure this out. Any help is appreciated. Thanks.

LevelGenerator:

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

public class LevelGenerator : NetworkBehaviour
{
    [SerializeField] private List<Block> blocks; // Blocks are pre-defined in the Unity Editor.
    [SerializeField] private int mapWidth; // How wide the map is.
    [SerializeField] private int mapHeight; // How deep the map is.
    public const int blockSize = 1;
    public Block[,] blockMatrix; // 2D array of all blocks.
    private List<Block> resourceBlocks = new List<Block>();

    private List<GameObject> blockObjects = new List<GameObject>();

    void Start()
    {
        CmdSpawnMap();
    }

    [Command]
    public void CmdSpawnMap()
    {
        blockMatrix = new Block[mapWidth, mapHeight];

        resourceBlocks = blocks.Where(z => z.blockType == "Resource").ToList(); // List of all the blocks that are tagged as resources.
        resourceBlocks = resourceBlocks.OrderBy(z => z.rarity).ToList();

        for (int r = 0; r < resourceBlocks.Count; r++)
        {
            resourceBlocks[r].spawnPercentagesAtLevels = new float[mapHeight];
            for (int s = 1; s < resourceBlocks[r].spawnPercentagesAtLevels.Length; s++)
            {
                resourceBlocks[r].spawnPercentagesAtLevels[s] = resourceBlocks[r].basePercentage * Mathf.Pow(resourceBlocks[r].percentageMultiplier, resourceBlocks[r].spawnPercentagesAtLevels.Length - s);
            }
        }

        // For every block in the map.
        System.Random random = new System.Random();
        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                if (y == mapHeight - 1)
                {
                    Block grass = blocks.Where(z => z.blockName == "Grass").FirstOrDefault();
                    blockMatrix[x, y] = grass;
                    GameObject blockInstance = Instantiate(grass.blockPrefabs[random.Next(0, grass.blockPrefabs.Count - 1)]);
                    blockInstance.transform.position = new Vector3(x * blockSize, y * blockSize, 0);
                    blockInstance.transform.SetParent(transform, false);
                    blockInstance.name = blockMatrix[x, y].blockName + " => " + x + ", " + y;
                    blockObjects.Add(blockInstance);
                    NetworkServer.Spawn(blockInstance);
                    continue;
                }

                bool resourceSpawned = false;
                for (int i = resourceBlocks.Count - 1; i >= 0; i--)
                {
                    float roll = Random.Range(0.0f, 100.0f);
                    if (roll <= resourceBlocks[i].spawnPercentagesAtLevels[y])
                    {
                        blockMatrix[x, y] = resourceBlocks[i];
                        GameObject blockInstance = Instantiate(resourceBlocks[i].blockPrefabs[random.Next(0, resourceBlocks[i].blockPrefabs.Count - 1)]);
                        blockInstance.transform.position = new Vector3(x * blockSize, y * blockSize, 0);
                        blockInstance.transform.SetParent(transform, false);
                        blockInstance.name = blockMatrix[x, y].blockName + " => " + x + ", " + y;
                        blockObjects.Add(blockInstance);
                        resourceSpawned = true;
                        NetworkServer.Spawn(blockInstance);
                        break;
                    }
                }

                if (!resourceSpawned)
                {
                    Block ground = blocks.Where(z => z.blockName == "Ground").FirstOrDefault();
                    blockMatrix[x, y] = ground;
                    GameObject blockInstance = Instantiate(ground.blockPrefabs[random.Next(0, ground.blockPrefabs.Count - 1)]);
                    blockInstance.transform.position = new Vector3(x * blockSize, y * blockSize, 0);
                    blockInstance.transform.SetParent(transform, false);
                    blockInstance.name = blockMatrix[x, y].blockName + " => " + x + ", " + y;
                    blockObjects.Add(blockInstance);
                    NetworkServer.Spawn(blockInstance);
                }

                resourceSpawned = false;
            }
        }
    }
}

LevelGenerator Script

Player:

using UnityEngine;
using UnityEngine.Networking;

[RequireComponent(typeof(Rigidbody2D))]
public class Player : NetworkBehaviour
{
    private Rigidbody2D rb;

    [Header("Player Movement Settings")]
    [SerializeField] private float moveSpeed;

    private bool facingRight;
    [HideInInspector] public int points;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        facingRight = true; // Start facing right
    }

    void FixedUpdate()
    {
        float horizontalSpeed = Input.GetAxis("Horizontal") * moveSpeed;
        float jumpSpeed = 0;

        if (horizontalSpeed > 0 && !facingRight || horizontalSpeed < 0 && facingRight) // If you were moving left and now have a right velocity
        { // or were moving right and now have a left velocity,
            facingRight = !facingRight; // change your direction
            Vector3 s = transform.localScale;
            transform.localScale = new Vector3(s.x * -1, s.y, s.z); // Flip the player when the direction has been switched
        }

        rb.velocity = new Vector2(horizontalSpeed, jumpSpeed);
    }

    void Update()
    {
        GameManager gm = FindObjectOfType<GameManager>();
        LevelGenerator levelGen = FindObjectOfType<LevelGenerator>();
        if (gm.playing)
        {
            Camera playerCam = GetComponentInChildren<Camera>();
            Vector3 direction = playerCam.ScreenToWorldPoint(Input.mousePosition) - transform.position;
            direction.z = 0;
            RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, 1);
            Debug.DrawRay(transform.position, direction, Color.red);
            if (hit)
            {
                if (Input.GetButtonDown("BreakBlock"))
                {
                    Destroy(hit.transform.gameObject);
                    points += levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)].blockPointsValue;
                    levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)] = null;
                }
            }
        }
    }
}
Tabski

Quite simply, you are never sending a network message from the client to tell the server to destroy the block. When you destroy the block on the server, it will automatically tell all of the clients to get rid of that block too (though you should be using NetworkServer.Destroy(), Destroy() is just nice enough to do this too). On the other hand, the client has no authority or way to tell other clients that a block has been broken.

What you will need to do is send a message from the client to the server to destroy the block, and then the server can tell all of the clients that the block should be destroyed.

This can be accomplished through a Command call from the client to the server. Instead of destroying the block, send a message from the client to the server with the block you want to destroy (as the block GameObject has a network identity you can pass GameObjects as arguments).

if (hit)
{
    if (Input.GetButtonDown("BreakBlock"))
    {
        CmdBreakBlock(hit.transform.gameObject);
        points += levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)].blockPointsValue;
        levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)] = null;
    }
}

And then add your Cmd function (which will be run on the server).

[Command]
void CmdBreakBlock(GameObject block)
{
    NetworkServer.Destroy(block);
}

Right away though you should notice an issue. There is now a bit of latency between when a client wants to break a block and when it actually happens. If the client hits the block again before the server destroys it and sends a message back to the client we're going to have a lot of issues (the client will send another message to destroy the now null block).

So why not just destroy the block on the client and tell the server to destroy their version of it too? Well when the server destroys it and sends a message to that client, the client won't know what should be destroyed as they have already destroyed it.

Unfortunately using the high level API there isn't much of a clean solution to this that I know of. Your best bet would be to deactivate the object on the client that is trying to destroy it while it is actually destroyed on the server, but that's a little bit of a janky solution.

Unfortunately even then that's not enough to fix this problem. Like you mentioned, there's the problem of the blocks matrix being null for clients. That makes sense because the client's version of LevelGenerator is never run, so let me explain why that is.

When you have an object with network identity but no authority, what will happen is that the Cmd call won't work for any of the clients. Without authority commands can only be sent from the server, so the only LevelGenerator script that is running on the server.

This probably a good thing though as if all of the LevelGenerators for each client and server ran, all they would do is tell the server to run the map generation script on the server a bunch of times; still nothing would happen for your clients (except there would be a lot more block GameObjects spawned on top of eachother).

At this point I would say you have three options:

  • Try to sync the block matrix to the clients. The ideal way of syncing this matrix to clients would be by using the automatically updating [SyncVar] attribute, but last I checked this had trouble with multidimensional arrays of a custom type (might be worth a shot though). Realistically I expect that you would have to write a custom OnSerialize and OnDeserialize function which is not fun so I'm not sure I recommend it.
  • Sync the seed used to generate the level, and then run the level generation code on the client without spawning objects. This might work if the client connects before blocks are destroyed, but you'll still need to maintain the block matrix on the client and server so I'm not sure I recommend it
  • Instead of trying to sync the matrix, just use the matrix on the server. Right now the only time the client cares about the matrix is for getting point values, but that could easily be changed by adding a ClientRPC call whenever a block is destroyed.

The last one I do recommend, and you could maybe try it like this:

  if (Input.GetButtonDown("BreakBlock")) // Ran on client
  {
      CmdBreakBlock(hit.transform.gameObject);
      hit.transform.gameObject.SetActive(false); // Mimic that it was destroyed on the client so they don't try to mine it again.
  }

//...

[Command]
void CmdBreakBlock(GameObject hit) // Ran on server, arguments from client
{
    NetworkServer.Destroy(hit);
    RpcAddPoints(levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)].blockPointsValue);
    levelGen.blockMatrix[Mathf.FloorToInt(hit.transform.position.x), Mathf.FloorToInt(hit.transform.position.y)] = null;
}

[ClientRpc]
void RpcAddPoints(int p) // Ran on client, arguments from server
{
    points += p;
}

Sorry if I couldn't be much help, I'm more used to the Low Level API but if you have any questions I would be happy to try to answer them!

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Networking - Games can't see/join anyone else's LAN servers, unless I host

From Dev

Unity Networking: Flipping sprites in Multiplayer doesn't flip host

From Dev

jQuery and checkboxes, i can't see the changes

From Dev

Can't see lowercase "g" in Unity Hud

From Dev

VirtualBox: Can't get Bridged Networking to work (Win7 host)

From Dev

Can't get bridged networking to work between linux guest virtual machine and Mac host

From Dev

Prestashop Cache...Can't see any changes

From Dev

Laravel and view caching in development -- can't see changes right away

From Dev

Can't see content of VCS changes tab in YouTrack?

From Dev

Laravel and view caching in development -- can't see changes right away

From Dev

Can't see SQLite database changes on a database open by multiple processes

From Dev

Release Management Client can't see Deployment Agent Heart Beat

From Dev

Release Management Client can't see Deployment Agent Heart Beat

From Dev

Unity 5.1 Networking - Spawn an object as a child for the host and all clients

From Dev

tcp client can't connect to local server randomly in unity

From Dev

Can't see the output

From Dev

I can't see changed files on "commit changes" screen in Android Studio

From Dev

Can't see changes in K8s after I apply yaml with the new API version

From Dev

Can' see the VirtualBox menu in my host

From Dev

Can Android device see WCF host?

From Dev

Can not see changes using git push

From Dev

How can I see dmesg output as it changes?

From Dev

Can people see the changes made in word document?

From Dev

Why i can't see Fixed Angle option in Rigidbody2D in the new Unity5?

From Dev

Angular doesn't see changes in JSON file

From Dev

PHPUnit doesn't see changes in fixtures

From Dev

Can't see the label nodes in Neo4j Web Client after store upgrade

From Dev

Can't see my wireless printer in the DHCP client list on my router

From Dev

Can't see the label nodes in Neo4j Web Client after store upgrade

Related Related

  1. 1

    Networking - Games can't see/join anyone else's LAN servers, unless I host

  2. 2

    Unity Networking: Flipping sprites in Multiplayer doesn't flip host

  3. 3

    jQuery and checkboxes, i can't see the changes

  4. 4

    Can't see lowercase "g" in Unity Hud

  5. 5

    VirtualBox: Can't get Bridged Networking to work (Win7 host)

  6. 6

    Can't get bridged networking to work between linux guest virtual machine and Mac host

  7. 7

    Prestashop Cache...Can't see any changes

  8. 8

    Laravel and view caching in development -- can't see changes right away

  9. 9

    Can't see content of VCS changes tab in YouTrack?

  10. 10

    Laravel and view caching in development -- can't see changes right away

  11. 11

    Can't see SQLite database changes on a database open by multiple processes

  12. 12

    Release Management Client can't see Deployment Agent Heart Beat

  13. 13

    Release Management Client can't see Deployment Agent Heart Beat

  14. 14

    Unity 5.1 Networking - Spawn an object as a child for the host and all clients

  15. 15

    tcp client can't connect to local server randomly in unity

  16. 16

    Can't see the output

  17. 17

    I can't see changed files on "commit changes" screen in Android Studio

  18. 18

    Can't see changes in K8s after I apply yaml with the new API version

  19. 19

    Can' see the VirtualBox menu in my host

  20. 20

    Can Android device see WCF host?

  21. 21

    Can not see changes using git push

  22. 22

    How can I see dmesg output as it changes?

  23. 23

    Can people see the changes made in word document?

  24. 24

    Why i can't see Fixed Angle option in Rigidbody2D in the new Unity5?

  25. 25

    Angular doesn't see changes in JSON file

  26. 26

    PHPUnit doesn't see changes in fixtures

  27. 27

    Can't see the label nodes in Neo4j Web Client after store upgrade

  28. 28

    Can't see my wireless printer in the DHCP client list on my router

  29. 29

    Can't see the label nodes in Neo4j Web Client after store upgrade

HotTag

Archive