(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).
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.
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();
}
}
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
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.
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).
Oh, ok 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