- Home /
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?
Answer by Statement · Mar 29, 2011 at 08:37 PM
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); }
Heck I even created yet another targetter but I can't be arsed to post three answers to one question so I'll paste it to pastebin: http://pastebin.com/QFCGrWfx
I realized it's quite useless to have ClearTarget since in Unity3D gameobjects are automatically nulled anyway! And that code reduce some complexity, just calling Find each time you try to set a new target. Code is so much cleaner and easier to use! On top of that, it selects next target based on distance, so it cycles through the nearest first and furthest last.
Sweet! That solved it! Now all that's left is the character selection menu and the constant skipping now, and my game will be one step closer!
Answer by Statement · Mar 25, 2011 at 01:33 PM
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); }
It works, but once an enemy is destroyed they are unable to select a new enemy. The AI will just stand there, while the player, who's movement and lock on function are centered around the AI's location, is unable to function as well.
Then you could call FindTargets again if you're spawning more enemies. 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. The code isn't foolproof, you need to plug in the code in the skeleton I provided so it matches your games expectations. For example you could prune all nulls (destroyed) out in update and reselect a target if you happened to have a null target.
So: function Update(){ if(Target == null){ //Prune Null // Find Targets(); }
}
How do I remove a Null from an array or list?
I guess you could iterate through every enemy in OnDisable (upon dying) and send message to signal to remove that opponent. This should avoid nulls to begin with.
Answer by SrBilyon · Mar 25, 2011 at 05:47 AM
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;
}
That conflicts with the scripts I have and rather than searching for closest, I'm going for the order listed in an array.
Your answer
Follow this Question
Related Questions
Character development with abilities 1 Answer
Array Selection(?) 1 Answer
A simple character selection screen,Help with characters selection screen 1 Answer
Using a selected character 1 Answer
Play with the selected character 0 Answers