I'm trying to create a static GameAssets class where I can drag into it's references my Prefabs in order to manage every GameObject of my game.
The problem I have here is that when I start the game, the instance of my GameAssets is null (which I don't want) and it's instantiating a clone of GameAssets without the references linked to it.
Code of the GameAssets class
public class GameAssets : MonoBehaviour
{
private static GameAssets _i;
public static GameAssets i
{
get
{
if (_i == null)
_i = Instantiate(Resources.Load<GameAssets>("GameAssets"));
return _i;
}
}
public GameObject ProjectileLaserBall;
}
Hierarchy & Inspector
We can see that I have an empty GameObject called GameAssets with prefabs linked to it's references already!
How can I make Unity understand to use the existing GameAssets instead of creating a clone of it without it's references?
(As asked in my Script, a clone is created)
Clone of class
In this case as shown in your hierarchy image, you don’t need to create a new instance of your class. You already have an instance in your scene.
As such, when you start the game, you want the static reference to point to the already instantiated class that Unity already created after the scene was loaded.
The code would then look like:
public class GameAssets : MonoBehaviour
{
private static GameAssets _i;
public static GameAssets i
{
get
{
if (_i == null)
_i = this;
return _i;
}
}
public GameObject ProjectileLaserBall;
}
Your code does make use of the Resources folder. This make me think that you’ve also got a prefab if this GameAssets item in there as well. In this case, maybe your intention was to not define a GamesAssets object in your scene, but to load one in at runtime.
With prefabs, you can’t add references to scene objects (in the common sense), but you can add references to other prefabs. So, if you want to do it that way instead, make sure that your ProjectileLaserBall is also a prefab, instantiate the GameAssets object, add make sure you don’t already have a GameAssets object in the scene (as a new one is going to be created). The code for this class would still be the same as I’ve shown above, but you’d instantiate the asset from a different class altogether. If you’ve done everything correctly, you should have a single GameAssets item in the hierarchy that can be accessed by GameAssets.i
I found a way around that problem, still it is not solving it.
So instead of including my GameAssets directly into the scene, I leave it in the project folder and assign the references in the prefab GameAssets.
This works as it is creating a clone of GameAssets with the references when I'm calling it.
The problem here is that when I call it from a script, because it is not instantiated, the function doesnt show after GameAssets.i.
And we can't get the instance of a static class with the 'this' keyword so I really don't know how to get this to work properly
Related
I am trying to implement a simple racing game in Unity 2021.2 and I have a GameManager Object that holds state across different scenes (i.e. races). To achieve this I use this code in my GameManager:
public static GameManager instance;
private void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this;
} else if (instance != this)
{
Destroy(gameObject);
}
}
This works fine as long as the fields of the GameManager are of primitive types, such as int and string. However, my GameManager also has a field of type RacerData[], where RacerData is a class that I created in a separate script, which holds data associated with a racer, such as the name, score, etc.
Using this approach I ran into the following problem: When I transition from the first race to the second, i.e. when a new scene is loaded, the GameManager keeps the values of all the fields of primitive types, but loses the references to the RacerData objects.
My first attempt to solve this was that I turned my RacerData script into a MonoBehavior. Instead of just instantiating RacerData Objects in GameManager I created a prefab with the RacerData script attached and instantiated GameObjects from this prefab in my GameManager.
GameObject myRacerDataInstance = Instantiate(myRacerDataPrefab, …, …);
DontDestroyOnLoad(myRacerDataInstance);
instead of:
RacerData myRacerData = new RacerData();
This had the desired effect fo keeping the RacerData game objects when loading a new scene, however the references of the GameManager to those game objects is still lost when a new scene is loaded. I then proceeded to write a hacky workaround to reassign the objects in the next scene which, so far, I failed to make work. However, I'm less interested in making this workaround work rather than to understand why the references to the RacerData gameObjects are lost in the first place and what better way there is to keep data across scene loads.
This appears to be a very common use case to me and I can't quite believe that a developer is supposed to rewire the references manually, especially since those could form a much more complex object graph than mine.
So: Is there a way to preserve those references across scenes? Or is DontDestroyOnLoad() not the right mechanism in the first place and in that case: what better option is there to preserve data (including object references) accross scenes?
It turns out the problem wasn't quite what I thought it to be: The references weren't set on the GameManager object at the time I tried to access them, i.e. from the Start() method of another game object called RaceManager.
A quick fix to the problem was to simply wait for the references to be recreated like so (in RaceManager):
void Start()
{
StartCoroutine(WaitForRacers());
}
private IEnumerator WaitForRacers()
{
while (racers == null)
{
yield return new WaitForSeconds(1);
gameManager = FindObjectOfType<GameManager>();
racers = gameManager.GetRacers();
}
InitializeRace(); // initialization logic that relies on racers to be != null
}
I want to keep my player when switching scenes but without getting lots of copies of it , so I found out about singletons.You do something like this:
public class Player : Monobehaviour
{
private static Player instance = null;
void Start()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameobject);
}
else
Destroy(gameobject);
}
}
And it's pretty hard for me to understand.I get how static variables work , they belong to the class not to the instance.So , first my objects is in scene A.I switch to scene B,a new gameobject will be created with the script Player on it.Now , since instance holds the reference to the gameobject in scene A , why won't the else statement get called and a new gameobject will actually be created?Also , if you could explain a little bit more detailed than unitydoc how DontDestroyOnLoad() works , I'd be thankful.
This code runs after the object is created. In fact it runs on a script on the GameObject. The else statement will run, and it will destroy the GameObject this script is attached to, which is what you want because you already had another copy in the world from previously (the one that is referenced by the "instance" variable).
JSchiff is correct that this code will run after an instance of this object is created, storing the current instance in your static variable for unconditional access to "the current instance" or destroying new instances if there is one already. The else branch in your code will run.
To answer your extended question about DontDestroyOnLoad and it's interaction with this static variable: all the objects in the Engine are held in memory by the Engine. A Scene can be thought of as a "collection of objects in memory" by the engine. When a scene is unloaded, all objects in that collection are destroyed, unless the engine has been told DontDestroyOnLoad (as in un-load, although documentation refers to it as a "swap". You can load scenes without unloading scenes, this might have been terminology which just grew outdated but that's conjecture on my part).
These ideas are combined to make your Singleton, a highly accessible (in this case static instance variable), persistent (through DontDestroyOnLoad), one of a kind (through destroying any instance after the first) object. This isn't the only way to apply the Singleton pattern, but it is a common one in Unity.
Suppose I have an audio manager singleton (present in all scenes) and all scenes have a button to enable and disable audio.
Suppose I'm in the scene A and I go to scene B.
In scene B, the buttons that referenced the functions of the singleton lose their reference because the audio manager object is destroyed, because a previous audio manager comes from scene A and the one from scene B is destroyed (since there cannot be 2 of it).
Is there a smart way to solve this problem?
Of course I can create a class whose methods call singleton functions and let them in all scenes, but that doesn't seem smart.
On mobile so sorry for the formatting. One way I can think of doing this is having an Init() function in your singleton. In the way you are describing it, this object is DontDestroyOnLoad() as well. In the method of Start() or Awake() you're find all other instances of this object and if another exists deleting the new one so the old one persists.
Inside of this function if another object is found, simply call a new Init() function that takes all the references you need as parameters so the old Singleton gets all the information of the new one, then the new one is deleted.
public class myClass : MonoBehaviour {
public static myClass i;
[SerializeField] private Gameobject myReference = null;
void Awake () {
if(!i) {
i = this;
DontDestroyOnLoad(gameObject);
}else
i.Init(myReference);
Destroy(gameObject);
}
public void Init(GameObject myNewRef)
{
myReference = myNewRef;
}
...
Hope the snippet adds a bit more clarify. Again formatting might be iffy.
I do my exercise following a blog,and met with trouble.
After coding, I create a ShapeFactory asset in project,and sign the prefabs.size to 3,add the Shape component to a cube,after these work, I tried to drag the cube to the ShapeFactory asset,but it stoped by editor without nothing info but a forbident icon.
Here the code is:
ShapeFactory.cs
public class ShapeFactory :ScriptableObject
{
[SerializeField] private Shape[] prefabs;
}
Shape.cs
public class Shape : PersistableObject
{
private int shapeId=int.MinValue;
}
PersistableObject.cs
public class PersistableObject : MonoBehaviour {}
ScriptableObject are assets that "live" only in the Assets and are not related to any Scene.
You (usually) can not have any references from a Scene in a Prefab/asset that lives only in the Assets. The reason is savety. If the according Scene is not loaded then the references simply would not exist.
You would need some kind of persistent dependency injection in order to reconstruct those references once the Scene is loaded (afaik e.g. the serialization system of Odin Inspector is capable of that) or use dynamic dependency injection on runtime (some Assets exist for doing that in the AssetStore as well).
According to the field name ShapeFactory.prefabs what you want to use is a Prefab of that cube GameObject instead.
Simply drag & drop that cube from the Scene (Hierachy) into the Assets folder (ProjectView). This creates a Prefab from it. That new created prefab you can now reference in the ScriptableObject asset and other prefabs.
Before I said (usually) because some workarounds exists in case you really need a reference from a Scene. The simpliest one I know would be to use another ScriptableObject like e.g.
Change your script to
public class ShapeFactory : ScriptableObject
{
[SerializeField] private PersistableObjectReference[] prefabsReferences;
}
then have the reference type
[CreateAssetMenu]
public class PersistableObjectReference : ScriptableObject
{
public PersistableObject value;
}
Then on the according object in the scene have a
[RequireComponent(typeof(PersistableObject))]
public class PersistentObjectReferenceSetter : MonoBehaviour
{
[SerializeField] private PersistableObjectReference reference;
// use Awake so it is also called if this object is
// deactivated in the hierachy
private void Awake()
{
reference.value = GetComponent<PersitableObject>();
}
}
and now reference the according PersistableObjectReference asset in reference. This will on runtime set the correct reference from the Scene into the ScriptableObject (it will be displayed as Type missmatch but work as expected).
Now all you have to do is later instead of using the former prefabs[index] rather use prefabReferences[index].value in ShapeFactory.
INVALID: You can't assign scene Game Objects to Scriptable Objects.
Scene Game Objects are in the Hierarchy Window.
VALID: You can assign Prefabs to Scriptable Objects.
Prefabs are in the Project Window.
Since you're creating a Factory you want the Prefab there and not a scene Game Object.
HOW TO DO SO
To achieve that you can simply do this:
CREATE A PREFAB: Drag the -Cube Scene GameObject- (the one in the Hierarchy Window) to the Project Folder => so you create a prefab
ASSIGN THE PREFAB IN THE SO: Drag the -Cube Prefab- (the one in the Project Window) on the SO.
Now you can use the Prefab in the SO factory for instantiation or any other goal.
I have a script with a public UnityEvent and I am trying to drag a scene object to the object slot so I can access its methods but its not accepting the object. I'm not sure what i'm doing wrong.
The scene object is a prefab instance and I did try unpacking the prefab but it didnt make a difference.
In a comment you mentioned the script you are talking about is a StateMachineBehaviour.
StateMachineBehaviour does not inherit from MonoBehaviour but rather from ScriptableObject
ScriptableObject instances "live" in the Assets not a certain Scene
You (usually) can not have any Scene references in any assets like Prefabs or ScriptableObjects.
There are some workarounds however that still allow you to do that. You can e.g. create ScriptableObject containers for every value you want to pass. Something like e.g.
[CreateAssetMenu]
public class GameObjectReference : ScriptableObject
{
public GameObject gameObject;
public void SetActive(bool value)
{
if(gameObject) gameObject.SetActive(value);
}
}
such an instance of GameObjectReference now is a ScriptableObject and thus also "lives" in the Assets => you can reference it in any other asset as Prefabs and other ScriptableObjects.
So all you need to do is make sure that you set this value from within your scene e.g. using
[ExecuteInEditMode]
public class GameObjectReferenceSetter : MonoBehaviour
{
public GameObjectReference gameObjectReferenceAsset;
private void Awake()
{
gameObjectReferenceAsset.gameObject = gameObject;
}
}
You can create such a reference-asset and setter pair for each type you need and transpass the required public methods so you can call them from the UnityEvent.
Having the [ExecuteInEditMode] this should also be set already in EditMode. However since "officially" it is still not possible to have a scene reference on a ScriptableObject field the value usually says Type missmatch but references the correct object as you should see when you click on the field.
Another alternative is using some kind of dependency-injection (for Unity an often mentioned and free solution is e.g. Zenject). It is a bit complex to set it up the first time but once you got it working it is more flexible and better scaleable since you wouldn't need to implement a wrapper for each type you want to pass in to a StateMachineBehaviour.
More information and how-tos about Zenject can be found on their github page