So basically im creating a space sandbox game on mobile where you can create your own universe. At the start of the game you can choose your rocket. Every rocket has its own Camera, Player and Spawner attached to it. Not the nicest solution but it works. After the player chooses his rocket, I destroy the spawners on the inactives ones, so they dont work.
My problem is when exiting the scene and choosing a different rocket and repeating the process, the SpawningManager with all the references to all Spawners lost the references to them. Even though I reloaded the scene all references are still lost.
Sorry if this was hard to follow but it was hard to explain.
Here is a part of the code that destroys the spawners(represented by colors)
if (BlueSelected == true)
{
Destroy(RedSpawner);
Destroy(OrangeSpawner);
Destroy(YellowSpawner);
Destroy(GreenSpawner);
}
And a part of one of the planet spawner(represented by colors)
{
public Transform SpawnPointBlue, SpawnPointRed, SpawnPointOrange, SpawnPointYellow;
public GameObject Mercury;
// Checks if the Spawner is greater than null (There or not) and Instantiates the Object at the Players Position
public void SpawnMercury()
{
if(SpawnPointBlue != null)
{
Instantiate(Mercury, SpawnPointBlue.position, SpawnPointBlue.rotation);
}
if(SpawnPointRed != null)
{
Instantiate(Mercury, SpawnPointRed.position, SpawnPointRed.rotation);
}
if(SpawnPointOrange != null)
{
Instantiate(Mercury, SpawnPointOrange.position, SpawnPointOrange.rotation);
}
if(SpawnPointYellow != null)
{
Instantiate(Mercury, SpawnPointYellow.position, SpawnPointYellow.rotation);
}
If I understood well, your SpawnerManager is in "don't destroy on load" and keep existing between scenes. When you start your game you have referenced manually the spawners to the manager and everything is fine then when you go to another scene and come back where the spawners should be, there is no more references into the manager ?
If so, this seams normal to me, the references between scenes are not supported and when a scene is unloaded, the references disappear.
If I misunderstood something I would be glad to dig for the solution a bit
Related
I'm currently in the process of making my first bigger game in Unity 2D with C#, and I have a problem. I have already searched a lot for answers, but I haven't been successful.
My problem is, that I want to make multiple doors that the player can open individually (there will be around 40 doors). I think I will deactivate the closed door GameObject and activate the open door GameObject.
And I feel like there must be a better way of controlling multiple GameObjects than to drag and drop to separate variables and make an if statement for each door in the script.
E.g:
if (door4 gets opened) {
door4Closed.setActive(false);
door4Open.setActive(true);
}
It has been a problem for some time, and making the doors is just one of the times I've run into this type of problem.
I have played around with GameObjects arrays and .find, but I still feel like there must be af better way.
So I would like help to:
1: Deactivate and activate a specific GameObject among many.
2: Maybe even making a better door system.
Thanks in advance! :D
Edited after OP provided more information.
If all doors can be freely opened without any checks, the easiest way would be creating a script that inherits MonoBehaviour and assign it to a prefab that you use to create the doors.
If the doors are already there, you can select all of them at once on the hierarchy and add this door component to all of them at the same time.
To avoid the need of dragging and droping every single variable of the door script manually you can do it on the door script itself.
This is a script assumes that:
The Collider of the door is in the same gameobject as this.
The Colllider has the "IsTrigger" ticked.
The player's gameobject that contains the collider is tagged as "Player" (change the tag on the script if it's not that one).
The script contains two different ways of assigning those variables in the editor.
1: Using the Reset() event function, which is called whenever you add this script inside the editor or when you click "Reset" on the context menu.
2 (My preferred): Using the OnValidate() event function, which is called whenever you load the gameobject on the editor or when you edit any value on the inspector.
Futhermore, this is a suggestion of implementation, you should customize your own based on your specific needs.
public class DoorScript : MonoBehaviour
{
private bool _isOpen;
private bool IsOpenHandler
{
get => _isOpen;
set
{
if (_isOpen && !value)
ExecuteCloseAnimation();
else if (!_isOpen && value)
ExecuteOpenAnimation();
_isOpen = value;
}
}
private void OnTriggerStay2D(Collider2D other)
{
if (other.CompareTag("Player") && Input.GetKeyDown(KeyCode.E))
{
IsOpenHandler = !IsOpenHandler;
}
}
private void Reset()
{
animator = GetComponent<Animator>();
}
#if UNITY_EDITOR // we can't let this compile on game builds because the UnityEditor namespace does not exist on builds (which we are using bellow)
private void OnValidate()
{
if (animator != null) // if we already have the animator we don't need to do anything
return;
animator = GetComponent<Animator>();
if (animator == null) // if we fail to get the animator we log a warning to let you know
Debug.LogWarning($"Could not find animator on {gameobject.name}!", this);
else
UnityEditor.EditorUtility.SetDirty(this); //this sets this gameobject as dirty and is needed to force the Editor to save the changes we made when you click "Save"
}
#endif
}
What language are you using? The following is a general template you can use, as long as it is object-oriented.
Create a Door class. Make each door an object of class door. Define class door to have an attribute that keeps track of state such as opened = false. It's also good practice to make getter and setter methods:
function open() {
opened = true;
}
This way, you can easily change the state of doors.
Door d = new Door();
d.open();
You could keep track of the doors using an ArrayList if you wanted.
ArrayList<Door> doors = new ArrayList<Door>();
doors.add(d);
doors.get(0).close();
I'm not sure how to switch scenes and bring all of my resources with me. I do understand that upon load of new scene, the previous scene gets destroyed on load. I've explored some with DontDestroyOnLoad() but had no luck with what I'm trying to do.
I tried to make my player controller a prefab and simply put him into the next scene; however, I returned a heck of a lot of errors because of the many scripts I have. Mostly stuff like checkpoint, HP bars, and even the weapon.
What I need to know is how to import everything into the next scene. How do I do this without having to recode or even re-make all of the stuff that I need?
You're looking for LoadSceneMode.Additive. This is the second parameter of the LoadScene method and will load the new scene into the current one.
If you need to import every object from the previous scene, then what's the point in creating a new one?
What you could do is saving the objects positions into a file and then loading that file back on the next scene or try (again) with DontDestroyOnLoad();
I recommend to check the documentation of Unity about this function.
If you want an individual object not to be destroyed on the scene change then on the void Awake() function of Unity make a DontDestroyOnLoad(this.gameObject);
Could you provide more information? I ask because it seems from your description that
DontDestoryOnLoad
should accomplish what you want but you say it does not. As long as the object holding the components whose states you want to save is persisted into the next scene using that method, then all of those components' states should be persisted as well. Please elaborate and we can possibly provide a better answer. As for how to use it to save the state of every game object:
GameObject[] allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach(GameObject go in allObjects) {
if (go.activeInHierarchy) { /* and any other logic you want. Maybe like !isTerrain */
Object.DontDestroyOnLoad(go);
}
}
For the above code, I ripped it from https://answers.unity.com/questions/329395/how-to-get-all-gameobjects-in-scene.html
Ok so I have this coroutine :
IEnumerator ShowCharTraits()
{
while(!hasPlayerChosen)
{
yield return null;
traitPanel.SetActive(true);
}
hasPlayerChosen = false;
traitPanel.SetActive(false);
// Debug.Log("Got called! job done");
}
It's being called like this from the awake method in my GameManager:
players = GameObject.FindGameObjectsWithTag("Player");
foreach (GameObject g in players)
{
ui_Controller.StartShowCharTraits();
g.GetComponent<PlayerToken>().isTurn = false;
}
StartShowCharTraits() is a simple method that does this :
public void StartShowCharTraits()
{
StartCoroutine("ShowCharTraits");
}
Now, I have checked the tags, no null reference exception, actually no errors or warnings are being thrown. If i load the scene in the editor and then play it everything works fine. traitPanel.SetActive(true); get called and my panel shows up. However when I load my scene from another scene using SceneManager.LoadScene(1); the above mentioned line is never reached. Any ideas why this is happening ?
Say you want to have one central place that is "like a singleton" in a Unity project. Example,
SoundEffects
LapTimer
Scores
SocialMediaConnections
All you do is this.
make your script SoundEffects.cs
recall that every Unity project must have a "preload" scene. (it's impossible to not have one)
in the preload scene, have a empty game object called "holder". make sure it is marked "DontDestroyOnLoad"
attach SoundEffects.cs to that holder
you're done.
there's just nothing more to it.
you're finished.
it's "just that simple"
So, any particular script, which happens to be attached to any particular object, in any particular scene, may need to access "SoundEffects"...
To do so, simply do this in Awake:
SoundEffects soundEffects = Object.FindObjectOfType<SoundEffects>();
then just use soundEffects anywhere in that script, for example soundEffects.PlayBoom(13) etc etc.
So in Awake()
Laps laps = Object.FindObjectOfType<Laps>();
Social social = Object.FindObjectOfType<Social>();
AI ai = Object.FindObjectOfType<AI>();
or whatever.
Unity is simple. Incredibly simple. It's just that easy.
write your script, LapTimer.cs or whatever
put it on a holder object in your preload scene (where else could it be?)
there's no "3", that's all there is. It's just that ridiculously simple.
Note that...
In the early days of Unity, someone unfortunately mentioned "singletons" on a QA board somewhere. (You can't have "singletons" in an ECS system, it's meaningless.) At the time, this led to huge discussions about how you can make a "singleton-like thingy" in Unity, which is piling confusion on confusion. Unfortunately from that day to this, you get people learning Unity, who see "singleton" mentioned here and there on the net, and it leads to endless confusion.
Once again, note that the idea of "managers" is just impossibly simple in Unity - explained above. It's trivial. It just couldn't be easier.
Note, above I mention you might want syntactic candy to avoid the incredible chore of typing Laps laps = Object.FindObjectOfType<Laps>();
There are very unfortunately no macros in Unity/c# so you need another way.
All you do is use a "Grid" script http://answers.unity3d.com/answers/1124859/view.html which has been popular for years in Unity.
But honestly, it's so simple to use Scores scores = Object.FindObjectOfType<Scores>(); that I usually just do that nowadays.
Ok how do I explain this. I have a singleton that acts as a data holder. While developing the scene with the game manager I had attached the singleton to the gamemanger object that hold a bunch of scripts. Now when I made the main menu I ofc added my singleton to it so I can carry info to the game scene and because of this my whole game manger object was being deleted. This is the culprit from the DataHolder :
void Awake()
{
if (instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject);//This right here.
DontDestroyOnLoad(gameObject);
}
So I changed that line to Destroy(gameObject.GetComponent(instance.GetType()));
I have a strange problem with DontDestroyOnLoad. I have a map as a starting scene. From there, the user can click on certain map-objects and a new level is loaded with Application.LoadLevel() When the level is finished the map is loaded again. But, the objects with DontDestroyOnLoad attached are duplicated on the second load.
Current script:
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
I searched online and found this possible solution to remove the duplicates:
public class NoDestroy : MonoBehaviour {
public static NoDestroy instance;
void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
return;
}
DontDestroyOnLoad(this.gameObject);
}
}
The above script simply does not work. How can I fix the problem?
Because the above script uses a static instance, it'll only work if a `single GameObject has it attached - worse yet, it'll delete other objects which try to use the same behavior.
The biggest problem is that whenever you load a scene, all objects in that scene get loaded. If they weren't unloaded (thanks to DontDestroyOnLoad) they'll be duplicated, since the scene doesn't know that they "already exist"
The above script might work better if you try to box your persistant objects under an umbrella, and only the umbrella object (usually called Toolbox) isn't destroyed. This is mostly appropriate for manager scripts, however.
If you know the objects that are meant to not be destroyed, consider loading them via a "Loading" scene. Since this moves the persistent objects out of your map scene, they won't get duplicated when reloading the map scene. Bonus to this pattern since it makes it easier to implement curtain drop.
If you want to implement this as a simple behavioural script, consider adding an ID like so
public class NoDestory : MonoBehaviour
{
private static Dictionary<string, GameObject> _instances = new Dictionary<string, GameObject>();
public string ID; // HACK: This ID can be pretty much anything, as long as you can set it from the inspector
void Awake()
{
if(_instances.ContainsKey(ID))
{
var existing = _instances[ID];
// A null result indicates the other object was destoryed for some reason
if(existing != null)
{
if(ReferenceEquals(gameObject, existing)
return;
Destroy(gameObject);
// Return to skip the following registration code
return;
}
}
// The following code registers this GameObject regardless of whether it's new or replacing
_instances[ID] = gameObject;
DontDestroyOnLoad(gameObject);
}
}
This will prevent the duplication of an object with the same ID value as one that already exists, as well as allowing recreation if said object has been Destory-ed elsewhere. This can be further refined by adding a special Editor script to hand over a new ID each time the script is implemented.
You could possibly make an "Initializer" scene that's the starting scene of the project, and all your "Don't Destroy On Load" objects get initialized in there. Then you immediately change to the real starting scene (your map screen) and all your objects exist and aren't duplicated. If you have a starting screen already, you might be able to use that instead of creating a whole new scene.
In case if anyone still needs the answer:
Answer which is available everywhere (WRONG ONE):
private static Sample sampleInstance;
void Awake()
{
DontDestroyOnLoad(this);
if (sampleInstance == null)
{
sampleInstance = this;
}
else
{
DestroyObject(gameObject);
}
}
Tl;dr Jump to solution.
What happens here is, when new scene is loaded, everything's fine. But as soon as you go back to previous scene, your original "Sample" game object is NOT destroyed and the new "Sample" game object which get created, is destroyed. So, what happens is, wherever you have referenced your Sample script (like in button onclick etc.) start referencing the duplicate script (which was destroyed of course), due to which they don't reference any script. Hence button.onClick no longer works.
So, the correct solution is to destroy the original Sample game object and not the duplicate, hence making the duplicate as the new original.
Here it is (THE CORRECT SOLUTION):
private static GameObject sampleInstance;
private void Awake()
{
if (sampleInstance != null)
Destroy(sampleInstance);
sampleInstance = gameObject;
DontDestroyOnLoad(this);
}
I have tested it. This works for me!!
if (Instance==null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
For use cases where you have some startup logic that only needs to be initialized once, consider creating a Startup scene that only loads once at the beginning of your game. That way, whatever scene switching you do from that point on won't create duplicates of the game objects created with the Startup scene.
In relation to networking, that's what Unity did in their Boss Room example:
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Scripts/ApplicationLifecycle/ApplicationController.cs#L94
I'm pretty new to Unity3D and C# so I'm not entirely sure what I'm doing wrong in the following scenario.
In Unity3D I'm attempting to get the value of a variable (target) that is updated when the player moves close enough to the enemy that the script changes target from null to 'enemy' and gets the position and so on and so forth.
The turret that has a script on it for auto-targetting is a child to the main player gameobject.
Whenever I use the following:
targettingScript target;
target = gameObject.GetComponent<targettingScript>();
// or---
target = gameObject.GetComponentInChildren<targettingScript>();
...I can then test with a debug.log that it does pull "null" as it should at start up, but it doesn't seem to update when the turret finds a target and changes target from null.
I've read a few forum posts in various areas that some say you can't do this (pass data from a child component script to another gameobject that is instantiated (IE a missile firing after targetting is completed and then doing a homing-missile style of tracking) and others said that they did a check through all the levels of the gameObject till they found what they wanted, but I'm not understanding what that really means.
My questions are, 1 - is it possible to relay a variable from a child gameobject's component to another instantiated gameobject
2 - if so, how?
I apologize if this is a rather simple concept, but I'm not sure how to troubleshoot it.
I'm going to build a new scene and try various gameObjects and child objects and see what I can muster and if I find anything useful I will post it here.
In the mean time, thank you all for your time in reviewing my question.
sure,
since you instantiate the object run time, you should keep track of it,
public GameObject target;
public void Update() {
// if we dont have a target (find it).
if(target == null) {
target = GameObject.Find("Player");
}
// if we found the target take some health each frame.
if(target != null) {
playerhealth = player.GetComponent<PlayerHealth>();
playerhealth.health--;
}
}
you can of course also add a distance detector to the find.
if(target != null)
if(Vector3.distance(transform.position, target.transform.position) < 5) {
// target in range
} else {
// to far away ignore it.
target = null;
}
The best thing to do when you are instansiating gameobjects runtime you have to keep track of them if you are going to reference them later on, so create a manager that does this, create a empty gameobject, add a MgrScript to it, and have other objects call it as MgrScript.SpawnEnemy() that way we can reference the enemy's from an array or something like that.
If you dont use a manager and your scene is simple and small you can always use tags to reference the gameobjects, since it a bad idea to reference by name when you have a large and complex scene, because you will most likely reference the wrong item.