How to organize the code to be reused

Asked

Viewed 489 times

12

Consider the following scenario:

inserir a descrição da imagem aqui

As you can see, Prefab (red) consists of 3 elements:

  1. The sprite (which is the ground, the brown squares and the green tube)
  2. The enemy (pink) which is another Prefab
  3. The Empty GameObject (blue)

Just to understand the context, the game works like this: every "x" seconds I add a new Prefab in the scenario, thus producing an infinite scenario (I have several Prefab’s and the image above contains only 1 for example). The player goes up and down and can not touch the enemy.

The part of moving the Prefab, of colliding with the enemy and die, etc... is done and working and that’s not the problem, so I did not enter codes here.

What I want is when the player (orange) collide with the GameObject (blue) the Enemy (pink) go up and down in looping.

To understand why each element within Prefab: as I have several prefabs (red) of different widths and enemies (pink) in different positions, Empty GameObject (blue) only serves to indicate to the enemy that he must start his animation when the player collide with it, so I have more flexibility to create new prefabs.

There are 2 types of animations for each enemy: the one I made through the tab Animation and is specific to each type of enemy and the shared animation, for example, go a little up and a little down in looping, or go left and right in looping, etc...

The second animation can be used on several enemies and I intend to do by code so that the boxcollider also moves, which is not possible using the Animation (as far as I know).

My question is how to implement what I described above? How to organize the code to be reused?

I can implement the relationship between two objects when one collides with the other, but we are talking about a collision that will have an effect on a third object.

2 answers

14


I assume you’re using the GameObject empty only as "activator" for animation of the enemy for performance reasons (that is, to avoid that it already comes animated since the instantiation of Prefab, even with the player’s avatar still far away from the enemy). This is a good thing to do, but still it would be interesting to put this control in the scope of the enemy himself.

IS more or less close to what preaches the agent orientation: Each "intelligent" entity in your game has its own goals, and so all the programming of these goals makes more sense in the agent’s scope. Think like this: your enemy starts or stops being excited because he aims to attack the player’s avatar. Then, in fantasy game, he is not moving or stopping for performance issues (although in the real world this is extremely useful!), but only because he is able to detect by itself the presence of the player.

So I’d do it this way:

  • I would not use an empty Gameobject as in your example to detect the player, but rather use a Trigger (the Colisor is more focused on physics) in the whole area of the Prefab (red). This Trigger capture the two input events (onTriggerEnter and onTriggerExit) to indicate to all existing enemies in Prefab that the player has entered and exited this threat area (Threat area).

  • The enemies' script/class would be inherited from a script/base class so that you could invoke an abstract method from the collision events of the Trigger. Thus, each enemy could implement his own behavior (i.e., each could do as he saw fit with the information that the enemy entered and/or left the threat area).

Sample Code

Basic class of the Enemy:

using UnityEngine;
using System.Collections;

public class EnemyBase : MonoBehaviour {

    private bool playerNear;

    public bool isPlayerNear { 
        get { return playerNear; }
        protected set { playerNear = value; }
    }

    public void playerEnteredThreat ()
    {
        isPlayerNear = true;
    }

    public void playerExitedThreat ()
    {
        isPlayerNear = false;
    }
}

Specific class of any enemy inherited from the basic class:

using UnityEngine;
using System.Collections;

public class EnemySpecific : EnemyBase {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

        if (isPlayerNear) {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            Debug.Log ("Player esta proximo! Distancia: " + Vector3.Distance(transform.position, player.transform.position));
        }

    }
}

Example of use (detection of collisions and message passage to the abstract class of the enemy - this is the code that goes on Trigger; I only referenced an enemy in it, but it still gives in your case):

using UnityEngine;
using System.Collections;

public class Teste : MonoBehaviour {

    public GameObject enemy;

    void OnTriggerEnter(Collider oOther) {
        if (oOther.tag == "Player")
            enemy.GetComponent<EnemyBase> ().playerEnteredThreat ();
    } 

