Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Question Updating HP bar of the player who got hit? (Mirror)

Discussion in 'Multiplayer' started by mandogperson, Jan 28, 2022.

  1. mandogperson


    Jan 28, 2022
    Hej guys! I'm making a multiplayer fps game where players can shoot eachother with projectiles and when they get hit, they take damage. I am using mirror. I have gotten the logic to work so that players take damage when hit, and once their healthpool is 0, they respawn at a set location with full health again. Now I want the players to have personal UI HP bars so that they know how much health they have, and here is where I am losing my mind. I have gotten it to work so that the last player that joins gets a working HP-bar. When receiving damage, it updates accordingly. However, it doesnt work the other way around. None of the other clients HP-bars will update at all when receiving damage.

    So here is the projectile/bullet. It is instantiated on the server. And when it collides with a player, it runs the TakeDamage() - method in the ReceiveDamage script on the player that was hit by the bullet.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Mirror;
    6. public class Bullet : NetworkBehaviour
    7. {
    9.     private Rigidbody rb;
    10.     [SerializeField]
    11.     private float launchForce = 10f;
    12.     public int damage = 1;
    13.     [SerializeField]
    14.     private string enemyTag;
    15.     // Start is called before the first frame update
    18.     void Start()
    19.     {
    20.         rb = GetComponent<Rigidbody>();
    21.         rb.velocity = transform.forward * launchForce;
    22.     }
    24.     [Server]
    25.     private void OnCollisionEnter(Collision collision)
    26.     {
    27.         if (collision.collider.tag == enemyTag)
    28.         {
    29.             collision.gameObject.GetComponent<ReceiveDamage>().TakeDamage(damage);
    30.             Destroy(gameObject);
    31.             /*
    32.             currentHealth -= collision.gameObject.GetComponent<Bullet>().damage;
    33.             TakeDamage();
    35.             */
    36.         }
    37.     }
    38. }
    And here is the ReceiveDamage script. It works fine for taking damage, dying and respawning. What seems to be bugging is the RpcHandleHealthUpdated()-method, which is where I'm trying to set the value of the player's HP-bar. And like I said, it works only on the player that last joined. The method is triggered by a syncvar hook of currentHealth.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Mirror;
    5. using System;
    7. public class ReceiveDamage : NetworkBehaviour
    8. {
    9.     [SerializeField]
    10.     private int maxHealth = 10;
    11.     [SyncVar(hook = nameof(UpdateHealth))]
    12.     private int currentHealth;
    13.     [SerializeField]
    14.     private string enemyTag;
    15.     [SerializeField]
    16.     private bool destroyOnDeath;
    17.     private Vector3 initialPosition;
    19.     void Start()
    20.     {
    21.         currentHealth = maxHealth;
    22.         initialPosition = transform.position;
    23.     }
    25.     [Client]
    26.     public  void TakeDamage(int damage)
    27.     {
    28.         currentHealth -= damage;
    29.         if (currentHealth <= 0)
    30.         {
    31.                 currentHealth = maxHealth;
    32.                 RpcRespawn();          
    33.         }
    34.     }
    36.     [ClientRpc]
    37.     void RpcRespawn()
    38.     {
    39.         CharacterController controller = GetComponent<CharacterController>();
    40.         controller.enabled = false;
    41.         controller.transform.position = initialPosition;
    42.         controller.enabled = true;
    43.     }
    45.     public void UpdateHealth(int oldHealth, int newHealth)
    46.     {
    47.         if (isServer)
    48.         {
    49.             RpcHandleHealthUpdated();
    50.         }
    51.         else
    52.         {
    53.             CmdHandleHealthUpdated();
    54.         }
    55.     }
    57.     [Command(requiresAuthority = false)]
    58.     public void CmdHandleHealthUpdated()
    59.     {
    60.         RpcHandleHealthUpdated();
    61.     }
    63.     [ClientRpc]
    64.     public void RpcHandleHealthUpdated()
    65.     {
    66.         if (isLocalPlayer)
    67.         {
    68.             if (GetComponent<HealthDisplay>())
    69.             {
    70.                 var display = gameObject.GetComponent<HealthDisplay>();
    71.                 display.healthBarImage.fillAmount = (float)currentHealth / maxHealth;
    72.             }
    73.         }
    74.     }
    75. }
    Does anyone have any idea what I'm doing wrong? It feels so weird that the damage is dealt to the right person when hit, but the HP-bar is just not working at all as expected. I am very new to networking so I have probably missed something trivial, but I have been banging my head against this wall for a few days now so any tips or help is greatly appreciated!
  2. Baklusha


    Dec 18, 2018
    I have briefly looked at your code and have a general comment: you have many unnecessary server-client message exchange. This makes the code less efficient and harder to debug.
    For example in ReceiveDamage class. SyncVar (visout hook) will automatically sync all clients when you change the value on the server. The hook function is an optional function which will be called only on CLIENT when the server changes the corresponding value. This means that in the following code if(isServer) will be never executed.
    Code (CSharp):
    1. public void UpdateHealth(int oldHealth, int newHealth)
    2.     {
    3.         if (isServer)
    4.         {
    5.             RpcHandleHealthUpdated();
    6.         }
    7.         else
    8.         {
    9.             CmdHandleHealthUpdated();
    10.         }
    11.     }
    The client will execute what is in else(). And it will go for a new loop client->server->client which is not needed. You client already has the latest updated currentHealth value from the server when enter UpdateHealth(). Just let it display it immediately. Server should not be involved in this.
    You can read more about SyncVar hooks in the Mirror documentation:

    Another thing what confuses me is OnCollisionEnter. You call it only on Server, and there you call .TakeDamage(...) function which is marked as [Client], and from there you call Rpc which is call from server to a client. Seems this should work only for a host machine which is client and server at the same time.

    There are several good examples in Mirror installed files. I think The tank game example can be very useful for you: \Mirror\Examples\Tanks\Scripts\Tank.cs

    Good luck with your game!
    mandogperson likes this.
  3. mandogperson


    Jan 28, 2022
    Thank you very much for your detailed answer, much appreciated! I did some changes according to your comments and was able to remove unneccesary code. Looking at the tank scene made me realize that the reason my HP bar is behaving this way is because the UI is a part of the player-prefabs. So for every new player that connects, their UI is put on top of every other players UI.

    Having the UI inactive in the player prefab, and activating it only for the local client that needs it solved the problem. I feel stupid because it feels so obvious now haha.

    Thank you very much for the help. :)
    Baklusha likes this.