Something that has been bothering me for a long time is why do the following lines of code have the same results.
Code 1:
Transform[] childs = gameObject.GetComponentsInChildren<Transform>();
foreach(Transform child in childs) { Debug.Log(child.name); }
Code 2:
foreach(Transform child in gameObject.transform) { Debug.Log(child.name); }
This is actuallly a pseudo-code, I didn't really test it but should be enough to explain.
My question is, what's happening on Code 2 ? Is gameObject.transform actually an array of Transform ? Why doesn't Code 2 print the name of the parent gameObject ?
Maybe this is something very simple and obvious I'm just overlooking but I can't make it out right now.
Transform implements the IEnumerable interface. This interface is what allows the use of the foreach keyword.
public partial class Transform : Component, IEnumerable
The IEnumerable interface requires implementation of the GetEnumerator() method. The enumerator is responsible for keeping track of the position in the underlying collection and indicating if there are more items to be iterated over.
This is implemented in Transform below
public IEnumerator GetEnumerator()
{
return new Transform.Enumerator(this);
}
private class Enumerator : IEnumerator
{
Transform outer;
int currentIndex = -1;
internal Enumerator(Transform outer)
{
this.outer = outer;
}
//*undocumented*
public object Current
{
get { return outer.GetChild(currentIndex); }
}
//*undocumented*
public bool MoveNext()
{
int childCount = outer.childCount;
return ++currentIndex < childCount;
}
//*undocumented*
public void Reset() { currentIndex = -1; }
}
Related
I tried to make a dynamic scroll view to display selectable options that can be both generated or destroyed during runtime but for some reason it only lets me to generate up to three, otherwise I get a Missing Reference Exception.
When I generate one then delete it, it will also give a Missing Reference Exception.
But when I generate two, then delete one or even two, everything works just fine no matter what I do.
How can I fix this strange Behaviour?
The code:
private List<JL> JLList;
public List<GameObject> JLID;
public GameObject ButtonPrefab;
public GameObject PrefabParent;
public GameObject JLMenuManager;
public GameObject ScrollViewContent;
private void Update()
{
JLList = JLMenuManager.GetComponent<AddJL>().JLList;
CheckForChange();
}
private void GenerateOptions()
{
foreach (Transform child in ScrollViewContent.transform)
{
Destroy(child.gameObject);
}
for (int i = 0; i < JLList.Count; i++)
{
GameObject newJLOption = Instantiate(ButtonPrefab, PrefabParent.transform);
JLID.Add(newJLOption);
newJLOption = JLID[i];
int JLIndex = i;
newJLOption.GetComponent<Button>().onClick.AddListener(() => LoadJLOptions(JLIndex));
}
}
private void LoadJLOptions(int JLIndex)
{
SendMessage("SetJL", JLIndex);
Debug.Log(JLIndex);
}
private void CheckForChange()
{
int allJLOptions = ScrollViewContent.transform.childCount;
if (allJLOptions != JLList.Count)
{
GenerateOptions();
}
}
It seems like it is only spawning in how many JLList.Count is set to, open your script JLList and there should be a variable called count . Check how many that is set to. If it is 3, then that is your issue. If not, then follow what #BugFinder said and try remove them from the list
I have a List that tracks "alive" objects in runtime. And when I apply some behavior in items from this list I've found something strange (at least to me), for example, let's say that I want to damage each enemy in current scene, so (Items is a List of custom class)
if (Input.GetKeyDown(KeyCode.C))
{
var enemiesWithLife = enemiesInScene.Items.FindAll(enemy => enemy.life > 1);
foreach (var enemy in enemiesWithLife)
enemy.TakeDamage(1);
}
The above code works as expected BUT if I want to apply something to ALL enemies and try something as
if (Input.GetKeyDown(KeyCode.C))
{
foreach (var enemy in enemiesInScene.Items)
enemy.TakeDamage(1);
}
When I press "C" only 1 enemy "TakeDamage" and an error about list been modified during iterating
EnemiesInScene is an ScriptableObject, so the enemies list exist only in assets
BUT if I try same code, but make an find before, as
if (Input.GetKeyDown(KeyCode.C))
{
var allEnemies = enemiesInScene.Items.FindAll(enemy => true);
foreach (var enemy in allEnemies)
enemy.TakeDamage(1);
}
Things work as expected again ... WHY?
Edit
Codes asked in comments:
public void TakeDamage(int damage)
{
if (currentLife <= 0)
return;
currentLife -= damage;
if (currentLife <= 0)
Die();
else
TakeHit();
}
EnemiesInScene is an RuntimeSet
public class RuntimeSet : ScriptableObject
{
public List<RuntimeItem> Items = new List<RuntimeItem>();
public void Add(RuntimeItem thing)
{
if (!Items.Contains(thing))
Items.Add(thing);
}
public void Remove(RuntimeItem thing)
{
if (Items.Contains(thing))
Items.Remove(thing);
}
}
Runtime item
public class RuntimeItem : LazyComponents
{
public RuntimeSet runtimeSet;
private void OnEnable()
=> runtimeSet.Add(this);
private void OnDisable()
=> runtimeSet.Remove(this);
}
And LazyComponents is only a lazy approach wrapper that inherits from monobehaviour to get some components as rigidbody2d, etc...
foreach uses an enumerator, and enumerators can't change the underlying collection, but can, however, change any objects referenced by an object in the collection see MSDN
2 solutions: copy the collection in Array or List (less performance)
if (Input.GetKeyDown(KeyCode.C))
{
foreach (var enemy in enemiesInScene.Items.ToArray())
enemy.TakeDamage(1);
}
or use for loop which allows the modification in collection (more performance) i recommand
for(int i = 0 ; i < enemiesInScene.Items.Length; i++)
enemiesInScene.Items[i].TakeDamage(1);
I think that I've found my error, enumerator runs asynchronous, so it'll change while other things happen, so
When I try it
if (Input.GetKeyDown(KeyCode.C))
{
var allEnemies = enemiesInScene.Items.FindAll(enemy => true);
foreach (var enemy in allEnemies)
enemy.TakeDamage(1);
}
AllEnemies is already filled, so any change in enemiesInScene.Items don't affect allEnemies, this is why in other case we got errors
for (int i = list.Count - 1; i != -1; i--)
list.RemoveAt(i); // replace with useful code that can modify this list
I'm trying to add an extension method to my gameobject, that's works, but my problem is the GameObject share the same result. My goal is to have a different result for each GameObject.
// AddExtension.cs
public static class GameObjectExtensions
{
private static int life;
public static int Life(this GameObject gameObject)
{
return life;
}
public static void ChangeLife(this GameObject gameObject, int numberToAdd)
{
life += numberToAdd;
}
}
And in my main code, I would like to manage GameObject like :
void Start()
{
GameObject.Find("Perso0").ChangeLife(2);
GameObject.Find("Perso1").ChangeLife(4);
GameObject[] rootGOs = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (GameObject g in rootGOs)
{
if(g.name == "Perso0")
{
Debug.Log("Perso0 : " + g.Life());
}
if(g.name == "Perso1")
{
Debug.Log("Perso1 : " + g.Life());
}
}
}
But both GameObject have 6 in "Life" ( 2 + 4 )
I whould like to get only 2 for "Perso0" with Life and 4 with "Perso1" with Life
Do you have some clue to helping me ?
Thank you and best Regards
Because your life variable is static, it's going to be the same value you're editing every time you call ChangeLife on a GameObject.
Since extension methods need to belong to static classes, and a static class can only have static members, you cannot achieve the goal you want with extension methods.
Even if you could, it's not the right way to go with the Unity paradigm. With this setup, you're essentially saying "Every GameObject in my scene has a life value," which I don't think you want to do.
Instead, you can create your own components, as below.
public class Enemy : MonoBehaviour
{
[SerializeField] private int _initialHealth = 100;
private int _health = -1;
public int health { get { return _health; } }
private void Awake()
{
_health = _initialHealth;
}
public void IncrementHealth(int health)
{
_health += health;
}
}
This is just an example, but you can make something to suit your needs.
static applied to a member means all instances share a single copy. That is, there's only one life variable, which is modified when you call ChangeLife() on any GameOject.
Since extension methods have to be in a static class, I don't think you can accomplish what you want this way.
However, you should be able to add custom properties to your players and other objects in Unity. I don't remember if they're called "custom properties" exactly, but I know some of the basic tutorials like Roll-a-Ball cover this (or at least used to).
I have a class, say Myclass, with a list variable, say string list, which I want to call from outside Myclass object instance, in a loop, succinctly like:
Myclass myclass = new Myclass();
foreach (string s in myclass)
{
}
I suspect it uses the implicit operator keyword inside of Myclass on a property. Syntax grrr..! Any help?
(Not sure if it's good practice but there are times when it comes in handy).
Foreach basically works on sequence. Your MyClass need to implement IEnumerable and eventually return IEnumerator implementation via GetEnumerator.
IEnumerator basically provides MoveNext and Current property which your foreach loop uses to query sequence elements one after another.
You can get more info around this by searching around Iterators in C#. Adding short snippet so you can visualize what i meant :
public class MyIterator : IEnumerable<string>
{
List<string> lst = new List<string> { "hi", "hello" };
public IEnumerator<string> GetEnumerator()
{
foreach(var item in lst)
{
yield return item;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
public class Consumer
{
public void SomeMethod()
{
foreach(var item in new MyIterator())
{
}
}
}
Hope this helps..
Is there a way of mimicking MonoBehaviour copy semantics in ScriptableObjects?
Say I have a MonoBehaviour like so:
public class DummyClassBehaviour : MonoBehaviour {
public DummyClass DummyClassTest; //ScriptableObject
public DummyClassBehaviour DummyBehaviourTest; //Another DummyClassBehaviour
}
And a ScriptableObject:
public class DummyClass : ScriptableObject {
public string Text = "";
}
When I duplicate(CTRL+D) a GameObject w/ DummyClassBehaviour attached, 'DummyBehaviourTest' copies as you would expect: If it references a MonoBehaviour in the GameObject I'm copying, the copy mechanism updates the reference to the same MonoBehaviour type in the new GameObject. If it references a MonoBehaviour in another GameObject, that reference remains unchanged.
The ScriptableObject, on the other hand, always references the original. So I end up with N GameObject's all sharing the same ScriptableObject (DummyClass) from the original GameObject. I'm using ScriptableObjects to allow serialization of non-Monobehaviour data classes.
As far as I can tell, and please someone correct me if I'm wrong, you cannot modify the serialization behavior of a ScriptableObject to match that of a MonoBehaviour. Namely that it should update references if a duplicate is made.
Instead I opted for a less than optimal solution, but it works. My class is assigned a unique identifier that gets serialized like everything else. I use this ID in DummyBehaviour.Awake() to create a lookup table that I can then use to reassign my DummyClass.
I'm not going to accept my own answer because I don't feel it answers my original question fully, but it's related:
[System.Serializable]
public class DummyClass {
// Unique id is assigned by DummyBehaviour and is unique to the game object
// that DummyBehaviour is attached to.
public int UniqueID = -1;
public string Text = "";
// Override GetHashCode so Dictionary lookups
public override int GetHashCode(){
int hash = 17;
hash = hash * 31 + UniqueID;
return hash;
}
// override equality function, allows dictionary to do comparisons.
public override bool Equals(object obj)
{
if (object.ReferenceEquals(obj, null))return false;
DummyClass item = obj as DummyClass;
return item.UniqueID == this.UniqueID;
}
// Allow checks of the form 'if(dummyClass)'
public static implicit operator bool(DummyClass a)
{
if (object.ReferenceEquals(a, null)) return false;
return (a.UniqueID==-1)?false:true;
}
public static bool operator ==(DummyClass a, DummyClass b)
{
if (object.ReferenceEquals(a, null))
{
return object.ReferenceEquals(b, null);
}
return a.Equals(b);
}
public static bool operator !=(DummyClass a, DummyClass b)
{
if (object.ReferenceEquals(a, null))
{
return object.ReferenceEquals(b, null);
}
return !a.Equals(b);
}
}
And my MonoBehaviour:
[ExecuteInEditMode]
public class DummyBehaviour : MonoBehaviour {
public List<DummyClass> DummyClasses = new List<DummyClass>();
// reassign references based on uniqueid.
void Awake(){
Dictionary<DummyClass,DummyClass> dmap = new Dictionary<DummyClass,DummyClass>();
// iterate over all dummyclasses, reassign references.
for(int i = 0; i < DummyClasses.Count; i++){
DummyClass2 d = DummyClasses[i];
if(dmap.ContainsKey(d)){
DummyClasses[i] = dmap[d];
} else {
dmap[d] = d;
}
}
DummyClasses[0].Text = "All items same";
}
// helper function, for inspector contextmenu, to add more classes from Editor
[ContextMenu ("AddDummy")]
void AddDummy(){
if(DummyClasses.Count==0)DummyClasses.Add(new DummyClass{UniqueID = 1});
else {
// Every item after 0 points to zero, serialization will remove refs during deep copy.
DummyClasses.Add(DummyClasses[0]);
}
UnityEditor.EditorUtility.SetDirty(this);
}
}