Explosive destructable objects

I had an idea to make a crate that would explode and damage nearby objects. The effect instantiates just fine, but when I run the code to detect nearby objects that it would damage, I seem to get a memory leak. Anyone have any thoughts or can point me in the right direction?

public class ExplosiveDestructableObject : MonoBehaviour
{
    [SerializeField] private Transform throwableVFXPrefab;
    [SerializeField] private float effectRadius;
    [SerializeField] private int effectAmount;
    public static event EventHandler OnAnyExplosiveDestroyed;
    private GridPosition gridPosition;
    private Vector3 explosionOrigin;

    private void Start()
    {
        explosionOrigin = transform.position;
        gridPosition = LevelGrid.Instance.GetGridPosition(transform.position);
    }
       // Returns grid position to object 'destroyer' to update pathfinding
    public GridPosition GetGridPosition() { return gridPosition; }


    public void Damage()
    {
        DamageNearbyObjects();
        Instantiate(throwableVFXPrefab, explosionOrigin, Quaternion.identity);
        Destroy(gameObject);
        OnAnyExplosiveDestroyed?.Invoke(this, EventArgs.Empty);
    }

    private void DamageNearbyObjects()
    {
        Collider[] colliderArray = Physics.OverlapSphere(explosionOrigin, effectRadius);

        foreach(Collider collider in colliderArray)
        {
            if (collider.TryGetComponent<Unit>(out Unit targetUnit))
            { targetUnit.Damage(effectAmount); }

            // Does not require effectAmount
            if (collider.TryGetComponent<DestructableObject>(out DestructableObject destructableObject))
            { destructableObject.Damage(); }

            // Does not require effectAmount
            if (collider.TryGetComponent<ExplosiveDestructableObject>(out ExplosiveDestructableObject explosiveDestructableObject))
            { explosiveDestructableObject.Damage(); }
        }
    }
}

What exactly do you mean by ‘Memory Leak’? I can see a potential recursive issue, but I’m not sure what you mean by memory leak.

The recursive issue is that this objects could trigger another ExplosiveDestructableObject which will then start the same sequence, but it will also pick up the object that triggered it, meaning the ‘ground zero’ object will start the same process again, even if it is still busy exploding. This will turn into a vicious cycle that will kill your Unity. Then, even if we prevent this, there will be a major drop in frame rate if you have a bunch of these near each other.

To solve the first issue you may need to mark this object as ‘destroyed’ in some way before destroying it. Let’s just put a flag in the code and we don’t run Damage() if this flag is set;

public class ExplosiveDestructableObject : MonoBehaviour
{
    [SerializeField] private Transform throwableVFXPrefab;
    [SerializeField] private float effectRadius;
    [SerializeField] private int effectAmount;
    public static event EventHandler OnAnyExplosiveDestroyed;
    private GridPosition gridPosition;
    private Vector3 explosionOrigin;

    private bool isDestroyed = false; // This is our flag

    private void Start()
    {
        explosionOrigin = transform.position;
        gridPosition = LevelGrid.Instance.GetGridPosition(transform.position);
    }
    
    // Returns grid position to object 'destroyer' to update pathfinding
    public GridPosition GetGridPosition() { return gridPosition; }


    public void Damage()
    {
        // Don't destroy if it already is
        if (isDestroyed) return;
        // Mark this object as destroyed
        isDestroyed = true;
        
        DamageNearbyObjects();
        Instantiate(throwableVFXPrefab, explosionOrigin, Quaternion.identity);
        Destroy(gameObject);
        OnAnyExplosiveDestroyed?.Invoke(this, EventArgs.Empty);
    }

    private void DamageNearbyObjects()
    {
        Collider[] colliderArray = Physics.OverlapSphere(explosionOrigin, effectRadius);

        foreach(Collider collider in colliderArray)
        {
            if (collider.TryGetComponent<Unit>(out Unit targetUnit))
            { targetUnit.Damage(effectAmount); }

            // Does not require effectAmount
            if (collider.TryGetComponent<DestructableObject>(out DestructableObject destructableObject))
            { destructableObject.Damage(); }

            // Does not require effectAmount
            if (collider.TryGetComponent<ExplosiveDestructableObject>(out ExplosiveDestructableObject explosiveDestructableObject))
            { explosiveDestructableObject.Damage(); }
        }
    }
}

With this, the Damage method won’t run if it is already running, so it will prevent the cycle.

The next issue is the frame-rate drop. I would turn DamageNearbyObjects into a coroutine. I would have to wait for it to complete before destroying the object itself, though. I will go with the pattern we’re currently using and give the coroutine a callback for when it is done

