Choosing from multiple Characters

I'm adding a V.S. mode to one of my games and there will be up to four characters on the field. The player will automatically lock onto an enemy when the character is spawned. All opponents will have

My problem is this:

1.) How do I allow the player to choose from the array of enemy characters? This allow the player to choose which enemy to Target with a key press.

2.) How to allow the enemy to choose who to target by random? Since they will all have the same tag, and I want them to fight against themselves and the player, how do I set it up so that they will target any active character other than themselves?

Use a List with targets and add all game objects with tags Enemy and Player to that list. Remove yourself from list and you have all other targets but yourself. To select a random target, select one with Random.Range. To select next target, increment the index and make it wrap around bounds. See the attached code, which should work for both player and AI if the input code is put elsewhere (or every character would change target at the same time). If you will be removing game objects, you need to make sure you remove them from the list as well or you could be targeting null all of a sudden.

Good luck!

import System.Collections.Generic;

var target : GameObject;
private var targetIdx : int;
private var targets : List.<GameObject>;

function Start()
{
    // 2.) How to allow the enemy to choose who to target by random? 
    //     Since they will all have the same tag, and I want them to 
    //     fight against themselves and the player, how do I set it up 
    //     so that they will target any active character other than 
    //     themselves?

    // A.) Create a list of all (other) characters and select one at 
    //     random. Maintain the index of the current character so
    //     you easily can cycle to the next (see below). Since we will 
    //     have up to 4 targets, we create the list with capacity for
    //     4 targets.

    targets = new List.<GameObject>(4);
    FindTargets();
    TargetRandom();
}

// ---------------------------
// UPDATED SINCE ORIGINAL CODE
// ---------------------------

// When an object is disabled (Destroyed), we want to notify all the 
// other targets about this so they can remove that target from their 
// list.
function OnDisable()
{    
    for (var target in targets)
        target.SendMessage("OnTargetExpired", gameObject);
}

// ---------------------------
// UPDATED SINCE ORIGINAL CODE
// ---------------------------

// This is called when one of our targets have expired. At this point
// we should remove that target from our list and select a random 
// target in case our current target was the one we removed. 
function OnTargetExpired(tgt : GameObject)
{    
    targets.Remove(tgt);
    if (target == tgt)
        TargetRandom();
}

function Update() 
{
    // 1.) How do I allow the player to choose from the array of enemy 
    //     characters? This allow the player to choose which enemy to 
    //     Target with a key press.

    // A.) Cycle through targets with tab key, just like you do in many
    //     popular games such as WoW. Note that you should probably do
    //     this check in your player controller code or you would end up
    //     with each targetter changing target. (You can then place this
    //     component on all combatants).

    if (Input.GetKeyDown(KeyCode.Tab))
        TargetNext();
}

// ---------------------------
// UPDATED SINCE ORIGINAL CODE
// ---------------------------

// Sets target randomly.
function TargetRandom()
{
    if (targets.Count == 0)
    {
        targetIdx = -1;
        target = null;
        return;
    }
    targetIdx = Random.Range(0, targets.Count);
    target = targets[targetIdx];
}

// ---------------------------
// UPDATED SINCE ORIGINAL CODE
// ---------------------------

// Sets next target, in a looping manner. 
function TargetNext()
{
    if (targets.Count == 0)
    {
        targetIdx = -1;
        target = null;
        return;
    }
    targetIdx = (targetIdx + 1) % targets.Count;
    target = targets[targetIdx];
}

// Populates targets array.
function FindTargets()
{   
    // Find all targets, which are objects tagged Enemy or Player, but
    // is not self.
    targets.Clear();
    targets.AddRange(GameObject.FindGameObjectsWithTag("Enemy"));
    targets.AddRange(GameObject.FindGameObjectsWithTag("Player"));
    targets.Remove(gameObject);
}

Hi, I post another answer since I rewrote the whole system to make it easier to manage the answers and since this solution uses a bit different architecture.

Basically it works like this:

  • On every Player and Enemy, add the Targetter script.
    • It accepts two messages "TargetNext" and "TargetRandom"
    • It sends two messages "SetTarget" and "ClearTarget".
  • The Player script should sit on the same gameObject as the Targetter script for the player.
  • Any AI script (not included) should sit on the same gameObject as the Targetter script for the enemy.
  • Implement function SetTarget(target : GameObject) and function ClearTarget() on Player and AI.
  • Send message "TargetNext" or "TargetRandom" when you want to get a new target.
  • New targets are automatically sent when a target is removed.

