Sunday, July 6, 2014

Cycling through targets on a tower defense game

Someone over on Unity Answers asked my to explain how to cycle through a list of targets for a tower defence game. The post responding became too long for a comment, so here it is.
The process in a nut shell
  1. Each tower has a trigger attached. OnTriggerEnter adds the enemy to a list. OnTriggerExit removes it from the list.
  2. Any null targets are removed from the list.
  3. The list is then scanned. The targets are scored, and the highest scoring target is returned
  4. The best scoring target is used in the towers aiming system.
If that's enough for you to implement then good on you, stop reading. If you want to see some code then read on. Note this code is untested and will probably have errors if copied straight into Unity. Also note the code is in C#. To be honest I'm not sure why Unity bothers with any other languages

The tower class

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

public class TargetFinder : MonoBehaviour  {

    // The main point of this class is to keep this target set
    // Hence we don't do anything unless something is actually looking for a target
    public Transform Target {
        get {
            return FindTarget();
        }
    }

    // This list holds all of the targets that are in range
    // I've chosen to populate the list using triggers
    // For a slower firing tower you could populate the list with an overlap sphere
    private List<transform> targets = new List<transform>();

    // The tower needs a trigger to determine if a target is in range
    // This code assumes the only colliders running around are enemies.
    //If not you will want to use tag checking or some other method to verify the collider is actually an enemy
    void OnTriggerEnter (Collider other) {
        targets.add(other.transform);
    }

    // The tower removes targets from the list that move out of range.
    void OnTriggerExit (Collider other){
        if (targets.contains(other.transform){
            targets.remove(other.transform);
        }
    }

    // This method is the work horse of the class
    // Be sure to understand this method, even if you don't get anything else
    private Transform FindTarget (){

        // First job is to see if there is anything in the list
        // No point running expensive code if nothing is in range
        if (targets.count <= 0){
            return null;
        }

        // Now we remove everything that is null from the list
        // Null transforms get on the list when a target is in range and destroyed
        // This is called a lambda function. Don't ask how it works, but it does.
        targets.RemoveAll(item => item == null);

        // And then we check again if there are any items left
                if (targets.count <= 0){
            return null;
        }

        // Now we check each remaining target in turn to see which one we should be aiming at
        // The code will check each possible target in turn to calculate a score
        // The score will be compared to the current best score
        // We will store the best target and score in bestTarget and bestScore
        Transform bestTarget;
        float bestScore = -999999;
        foreach (Transform currentTarget in targets){
            float currentScore = ScoreTarget(currentTarget);
            if (currentScore > bestScore){
                bestTarget = currentTarget;
                bestScore = currentScore;
            }
        }

        // Now our only job is to return the target we found
        return bestTarget;
    }

    // This method is used to score the targets
    // My implementation will just check the distance from the tower, closer targets get a higher score
    // However you can make this as complex as you like
    private float ScoreTarget (Transform target){
        float score = 0;
        score = 100 - (target.position - transform.position).sqrMagnitude;
        return score;
    }
}

No comments:

Post a Comment