Most economical way to Assign targets

Hello, im creating a game of the kind “Defend your base”, but i need lots of creatures at the same time on screen, that wasnt a problem, but since im using “findwithtags” on an array to get the closer enemy, when a lot of creatures are on screen things start to lag and get sluggy. Is there a better way to acomplish that?

Are you using FindWithTag in the update? In the Start() function and then iterating through the.array every update?

We need more info about how you are using it. Post the script? You may be able to use OverlapSpere or a spherecast from your position, but it will depend on what you actually need to do. Sorry I cam not.posting linls to docs, I am on my phone. The more info you can provide about the situation, the better.

Hello, sorry for late reply but i have been away from any computer the whole weekend.

The concept for the enemy AI is simple, they are spawned in the borders, also my base spawn robots for protection (in the center). The enemy must charge for the nearest robot, and when there is none of them around, attack directly the base. Im aiming for something starship troopers alike, so i want to have like a 50+ or more enemies on screen pitted against the robot defender and myself (fps style, everyone except the fps uses the same ai script with just a change on targeted tags).

Uploaded with ImageShack.us

For that i modified the code from the FPS tutorial, but somehow i get the feeling im doing something more times than needed:

var speed = 3.0;
var rotationSpeed = 5.0;
var shootRange = 15.0;
var attackRange = 30.0;
var shootAngle = 4.0;
var dontComeCloserRange = 5.0;
var delayShootTime = 0.35;
var pickNextWaypointDistance = 2.0;
var target : Transform;
var TargetFaction = "";

private var lastShot = -10.0;

// Make sure there is always a character controller
@script RequireComponent (CharacterController)

function Start () 
{
	Targeting ();
	Patrol();
}

function Targeting ()
{
// Auto setup player as target through tags
	if (target == null  GameObject.FindWithTag(TargetFaction))
		
	//Var for getting all tagged objects
	var gos : GameObject[];
	gos = GameObject.FindGameObjectsWithTag(TargetFaction);
	var closest : GameObject;
	var distance = Mathf.Infinity;
	var position = transform.position;
	
	// Iterate through them and find the closest one
	for (var go : GameObject in gos) {
		var diff = (go.transform.position - position);
		var curDistance = diff.sqrMagnitude;
	if (curDistance < distance) {
		closest = go;
		distance = curDistance;
		}
	}
	target = closest.transform;
}



function Patrol () {
	var curWayPoint = AutoWayPoint.FindClosest(transform.position);
	while (true) {
		var waypointPosition = curWayPoint.transform.position;
		// Are we close to a waypoint? -> pick the next one!
		if (Vector3.Distance(waypointPosition, transform.position) < pickNextWaypointDistance)
			curWayPoint = PickNextWaypoint (curWayPoint);

		// Attack the player and wait until
		// - player is killed
		// - player is out of sight
		Targeting ();
		if (CanSeeTarget ())
			yield StartCoroutine("AttackPlayer");
		
		// Move towards our target
		MoveTowards(waypointPosition);
		
		yield;
	}
}

function CanSeeTarget () : boolean {
	
	if ( target == null)
		Start();
	
	if (Vector3.Distance(transform.position, target.position) > attackRange)
		return false;
	
	var hit : RaycastHit;
	if (Physics.Linecast (transform.position, target.position, hit))
		return hit.transform == target;

	return false;
}

function Shoot () {
	// Start shoot animation
	animation.CrossFade("shoot", 0.3);

	// Wait until half the animation has played
	yield WaitForSeconds(delayShootTime);
	
	// Fire gun
	BroadcastMessage("Fire");
	
	// Wait for the rest of the animation to finish
	yield WaitForSeconds(animation["shoot"].length - delayShootTime);
}

function AttackPlayer () {
	var lastVisiblePlayerPosition = target.position;
	while (true) {
		if (CanSeeTarget ()) {
			// Target is dead - stop hunting
			if (target == null)
			return;
			
			
			// Target is too far away - give up	
			var distance = Vector3.Distance(transform.position, target.position);
			if (distance > shootRange * 3)
				return;
			
			lastVisiblePlayerPosition = target.position;
			if (distance > dontComeCloserRange)
				MoveTowards (lastVisiblePlayerPosition);
			else
				RotateTowards(lastVisiblePlayerPosition);

			var forward = transform.TransformDirection(Vector3.forward);
			var targetDirection = lastVisiblePlayerPosition - transform.position;
			targetDirection.y = 0;

			var angle = Vector3.Angle(targetDirection, forward);

			// Start shooting if close and play is in sight
			if (distance < shootRange  angle < shootAngle)
				yield StartCoroutine("Shoot");
		} else {
			yield StartCoroutine("SearchPlayer", lastVisiblePlayerPosition);
			// Player not visible anymore - stop attacking
			if (!CanSeeTarget ())
				return;
		}

		yield;
	}
}

