[Closed] GetComponent alternative or get access to another class

Hi! I am new to unity and programming (and english)) and i’ve got a problem.
I have Hero object and Enemy object with scripts with same name attached. They move on gamemap and start combat, if they close enough.
So, i have

public Enemy enemyObject; string in Hero class and

public Hero enemyObject; string in Enemy class.

When i need to do some damage from hero to enemy, i check objects takin part in collision and use GetComponent:

if (hit.collider.tag=="Enemy") { enemyObject=hit.transform.GetComponent<Enemy>(); }

And it works good.


But what if i need have different types of enemies, or enemies to fight each other?
I can not declare variable of enemy type, because i dont know, what type of opponent i’ve just collised, so i cant use GetComponent.

VariableofWhatType???=hit.transform.GetComponent<Enemy>();

Is there a way to get another object variables?

First, an advice every person beginning in programming should know: avoid as much as you can string comparisons. This is a slow process that can turn into big performances issues and raise code complexity. Most of the time, they can be avoided easily.

Since I don’t know how you get your ‘hit’ object, it can be a Collision or a RaycastHit. If it is a Collision, it means you got it from OnCollisionEnter() and it’s mostly fine (for now) since the comparision is only tested when a collision happens on a single item. But be aware that collisions can happen pretty often in some situations. However, if it’s a RaycastHit you got from Physics.Raycast() it could have been inside an Update() loop (or any method called from within). Avoid that !

Since Update is called on every frame, this is a critical place where code should be optimized (according to the programer’s skill), and is the worst place to put a string comparison check.



This said, GetComponent() is a generic method. It means that ‘T’ (a type) can be of any type inside a constrained field, which for GetComponent is a MonoBehaviour (erroneously called “scripts”). It will return you a ‘T’ reference you can use and access all its public datas (fields, properties, methods). So, when you call for GetComponent(), you get back an ‘Enemy’ reference and can access all the variables you preceed with the “public” keyword within the class. In order to catch the reference, you need to assign it to a variable of the same type. Here is an example:

Enemy hitEnemy = hit.transform.GetComponent<Enemy>();
// 'hitEnemy' is our freshly acquired reference, now you can access any public data the regular way

// we suppose there is a public field 'life' in the Enemy class...
hitEnemy.life -= 1;

// ... and a public method 'Hit(int damages)'
hitEnemy.Hit(1);

When your ‘Hero’ and ‘Enemy’ classes are doing pretty much the same thing, you can use a more generic component, such as “Character” or “BattleCharacter” (these are example names) that will contain all the needed informations they can do.

In order to achieve that, you can use inheritance with components, which will let you store common informations for different derived classes.

Here is a simple example.



Character.cs

public class Character : MonoBehaviour
{
    // every character have life and can move
    public int life;
    public int speed;

    // every character can be Hit
    public void Hit(int amount)
    {
        life -= amount;

        if (life <= 0)
            Die();
    }

    // every character can Die, yet it can't be called from outside the class
    // note the use of 'virtual' wich let derived class override this method
    private virtual void Die()
    {
        Destroy (gameObject);
    }
}

Hero.cs

// note that we tell 'Hero' to derive from 'Character' (which derive from 'MonoBehaviour')
public class Hero : Character
{
    // only the Hero can have experience and level
    public int level;
    public int experience;

    // 'Hero', as any character can Die, but it will instead lead to a GameOver screen
    // note the use of the 'override' keyword which will bypass the
    // method 'Die' from base class 'Character' whenever it is called
    private override void Die()
    {
        GameOver();
    }

    // 'Hero' only can level up
    public void LevelUp()
    {
        level++;
        experience = 0;
    }
}

Enemy.cs

// as we've done for 'Hero', we derive 'Enemy' from 'Character'
public class Enemy : Character
{
    // only 'Enemy' characters drop money & experience
    public int givenGold;
    public int givenExperience;

    // as we did for 'Hero', we use the 'override' keyword, however,
    // this time we will call the base method with 'base.Die();'
    private override void Die()
    {
        // create a new GameObject at the current Enemy Transform's position
        GameObject loot = new GameObject("loot", transform.position)

        // just as GetComponent<T>(), AddComponent<T>() returns a 'T' reference
        // note 'Loot' is not defined in this example, you'll have to do it yourself
        Loot lootComponent = loot.AddComponent<Loot>();

        // we suppose lootComponent have public field: gold and experience
        lootComponent.gold = givenGold;
        lootComponent.experience = givenExperience;

        // will destroy the GameObject
        base.Die();
    }
}

You can attach ‘Hero’ or ‘Enemy’ to any GameObject in your scene (note that ‘Character’ can be attached to a GameObject too, but you mustn’t, not for this example please read footnote).

Now that you have distinct classes derived from the same base class, you can either detect collisions from within ‘Character’ or inside ‘Hero’ and ‘Enemy’. Thanks to polymorphism, you will be able to access any derived class from its base class. Of course, GetComponent() can be used to get back a base class.

Back to Character.cs

public class Character : MonoBehaviour
{
    // [...] (previous code...)

    void OnCollisionEnter (Collision collision)
    {
        // use of a more generic tag, so we don't care if it is a 'Hero' or 'Enemy'
        if (hit.collider.tag == "Character")
        {
            Character characterComponent = collision.collider.transform.GetComponent<Character>();

            // Hit() is accessible because it is defined within
            // 'Character' and is marked with the 'public' keyword
            characterComponent.Hit();

            // however, Die() is not accessible since it is
            // 'private' and 'Hero.LevelUp()' is not accessible
            // because it is not defined in 'Character'
        }
    }
}

Last, you can get the derived class from a reference to the base class. But, you first need to check if the reference is of the derived type you want.

still inside Character.cs

public class Character : MonoBehaviour
{
    // [...] (previous code...)

    void OnCollisionEnter (Collision collision)
    {
        if (hit.collider.tag == "Character")
        {
            // [...] (previous code...)

            IfHeroLevelUp(characterComponent);
        }
    }

    public void IfHeroLevelUp(Character characterComponent)
    {
        // check if the instance is of derived class is as simple as
        if (characterComponnent is Hero)
        {
            // but we still can't do 'characterComponent.LevelUp()'
            // we need to 'cast' our reference this way
            Hero heroComponent = (Hero) characterComponent;

            // now, we can access 'LevelUp' method !
            heroComponent.LevelUp();
        }
    }
}


Footnote: If there are multiple Component of the same base class on the same GameObject, GetComponent() (assuming we search for a Character component) will only return the first instance it finds (base or derived). In this case, you will need to use GetComponents() (note the ‘s’) which will then return an array of Character that you must loop through in order to get the desired component.

Hope this help !
Happy coding’