@McPeppergames
Letâs see
This canât be working the way youâre describing it. You are missing something in your code that makes it behave weirdly.
listOfEnemies is just a variable name. You need to declare the type of data this variable name is supposed to refer to. For simplicity sake, letâs just say itâs a List, no angled brackets.
So
List listOfEnemies;
Letâs pretend for a moment this is a valid declaration.
Now the variable is declared so that the compiler knows what to expect from it, but it refers to nothing. Why?
Because thatâs the default behavior for reference types.
Hereâs an explanation:
You see, there are some data types like integers and floating point numbers which are considered basic and simple. Because theyâre seen as basic and simple and limited in size they can be used much faster than some other, perhaps more complex data structures. For this reason they âliveâ closer to the CPU, at least most of the time. In C# we call these types value types, as opposed to reference types.
Reference types work by storing their contents on some remote location, but then keep a primitive pointer locally. This pointer is then used to address the allocated space on demand. This is exactly what value types try to avoid, they donât keep the pointers, they actually pass their value around by producing a copy.
Letâs get back to a variable pointing to nothing. By default, C# wonât actually allocate anything before you say so, and when you declare a reference type variable it will be set to null immediately (basically the internal pointer has nowhere to point at). Value types have no nulls, they assume some initial value (aka default), for example 0.
So you declared the variable and it now points to null. What would happen if you were to use it?
listOfEnemies.Add(myEnemy);
This would produce a runtime error. Why?
Because you have never created anything this variable would point to. It refers to null, and thus there is no Add method in it, because when you call a method, you operate on some instance in memory.
To actually create an instance in memory in C# (and many other languages), we use the keyword new.
This keyword has to be used with an object constructor in tandem, and will allocate the necessary memory and return a valid reference to it. And because the variable was declared with the List type, the only thing it can refer to is an object of type List.
listOfEnemies = new List();
listOfEnemies.Add(myEnemy);
Now this would work, if myEnemy was something compatible with the List.
Btw itâs worth pointing out that primitive types do not usually need new (because you can use literals). However not all value types work like that. For example, structs are value types which require new. This has nothing to do with what your question, just keep that in mind.
But how can we tell if myEnemyâs type is compatible with List? The List is something that owns, collects and manages multiple elements of some type, but we have never specified which type is it. Thatâs why the generic List declaration must include the angled brackets.
List<Enemy> listOfEnemies;
listOfEnemies = new List<Enemy>();
listOfEnemies.Add(myEnemy);
List is supposed to be read as âa list of type Enemyâ
This should have been the case for the previous scenario as well. You should investigate into what youâre doing wrong so that it doesnât fire an error, because it seems that youâve lost control over your code! (stay to the end because Iâll explain to you whatâs wrong)
A type of List is always a reference type. This is because there is nothing simple or primitive with potentially enormous lists of any data, however simple or primitive it is on its own. So the default value for a variable declared as a list will always be null, until it gets reassigned to something that is specifically allocated with new.
Yes, but if something is necessary I wouldnât call it a fix, though. A fix is when something is broken or insurmountable. Here youâre fully expected to declare type and allocate a new object.
Now this is an illusion that you have inadvertently created for yourself. It is incredible how complicated things can get if you donât understand the underlying system properly.
Letâs get to what Kurt-Dekker was saying above. In Unity, when you create a MonoBehaviour derivative, all public fields will be automatically serialized. This adds a layer of complexity to your C# code thatâs somewhat invisible.
In this particular case, your GameObject list would deserialize on its own, which means it would fetch its state from the scene file, and even if it was empty, the object would be allocated on its own, because thatâs how deserialization works, it will never get back to null. So no error when you try to access it!
edit:
The reason why this doesnât work the same with your struct is because you havenât made that struct serializable so Unity decides to not serialize that list. And so it fires the error as expected.
To make your struct serializable you must add the attribute [System.Serializable] before the struct declaration and either leave its fields public or explicitly add [UnityEngine.SerializeField] in front of them.
Iâd advise always doing the latter because it is much easier to tell which fields are intended for serialization. If you did that, you would be able to track much more easily what is going on in this particular scenario.