function SearchPlayer (position : Vector3) {
	// Run towards the player but after 3 seconds timeout and go back to Patroling
	var timeout = 3.0;
	while (timeout > 0.0) {
		MoveTowards(position);

		// We found the player
		if (CanSeeTarget ())
			return;

		timeout -= Time.deltaTime;
		yield;
	}
}

function RotateTowards (position : Vector3) {
	SendMessage("SetSpeed", 0.0);
	
	var direction = position - transform.position;
	direction.y = 0;
	if (direction.magnitude < 0.1)
		return;
	
	// Rotate towards the target
	transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);
	transform.eulerAngles = Vector3(0, transform.eulerAngles.y, 0);
}

function MoveTowards (position : Vector3) {
	var direction = position - transform.position;
	direction.y = 0;
	if (direction.magnitude < 0.5) {
		SendMessage("SetSpeed", 0.0);
		return;
	}
	
	// Rotate towards the target
	transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);
	transform.eulerAngles = Vector3(0, transform.eulerAngles.y, 0);

	// Modify speed so we slow down when we are not facing the target
	var forward = transform.TransformDirection(Vector3.forward);
	var speedModifier = Vector3.Dot(forward, direction.normalized);
	speedModifier = Mathf.Clamp01(speedModifier);

	// Move the character
	direction = forward * speed * speedModifier;
	GetComponent (CharacterController).SimpleMove(direction);
	
	SendMessage("SetSpeed", speed * speedModifier, SendMessageOptions.DontRequireReceiver);
}

function PickNextWaypoint (currentWaypoint : AutoWayPoint) {
	// We want to find the waypoint where the character has to turn the least

	// The direction in which we are walking
	var forward = transform.TransformDirection(Vector3.forward);

	// The closer two vectors, the larger the dot product will be.
	var best = currentWaypoint;
	var bestDot = -10.0;
	for (var cur : AutoWayPoint in currentWaypoint.connected) {
		var direction = Vector3.Normalize(cur.transform.position - transform.position);
		var dot = Vector3.Dot(direction, forward);
		if (dot > bestDot  cur != currentWaypoint) {
			bestDot = dot;
			best = cur;
		}
	}
	
	return best;
}

Do you know of a better way to do it? or im in the rigth tracks already? Thanks

PD. I planned to make their spotting distance huge so they would “see” the whole map thus somehow overriding the patrol function (i know, ugly, thats why im asking for help)

Bump, please people i need help with his

Bump again, one more and i surrender

FindWithTag is horribly slow especially since you know you’re looking for a Unit of some sort instead of some generic game object like a Light or a Particle effect.

I suggest creating a “FactionManager” of sorts that has a list of spawned units maybe organized into faction-named hashtables. When enemies/players/units spawn you have them Report/Register their existence with the Manager.

Look up programming concepts such as Singletons and Managers.

Thanks, i will make that for sure, i wanted the ally units i can spawn from my base to have a rpg element (xp and levels) and i get the feeling thats the way to do it. is that rigth? i guess i can store them there, modify them on realtime, and if they die even respawn them using that stored data?

Also i have another doubt, can i use the above script to animate a model? (that one is for the fps tutorial robot) since each time i attach it to another model it stops moving or start flying upwards… i would appreciate if you can point me to some tutorial for dummies to using outside models correctly in unity (examples of it would be great), since it seens it maybe so simple nobody cares to explain it.

Thanks a lot for your help.

bump

Wish you were using C# so i could be more of a help. But basically say you had a component that kept a list and anything wtih that component on it was added to it and when destroyed removed from it. Heres what it would look like in C# since i know nothing about generics in JS

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

public class ListedElement<T> : MonoBehaviour where T : ListedElement<T> {
    static LinkedList<T> list = new LinkedList<T>();
    public static LinkedList<T> all { get { return list; }  }
    LinkedListNode<T> self;
    protected virtual void Awake() {
        self = list.Add(this as T);
    }
    protected virtual void OnDestroy() {
         list.Remove(self);self = null;
    }
}

