Search Unity

  1. Looking for a job or to hire someone for a project? Check out the re-opened job forums.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Another null ref. thread?

Discussion in 'Scripting' started by SparrowGS, Mar 27, 2018.

  1. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,518
    okay, so this isn't the usual newbie forgetting to put stuff in the inspector(although i often do, haha)

    my problem is this, i have enemies that hunt down the player and his base, the base is made out of blocks that the player builds, they can be destroyed either by the player interacting with the build/destroy menu or by taking damage(like all other IDamageable interface stuff in the game)

    the problem is this, when an enemy is tracking a block and the block gets destroyed, the enemy script seems to go wonky, i'm clearly checking if it's null 3 line before the error occurs so my first guess is that the object was destroyed by another thing between those lines, by sadly this isn't this simple, for starters i use Destroy and not DestroyImmediate, second is even if that's the case, the error keeps on happening, spamming the log and the enemy freezes while all else plays the same.

    am i missing something that happens with a ref. to an interface instead an any "normal" script, mb or not?

    this is the part of the enemy script getting the error, the strafe part doesn't really have anything to do with it.
    Code (CSharp):
    1.  
    2. public class Mob_Flying : MonoBehaviour, IDamageable, Ipool {
    3.  
    4.    
    5.    Rigidbody rb;
    6.    IDamageable target;
    7.    [SerializeField]GameObject destroyedPrefab;
    8.    public bool active{ get; private set; }
    9.    float health;
    10.    //bool engaged = false;
    11.    bool strafe = false;
    12.    Vector3 wantedPosition;//target position
    13.    Quaternion wantedRotation;
    14.    bool move = false;
    15.    bool stop = false;
    16.  
    17.    [SerializeField]float maxHealth = 100;
    18.    [SerializeField]float detectionRange = 350f;
    19.    [SerializeField]float shootingRange = 75f;
    20.    [SerializeField]float attackBubble = 250f;
    21.    [SerializeField]float rotSpeed = 2;
    22.    [SerializeField]float speed = 10;
    23.    [SerializeField]float explosionStr = 250f;
    24.    [SerializeField]float explosionSize = 10f;
    25.    [SerializeField]float DPS = 10;
    26.    [SerializeField]float maxFiringAngle = 3f;
    27.    AudioSource audioS;
    28.    LineRenderer lazer;
    29.    float crashCheckDis = 30;
    30.    [SerializeField]List<RayDir>Rays = new List<RayDir>();
    31.  
    32.    [SerializeField]LayerMask enemyLM;
    33.  
    34.    public Transform _transform{ get { return transform; } }
    35.  
    36.    float sqrShootingRange { get { return shootingRange * shootingRange; } }//TODO maybe cache this?
    37.  
    38.    Vector3 AttackBubble(){
    39.        return new Vector3 (Random.Range (-attackBubble, attackBubble), attackBubble, Random.Range (-attackBubble, attackBubble));
    40.    }
    41.  
    42.    void Update(){
    43.  
    44.        if (active == false || GameMaster.instance.paused)
    45.            return;
    46.  
    47.        if (target != null) { //attack target
    48.            
    49.            if (strafe) {
    50.                wantedPosition = target._transform.position;
    51.                move = true;
    52.  
    53.                float sqrDist = (transform.position - wantedPosition).sqrMagnitude;
    54.                if (sqrDist < sqrShootingRange) {//target below min range
    55.                    wantedPosition = target._transform.position + AttackBubble ();
    56.                    strafe = false;
    57.                    lazer.enabled = false;
    58.                    audioS.Stop ();
    59.                } else if (sqrDist < sqrShootingRange * 2) {//target below max range
    60.                    ShootLazer ();
    61.                }
    62.            } else {//hover around target
    63.                
    64.                if (Vector3.Distance (transform.position, wantedPosition) < crashCheckDis) {
    65.                    
    66.                    if (CanSeeTarget()) {
    67.                        strafe = true;
    68.                    } else {
    69.                        wantedPosition = target._transform.position + AttackBubble ();
    70.                    }
    71.                }
    72.                move = true;
    73.            }
    74.            wantedRotation = Quaternion.LookRotation (wantedPosition - transform.position);
    75.        } else { //move toward cented
    76.  
    77.            target = Scan ();
    78.            wantedPosition = (target == null) ? Vector3.up * 10 : target._transform.position + AttackBubble (); //TODO replace ve3.up with highest hex in center
    79.            wantedRotation = Quaternion.LookRotation (wantedPosition - transform.position);
    80.            move = true;
    81.        }
    82.  
    83.        //Movement & collision detection
    84.  
    85.        Quaternion offset = rb.rotation;
    86.        foreach (RayDir ray in Rays) {
    87.            if (Physics.Raycast (ray.rayDirection.position, ray.rayDirection.forward, crashCheckDis)) {
    88.                offset *= Quaternion.LookRotation (ray.offsetDirection);
    89.                Debug.DrawRay (ray.rayDirection.position, ray.rayDirection.forward * crashCheckDis, Color.red);
    90.            } else {
    91.                Debug.DrawRay (ray.rayDirection.position, ray.rayDirection.forward * crashCheckDis, Color.cyan);
    92.            }
    93.        }
    94.  
    95.  
    96.        if (Physics.Raycast (transform.position, transform.forward, crashCheckDis / 2))
    97.            stop = true;
    98.  
    99.        rb.rotation = Quaternion.Slerp (rb.rotation, (offset == rb.rotation) ? wantedRotation : offset, rotSpeed * 2 * Time.deltaTime);
    100.    }
    101.  
    102.    IDamageable Scan () { // Returns first enemy found
    103.  
    104.        Collider[] cols = Physics.OverlapSphere (transform.position, detectionRange, enemyLM);
    105.        RaycastHit hit;
    106.        for (int i = 0; i < cols.Length; i++) {
    107.  
    108.            hit = new RaycastHit ();
    109.            IDamageable id = cols[i].gameObject.GetComponentInParent<IDamageable>();
    110.            if (id != null && Physics.Raycast (transform.position, cols [i].transform.position - transform.position, out hit, detectionRange) && hit.collider == cols [i])
    111.                return id;
    112.        }
    113.        return null;
    114.    }
    115.  
    116.    /*IDamageable[] Scan () { // Returns all enemies in sight
    117.  
    118.        Collider[] cols = Physics.OverlapSphere (transform.position, detectionRange, enemyLM);
    119.        List<IDamageable> ids = new List<IDamageable> ();
    120.        RaycastHit hit;
    121.        for (int i = 0; i < cols.Length; i++) {
    122.  
    123.            hit = new RaycastHit ();
    124.            IDamageable id = cols[i].gameObject.GetComponentInParent<IDamageable>();
    125.            if (id != null && Physics.Raycast (transform.position, cols[i].transform.position - transform.position, out hit, detectionRange) && hit.collider == cols[i])
    126.                ids.Add (id);
    127.        }
    128.  
    129.        if (ids.Count > 0)
    130.            return ids.ToArray ();
    131.        else
    132.            return null;
    133.  
    134.    }*/
    135.  
    136.    bool CanSeeTarget(){
    137.        
    138.        RaycastHit hit;
    139.        Physics.Raycast (transform.position, target._transform.position - transform.position, out hit, detectionRange);
    140.        if (hit.rigidbody != null && hit.rigidbody.transform == target._transform)
    141.            return true;
    142.        else
    143.            return false;
    144.    }
    145.  
    146.    void FixedUpdate(){
    147.  
    148.        if (stop)
    149.            rb.drag = 35;
    150.        else
    151.            rb.drag = 1;
    152.  
    153.        if (move && !stop)
    154.            rb.AddForce (transform.forward.normalized * speed * Time.timeScale, ForceMode.Impulse);
    155.  
    156.        move = false;
    157.        stop = false;
    158.    }
    159.        
    160.    void ShootLazer(){
    161.        
    162.        if (Mathf.Abs (Vector3.Angle (lazer.transform.forward, target._transform.position - lazer.transform.position)) < maxFiringAngle && CanSeeTarget ()) {
    163.            if (!audioS.isPlaying)
    164.                audioS.Play ();
    165.            lazer.enabled = true;
    166.            lazer.SetPosition (0, transform.position);
    167.            lazer.SetPosition (1, target._transform.position);
    168.            target.GetDamage (new HitInfo(DPS*Time.deltaTime,this.gameObject));
    169.        } else{
    170.            //lazer.SetPosition (0, Vector3.zero);
    171.            audioS.Stop ();
    172.            lazer.enabled = false;
    173.        }
    174.    }
    175.    public void GetDamage (HitInfo hit){
    176.  
    177.        if (active) {
    178.            health -= hit.damage;
    179.            if (health <= 0) {
    180.                Die ();
    181.            } else if (hit.attacker != null) {
    182.                //attack back
    183.                IDamageable id = hit.attacker.GetComponent<IDamageable> ();
    184.                if (id != null) {
    185.                    strafe = true;
    186.                    target = id;
    187.                }
    188.            }
    189.        }
    190.    }
    191.  
    192.    public void Ini (){
    193.        
    194.        active = false;
    195.        rb = GetComponent<Rigidbody> ();
    196.        audioS = GetComponent<AudioSource> ();
    197.        lazer = GetComponentInChildren<LineRenderer> ();
    198.        gameObject.SetActive (false);
    199.    }
    200.  
    201.    public void SpawnObj (Vector3 pos){
    202.  
    203.        target = null;
    204.        health = maxHealth;
    205.        transform.position = pos + (Vector3.up * 350);
    206.        transform.rotation = Quaternion.LookRotation (Vector3.up);
    207.        wantedPosition = Vector3.up * 10;
    208.        wantedRotation = transform.rotation;
    209.        active = true;
    210.  
    211.    }
    212.  
    213.    void OnCollisionEnter(Collision col){
    214.        if (col.relativeVelocity.sqrMagnitude > 75)
    215.            GetDamage (new HitInfo (col.relativeVelocity.magnitude));
    216.    }
    217.  
    218.    void Die(){
    219.  
    220.        //Debug.Log ("Enemy dead " + Time.time);
    221.        active = false;
    222.        Wreck w = PoolManager.instance.Spawn (destroyedPrefab, transform.position).GetComponent<Wreck>();
    223.        w.SetRotation (rb.rotation, rb.angularVelocity);
    224.        w.SetVelocity (rb.velocity);
    225.        Explode ();
    226.        gameObject.SetActive (false);
    227.    }
    228.  
    229.    void Explode(){
    230.        Collider[] trgs = Physics.OverlapSphere (transform.position, explosionSize);
    231.        foreach (Collider col in trgs) {
    232.            IDamageable id = col.GetComponentInParent<IDamageable> ();
    233.            if (id != null) {
    234.                float dis = Vector3.Distance (transform.position, col.transform.position);
    235.                if (dis < 1)
    236.                    id.GetDamage (new HitInfo (explosionStr));
    237.                else
    238.                    id.GetDamage (new HitInfo (explosionStr / dis));
    239.            }
    240.        }
    241.  
    242.        Collider[] cols = Physics.OverlapSphere (transform.position, explosionSize);
    243.        Vector3 t = new Vector3 (Random.Range (-explosionStr, explosionStr), Random.Range (-explosionStr, explosionStr), Random.Range (-explosionStr, explosionStr));
    244.        foreach (Collider col in cols) {
    245.            if (col.attachedRigidbody != null) {
    246.                col.attachedRigidbody.AddExplosionForce (explosionStr, transform.position, 25, 0, ForceMode.Impulse);
    247.                col.attachedRigidbody.AddTorque (t,ForceMode.Impulse);
    248.            }
    249.        }
    250.    }
    251. }
    252.  
    this is the way i destroy the game object, please tell me if i'm wrong
    this is the script attached to the GameObject
    Code (CSharp):
    1. public class HexRep : MonoBehaviour, IDamageable {
    2.  
    3.     bool active;
    4.     float health;
    5.     [SerializeField]float maxHealth;
    6.     public Transform _transform{ get { return transform; } }//Part of IDamageable
    7.  
    8.     public Hex myHex;
    9.  
    10.     void Start(){
    11.         health = maxHealth;
    12.         active = true;
    13.     }
    14.  
    15.     public void GetDamage(HitInfo hi){ //Part of IDamageable
    16.         health -= hi.damage;
    17.         if (health < 0 && active)
    18.             Die ();
    19.     }
    20.  
    21.     void Die(){
    22.         active = false;
    23.         MapMaster.instance.RemoveHex (myHex);
    24.  
    25.     }
    26. }
    and this is the MapMaster RemoveHex function:
    Code (CSharp):
    1.     public void RemoveHex(Hex hex){
    2.  
    3.         if (hex == null) {
    4.             Debug.LogError ("No hex to destroy!");
    5.             return;
    6.         }
    7.  
    8.         if (hexes.ContainsKey (hex.coord) == false) {
    9.             Debug.LogError ("Hex at: " + hex.coord.ToString() + " isn't registered!");
    10.             return;
    11.         }
    12.  
    13.         Destroy (hex.rep.gameObject);
    14.         hex.rep = null;
    15.         hexes.Remove (hex.coord);
    16.     }
     
    Last edited: Mar 27, 2018
  2. Mokzen

    Mokzen

    Joined:
    Oct 10, 2016
    Posts:
    91
    Where/when is "strafe" defined?
     
  3. N00MKRAD

    N00MKRAD

    Joined:
    Dec 31, 2013
    Posts:
    210
    ...what is _transform? Maybe the underscore is causing the problems?
     
  4. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,115
    When I use Interfaces I tend to make a GameObject GameObject { get; } in the interface, to make sure that the scene object exists and not just the reference. Plus it's nice to have a gameObject when you have an interface. Perhaps you could just have a bool Exists { get; } and internally do a gameObject check on the interface.

    However really what you want to do is do a breakpoint on strafe and see what's happening. Make sure strafe exists, check whether target exists, check whether it's transform exists. Without a breakpoint you'll be missing your vital eyes and ears on what's actually happening in your code. I can't confirm the issue isn't somewhere else, as I don't know how target's assigned, or what strafe is, but it does sound likely that the interface reference is there, but not the gameObject. Perhaps check that.
     
    SparrowGS likes this.
  5. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Yep, this. It's transform, not _transform.
     
  6. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,518
    Didn't really understand this part, I think i'm doing as you suggest only using a Transform and not a GameObject


    I'll update the op with the enitre enemy script, but i think looking at the update loop is enough, please tell me if you see something wrong.
    I'll go try again, thanks for the advice

    strafe is defined within this enemy class and is just used to determine if the enemy should go straight up to the player or do something else(like flanking), if strafe is equal to false the error occurs on another line, but the problem remains the same, the check for if(target != null) returns true even after the target is destroyed.
    Code (CSharp):
    1.  
    2.     Vector3 AttackBubble(){
    3.        return new Vector3 (Random.Range (-attackBubble, attackBubble), attackBubble, Random.Range (-attackBubble, attackBubble));
    4.    }
    5.  
    6.  
    7. //inside the update loop
    8. wantedPosition = target._transform.position + AttackBubble ();
    both of you obviously didn't read the entire post, because i'm using an interface i dont have access to the mb.transform so _transform belongs to the IDamageable interface and is returning the transform.

    Code (CSharp):
    1.     public Transform _transform{ get { return transform; } }//Part of IDamageable
     
  7. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,518
    Did some more testing, how is it possible that this statement is causing a runtime error?

    Code (CSharp):
    1.         if (target != null && target._transform != null) {
    MissingReferenceException: The object of type 'HexRep' has been destroyed but you are still trying to access it.
    Your script should either check if it is null or you should not destroy the object.
    HexRep.get__transform () (at Assets/Scripts/HexRep.cs:10)
    Mob_Flying.Update () (at Assets/Scripts/Mob_Flying.cs:55)
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    7,545
    This is an issue that infuriates me about Unity.

    See, Unity has overloaded the == operator for UnityEngine.Object. When you use compare on UnityEngine.Object it does this instead:
    Code (csharp):
    1.  
    2.     public static bool operator ==(Object x, Object y)
    3.     {
    4.       return Object.CompareBaseObjects(x, y);
    5.     }
    6.  
    7.     private static bool CompareBaseObjects(Object lhs, Object rhs)
    8.     {
    9.       bool flag1 = (object) lhs == null;
    10.       bool flag2 = (object) rhs == null;
    11.       if (flag2 && flag1)
    12.         return true;
    13.       if (flag2)
    14.         return !Object.IsNativeObjectAlive(lhs);
    15.       if (flag1)
    16.         return !Object.IsNativeObjectAlive(rhs);
    17.       return lhs.m_InstanceID == rhs.m_InstanceID;
    18.     }
    19.  
    The object will return true when compared to null if the native object (the unity C++ side of things) has been destroyed. Despite the C# managed object still exists.

    Thing is, this only works if the variable is typed to UnityEngine.Object or one if it's child types.

    If it's typed to say System.Object, or as an interface... the compiler doesn't know this overload should be used. And it just does the usual object.ReferenceEquals(target, null) instead. Effectively only testing the C# side of things. And well... the managed C# object isn't null.

    ...

    Unity implemented this years ago back when Unity was intended to be VERY newb friendly. They didn't consider this use case like interfaces. They since realized the issue, and even had a blog post about it where they considered fixing it. But since it would break existing code... they didn't.

    I wish they did.

    ...

    So in your code:
    Code (csharp):
    1. if (target != null && target._transform != null)
    target isn't null, BUT target was destroyed, to you can access the 'transform' of it. Just like the exception says.
     
    SparrowGS likes this.
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    7,545
    You'll have to cast it to the UnityEngine.Object to check if it was destroyed.

    What I do is I created a 'IsNullOrDestroyed' static function to do this for all System.Objects. It also evaluates for some other special interface contracts I have defined.

    https://github.com/lordofduct/space...pacepuppyUnityFramework/Utils/ObjUtil.cs#L745

    Unfortunately unity has made the method IsNativeObjectAlive an internal function, so I have to reflect it out. As you can see here:

    Code (csharp):
    1.  
    2.         private static System.Func<UnityEngine.Object, bool> _isObjectAlive;
    3.         public static System.Func<UnityEngine.Object, bool> IsObjectAlive
    4.         {
    5.             get { return _isObjectAlive; }
    6.         }
    7.      
    8.  
    9.         static ObjUtil()
    10.         {
    11.             try
    12.             {
    13.                 var tp = typeof(UnityEngine.Object);
    14.                 var meth = tp.GetMethod("IsNativeObjectAlive", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
    15.                 if (meth != null)
    16.                 {
    17.                     var d = System.Delegate.CreateDelegate(typeof(System.Func<UnityEngine.Object, bool>), meth) as System.Func<UnityEngine.Object, bool>;
    18.                     _isObjectAlive = (a) => !object.ReferenceEquals(a, null) && d(a);
    19.                 }
    20.                 else
    21.                     _isObjectAlive = (a) => a != null;
    22.             }
    23.             catch
    24.             {
    25.                 //incase there was a change to the UnityEngine.dll
    26.                 _isObjectAlive = (a) => a != null;
    27.                 UnityEngine.Debug.LogWarning("This version of Spacepuppy Framework does not support the version of Unity it's being used with. (ObjUtil)");
    28.                 //throw new System.InvalidOperationException("This version of Spacepuppy Framework does not support the version of Unity it's being used with.");
    29.             }
    30.         }
    31.  
    32.         /// <summary>
    33.         /// Returns true if the object is either a null reference or has been destroyed by unity.
    34.         /// This will respect ISPDisposable over all else.
    35.         /// </summary>
    36.         /// <param name="obj"></param>
    37.         /// <returns></returns>
    38.         public static bool IsNullOrDestroyed(this System.Object obj)
    39.         {
    40.             if (object.ReferenceEquals(obj, null)) return true;
    41.  
    42.             if (obj is ISPDisposable)
    43.                 return (obj as ISPDisposable).IsDisposed;
    44.             else if (obj is UnityEngine.Object)
    45.                 return !_isObjectAlive(obj as UnityEngine.Object);
    46.             else if (obj is IComponent)
    47.                 return !_isObjectAlive((obj as IComponent).component);
    48.             else if (obj is IGameObjectSource)
    49.                 return !_isObjectAlive((obj as IGameObjectSource).gameObject);
    50.          
    51.             return false;
    52.         }
    53.  
    Of course you could just cast and do ==, I did this to avoid the lengthy overload that unity implemented and instead directly call IsNativeObjectAlive.

    But yeah, you can do it like so:
    Code (csharp):
    1.  
    2.         /// <summary>
    3.         /// Returns true if the object is either a null reference or has been destroyed by unity.
    4.         /// This will respect ISPDisposable over all else.
    5.         /// </summary>
    6.         /// <param name="obj"></param>
    7.         /// <returns></returns>
    8.         public static bool IsNullOrDestroyed(this System.Object obj)
    9.         {
    10.             if (object.ReferenceEquals(obj, null)) return true;
    11.            
    12.             if(obj is UnityEngine.Object) return (obj as UnityEngine.Object) == null;
    13.  
    14.             return false;
    15.         }
    16.  
     
    Zikran, Nigey and SparrowGS like this.
  10. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,518
    THANK YOU, i was going crazy over this!

    i knew it something to do with the c++ c#, how do you suppose the best way to get around this?
     

    Attached Files:

    • WTF.png
      WTF.png
      File size:
      166.6 KB
      Views:
      573
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    7,545
    See my second post.

    The second code snippet is what you'll want.
     
    SparrowGS likes this.
  12. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,518
    was just reading through it, you sir are the man, thanks a ton.
     
    Last edited: Mar 28, 2018
unityunity