Search Unity

  1. Unity 2017.2 beta is now available for download.
    Dismiss Notice
  2. Unity 2017.1 is now released.
    Dismiss Notice
  3. Introducing the Unity Essentials Packs! Find out more.
    Dismiss Notice
  4. Check out all the fixes for 5.6 on the patch releases page.
    Dismiss Notice
  5. Help us improve the editor usability and artist workflows. Join our discussion to provide your feedback.
    Dismiss Notice

Unity Multiplayer Bullets are only visible on host process

Discussion in 'Multiplayer Networking' started by Iceball457, Mar 20, 2017.

  1. Iceball457

    Iceball457

    Joined:
    Mar 20, 2017
    Posts:
    2
    I made a simple proof of concept game where you can shoot other players spaceships. When you hold down the X button on an Xbox controller, lots of bullets spawn. I think the bullets exist on the client process, because the client's ship's health changes as expected, but the bullets are invisible...
    Code (CSharp):
    1.  using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Networking;
    5. using UnityEngine.UI;
    6.  
    7. public class shipmovement : NetworkBehaviour {
    8.     [SyncVar]
    9.     public float enginelevel;
    10.     [SyncVar]
    11.     public float armorlevel;
    12.     [SyncVar]
    13.     public float weaponlevel;
    14.     [SyncVar]
    15.     public float ftlspeed;
    16.     [SyncVar]
    17.     public float hullpoints;
    18.     [SyncVar]
    19.     public float maxhullpoints;
    20.     Rigidbody rb;
    21.     float lh;
    22.     float lv;
    23.     float rh;
    24.     float rv;
    25.     bool clutch;
    26.     int gear;
    27.     bool shoot;
    28.     public GameObject camera;
    29.     public GameObject rearthrusterleft;
    30.     public GameObject rearthrusterright;
    31.     public GameObject leftthruster;
    32.     public GameObject rightthruster;
    33.     public GameObject reversethruster;
    34.     public GameObject clutchparticles;
    35.     public GameObject ftldrive;
    36.     GameObject hpUI;
    37.     GameObject maxhpUI;
    38.     public GameObject bullet;
    39.     // Use this for initialization
    40.     void Start () {
    41.         hullpoints = maxhullpoints;
    42.         gear = 1;
    43.         rb = GetComponent<Rigidbody> ();
    44.         SetupTrails ();
    45.     }
    46.    
    47.     // Update is called once per frame
    48.     void FixedUpdate () {
    49.         if (!isLocalPlayer) {
    50.             return;
    51.         }
    52.         InputUpdate ();
    53.         Control ();
    54.         UIUpdate ();
    55.         Debug.Log (hpUI.GetComponent<RectTransform>().rect.width);
    56.         rb.AddRelativeTorque (new Vector3 (rv, rh, 0f) * 2500 * enginelevel);
    57.         if (shoot) {
    58.             CmdFire ();
    59.         }
    60.     }
    61.     void InputUpdate () {
    62.         lh = Input.GetAxis ("Horizontal");
    63.         lv = Input.GetAxis ("Vertical");
    64.         rh = Input.GetAxis ("RightHorizontal");
    65.         rv = Input.GetAxis ("RightVertical");
    66.         if (Input.GetAxis ("Clutch") > 0f) {
    67.             clutch = true;
    68.         } else {
    69.             clutch = false;
    70.         }
    71.         if (Input.GetKey (KeyCode.Joystick1Button2)) {
    72.             shoot = true;
    73.         } else {
    74.             shoot = false;
    75.         }
    76.     }
    77.     void Control () {
    78.         if (clutch) {
    79.             clutchparticles.GetComponent<ParticleSystem> ().Play ();
    80.             if (lv > 0f) {
    81.                 gear = 2;
    82.             } else if (lv < 0f) {
    83.                 gear = 1;
    84.             }
    85.         } else if (gear == 1) {
    86.             rb.AddRelativeForce (new Vector3 (lh, 0f, lv) * 50000 * enginelevel);
    87.             if (lv + lh == 0f) {
    88.                 rb.drag = 0f;
    89.             } else {
    90.                 rb.drag = 0.1f;
    91.             }
    92.  
    93.             clutchparticles.GetComponent<ParticleSystem> ().Stop ();
    94.             ftldrive.GetComponent<ParticleSystem> ().Stop ();
    95.             if (lv > 0f) {
    96.                 rearthrusterleft.GetComponent<ParticleSystem> ().Play ();
    97.                 rearthrusterright.GetComponent<ParticleSystem> ().Play ();
    98.             } else {
    99.                 rearthrusterleft.GetComponent<ParticleSystem> ().Stop ();
    100.                 rearthrusterright.GetComponent<ParticleSystem> ().Stop ();
    101.             }
    102.             if (lv < 0f) {
    103.                 reversethruster.GetComponent<ParticleSystem> ().Play ();
    104.             } else {
    105.                 reversethruster.GetComponent<ParticleSystem> ().Stop ();
    106.             }
    107.             if (lh > 0f) {
    108.                 leftthruster.GetComponent<ParticleSystem> ().Play ();
    109.             } else {
    110.                 leftthruster.GetComponent<ParticleSystem> ().Stop ();
    111.             }
    112.             if (lh < 0f) {
    113.                 rightthruster.GetComponent<ParticleSystem> ().Play ();
    114.             } else {
    115.                 rightthruster.GetComponent<ParticleSystem> ().Stop ();
    116.             }
    117.         } else if (gear == 2) {
    118.  
    119.             clutchparticles.GetComponent<ParticleSystem> ().Stop ();
    120.             if (lv > 0f) {
    121.                 transform.position = transform.TransformPoint(new Vector3 (0f, 0f, ftlspeed));
    122.                 ftldrive.GetComponent<ParticleSystem> ().Play ();
    123.                 ftldrive.transform.position = new Vector3 (Random.Range(-3f, 3f), Random.Range(-3f, 3f));
    124.             } else {
    125.                 ftldrive.GetComponent<ParticleSystem> ().Stop ();
    126.             }
    127.             clutchparticles.GetComponent<ParticleSystem> ().Stop ();
    128.         }
    129.     }
    130.     public override void OnStartLocalPlayer () {
    131.         base.OnStartLocalPlayer ();
    132.         FindLocalPlayer ();
    133.     }
    134.     public void FindLocalPlayer () {
    135.         foreach (GameObject cur in GameObject.FindGameObjectsWithTag("Player")) {
    136.             if (cur.GetComponent<shipmovement> ().isLocalPlayer == true) {
    137.                 Instantiate (camera).transform.SetParent (cur.transform);
    138.                 cur.transform.FindChild ("Main Camera(Clone)").transform.position = new Vector3 (0f, 2f, -8f);
    139.                 hpUI = cur.transform.FindChild ("Main Camera(Clone)").GetChild (0).GetChild(1).gameObject;
    140.                 maxhpUI = cur.transform.FindChild ("Main Camera(Clone)").GetChild (0).GetChild(0).gameObject;
    141.             }
    142.         }
    143.     }
    144.     public void SetupTrails () {
    145.         foreach (GameObject cur in GameObject.FindGameObjectsWithTag("ShipParts")) {
    146.             Debug.Log (cur.transform.parent.name);
    147.             if (cur.GetComponentInParent<shipmovement> ().isLocalPlayer) {
    148.                 if (cur.name == "rearthrusterleft") {
    149.                     rearthrusterleft = cur.transform.GetChild (0).gameObject;
    150.                 }
    151.                 if (cur.name == "rearthrusterright") {
    152.                     rearthrusterright = cur.transform.GetChild (0).gameObject;
    153.                 }
    154.                 if (cur.name == "leftthruster") {
    155.                     leftthruster = cur.transform.GetChild (0).gameObject;
    156.                 }
    157.                 if (cur.name == "rightthruster") {
    158.                     rightthruster = cur.transform.GetChild (0).gameObject;
    159.                 }
    160.                 if (cur.name == "reversethruster") {
    161.                     reversethruster = cur.transform.GetChild (0).gameObject;
    162.                 }
    163.                 if (cur.name == "clutch") {
    164.                     clutchparticles = cur.transform.GetChild (0).gameObject;
    165.                 }
    166.                 if (cur.name == "ftldrive") {
    167.                     ftldrive = cur.transform.GetChild (0).gameObject;
    168.                 }
    169.             }
    170.         }
    171.     }
    172.     void UIUpdate () {
    173.         hpUI.GetComponent<RectTransform> ().sizeDelta = new Vector2 (500f * (hullpoints / maxhullpoints), 5f);
    174.         maxhpUI.GetComponent<RectTransform> ().sizeDelta = new Vector2 (500f, 5f);
    175.     }
    176.     [Command]
    177.     void CmdFire () {
    178.         GameObject newest;
    179.         newest = Instantiate (bullet, transform.GetChild(8).position + Random.onUnitSphere * 3f, transform.rotation);
    180.         newest.GetComponent<bulletscript> ().power = weaponlevel;
    181.         newest.GetComponent<Rigidbody> ().velocity = rb.velocity;
    182.         NetworkServer.Spawn (bullet);
    183.     }
    184.     void OnCollisionEnter (Collision col) {
    185.         if (col.gameObject.name == "bullet(Clone)") {
    186.             hullpoints -= Mathf.Round (col.gameObject.GetComponent<bulletscript> ().power / armorlevel);
    187.         }
    188.     }
    189. }
    That is about 90% of the code in the game.
     
  2. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    199
    There are considerable problems in your code here:

    - Calls to GameObject.GetComponent are expensive and 90% of the time unnecessary. In your case, every call that you make to GetComponent is complete unnecessary. Instead of making a GetComponent call every time, store the reference to the component on the first call to GetComponent (so that you don't need to get it again), OR, create a public property in your script for the component that you want to reference, and drag the associated object into the slot in the inspector (avoiding ever having to call GetComponent). This isn't what is causing you problems here, but yeah, you need to nip this one in the bud :)

    - Your code looks to be making a CmdFire call every fixed update frame while "shoot" is true (based on the x button). This will cause a bullet to be spawned at strange intervals, and also potentially far too fast. Any scenario where you have a weapon that fires projectiles, there should be some form of "firing rate" involved, where the weapon can fire up to a certain amount of bullets over time. You should never rely on a "frame" as a unit of measurement (in time) because frames never run on a fixed interval (even if you use FixedUpdate!). Always implement your own measurement of time using the Time class, and it's various properties such as Time.deltaTime to tell you how much time has passed since the last frame, or Time.realTimeSinceStartup to tell you how much time has passed since the game started running. Using these values, you can wait until a certain amount of time has passed before firing another bullet (for example).

    - Your concept is also a little off. Unless your weapon is something like a rocket launcher where a projectile will be fired very infrequently, and the position of the projectile needs to be visually correct between all players, you shouldn't be spawning networked objects to represent your projectile. Every single networked object takes up ram, cpu and bandwidth. Bandwidth, in this case, will be your first bottleneck due to the fact that you could, in worst case scenario, end up spawning hundreds of thousands of "bullets" easily, with just a few players.

    The way to get around this is, in cases where your weapon has a moderate to high rate of fire, it isn't important that each bullet's position/trajectory are accurately represented on all clients. For this reason, you don't need to network your bullets. You merely need to network whether the ship is firing or not, and have the ship spawn non-networked bullets on each client when it is, and stop when it isn't. There will be some visual discrepancies caused, because in some clients a bullet may hit and not appear to cause damage etc, but this is something that we have come to expect when playing online games. It is very common.

    So I recommend re-thinking things a little bit. It's not a bad start but you're currently setting yourself up for a lot of problems.

    Good luck, and feel free to ask away if you have any other questions.
     
  3. Iceball457

    Iceball457

    Joined:
    Mar 20, 2017
    Posts:
    2
    I went through and fixed a lot of the problems you (@donnysobonny ) pointed out, but one still remains:

    I highlight the problem there, and I dunno why it does that.

    EDIT: if you look at the script you'll see when I instantiate a bullet it is called "newest", but I dont ever actually spawn "newest", I spawn bullet... soo...
     
    Last edited: Mar 20, 2017
  4. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    199
    Good point yeah, that's exactly your issue. You should be calling NetworkServer.Spawn on the "newest" object. Instantiate merely clones an object (creates a completely fresh copy of it in memory) so calling NetworkServer.Spawn on the "bullet" object would only partially work.

    If you are still doing it this way though, where you spawn your bullets over the network, I advise again to avoid this. Once you fix the above issue and have a few players playing the game over the network, you'll see unity firing up NoResource errors and in some cases failing without error because spawning a networked object as frequently as you could potentially spawn them will kill the server. Again, you don't need to spawn networked objects here. If you were to simply sync your "shoot" boolean, then you could spawn the bullets locally on each player's client and not have to spawn them over the network.

    Hopefully this helps, good luck!
     
    Iceball457 likes this.