- Home /
List/Array Not Updating
Hi guys,
I've been trying to hash together a script to find the nearest target objects with tag 'Target' in proximity to a player so I can use them to target the nearest target, next nearest target etc.
I attempted to do this by instantiated an array to hold all the targets in the level, then applying conditions to them and filling a List based on these conditions, which are then converted into an a different, local array.
However, despite the function being called from Update, the function only seems to run once-i.e. when the player moves out of range, the target_local array is not updated. I get the feeling this is because I'm not familiar with lists and therefore have used something wrongly.
Cheers,
Popup Pirate
// This is an excerpt from a camera script so other functions (i.e. Start()) are present that are not used in my current problem.
using UnityEngine;
using System.Collections;
public class Camera_Control : MonoBehaviour {
public GameObject player;
public float targeting_distance=500f;
public GameObject[] targets;
public GameObject[] targets_local;
private System.Collections.Generic.List<GameObject> target_list = new System.Collections.Generic.List<GameObject>();
void Start() {
}
void Update(){
FindTargets ();
}
void LateUpdate() {
}
void FindTargets(){
targets= GameObject.FindGameObjectsWithTag("Target");
for (int i=0; i<targets.Length; i++){
float distance=Vector3.Distance(player.transform.position, targets[i].gameObject.transform.position);
if (distance<targeting_distance && targets[i].gameObject.tag=="Target" && targets[i].gameObject.name!=player.gameObject.name&& target_list.Contains(targets[i].gameObject)==false){
target_list.Add(targets[i].gameObject);
}
if (distance>targeting_distance && targets[i].gameObject.name!=player.gameObject.name){
target_list.Remove(targets[i].gameObject);
}
targets_local= target_list.ToArray();
}
}
I have just done some quick research in my lunchbreak to find out how to use lists. It seems that I can use the following code ins$$anonymous$$d. I shall give it a try later:
void FindTargets(){
targets= GameObject.FindGameObjectsWithTag("Target");
for (int i=0; i<targets.Length; i++){
float distance=Vector3.Distance (player.transform.position, targets[i].gameObject.transform.position);
if (distance<targeting_distance && targets[i].gameObject.tag=="Target" && targets[i].gameObject.name!=player.gameObject.name&& target_list.Contains(targets[i].gameObject)==false){
target_list[i]=targets[i].gameObject;
targets_local= target_list.ToArray();
}
if (distance>targeting_distance && targets[i].gameObject.name!=player.gameObject.name{
``target_list.RemoveAt(i);
targets_local= target_list.ToArray();
}
}
Answer by fafase · Oct 14, 2014 at 12:02 PM
You could use this thread:
m_targetList.Sort(delegate(GameObject c1, GameObject c2){
return Vector3.Distance(this.transform.position, c1.transform.position).CompareTo
((Vector3.Distance(this.transform.position, c2.transform.position)));
});
this code above will order your list so the first one is the closest.
I would call the ordering at some frequency like every 0.5f second so that it does not affect too much. Even once per second would be enough.
Then when you iterate through the list to find the closest ones, you can stop the iteration as soon as one is too far since any other after will automatically be too far.
Now for the method:
GameObject [] FindTargets(){
List<GameObject> list = new List<GameObject>();
GameObject [] targets= GameObject.FindGameObjectsWithTag("Target");
foreach(GameObject obj in targets){
float distance=Vector3.Distance(player.transform.position, obj.transform.position);
if (distance < targeting_distance && target_list.Contains(obj)==false){
list.Add(obj);
}
}
return list.ToArray();
}
So now you do not need the List as global, you create a new list when needed and return it as array. So your Update becomes:
void Update()
{
target_local = FindTargets();
}
It is not necessary to check again for tag since the objects in the collection are sure to be with it. If your player is not tag as target then no need to check for match. if so add this line at the beginning of the foreach loop:
if(obj == player)
{
continue;
}
Using a new list remove the checking if the old one contains and the removal as well.
One thing I would recommend is that the target object should probably be added to a list when they are created so that you do not need to fetch them each time.
This is a truly excellent answer, fafase! I'll try and implement this tonight and see how it works in practice, but just glancing over the code it seems really good- you even answered my array sorting issue! $$anonymous$$uch thanks.
Answer by popuppirate · Oct 14, 2014 at 07:35 PM
Here is the complete code that returns an array of the players nearest targets within a set targeting range, with target_local[0] being the closest gameobject within range and target[n] being the nth largest gameobject in range:
using UnityEngine;
using System.Collections;
public class Target_Control : MonoBehaviour {
public float targeting_distance=50f;
public bool targeting_on;
public GameObject[] targets_local;
private GameObject temp;
private GameObject[] targets;
void Start () {
}
// Update is called once per frame
void Update () {
targets_local = FindTargets ();
Sort ();
}
GameObject [] FindTargets(){
GameObject [] targets= GameObject.FindGameObjectsWithTag("Target");
System.Collections.Generic.List<GameObject> target_list = new System.Collections.Generic.List<GameObject>();
for(int i=0; i<targets.Length; i++){
float distance=Vector3.Distance(transform.position, targets[i].transform.position);
if(targets[i].gameObject.name==this.gameObject.name){
continue;
}
if (distance <= targeting_distance && target_list.Contains(targets[i].gameObject)==false){
target_list.Add(targets[i].gameObject);
}
if (distance > targeting_distance && target_list.Contains(targets[i].gameObject)==true){
target_list.Remove (targets[i].gameObject);
}
}
return target_list.ToArray();
}
void Sort(){
for (int i=0; i<targets_local.Length; i++) {
for (int j=0; j<targets_local.Length; j++) {
if(Vector3.Distance (transform.position, targets_local[i].transform.position)<Vector3.Distance (transform.position, targets_local[j].transform.position)){
temp=targets_local[i];
targets_local[i]=targets_local[j];
targets_local[j]=temp;
}
}
}
}
You should change this:
if(targets[i].gameObject.name==this.gameObject.name){
continue;
}
to that:
if(targets[i]==this.gameObject){
continue;
}
targets is of type GameObject so you can compare them and it is faster than string.
if (distance <= targeting_distance && target_list.Contains(targets[i].gameObject)==false){
target_list.Add(targets[i]);
}
Why are you checking if the list contains the object, you are using a clear list to which you add items, they cannot be there already Also, no need to use target[i].gameObject since target[i] is the same.
if (distance > targeting_distance && target_list.Contains(targets[i].gameObject)==true){
target_list.Remove (targets[i].gameObject);
}
Why do you need to remove items since the list is a new one each time. Add a print in there and you will see it is never called.
private GameObject[] targets;
this becomes useless since you declare a new one in the method that will hide this global one
GameObject [] FindTargets(){
GameObject [] targets= GameObject.FindGameObjectsWithTag("Target");
System.Collections.Generic.List<GameObject> target_list = new System.Collections.Generic.List<GameObject>();
for(int i=0; i<targets.Length; i++){
float distance=Vector3.Distance(transform.position, targets[i].transform.position);
if(targets[i] == this.gameObject){
continue;
}
if (distance <= targeting_distance){
target_list.Add(targets[i]);
}
}
return target_list.ToArray();
}
Cheers for all your insightful help, fafase! You really have gone all out to help me! Your improvements work in exactly the same way as my answer and is much nicer than $$anonymous$$e to look at.
To clarify why I did what I did on $$anonymous$$e:
had the Target_list.Contains condition in there from yesterday because I wasn't actually sure at the time whether the list was being updated per frame or recreated per frame as per my misunderstandings of lists- better safe than sorry when building an idea, if you get what I mean. Clearly it is the latter and so right you are.
also didn't know that the object was easier to call than it's name, shall remember this and edit any existing code I can where I have used this.
-The private variable slipped through the net from when I was testing I think at one point I was going to have one 'target' array feed into a 'target_local' array without any lists- this is why it was there.
Once again, a million thanks for your time!
Popuppirate
One last word:
"-I also didn't know that the object was easier to call than it's name, shall remember this and edit any existing code I can where I have used this."
$$anonymous$$ore than easier it is also faster, comparing a string first is prone to error, you may misspell the string and for some reason it never happens and you have no error. One approach is to use a class to wrap your string:
public class StringUtility
{
public static string Player = "Player";
}
and you use as :
if(aString == StringUtility.Player){}
But comparing string is always slower than comparing object, a string requires to check for each character until the end to see if fully matching, object comparison is a mere 4 bytes comparison (32-bits build).
Your answer
Follow this Question
Related Questions
What do you think of this? List, Vector 3 and Update 1 Answer
Updating Array inside a List 1 Answer
A node in a childnode? 1 Answer
Storing functions in a list/array? 1 Answer
How to make a List or Array of functions with type Void 4 Answers