    void OnTriggerExit(Collider oOther) {
        if (oOther.tag == "Player")
            enemy.GetComponent<EnemyBase> ().playerExitedThreat ();
    }
}

Explanation of the Sample Code

  1. The enemy class inherits from a base class, which contains all the code to receive input/output indications from the Competitor/Rigger (use whichever you prefer, but if you have no intention of physical results, Rigger is more suitable). These values are reused by the child class (via a property I created called isPlayerNear, but you could overwrite the notification methods if it so wished.

  2. The script that is in fact added to the object of the enemy is the EnemySpecific, but note how in Trigger’s code he does the following:

enemy.GetComponent<EnemyBase> ().playerEnteredThreat ();

That is, it references whatever is in the enemy through the abstract class. How all enemies will inherit from this class (and consequently have in its interface the method playerEnteredThreat, Whether it’s the original of the base class or a rewritten in the daughter class), it’s guaranteed to work. And allows Trigger to give generic warning, and every enemy implementation does with it what it wants.

Concluding

This approach has the advantage that it facilitates the creation of enemies with distinct behaviors, regardless of player movement detection. Today, for example, you can only detect the player’s approach. But imagine that the game is almost ready and then the game designer (the game designer) comes to you and asks for a "small change": he wants that if the player can get rid of the enemy and walk away, the enemy activates a single long-range shooter (I don’t know, spit out an explosive seed, for example). In that case, you’ll need also detect the player’s departure, and with the current architecture this will be much more difficult (maybe you will have to include a new GameObject empty at the end, and more duplicated code to do the same as it already does).

Another advantage of knowing that the player’s avatar is in the threat area is that the enemy’s "intelligence" can change their attitude from the point of view of the observation. It is very easy for every enemy in the game to have a reference to the player’s object and calculate the Euclidean distance from themselves to it. The problem is that it has a very large computational cost. Imagine each enemy in a set of 100 doing these calculations (which involve square root, a very costly operation) to each frame. Using the indication of the presence or absence of the player in the threat area, this type of calculation can be performed with good performance only by enemies who are in the same threat area as the player. It may sound silly, but it facilitates the future implementation of more complex behaviors with better performance. For example, an enemy could intentionally expect the player to be too close (very short distance) to activate their surprise animation.

  • The goal of the blue object is just what you said, animate only when the enemy actually enters the scene and also to catch the player by surprise, for example, in the Prefab I showed above, the "flower" comes out from behind the barrel when the player hits the blue object, however there is a third that I did not mention (I forgot), which is to run the move only if the player goes through a specific area and so only have to reposition/resize the blue gameobject to where I want inside the Prefab.

  • Well, then the "blue object" is your Threat area. Still, it may be advantageous for you to capture both events (in and out), making it wider - just to facilitate possible future needs. Anyway, it makes a lot of sense for you to communicate these collision events from him directly to the enemy, and for him to turn around with what he has to do about it. As I said in your other question, using a generic class to center these behaviors will only make your life difficult.

  • Yes, I have come to understand that using a class for everything would not be a good solution... I will apply what was said here, including the 2 triggers mentioned! However how do I communicate to the enemy that the player has entered the Threat area? Also applying the solution that is in the Lollipop response?

  • About "how do I communicate to the enemy [...]", I edited the answer to add some examples. I hope it helps. Good luck. :)

  • 1

    Thank you very much for your help Luiz!

0

The concept:

Trigger in a child object to give OnTriggerEnter in a parent object.

The way:

Add a script to Object Azul to give a script reference to the Object Rosa.

The practice:

Write 3 methods on Object Rosa(OnCollisionEnter2DChild, OnCollisionStay2DChild, OnCollisionExit2DChild) and call the respective methods (OnCollisionEnter2D, OnCollisionStay2D, OnCollisionExit2D) in the object Object Azul, passing the collision object.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.