Now what that will do for you is give you a base class which different components would signal different types so if you wanted to keep track of all enemies you would have a component like

using UnityEngine;

public class Enemy : ListedElement<Enemy> {
    protected override Awake() { 
        // do awake if you need to here
        base.Awake();// must call.
    }
    protected override OnDestroy() {
         // do any destroy if you need to here
         base.OnDestroy();
    }     
}

Then all enemies you would add the Enemy component onto their game objects and inside the code whenever you would need a list of all enemies you would do

foreach( Enemy enemy in Enemy.all ) {
    // tada
    GameObject go = enemy.gameObject;// get game object
}

One thing i’d suggest against is Destroying while in a foreach. but linked list should actually allow for it if your using foreach or for in.

If you absolutely must use tags you could modify the base class like so, not using generics

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

public class Tagged : MonoBehaviour {
    static readonly LinkedList<GameObject> emptyList = new LinkedList<GameObject>();
    static Dictionary<string, LinkedList<GameObject> > list = new Dictionary<string, LinkedList<GameObject> >();

    public LinkedList<GameObject> AllTaggedGameObjects( string tagName ) { 
        LinkedList<GameObject> getList;
        if( !list.TryGetValue( tagName, out getList ) ) getList = emptyList;
        return getList;
     }
 
     LinkedListNode<GameObject> self;

     protected virtual void Awake() {
         string tagName = gameObject.tag;
         var tagList = AllTaggedGameObjects(tagName);
         if( tagList == emptyList ) {
             tagList = new LinkedList<GameObject>();
             list.Add(tagName, tagList);
         }
         self = tagList.Add(gameObject);
     }
    protected virtual void OnDestroy() {
         self.List.Remove(self); self = null;
    }
}

and to use it

foreach( GameObject go in Tagged.AllTaggedGameObjects( "enemy" ) ) print(go.name);

[/code]
I use LinkedList because it should be safer and doesnt leave a big memory footprint, just small ones for each node. Instead of returning null i use emptyList so you do not need to check for null.

One thing i should mention also is you should not manually edit any of the lists. Awake and OnDestroy should be the only things modifying it, otherwise you’ll run into issues.

The reason people suggest not to call FindGameObjectsWithTag every frame is because it has to allocate a new array and might actually do a search if its not precached ( i do not know ). the allocation of the array will need to be GC’d normally which can make your GC spikes longer. This however should be very minimal on memory and iterate only a little longer than it would take to do for a array. Otherwise the only performance impacts ( which should be minimal ) are done at Awake and OnDestroy. The Tagged Component will not detect tag changes if you plan on changing gameObject.tag. imho tags are a last resort, they cause you to program in a way that adds complexity and structural rules that make odd dependencies that you must maintain. Also since were talking about tags, Never do gameObject.tag == “tag name here” always use gameObject.CompareTag as for some reason calling gameObject.tag actually copies the string.

This ends a long post of code that probably will not be used :slight_smile: Sorry i don’t know more JS

Hello, thanks for the insight, while its true i use js, i think i can learn something out of your code along with some good programming practices. If you could give me apointer in how to handle animation states of a code with the ai i posted it would be great. Unity certainly lacks a good tutorial about some basics aspects as that (at least a free one). Thanks again.

bump

bump

All that i posted is just a better way to cache a list of things you would need. So you would keep your code the same but just use the code to handle the lists.

As far as animation goes, i don’t have alot of experience using animation in unity. I would suggest making a new post titled with something about your problems with animation so someone who might know could help further instead of bumping this one with a subject of assigning targets.

In my tower defense demo, I have all of my defenses worry about their own targets… I have each one use Physics.OverlapShere with their range as the size when they have no target currently available. Then they can check for closest out of only the enemies that are within their range instead of all enemies in the level.

heres a piece of the code (c#)

                // returns all Colliders within sphere around this turret

                // myPos = turrets position      attackRange = turrets...attack range      enemyLayer = layer i have all enemies on
            Collider[] possibleTargets = Physics.OverlapSphere(myPos, attackRange, enemyLayer);

            foreach (Collider tar in possibleTargets)
            {
                // throw your existing distance and whatever checks in here to make sure its a viable target

                // if this current target makes it through checks set it to target like

                target = tar.gameObject;  // assuming your targets are gameobjects and predefined elsewhere
            }