Search Unity

Question Making my first simple multiplayer MLAPI game. Mostly working but small bug. Need help.

Discussion in 'Netcode for GameObjects' started by moneylife, Mar 31, 2021.

  1. moneylife

    moneylife

    Joined:
    Mar 21, 2021
    Posts:
    10
    Hi all

    I'm trying to learn more about making online multiplayer games in Unity. I decided to make a 2 player space ship shooter in 3d. Its only going to be very basic (Ie. Cubes as enemies that just spawn randomly and you get score for shooting it with your laser)

    It all works pretty great (after much trouble understanding RPC's and NetworkVariables). However when i spawn the Lasers (same with Enemies, I guess 'non player objects'). For a brief moment perhaps just one video frame I see the object spawn at Vector3[0,0,0] (lower-middle of my game screen). Within milliseconds the laser goes to where it was supposed to start from (ie. Whichever player pressed fire) and carries on as expected.

    This 'bug' only occurs on a 'client' connection. The host looks perfect.
    I've spawned all the lasers and enemies only on the Server (Host) using NetworkObject.Spawn()

    Here is the code:

    Code (CSharp):
    1. using MLAPI;
    2. using MLAPI.Messaging;
    3. using MLAPI.NetworkVariable;
    4. using UnityEngine;
    5.  
    6.  
    7. namespace RatForce
    8. {
    9.     public class Player : NetworkBehaviour
    10.     {
    11.         static int player_count = 0;
    12.         static float speed = 5f;
    13.         static float spawnOffset = 8f;
    14.         Vector3 pos;
    15.         public GameObject laserPrefab;
    16.  
    17.         public NetworkVariableVector3 Position = new NetworkVariableVector3(new NetworkVariableSettings
    18.         {
    19.             WritePermission = NetworkVariablePermission.ServerOnly,
    20.             ReadPermission = NetworkVariablePermission.Everyone
    21.         });
    22.  
    23.         public override void NetworkStart()
    24.         {
    25.             base.NetworkStart();
    26.             pos = transform.position;
    27.  
    28.             if (player_count++ == 0)
    29.                 pos.x = -spawnOffset;
    30.             else
    31.                 pos.x = spawnOffset;
    32.  
    33.             if (IsServer)
    34.                 Position.Value = pos;
    35.             else if (IsLocalPlayer)
    36.                 SubmitNewPosition_ServerRpc(pos);
    37.  
    38.             transform.position = Position.Value;
    39.         }
    40.  
    41.         [ServerRpc]
    42.         void SubmitNewPosition_ServerRpc(Vector3 newPosition, ServerRpcParams rpcParams = default)
    43.         {
    44.             Position.Value = newPosition;
    45.         }
    46.  
    47.         void LateUpdate()
    48.         {
    49.             if (IsLocalPlayer)
    50.             {
    51.                 pos.x += (Input.GetAxis("Horizontal") * speed * Time.deltaTime);
    52.  
    53.                 if (IsServer)
    54.                     Position.Value = pos;
    55.                 else
    56.                     SubmitNewPosition_ServerRpc(pos);
    57.  
    58.                 if (Input.GetButtonDown("Fire1"))
    59.                 {
    60.                     Vector3 spawnPos = transform.position;
    61.                     if (IsServer)
    62.                         ShootLaser_Server(spawnPos, OwnerClientId);
    63.                     else
    64.                         ShootLaser_ServerRpc(spawnPos);
    65.                    
    66.                 }
    67.  
    68.             }
    69.  
    70.  
    71.            
    72.  
    73.             transform.position = Position.Value;
    74.         }
    75.  
    76.  
    77.  
    78.         [ServerRpc]
    79.         void ShootLaser_ServerRpc(Vector3 spawnPos, ServerRpcParams rpcParams = default)
    80.         {
    81.             GameObject go = Instantiate(laserPrefab, spawnPos, Quaternion.identity);
    82.             go.GetComponent<NetworkObject>().Spawn();
    83.             go.GetComponent<Laser>().OnSpawn(OwnerClientId);
    84.            
    85.  
    86.         }
    87.  
    88.         void ShootLaser_Server(Vector3 spawnPos, ulong ownerID, ServerRpcParams rpcParams = default)
    89.         {
    90.             GameObject go = Instantiate(laserPrefab, spawnPos, Quaternion.identity);
    91.             go.GetComponent<NetworkObject>().Spawn();
    92.             go.GetComponent<Laser>().OnSpawn(ownerID);
    93.         }
    94.     }
    95. }
    96.  

    Code (CSharp):
    1. using MLAPI;
    2. using MLAPI.Messaging;
    3. using MLAPI.NetworkVariable;
    4. using UnityEngine;
    5.  
    6. namespace RatForce
    7. {
    8.     public class Laser : NetworkBehaviour
    9.     {
    10.  
    11.         static float speed = 25f;
    12.         static float maxDistance = 10f;
    13.         ulong firedBy = 0;
    14.  
    15.         public NetworkVariableVector3 Position = new NetworkVariableVector3(new NetworkVariableSettings
    16.         {
    17.             WritePermission = NetworkVariablePermission.ServerOnly,
    18.             ReadPermission = NetworkVariablePermission.Everyone
    19.         });
    20.  
    21.    
    22.         public override void NetworkStart()
    23.         {
    24.             base.NetworkStart();
    25.  
    26.            
    27.         }
    28.  
    29.         public void OnSpawn(ulong firedByPlayer)
    30.         {
    31.             firedBy = firedByPlayer;
    32.             Debug.Log("Laser shot by: " + firedBy);
    33.         }
    34.  
    35.  
    36.         private void Update()
    37.         {
    38.             if (IsServer)
    39.             {
    40.  
    41.                 Vector3 position = transform.position;
    42.                 position.z += speed * Time.deltaTime;
    43.                 transform.position = position;
    44.                 Position.Value = position;
    45.  
    46.                 if (Position.Value.z > maxDistance)
    47.                 {
    48.                     gameObject.GetComponent<NetworkObject>().Despawn(true);
    49.                 }
    50.  
    51.             }
    52.             else
    53.                 transform.position = Position.Value;
    54.  
    55.  
    56.            
    57.         }
    58.     }
    59. }
    60.  
    61.  


    Any help here would be massively appreciated. I am really starting to get going on the MP game dev and fixing this bug would help me understand what I'm doing wrong if anything. I've tried to keep this game as simple as possible, but some day I hope to make some more complex online games.

    Let me know if you need to see more of the code, or a better explanation of the problem. Like i say in a nutshell, the Laser prefab is spawning as expected except on the Client connection you see the laser appear at 0,0,0 position for 1 frame or so in video and then jump to where it was supposed to be.
     
  2. luke-unity

    luke-unity

    Joined:
    Sep 30, 2020
    Posts:
    306
    I think this is actually a bug in MLAPI. During the frame where the NetworkObject is spawned and NetworkStart is called Update does not run. This means your function which sets the object to its correct position does not run in the first frame. A workaround for this would be to change the NetworkStart code of your laser to the following:

    Code (CSharp):
    1.         public override void NetworkStart()
    2.         {
    3.             base.NetworkStart();
    4.             Update();
    5.         }
     
    moneylife likes this.
  3. moneylife

    moneylife

    Joined:
    Mar 21, 2021
    Posts:
    10

    Thanks it works perfectly. I love the thinking on the workaround . I had played around adding some 'transform position = 0,0,-1000) in there earlier but it didnt seem to work.

    Whilst I have your attention hopefully. Perhaps I can ask you this...
    Should I be spawning gunfire on the server as I have been? Or I remember reading once that the bullets should all be 'client side' and only message for the collisions. Perhaps I am wrong or outdated.
    Would this implementation I have work if I spawn many bullets and players and enemies at once?
    Really I'd just like reassurance that I am indeed heading in the right direction writing code like this.

    Thanks again for helping me fix the threads problem.
     
  4. luke-unity

    luke-unity

    Joined:
    Sep 30, 2020
    Posts:
    306
    I don't think I have an easy answer for that. Having bullets as server spawned network objects is an option but just displaying them with client side VFX and synchronizing hits can work as well.
    What does not work well currently in MLAPI is predicting to spawn a NetworkObject on a client. So if you go the network object route you will always have some delay until your bullets are spawned currently.
     
    moneylife likes this.
  5. moneylife

    moneylife

    Joined:
    Mar 21, 2021
    Posts:
    10
    Many thanks for the help. I'll continue as I am for now and when i got this working fully I can try some more advanced routines.

    Much appreciated.