I have tested this code myself and it works pretty nicely. The only time you will have a null target in your player script is if there is no other targets. If you spawn new targets they will automatically get included in every other targetters list of target - it's fully automatical. The only thing you need to do is make sure that every Enemy and every Player has a targetter. Study Player.js about how you maintain the target. See the two functions SetTarget and ClearTarget, and the call to TargetNext to cycle to the next target.

  • Due to a bug in SendMessage, I was unable to make it simpler such that SetTarget would had accepted null. It's a limitation in the current version of Unity3D.

I hope it works out as you wanted! It was a fun problem and I learnt a lot myself! Without further ado: The Source.

Player.js

var target : GameObject;

function Update() {
    if (Input.GetKeyDown(KeyCode.Tab))
        SendMessage("TargetNext");
}

function LateUpdate() {
    if (target)
        Debug.DrawLine(transform.position, target.transform.position);
}

function SetTarget(newTarget : GameObject) {
    target = newTarget;
}

function ClearTarget() {
    target = null;
}

Targetter.js

import System.Collections.Generic;

private var targetIdx : int;
private var targets : List.<GameObject> = new List.<GameObject>(4);

TargetRandom();

function OnEnable() {       
    AddTargets(GameObject.FindGameObjectsWithTag("Enemy"));
    AddTargets(GameObject.FindGameObjectsWithTag("Player"));
    SendToTargets("AddTarget");
}        

function OnDisable() {
    SendToTargets("RemoveTarget");  
    targetIdx = 0;  
    targets.Clear();
}

function SendToTargets(message : String) {
    for (var target in targets)
        target.SendMessage(message, gameObject);
}

function AddTargets(targets : GameObject[]) {
    for (var target in targets)
        AddTarget(target);        
}

function AddTarget(target : GameObject) {
    if (target != gameObject && !targets.Contains(target)) {               
        targets.Add(target);
        if (targetIdx == -1)
            TargetNext();
    }
}

function RemoveTarget(target : GameObject) {    
    if (targetIdx < 0 || targetIdx >= targets.Count || targets[targetIdx] == target)
        targetIdx = -1;

    targets.Remove(target);

    if (targetIdx == -1)
        TargetRandom();
}

function TargetRandom() {
    if (targets.Count == 0) {
        targetIdx = -1;
        SendSetTarget(null);
    } else {
        targetIdx = Random.Range(0, targets.Count);
        SendSetTarget(targets[targetIdx]);
    }
}

function TargetNext() {
    if (targets.Count == 0) {
        targetIdx = -1;
        SendSetTarget(null);
    } else {
        targetIdx = (targetIdx + 1) % targets.Count;
        SendSetTarget(targets[targetIdx]);
    }
}

// Due to a bug in SendMessage, we call ClearTarget if target is null.         
function SendSetTarget(target : GameObject) {
    if (target)
        SendMessage("SetTarget", target, SendMessageOptions.DontRequireReceiver);
    else
        SendMessage("ClearTarget", SendMessageOptions.DontRequireReceiver);
}

As far as selecting an enemy target, here is a script I had a little help making, It returns the closet enemy and sets it as target, you can even have a little indicator on the selected enemy.

    //================================================
//Lock On Script
//================================================
private var current : int = 0; 
private var locked : boolean = false; 
var playerController : ThirdPersonController ;
var enemyLocations : GameObject[];
var closest : GameObject;
var activeIcon : Transform; //Current targeted enemy indicator

function Update() 
{       
    var playerController : ThirdPersonController = GetComponent(ThirdPersonController);

    if (closest != null && locked)
{
activeIcon.active = true;
activeIcon.transform.position.y = (closest.transform.position.y+1);
activeIcon.transform.position.x = (closest.transform.position.x);
activeIcon.transform.position.z = (closest.transform.position.z);
}
else
{       
activeIcon.active = false;
}

    if(Input.GetButtonDown("Lock")) 
    {       
    //Looks for the closest enemy
    FindClosestEnemy();
    locked = !locked;
    }

    if(locked) 
    {
        //If there aren't any enemies (or the player killed the last one targeted) make sure that the lock is false
        if (!closest)
        {       
            activeIcon.active = false;
            locked = false;
            closest = null;
        }

        if (playerController.isAttacking)
        transform.LookAt(Vector3(closest.transform.position.x, transform.position.y, closest.transform.position.z));
    }
}

function FindClosestEnemy () : GameObject 
{
    // Find all game objects with tag Enemy
    enemyLocations = GameObject.FindGameObjectsWithTag("Enemy"); 
    //var closest : GameObject; 
    var distance = Mathf.Infinity; 
    var position = transform.position; 
    // Iterate through them and find the closest one
    for (var go : GameObject in enemyLocations) 
        { 
            var diff = (go.transform.position - position);
            var curDistance = diff.sqrMagnitude; 

            if (curDistance < distance) 
            { 
                closest = go; 
                distance = curDistance; 
            } 
        } 
    return closest; 
}