Search Unity

Checking for null reference in Update

Discussion in 'Scripting' started by MoonJellyGames, May 1, 2016.

  1. MoonJellyGames

    MoonJellyGames

    Joined:
    Oct 2, 2014
    Posts:
    331
    Hey all,

    I was looking at one of my "finished" scripts to add a new feature to it and I noticed something that I did which may be considered poor practice and because I've probably done it with several scripts, I'd like to know what you guys/gals think.

    Basically, there are certain objects that have references to other objects that, once the referenced object is destroyed, something happens. For example, my remote bombs have a reference to the player who deployed them: player 1 or player 2 (this is a local two-player versus game). Player 1 deploys red remote bombs and player 2 deploys blue. When a player dies, however, they lose ownership over their remote bombs. The way that it has worked since I introduced the remote bomb power-up is that these remote bombs become neutral (animated with red and blue colours) indicating that either player can detonate them. In most cases, players will immediately detonate their remote bombs as soon as they become neutral, so it's usually not very interesting. Instead, I'd like to swap ownership.

    Here is the remote bomb script:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class RemoteBombScript : BombScript
    5. {
    6.     public bool neutral = true;        // is this bomb controlled by a player?  If not, neutral is true.
    7.     private int playerNum = 1;        // the playerNum of the player that threw this bomb.  Default is 1.  It would be zero, but that might cause errors with the Input Manager.
    8.     private PlayerController playerController;    // a reference to the player's PlayerController script (to avoid repeated GetComponent calls)
    9.  
    10.  
    11.     protected override void SetUpBomb()
    12.     {
    13.         hazardName = "remote";
    14.         throwPower = new Vector2 (10, 10);
    15.         neutralThrowAngle = new Vector2(1, 0);
    16.         yHop = 0;                               // how much y-velocity is added when this bomb is hit with a melee attack
    17.         blastPower = 100;
    18.         anim = GetComponent<Animator>();
    19.     }
    20.  
    21.     public override void Start()
    22.     {
    23.         base.Start();
    24.         if (transform.parent && transform.parent.gameObject.GetComponent<PlayerController>())
    25.         {
    26.             playerController = transform.parent.gameObject.GetComponent<PlayerController>();
    27.             playerNum = playerController.playerNum;
    28.             anim.SetInteger("player", playerNum);
    29.             neutral = false;
    30.         }
    31.         else
    32.             SetNeutral();
    33.     }
    34.  
    35.     // Update is called once per frame
    36.     override public void Update ()
    37.     {
    38.         // if there is no reference to the player who threw this (or there was no such player; it came from an exploded crate)...
    39.         if (Time.timeScale != 0)
    40.         {
    41.             if (!player && !exploded)
    42.             {
    43.                 if (!neutral)
    44.                     SetNeutral();
    45.  
    46.                 if (Input.GetButtonDown("Circle_1") || Input.GetButtonDown("Circle_2"))
    47.                     StartCoroutine(Explode ());
    48.             }
    49.             else
    50.             {
    51.                 if (Input.GetButtonDown("Circle_" + playerNum) && !exploded && thrown)
    52.                     StartCoroutine(Explode ());
    53.             }
    54.         }
    55.     }
    56.  
    57.     public override void Cook(GameObject p = null)
    58.     {
    59.         sprite.enabled = true;
    60.        
    61.         if (p)
    62.             player = p;
    63.     }
    64.  
    65.     public override void Throw(Vector2 aimdir)
    66.     {
    67.         base.Throw(aimdir);
    68.         anim.SetBool("thrown", thrown);
    69.         GetComponent<Rigidbody2D>().AddTorque(20 * -Mathf.Sign(GetComponent<Rigidbody2D>().velocity.x));    // add a bit of a spin
    70.         // Play looping "beep" sound effect !!!
    71.     }
    72.  
    73.     // deploy this bomb wihout applying any throwing force
    74.     public override void Drop()
    75.     {
    76.         try
    77.         {
    78.             if (bombCollider)
    79.             {
    80.                 bombCollider.enabled = true;
    81.             }
    82.             if (body)
    83.                 body.isKinematic = false;
    84.            
    85.             GetComponent<Rigidbody2D>().velocity = Vector2.up * -1;
    86.            
    87.             if (transform.parent)
    88.             {
    89.                 transform.parent = null;
    90.                 transform.localScale = new Vector3(1,1,1);
    91.             }
    92.            
    93.             transform.rotation = Quaternion.Euler(new Vector3(0, 0, Mathf.Atan2(GetComponent<Rigidbody2D>().velocity.y, GetComponent<Rigidbody2D>().velocity.x) * Mathf.Rad2Deg));
    94.  
    95.             thrown = true;
    96.  
    97.             justThrown = true;
    98.             StartCoroutine(TransitionFromJustThrown());
    99.  
    100.             anim.SetBool("thrown", thrown);
    101.             GetComponent<Rigidbody2D>().AddTorque(20f);
    102.         }
    103.         catch (UnityException e)
    104.         {
    105.             Debug.Log (e.Message);
    106.         }
    107.  
    108.     }
    109.  
    110.     public override void TakeExplosionDamage(Vector2 dir, int dmg, BombScript explosionSource)
    111.     {
    112.         // remote bombs do not explode from other bombs, instead they are knocked around.
    113.         GetComponent<Rigidbody2D>().AddForce(dir * dmg * 7);
    114.     }
    115.  
    116.     private void SetNeutral()
    117.     {
    118.         playerController = null;
    119.         anim.SetInteger("player", 0);
    120.         neutral = true;
    121.     }
    122.  
    123.     private void SwitchOwnership()
    124.     {
    125.         // Not sure what the best way to find the other player is.  Could use FindObjectWithTag.
    126.     }
    127. }
    128.  
    As you can see, the update function is constantly checking to see if its player reference is null. I really don't know how expensive/wasteful this is. Stages that have remote bomb power-up crates generally have less than four remote bombs on the stage at once, and my game's performance has never lead me to believe this has been harmful in this case. This is just the best example I have for my general question about checking for null object references.

    Thanks for reading. :)

    http://gph.is/1rL7Ujc

    PS: I know it's ugly, but it works for now. ;)
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    A cleaner way to structure it might be to have the bomb subscribe to an event on the player. The player can fire the event in OnDestroy.
     
    MoonJellyGames likes this.
  3. MoonJellyGames

    MoonJellyGames

    Joined:
    Oct 2, 2014
    Posts:
    331
    I was thinking something like that. The problem then becomes a matter of finding the remote bombs when a player dies. Or, I suppose, each player could have a list of the remote bombs that they own. Hmmm, I'm wondering how messy that would be. Adding to the list would be easy enough, I think. And when you press the detonate button, all of your remote bombs detonate at the same time, so the list could be cleared at that time. So the OnDestroy event would call SwapOwner(int playerNum) or something on each remote bomb in the list.

    Maybe I'll try something like that. Thanks for the response.
     
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Try a regular C# event.

    The only thing you need is for the bombs to be aware of your player. And it sounds like this is already in place.
     
  5. MoonJellyGames

    MoonJellyGames

    Joined:
    Oct 2, 2014
    Posts:
    331
    This must be a little outside of my C# knowledge then. Would you be able to elaborate?
     
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
  7. MoonJellyGames

    MoonJellyGames

    Joined:
    Oct 2, 2014
    Posts:
    331
    Thank you. That was very informative. :)

    With this design change, remote bombs aren't detonated as often leading them to build up in the stage (which makes matches that much more exciting). I think it was probably worth the trouble. ;)