Keeping Track of Targets in a Trigger

Unity provides the OnTriggerEnter() and OnTriggerExit() events that fire whenever a collider enters or exits the designated trigger, and also an OnTriggerStay() that fires in every frame in which the collider is within the trigger. However, it doesn’t provide a one-off “What objects are in this trigger” function, which is what I needed earlier in order to create a “lock-on” targetting system (Note that you can emulate this functionality somewhat with a Physics.Raycast / Physics.SphereCast, but you’d have to call it every frame to keep the list updated)

image

I could have sworn that OnTriggerExit() was buggy when my code wasn’t behaving the way I thought it should have, but it turns out my logic was faulty. For my own reference, here’s a description of the problem and the solution:

The “lock-on” target system is as follows: if the player presses a button while an enemy is within the target trigger zone, they lock-on to that enemy (by setting their own transform.parent to the transform of the enemy). Seemed simple enough, and my first thought was simply to keep track of the object currently in the target trigger like this:

GameObject enemyInTarget;

OnTriggerEnter(Collider other) {
  if(other.CompareTag(“Enemy”){
    enemyInTarget = other.gameObject;
  }
}

OnTriggerExit(Collider other){
  if(other.CompareTag(“Enemy”){
    enemyInTarget = null;
  }
}

void Update() {
  if(Input.GetButton(“LockOn”)){
    // Lock on by parenting to the enemy transform
    transform.parent = enemyInTarget.transform;
  }
}

 

The problem with this is that I have many enemies in my scene. So it’s perfectly possible after the player has already locked onto one enemy for another enemy to enter the trigger. The code above would immediately change the target to whatever the latest enemy to enter the trigger was (i.e. the one that most recently caused OnTriggerEnter to fire), causing it to jump around which wasn’t what I wanted. This is a pretty simple fix by adding an extra enemyInTarget == null condition to the OnTriggerEnter function to make sure we haven’t already got a target:

GameObject enemyInTarget;

OnTriggerEnter(Collider other) {
  if(other.CompareTag(“Enemy”) && enemyInTarget == null ){
    enemyInTarget = other.gameObject;
  }
}

OnTriggerExit(Collider other){
  if(other.CompareTag(“Enemy”)){
    enemyInTarget = null;
  }
}

void Update() {
  if(Input.GetButton(“LockOn”)){
    // Lock on by parenting to the enemy transform
    transform.parent = enemyInTarget.transform;
  }
}

 

But there’s still a problem. Although when a second enemy enters the trigger it will no longer cause the target to lock on to it, when it exits it will still clear the target. So enemies simply “passing through” the trigger caused the player to lose their target focus. Another fix – this time to the OnTriggerExit:

GameObject enemyInTarget;

OnTriggerEnter(Collider other) {
  if(other.CompareTag(“Enemy”) && enemyInTarget == null ){
    enemyInTarget = other.gameObject;
  }
}

OnTriggerExit(Collider other){
  if(other.CompareTag(“Enemy”) && other.gameObject == enemyInTarget){
    enemyInTarget = null;
  }
}

void Update() {
  if(Input.GetButton(“LockOn”)){
    // Lock on by parenting to the enemy transform
    transform.parent = enemyInTarget.transform;
  }
}

 

Getting better, but still not right. This broke in the following scenario (although this is the bug in my logic that took me a while to realise…)

  • Scene start. enemyInTarget = null; transform.parent = null;
  • EnemyA enters the trigger. enemyInTarget = EnemyA; transform.parent = null;
  • Player presses lock on button. enemyInTarget = EnemyA; transform.parent = EnemyA;
  • EnemyB also enters the trigger. Code fixes above mean still enemyInTarget = EnemyA; transform.parent = EnemyA;
  • Player stops locking onto EnemyA. enemyInTarget = EnemyA; transform.parent = null;
  • EnemyA leaves the trigger. enemyInTarget = null; transform.parent = null;

At the end of this series of events, the game state is identical to that at the start, except that EnemyB is already in the trigger, and pressing the “LockOn” button will have no effect because there is no record of that fact (since, at the point they entered the trigger, we were locking onto something else).

Sigh. I did consider changing the code logic completely at this point to ditch triggers and instead use a RayCast hit test to find the best target at the point the player presses the lock-on button. However, I also want to be able to highlight enemies in the target, and to do this using the RayCast method would require an expensive RayCast every frame (The PhysX Trigger calls are pretty optimised in contrast).

And then the strikingly simple solution hit me – I simply needed to maintain a local array of targets as they enter or leave the trigger and, at the point the player attempts to lock-on, choose the item at the top of the list (which, in the case of multiple enemies, is the one that has been there longest). Note that I’ve also changed the LockOn button to toggle between locked/unlocked to a parent target.

// Set up a list to keep track of targets
public List targets = new List();

// If a new enemy enters the trigger, add it to the list of targets
void OnTriggerEnter(Collider other){
    if (other.CompareTag("Enemy")) {
        GameObject go = other.gameObject;
        if(!targets.Contains(go)){
            targets.Add(go);
        }
    }
}

// When an enemy exits the trigger, remove it from the list
void OnTriggerExit(Collider other){
    if (other.CompareTag("Enemy")) {
      GameObject go = other.gameObject;
      targets.Remove(go);
    }
}

// When the player attempts to lock on, choose the top target from the list
if(Input.GetButtonDown("LockOn”)){
    if(targets.Count > 0 && transform.parent == null){
        transform.parent = targets[0].transform;
    }
    else {
        transform.parent = null;
    }
}

 

This solution is somewhat esoteric, but I wanted to document it for my own personal reference; if you want a more general reusable class to keep track of gameobjects in a Unity trigger, I highly recommend you check out http://technology.blurst.com/unity-physics-trigger-collider-examples/

Advertisement
This entry was posted in Game Dev and tagged , . Bookmark the permalink.

1 Response to Keeping Track of Targets in a Trigger

  1. Jaysta says:

    Hi, been looking for something like this as it’s what I need in my RTS game.

    Only problem though, I use a pooling system, so my GO get despawned not destroyed, and there for the OnTriggerExit function does not get called.

    Do you know of a way to implement this feature?

    Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s