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.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Send COMMAND from a NetworkServer.Spawn object

Discussion in 'Multiplayer' started by DaVeF101, Mar 14, 2016.

  1. DaVeF101

    DaVeF101

    Joined:
    Sep 7, 2014
    Posts:
    134
    I have a situation were a player throws a grenade. The grenade is spawned across the network using NetworkServer.Spawn. The grenade has a Network Identity (both Server Only and Local Player Authority are un-checked) and a Network Transform attached to it.

    The grenade has a timer script which detonates and applies damage to players around it.

    It applies damage by sending a Command to the server, which then sends an RPC to each player deducting health.

    My problem is, the Command isn't getting called if the grenade is thrown by a client player. It only works when the Server player throws the grenade.

    //Code on each player combat script to throw a grenade:

    GameObject grenade = Instantiate (inventory.currentGrenade, grenadeLaunchPoint.position, grenadeLaunchPoint.rotation) as GameObject;

    NetworkServer.Spawn (grenade);

    //Code on the grenade when it detonates:

    if (grenadeDamageDetector.colliders.Length > 0) {
    for (int i = 0; i < grenadeDamageDetector.colliders.Length; i++) {
    Rigidbody targetRigidbody = grenadeDamageDetector.colliders .GetComponent<Rigidbody> ();
    if (targetRigidbody == null) {
    continue;
    }
    float damage = CalculateDamage (targetRigidbody.position);

    CmdTakeDamageOnObject (targetRigidbody.gameObject, damage);
    }
    }


    //Command to server

    [Command]
    void CmdTakeDamageOnObject(GameObject obj, float damage) {

    TakeDamage takeDamage = obj.GetComponent<TakeDamage> ();
    if (takeDamage != null) {
    takeDamage.RpcResolveHit(obj.transform.position + Vector3.up, damage);
    }

    }

    I get this error:
    Trying to send command for object without authority.
    UnityEngine.Networking.NetworkBehaviour:SendCommandInternal(NetworkWriter, Int32, String)
    Grenade:CallCmdTakeDamageOnObject(GameObject, Single)

    Any ideas???
     
  2. sovium

    sovium

    Joined:
    Mar 8, 2016
    Posts:
    27
    Hello, I have an idea. Since you are spawning the grenade on the server, why not do damage calculations on the server only? The grenade exists on the server, so make the detonate script only run on the server. You don't need commands for this, instead you could do something like:

    Code (CSharp):
    1.  
    2. [Server]
    3. void DoDamage(){
    4.     if (grenadeDamageDetector.colliders.Length > 0) {
    5.         for (int i = 0; i < grenadeDamageDetector.colliders.Length; i++) {
    6.             Rigidbody targetRigidbody = grenadeDamageDetector.colliders .GetComponent<Rigidbody> ();
    7.             if (targetRigidbody == null) {
    8.             continue;
    9.             }
    10.             float damage = CalculateDamage (targetRigidbody.position);
    11.             targetRigidbody.GetComponent<PlayerScript>().TakeDamage(damage);
    12.         }
    13.     }
    14. }
    15.  
    This will cause the TakeDamage method to be called on the server only, so if your health is a SyncVar, it will be synced to all clients as well.
     
  3. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    683
    I agree with the post above...but just to be clear Commands don't get called on the server, because they don't need to be called on the server. Commands are sent FROM Clients to the Sever.
     
    sovium likes this.
  4. sovium

    sovium

    Joined:
    Mar 8, 2016
    Posts:
    27
    Also, please try to use code blocks when pasting code, it is a lot easier to read.
     
  5. DaVeF101

    DaVeF101

    Joined:
    Sep 7, 2014
    Posts:
    134
    sovium thanks for the reply. I'll give that a try.

    I was looking for code blocks when initially posting. Found the option now.
     
  6. DaVeF101

    DaVeF101

    Joined:
    Sep 7, 2014
    Posts:
    134
    I'm getting weird behaviour now. When I add the [Server] tag to the take damage function, when a client throws the grenade it doesn't detonate!

    I'll post my full code now:

    Local Player Combat Script

    Code (CSharp):
    1. public void ThrowGrenade() {
    2.         if (!isLocalPlayer)
    3.             return;
    4.  
    5.         GameObject grenade = Instantiate (inventory.currentGrenade, grenadeLaunchPoint.position, grenadeLaunchPoint.rotation) as GameObject;
    6.         NetworkServer.Spawn(grenade);
    7.  
    8.         Rigidbody body = grenade.GetComponent<Rigidbody> ();
    9.  
    10.         body.velocity = cc.velocity;
    11.  
    12.         body.AddForce (cameraTransform.forward * grenadeThrowForce);
    13.     }
    Grenade Script

    Code (CSharp):
    1. void Start() {
    2.  
    3.         if (!isServer)
    4.             return;
    5.        
    6.         //Set the damage range.
    7.         damageCollider.radius = explosionRadius;
    8.  
    9.         myRigidbody = GetComponent<Rigidbody> ();
    10.  
    11.  
    12.         grenadeDamageDetector = damageCollider.gameObject.GetComponent<GrenadeDamageDetector> ();
    13.         grenadeDamageDetector.radius = explosionRadius;
    14.  
    15.         StartCoroutine (LightFuse ());
    16.     }
    17.        
    18.     [Server]
    19.     IEnumerator LightFuse() {
    20.         yield return new WaitForSeconds (fuseTime);
    21.  
    22.         Instantiate (explosionPrefeb, transform.position, Quaternion.identity);
    23.  
    24.  
    25.         damageCollider.enabled = true;
    26.  
    27.         yield return new WaitForSeconds (0.05f);
    28.  
    29.         if (grenadeDamageDetector.colliders.Length > 0) {
    30.             for (int i = 0; i < grenadeDamageDetector.colliders.Length; i++) {
    31.                 Rigidbody targetRigidbody = grenadeDamageDetector.colliders [i].GetComponent<Rigidbody> ();
    32.                 if (targetRigidbody == null) {
    33.                     continue;
    34.                 }
    35.  
    36.                 //Don't add force to players, only scene objects
    37.                 if(targetRigidbody.gameObject.tag != "Player")
    38.                     targetRigidbody.AddExplosionForce (explosionForce, transform.position, explosionRadius);
    39.  
    40.                 float damage = CalculateDamage (targetRigidbody.position);
    41.  
    42.                 TakeDamage takeDamage = targetRigidbody.GetComponent<TakeDamage> ();
    43.                 if (takeDamage != null && isServer) {
    44.                     takeDamage.RpcResolveHit(targetRigidbody.transform.position + Vector3.up, damage);
    45.                 }
    46.             }
    47.         }
    48.         yield return null;
    49.  
    50.         Destroy (gameObject);
    51.     }
    52.  
    53.     float CalculateDamage(Vector3 targetPosition) {
    54.  
    55.         Vector3 explosiontoTarget = targetPosition - transform.position;
    56.  
    57.         float explosionDistance = explosiontoTarget.magnitude;
    58.  
    59.         float relativeDistance = (explosionRadius - explosionDistance) / explosionRadius;
    60.  
    61.         float damage = relativeDistance * maxDamage;
    62.  
    63.         damage = Mathf.Max (0f, damage);
    64.  
    65.         return damage;
    66.  
    67.     }
    68.  

    Take Damage Script

    Code (CSharp):
    1. [ClientRpc]
    2.     public void RpcResolveHit(Vector3 hitPoint, float damage) {
    3.         if (healthScript.isDead)
    4.             return;
    5.  
    6.         GameObject bloodObj = Instantiate (blood, hitPoint, Quaternion.identity) as GameObject;
    7.         bloodObj.transform.parent = transform;
    8.  
    9.         healthScript.DeductHealth (damage);
    10.  
    11.  
    12.         //Show Blood hud effect on local player
    13.         if (isLocalPlayer && hudManager) {
    14.             hudManager.ShowHurtEffect ();
    15.         }
    16.  
    17.     }
    Health Script

    Code (CSharp):
    1.     public GameObject DeathCam;
    2.     public GameObject playerCameras;
    3.     private CustomNetworkManager networkManager;
    4.  
    5.     private GameObject deathCamInstance;
    6.     private InvisibleShadowCasterSwitcher invisibleShadowCasterSwitcher;
    7.  
    8.     [SyncVar (hook="CheckForDead")] private float health = 100;
    9.  
    10.     [HideInInspector] public bool isDead { get; protected set; }
    11.  
    12.     void Start() {
    13.         invisibleShadowCasterSwitcher = GetComponent<InvisibleShadowCasterSwitcher> ();
    14.         networkManager = FindObjectOfType<CustomNetworkManager> ();
    15.     }
    16.  
    17.     public void DeductHealth(float amount) {
    18.         if (!isServer)
    19.             return;
    20.  
    21.         print (amount + " Health deducted");
    22.  
    23.         health -= amount;
    24.  
    25.         if (health <= 0) {
    26.             isDead = true;
    27.             StartCoroutine (Respawn ());
    28.         }
    29.     }
    30.  
    31.     void CheckForDead(float newHealth) {
    32.  
    33.         health -= newHealth;
    34.  
    35.         if (newHealth <= 0 && isLocalPlayer) {
    36.             playerCameras.SetActive (false);
    37.             deathCamInstance = Instantiate (DeathCam, transform.position, Quaternion.identity) as GameObject;
    38.             Destroy(deathCamInstance, 4.0f);
    39.         }
    40.     }
    41.  
    42.     IEnumerator Respawn() {
    43.         invisibleShadowCasterSwitcher.ResetMaterials ();
    44.         yield return new WaitForSeconds (4.0f);
    45.         health = 100;
    46.         isDead = false;
    47.         invisibleShadowCasterSwitcher.MakeInvisible ();
    48.         playerCameras.SetActive (true);
    49.         networkManager.Respawn (transform.gameObject);
    50.     }
    Still not working.. I'm sure it's painfully obvious, but I can see the problem.
     
  7. sovium

    sovium

    Joined:
    Mar 8, 2016
    Posts:
    27
    Not exactly what i mean, here is an example:

    Player script

    Code (CSharp):
    1.  
    2. public class Player : NetworkBehaviour{
    3.  
    4.     [SyncVar]
    5.     int health;
    6.  
    7.     public GameObject grenade;
    8.  
    9.     void OnEnable(){
    10.         health = 100;
    11.     }
    12.  
    13.     void Update(){
    14.         if(Input.GetKeyDown(KeyCode.Space) && isLocalPlayer){
    15.             // Throw grenade on server.
    16.             CmdThrowGrenade();
    17.         }
    18.     }
    19.  
    20.     [Command]
    21.     void CmdThrowGrenade(){
    22.         var grenade = Instantiate(grenade, Vector3.zero, Quaternion.identity) as GameObject;
    23.         NetworkServer.Spawn(grenade);
    24.     }
    25.  
    26.     [Server]
    27.     public void TakeDamage(int amount){
    28.         health -= amount;
    29.     }
    30. }
    31.  
    Grenade script

    Code (CSharp):
    1.  
    2. public class Grenade : NetworkBehaviour{
    3.     public GameObject explosionPrefab;
    4.     float fuseTime = 2.0f;
    5.     int damage = 10;
    6.  
    7.     void Start(){
    8.         StartCoroutine("LightFuse");
    9.     }
    10.  
    11.     IEnumerator LightFuse(){
    12.         yield return new WaitForSeconds(fuseTime);
    13.         // create explosion
    14.         // collision checks
    15.         // now since TakeDamage is marked to be ran only on the server, this will run only on the server and if
    16.         // player is hit by the grenade, their hp will go down on the server and since its a sync var it will also go down
    17.         // on the clients.
    18.         Player player = collidedObject.GetComponent<Player>().TakeDamage(damage);
    19.         // ...
    20.     }
    21. }
    22.  
     
  8. DaVeF101

    DaVeF101

    Joined:
    Sep 7, 2014
    Posts:
    134
    Thank you sovium, this seems a more logical approach. I will re-write my player take damage and health code. Currently i have separate scripts for Health, Combat and Take Damage. But I can see the easier way to do it is combine them in the way you've shown. Many thanks for your reply once again.
     
  9. sovium

    sovium

    Joined:
    Mar 8, 2016
    Posts:
    27
    You don't have to combine them, in fact having them in different components may help if you plan on doing other entities with health and stuff. Just make the damage calculations etc happen on server and you should be good.