private IEnumerator DamageNearbyObjects(System.Action onDamageComplete)
{
    Collider[] colliderArray = Physics.OverlapSphere(explosionOrigin, effectRadius);

    foreach(Collider collider in colliderArray)
    {
        if (collider.TryGetComponent<Unit>(out Unit targetUnit))
        { targetUnit.Damage(effectAmount); }

        // Does not require effectAmount
        if (collider.TryGetComponent<DestructableObject>(out DestructableObject destructableObject))
        { destructableObject.Damage(); }

        // Does not require effectAmount
        if (collider.TryGetComponent<ExplosiveDestructableObject>(out ExplosiveDestructableObject explosiveDestructableObject))
        { explosiveDestructableObject.Damage(); }
    
        yield return null; // This may be a little frequent. If there are lots of entries, perhaps only do this every 10 or so
    }
    
    onDamageComplete?.Invoke();
}

With this, we don’t really need the flag anymore. We can just hold a reference to the coroutine and if it’s running, don’t execute again. Here’s the updated class

public class ExplosiveDestructableObject : MonoBehaviour
{
    [SerializeField] private Transform throwableVFXPrefab;
    [SerializeField] private float effectRadius;
    [SerializeField] private int effectAmount;
    public static event EventHandler OnAnyExplosiveDestroyed;
    private GridPosition gridPosition;
    private Vector3 explosionOrigin;

    private Coroutine damageCoroutine; // changed the flag here

    private void Start()
    {
        explosionOrigin = transform.position;
        gridPosition = LevelGrid.Instance.GetGridPosition(transform.position);
    }
    
    // Returns grid position to object 'destroyer' to update pathfinding
    public GridPosition GetGridPosition() { return gridPosition; }


    public void Damage()
    {
        // Don't destroy if it already is
        if (damageCoroutine) return;
        
        // Swapped these around
        Instantiate(throwableVFXPrefab, explosionOrigin, Quaternion.identity);
        // start coroutine and grab a reference. Also pass the complete method for when it's done
        damageCoroutine = StartCoroutine(DamageNearbyObjects(DamageComplete));
    }

    // Cleanup this object when damage is done
    private void DamageComplete()
    {
        Destroy(gameObject);
        OnAnyExplosiveDestroyed?.Invoke(this, EventArgs.Empty);
    }

    private IEnumerator DamageNearbyObjects(System.Action onDamageComplete)
    {
        Collider[] colliderArray = Physics.OverlapSphere(explosionOrigin, effectRadius);

        foreach(Collider collider in colliderArray)
        {
            if (collider.TryGetComponent<Unit>(out Unit targetUnit))
            { targetUnit.Damage(effectAmount); }

            // Does not require effectAmount
            if (collider.TryGetComponent<DestructableObject>(out DestructableObject destructableObject))
            { destructableObject.Damage(); }

            // Does not require effectAmount
            if (collider.TryGetComponent<ExplosiveDestructableObject>(out ExplosiveDestructableObject explosiveDestructableObject))
            { explosiveDestructableObject.Damage(); }
        
            yield return null; // This may be a little frequent. If there are lots of entries, perhaps only do this every 10 or so
        }
        
        onDamageComplete?.Invoke();
    }
}

This should solve the issues.

Just a note ('cos it popped into my head); You could sort the colliders by distance from the origin (closest first) before running the loop. This, coupled with the coroutine, will then give a ‘shockwave’ effect, damaging objects nearby just a moment before the ones further away. you could then also scale the damage amount with a lerp, meaning objects further away would take less damage

Unchecked recursion. Older versions of Unity would have hung indefinitely until the stack overflowed and then it would have crashed. @bixarrio’s fix should resolve the issue.

Indeed; His solution did solve the problem. Thank you for commenting as well!

Bixarrio,

Well, I gotta tell ya - That certainly solved the issue I was having. The alternative I was floating in my head was going through the trouble of creating a gridObject with scripts to boot for that.

I take your word for it regarding the co-routines, but I have yet to really work with them so I’m gonna research it a bit to add to the arsenal, so to speak. I don’t have any performance issues with the simple bool solution (I tested it with about 30 explosive creates without any performance issues, and my computer’s CPU has been aging out for a while now), but it’s still something I’d like to learn about.

Thanks for the Assist. Master Problem Solver, indeed.

It’s fine. it’s just something I thought may be an issue because everything will be happening in the same frame and you’re looping through a bunch of colliders and each one of those is doing the same thing as well - if they are ExplosiceDestructableObject. Just something to keep in mind if you do see a drop in frame rate.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms