Using getComponent in one update call

Hi all,
I’m creating a game with two players a lot of enemies. The player has various ways of killing enemies and when that happens, what I do is:

EnemyHealth enemyHealth = enemy.GetComponent<EnemyHealth>();
enemyHealth.Die();

(where enemy is the Enemy GameObject which is about to die).

I know GetComponent isn’t really performant so I was wondering if this is a good way to do things. Should I store a list of all enemies and then find the one that has to die and use that reference instead? I think that would be even worse…

I usually try to have all my Find() and GetComponent() calls in the start or awake functions, but this is in one Update call (note that it’s only one call, not Every Update call).

Thanks!

Jonas

Should be fine.

GetComponent() can be slow and its good to avoid abusing it but its still quite necessary. At some point you have to communicate with the other script so the call will happen, you just have to decide when and where.

When you hear “don’t use it in Update()” it means “don’t use it in Update() because it runs every frame”. It doesn’t mean that its bad just because its Update, its bad because its assumed that the code inside is going to be processed every frame.

cache if you can, use if you can’t… profile the game later when you’re closer to being project complete and see if it is causing you any real problems and refactor if you have to.

Thanks for the answers, I’ll keep it the way it is in this case and see how it performs later. For now I didn’t notice any performance issue even though you kill quite a lot of those enemies (it’s a fast game, sometimes you kill 10 in a matter of 1 second). I know the “Update” thing shouldn’t be a problem in my case as it’s just one call but I still wanted to make sure that I wasn’t going off track with the project so I thought I’d ask the question. Thanks again

As an alternative scheme, a dictionary was used in our mobile titles for this purpose. Our general structure is to use a collider as key for value basescript which is inherited and from there the world is your oyster.

Dictionary<Collider, EnemyBaseClass>

with:

class MyEnemy:EnemyBaseClass

Then you can either use override/virtual to kick upstairs, or just have the health variable in the base class. This is a pretty simplistic pattern but allows you to easily avoid getcomponent altogether and while I haven’t tested I’m pretty sure it’s always going to be faster than get component and it’s safe to run it per frame (not that you should!).

Also we have used GetComponent a few times and it’s really not harmful if it’s done here and there infrequently. Tool for the job and all that. My scheme was mostly because I needed rich info for a lot of things colliding.

Note: we pooled our enemies so using a dictionary for a fast lookup was a practical choice. Possibly, there are many better ways, it’s just this one stood out for me because it was so simple and easy to manage without manager code.

Thanks for the suggestion, I’ll try your solution if I find GetComponent has performance issues.

A second thread discussing GetComponent under a few minutes, might as well continue rolling on the subject!
Using GetComponent is not the devil, lol…

You sometime need to use GetComponent in the Update method.
What is important, is that it’s hidden under conditions:

Example

void Update()
{
    //pseudo code

    if shooting
       if raycast hit Enemy
            GetComponent<EnemyHealth>().Die();
}

In this pseudo-code example, using GetComponent is perfectlyFine because:

  • You don’t know who Enemy game is in the Awake/Start Method. (because you have a lot of them)
  • The GetComponent is not being called on every frame.
  • It is call on every frame where the above conditions are true (shooting and raycast hit on the Enemy)

What you wrote is roughly what I’m doing in my case :slight_smile: Thanks again

When the enemy is created you could register it with some kind of enemy manager component. That manager component could be queried when you hit an object and return if the object is an enemy or not.

Maybe something like:

Enemy Manager:

 public class EnemyManager
    {
        protected Dictionary<GameObject, Enemy> mEnemiesList;

        public static EnemyManager Instance
        {
            get
            {
                if(mInst == null)
                {
                    mInst = new EnemyManager();
                }
                return mInst;
            }
          
        }
        private static EnemyManager mInst;

        private EnemyManager()
        {
            mEnemiesList = new Dictionary<GameObject, Enemy>();
        }

        public void Register(GameObject _key, Enemy _val)
        {
            if(mEnemiesList.ContainsKey(_key) == false)
            {
                mEnemiesList.Add(_key, _val);
            }
        }

        public void UnRegister(GameObject _key)
        {
            if(mEnemiesList.ContainsKey(_key))
            {
                mEnemiesList.Remove(_key);
            }
        }

        public bool IsEnemy(GameObject _go, Enemy _outEnemy)
        {
            return mEnemiesList.TryGetValue(_go, out _outEnemy);
        }

    }

Enemy class - registers and unregisters - you might modify this in your code:

public class Enemy : MonoBehaviour
    {
        public void Die();

        public void Awake()
        {
            EnemyManager.Instance.Register(this.gameObject, this);
        }


        public void OnDestroy()
        {
            EnemyManager.Instance.UnRegister(this.gameObject);
        }

    }

Your collision code would do this:

public void OnCollisionEnter(Collider _other)
    {
        Enemy e = null;
        if(EnemyManager.Instance.IsEnemy(_other.gameObject, e))
        {
            e.Die();
        }
    }

How about calling just

enemy.Die();

and in your Enemy class add Die function

public class Enemy
{
    EnemyHealth enemyHealth;

    void Start()
    {
        enemyHealth = enemy.GetComponent<EnemyHealth>();       
    }

    public void Die()
    {
        enemyHealth.Die();
    }
}

I think I would still need to get a reference to it, right?

yes of course you need a reference to your enemy!!

what is the condition in your Update to call the Die function ?

There’s a couple, but to summarize I can say that I’m sure the conditions will be met only once, when the enemy will Die. I don’t have more than one GetComponent running per each enemy per frame

where did you call GetComponent for your enemy ?

you can always cache your reference to your enemy as well !!

Ok let me show you the whole thing. Here’s an old video of the gameplay (even though I reworked it a little bit and the GetComponent no longer is in the Update):

I’m talking about the green arm Slamming down. That Arm is an enemy and a friend: it doesn’t car what it has in front of him, if something gets close, the arm slams down and kills whatever is there (meaning the player can lead enemies in front of it and kill many of them in one shot). In my old script I had box collider in the zone where the hand hits the floor. On trigger enter I would add the enemy that entered that area in an array of enemies that are in the danger zone. When the hand senses someone’s there it will slam, the script waits 0.3 seconds for the hand animation and then checks who’s still in the box collider (and the array) and kills whoever is still there.

After the rework (it’s still not perfect) I have a box collider attached to a bone of the hand, so the collider is animated like the hand. When the hand senses someone is in front of it, it will slam down and whoever collides with the boxcollider gets squished. To accomplish this I have this script attached to the bone that has the box collider:

using UnityEngine;
using System.Collections;

public class ArmBoneCollision : MonoBehaviour {
   
    ArmSlams armSlams;
    // Use this for initialization
    void Start () {
        armSlams = GetComponentInParent<ArmSlams>();
    }
   
    // Update is called once per frame
    void Update () {
   
    }
   
    void OnCollisionEnter(Collision collider_object) {
        if (armSlams.isArmSlamming()) {
            armSlams.SquishVictim(collider_object.gameObject);
        }
    }
}

And the ArmSlams (which controls animation and senses players and so on):

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ArmSlams : MonoBehaviour {
    //List<GameObject> victims;
    bool isSlamming = false;
    bool isLowering = false;
    public float slamTime = .3f;
    Animator animator;
    Transform rayOrigin;
    BoxCollider boxCollider;

    void Start() {
        //victims = new List<GameObject>();
        animator = GetComponent<Animator> ();
        animator.SetBool ("isAlive", true);
        rayOrigin = transform.FindChild("RayOrigin");
        boxCollider = transform.GetComponentInChildren<BoxCollider>();
    }
   
    void Update() {
        if(!isSlamming) {
            bool has_victims_in_front = CheckForVictims();
            if (has_victims_in_front) {
                Slam();
            }
        }
    }

    IEnumerator WaitForAnimation() {
        yield return new WaitForSeconds (2f);
        isSlamming = false;
    }
   
    IEnumerator HandIsLowering() {
        yield return new WaitForSeconds (slamTime);
        isLowering = false;
    }
   
    void Slam() {
        if (!isSlamming) {
            animator.SetTrigger("Slam");
            StartCoroutine (WaitForAnimation ());
            StartCoroutine (HandIsLowering ());
        }
        isSlamming = true;
        isLowering = true;
    }
   
    // This checks if there's someone to slam, and triggers the slam itself
    bool CheckForVictims() {
        RaycastHit hit;
        Vector3 fwd = rayOrigin.TransformDirection(Vector3.forward);
        if (Physics.Raycast(rayOrigin.position, fwd * 50, out hit, 50)) {
            if (hit.collider.transform.root.name != transform.root.name) { // if it's not hitting itself
                return true;
            }
        }
        Debug.DrawRay(rayOrigin.position, fwd * 50, Color.green, 2, false);
        return false;
    }
   
    public void SquishVictim(GameObject victim) {
        if (victim.tag == "Enemy") {
            EnemyDamageController enemyDamageController = victim.GetComponent<EnemyDamageController>();
            enemyDamageController.Squish();
            enemyDamageController.Die();
        } else if (victim.tag == "Player") {
            PlayerHealth playerHealth = victim.GetComponent<PlayerHealth>();
            playerHealth.Squish();
        }
    }
   
    public bool isArmSlamming() {
        return isLowering;
    }
}

A you can see the GetComponent call is in the SquishVictim call which gets called by the OnCollisionEnter in the ArmBoneCollision script. So this only happens once per enemy.

PS I’m a beginner and I’m trying to learn game development on my own so if anyone sees any bad practice or error in the script, feedback is welcome.

Cheers and thanks

Doesn’t seem like GetComponent<> was profiled as being the problem then. You should worry about functionality first especially if this is your first proper game.

What do you mean with “functionality”? I never found GetComponent to be a problem in my script, I just wanted to make sure to not get used to a bad practice from the beginning. Currently the scrips work pretty well (there’s only a small bug where enemies get killed when they hit the side of the hand, as they hit the collider anyway).

What I mean is premature optimisation is the bad practise :stuck_out_tongue:

Oh, ok :slight_smile: Yes you’re probably right, but I’m trying to not pick up bad practices from the beginning. I want to release this game on iOs one day so I’m trying to do stuff properly. Thanks again

it’s ok if it called from OnCollisionEnter and not from Update