How to preserve the full state of objects between scenes? - c#

When loading a new Scene I run into the trouble of having my mouse drag not carry over to the next scene and having to re-click when the new scene is loaded.
I would like the mouse click to carry over seamlessly to the next scene without the player noticing and more generally I would like to know how best to preserve certain game objects and make them carry over to the next scene.
In essence, what I'm trying to do is have the entire game act like one big scene that the player can play trough but still be broken down into smaller scenes that could be accessed or transformed into levels at a later stage.
Thanks in advance.
This is the code I'm currently using
using UnityEngine;
using System.Collections;
using UnityEngine.Profiling;
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
private void OnMouseDrag()
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = mousePos;
DontDestroyOnLoad(this.gameObject);
}
}
Bellow is the script that is responsible for the loading of the scene:
public class StarCollision : MonoBehaviour
{
private bool alreadyScored = false;
private void OnEnable()
{
alreadyScored = false;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("White Ball"))
{
if (!alreadyScored)
{
ScoreScript.scoreValue += 1;
StartCoroutine(ChangeColor());
alreadyScored = true;
}
}
if (ScoreScript.scoreValue > 4)
{
SceneManager.LoadScene(1);
}
}
private IEnumerator ChangeColor()
{
ScoreScript.score.color = Color.yellow;
yield return new WaitForSeconds(0.1f);
ScoreScript.score.color = Color.white;
gameObject.SetActive(false);
}
}

I think the main reason why it doesn't work is that you probably also have another Camera in the new Scene.
The OnMouseDrag rely on the Physics system internally using the objects Collider and raycasts from the Camera. Now if you switch Scene I'ld guess the one Camera gets disabled so your drag gets interrupted.
Also using LoadScene instead of LoadSceneAsync causes a visible lag and might also be related to the issue.
I have a maybe a bit more complex solution but that is what I usually do:
1. Have one Global Scene "MainScene"
This Scene contains stuff like e.g. the MainCamera, global ligthning, global manager components that should never be destroyed anyway.
2. Use additive async Scene loading
You said you do not want your user to not note when the scene switches so I would recommend using SceneManager.LoadSceneAsync anyway.
Then in order to not unload the before mentioned MainScene you pass the optional parameter LoadSceneMode.Additive. This makes the new Scene be loaded additional to the already present one. Then later you only have to exchange those by unloading the previously additive loaded scene.
I created a very simple static manager for this:
public static class MySceneManager
{
// store build index of last loaded scene
// in order to unload it later
private static int lastLoadedScene = -1;
public static void LoadScene(int index, MonoBehaviour caller)
{
caller.StartCoroutine(loadNextScene(index));
}
// we need this to be a Coroutine (see link below)
// in order to correctly set the SceneManager.SetActiveScene(newScene);
// after the scene has finished loading. So the Coroutine is required
// in order to wait with it until the reight moment
private static IEnumerator loadNextScene(int index)
{
// start loading the new scene async and additive
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
// optionally prevent the scene from being loaded instantly but e.g.
// display a loading progress
// (in your case not but for general purpose I added it)
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
// e.g. show progress of loading
// yield in a Coroutine means
// "pause" the execution here, render this frame
// and continue from here in the next frame
yield return null;
}
_async.allowSceneActivation = true;
// loads the remaining 10%
// (meaning it runs all the Awake and OnEnable etc methods)
while (!_async.isDone)
{
yield return null;
}
// at this moment the new Scene is supposed to be fully loaded
// Get the new scene
var newScene = SceneManager.GetSceneByBuildIndex(index);
// would return false if something went wrong during loading the scene
if (!newScene.IsValid()) yield break;
// Set the new scene active
// we need this later in order to place objects back into the correct scene
// if we do not want them to be DontDestroyOnLoad anymore
// (see explanation in SetDontDestroyOnLoad)
SceneManager.SetActiveScene(newScene);
// Unload the last loaded scene
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
// update the stored index
lastLoadedScene = index;
}
}
This MySceneManager is a static class so it is not attached to any GameObject or Scene but simply "lives" in the Assets. You can now call it from anywhere using
MySceneManager.LoadScene(someIndex, theMonoBehaviourCallingIt);
The second parameter of type MonoBehaviour (so basically your scripts) is required because someone has to be responsible for running the IEnumerator Coroutine which can't be done by the static class itself.
3. DontDestroyOnLoad
Currently you are adding any GameObject you dragged at any time to DontDestroyOnLoad. But you never undo this so anything you touched meanwhile will be carried on from that moment ... forever.
I would rather use e.g. something like
public static class GameObjectExtensions
{
public static void SetDontDestroyOnLoad(this GameObject gameObject, bool value)
{
if (value)
{
// Note in general if DontDestroyOnLoad is called on a child object
// the call basically bubbles up until the root object in the Scene
// and makes this entire root tree DontDestroyOnLoad
// so you might consider if you call this on a child object to first do
//gameObject.transform.SetParent(null);
UnityEngine.Object.DontDestroyOnLoad(gameObject);
}
else
{
// add a new temporal GameObject to the active scene
// therefore we needed to make sure before to set the
// SceneManager.activeScene correctly
var newGO = new GameObject();
// This moves the gameObject out of the DontdestroyOnLoad Scene
// back into the currently active scene
gameObject.transform.SetParent(newGO.transform, true);
// remove its parent and set it back to the root in the
// scene hierachy
gameObject.transform.SetParent(null, true);
// remove the temporal newGO GameObject
UnityEngine.Object.Destroy(newGO);
}
}
}
This is an Extension Method which allows you to simply call
someGameObject.SetDontDestroyOnLoad(boolvalue);
on any GameObject reference.
Then I changed your script to
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
// On mouse down enable DontDestroyOnLoad
private void OnMouseDown()
{
gameObject.SetDontDestroyOnLoad(true);
}
// Do your dragging part here
private void OnMouseDrag()
{
// NOTE: Your script didn't work for me
// in ScreenToWorldPoint you have to pass in a Vector3
// where the Z value equals the distance to the
// camera/display plane
mousePos = Camera.main.ScreenToWorldPoint(new Vector3(
Input.mousePosition.x,
Input.mousePosition.y,
transform.position.z)));
transform.position = mousePos;
}
// On mouse up disable DontDestroyOnLoad
private void OnMouseUp()
{
gameObject.SetDontDestroyOnLoad(false);
}
}
And in your StarCollision script you only have to exchange
SceneManager.LoadScene(1);
with
MySceneManager.LoadScene(2, this);
Demo
For a little demonstration I "faked" it using two simple scripts
This one in the Main scene
public class LoadFirstscene : MonoBehaviour
{
// Start is called before the first frame update
private void Start()
{
MySceneManager.LoadScene(1, this);
}
}
And this one in the other scenes
public class LoadNextScene : MonoBehaviour
{
[SerializeField] private int nexSceneIndex;
private void Update()
{
if (!Input.GetKeyDown(KeyCode.Space)) return;
MySceneManager.LoadScene(nexSceneIndex, this);
}
}
And have 3 Scenes:
Main: As mentioned contains
the MainCamera
a DirectionalLight
the LoadFirstScene
test: contains
a MoveBall "Sphere"
the LoadNextScene
test2: contains
a MoveBall "Cube"
the LoadNextScene
With the indexes matching the build settings (make sure Main is always at 0 ;) )
I can now switch between test and test2 using the Space key.
If I drag one of the objects meanwhile I can carry it on into the next scene (but only 1 at a time). I can even take it on again back to the first scene in order to have e.g. two sphere objects I can play with ;)

Related

Can only spawn one object at once in unity

In my game I have a game object called ExclamationMark which I want to spawn above enemies heads when the player gets into range and they become "Alerted".
I've made this simple script to do that, but for some reason it will only work on one game object.
My enemy script:
void CheckForPlayer()
{
// Define player and get position
var player = GameObject.FindWithTag("Player");
var playerPos = (int)player.transform.position.x;
if (transform.Find("Graphics"))
{
// Define gameobject position
var enemyPos = transform.Find("Graphics").gameObject.transform.position.x;
// Define range to spawn tiles in
var range = 5;
var rangeInfront = enemyPos + range;
var rangeBehind = enemyPos - range;
if (playerPos >= rangeBehind && playerPos <= rangeInfront)
{
enemyIsActive = true;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark();
}
}
else
{
enemyIsActive = false;
}
}
}
My ! script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExclamationMarkSpawn : MonoBehaviour {
public GameObject spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
public void SpawnExclamationMark()
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.transform.position, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, spawnPos.transform.position, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
IEnumerator DestroyExclamationMark()
{
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (Transform child in transform) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
}
}
Just to be sure: I assume every player has its own instance of both of your scripts attached (some maybe nested further in their own hierarchy).
I assume that since you are using transform.Find which looks for the object by name within it's own children.
In general using Find and GetComponent over and over again is very inefficient! You should in both classes rather store them to fields and re-use them. Best would be if you can actually already reference them via the Inspector and not use Find and GetComponent at all.
In general finding something by name is always error prone. Are you sure they are all called correctly? Or are others maybe further nested?
Note: Find does not perform a recursive descend down a Transform hierarchy.
I would prefer to go by the attached components. You say it has e.g. a RigidBody. If this is the only Rigidbody component in the hierarchy below your objects (usually this should be the case) then you could instead rather simply use
// pass in true to also get disabled or inactive children
Rigidbody graphics = GetComponentInChildren<Rigidbody>(true);
the same for the ExclamationMarkSpawn
// Would be even beter if you already reference these in the Inspector
[SerializeField] private Rigidbody graphics;
[SerializeField] private ExclamationMarkSpawn exclamationMark;
[SerializeField] private Transform player;
private void Awake()
{
if(!player) player = GameObject.FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
}
private void CheckForPlayer()
{
// If really needed you can also after Awake still use a lazy initialization
// this adds a few later maybe unnecessary if checks but is still
// cheaper then using Find over and over again
if(!player) player = FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
var playerPos = (int)player.position.x;
// always if making such a check also give a hint that something might be missing
if (!graphics)
{
// by adding "this" you can now simply click on the message
// in the console and it highlights the object where this is happening in the hierarchy
Debug.LogWarning("graphics is missing here :'( ", this);
return;
}
// Define gameobject position
var enemyPos = graphics.transform.position.x;
// Define range to spawn tiles in
// this entire block can be shrinked down to
if (Mathf.Abs(playerPos - enemyPos) <= 5)
{
enemyIsActive = true;
if (exclamationMark) exclamationMark.SpawnExclamationMark();
}
else
{
enemyIsActive = false;
}
}
The same also in ExclamationMarkSpawn.cs.
I would additionally only allow 1 exclamation mark being visible at the same time. For example when a player jitters in the distance especially assuming both, the player and the enemy, I would move the entire instantiation to the routine and use a flag. Especially since this is called every frame in Update while the player stays in the range!
Also re-check and make sure your enemies are not maybe referencing the same spawnPos and thus all instantiating their exclamation marks on top of each other.
public class ExclamationMarkSpawn : MonoBehaviour
{
public Transform spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
[SerializeField] private CameraShake cameraShake;
// only serialized for debug
[SerializeField] private bool isShowingExclamation;
private void Awake()
{
if(!cameraShake) cameraShake = Camera.main.GetComponent<CameraShake>();
// or assuming this component exists only once in the entire scene anyway
if(!cameraShake) cameraShake = FindObjectOfType<CameraShake>();
}
public void SpawnExclamationMark()
{
StartCoroutine(ShowExclamationMark());
}
private IEnumerator ShowExclamationMark()
{
// block concurrent routine call
if(isShowingExclamation) yield brake;
// set flag blocking concurrent routines
isShowingExclamation = true;
// NOTE: Also for this one you might want to rather have a flag
// multiple enemy instances might call this so you get concurrent coroutines also here
StartCoroutine(cameraShake.Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.position, Quaternion.identity);
if (exclamationMarkAudio) Instantiate(exclamationMarkAudio, spawnPos.position, Quaternion.identity);
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (var child in transform.ToList()) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
// give the flag free
isShowingExclamation = false;
}
}
Try this;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark(transform.position); //Add transform.position here
}
public void SpawnExclamationMark(Vector3 EnemyPos)
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, EnemyPos, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, EnemyPos, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}

Understanding Unity's GameObject.Find(), GetComponent() and objects recycling

New to unity.
So I created a simple a simple muzzle flash particle animation that is supposed to be displayed on enemies gun when the player gets close to him, simulating a shot without the actual bullet. However I get a null reference exception in this part muzzleFlash.Play(); I believe it's because I am not actually getting the muzzle flash component in the start function with the code I have, actually I know that is it after going to in to debug mode I found out. I am having a really hard time figuring out how to access that component. Below is my code and I'm also posting a picture of my hierarchy. Thanks in advance.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StaticShootingEnemy : MonoBehaviour
{
[SerializeField] private float _range = 12f;
private Transform _player;
private bool _alive;
private float _distance;
private ParticleSystem muzzleFlash;
// Use this for initialization
void Start()
{
_player = GameObject.Find("Player").transform;
_alive = true;
muzzleFlash = (ParticleSystem)this.gameObject.GetComponent("muzzleFLash");
}
// Update is called once per frame
void Update()
{
_distance = Vector3.Distance(this.transform.position, _player.transform.position);
if (_alive && _distance < _range)
AttackPlayer();
}
private void AttackPlayer()
{
//Turning enemy to look at player
transform.LookAt(_player);
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hit;
if (Physics.SphereCast(ray, 0.75f, out hit))
{
//TODO: Fix enemy shooting fast when gettting close to him.
GameObject hitObject = hit.transform.gameObject;
if (hitObject.GetComponent<PlayerController>())
{
muzzleFlash.Play();
Debug.Log("Player Hit!");
}
else
muzzleFlash.Stop();
}
}
public void SetAlive(bool alive)
{
_alive = alive;
}
}
You probably have an object "muzzleFlash" as child to object your script attached to. So, in this case you'd better have a reference to your ParticleSystem object that is called muzzleFlash.
[SerializeField] private ParticleSystem muzzleFlash; // drag and drop your ParticleSystem muzzleFlash in inspector
or at least you could find that muzzleFlash like this
GameObject muzzleFlashObj = GameObject.Find("muzzleFlash");
ParticleSystem muzzleFlash = muzzleFlashObj.GetComponent<ParticleSystem>();
In your case it's null because there is probably no component that is called MuzzleFlash on that object. The component you want to get is ParticleSystem.
What component is the staticshootingenemy script on? if it is not on the same component as the particle system then its not finding it because this.gameObject.GetComponent("muzzleFLash") does not exist on that component. You can use GameObject.Find("muzzleFLash") to search for the particle system.
So back to your comment, you could implement something like a pool for your muzzle flashes.
public class MuzzleFlashEffect : MonoBehaviour
{
[SerializeField] private ParticleSystem particleEffect;
private Queue<MuzzleFlashEffect> poolQueue;
public void SetPoolQueue(Queue<MuzzleFlashEffect> queue)
{
poolQueue = queue;
}
public void Play()
{
StartCoroutine(Playing());
}
private IEnumerator Playing()
{
particleEffect.Play();
while (particleEffect.isPlaying)
{
yield return null; // wait until particle animation is done, then recycle effect
}
particleEffect.Stop();
poolQueue.Enqueue(this); // recycle this effect
}
// you can do the same thing for Animation as well, or even write some abstract PoolableVFX class that would be usefull for Animation , ParticleSystems etc..
}
//assume you have some game controller that manage what is going on in the scene
public class GameController : MonoBehaviour
{
[SerializeField] private MuzzleFlashEffect muzzleFlashPrefab;
private Queue<MuzzleFlashEffect> poolQueue = new Queue<MuzzleFlashEffect>(10); // 10 is enough i guess and it's good to set it at instantiation to avoid memory fragmentation
private MuzzleFlashEffect GetMuzzleFlash(Vector3 pos, Quaternion rot)
{
MuzzleFlashEffect muzzleFlash;
// if we already have some effects, then play them, otherwise make a new one and recycle it then
if (poolQueue.Count > 0)
{
muzzleFlash = poolQueue.Dequeue();
}
else
{
muzzleFlash = Instantiate(muzzleFlashPrefab);
muzzleFlash.SetPoolQueue(poolQueue);
}
muzzleFlash.transform.position = pos;
muzzleFlash.transform.rotation = rot;
return muzzleFlash;
}
void Update()
{
// your fancy logic ...
GameObject mutantGunEnd = new GameObject("mutant");
//assume that here you want your muzzle flash effect, so you do:
var muzzleFlash = GetMuzzleFlash(mutantGunEnd.transform.position, mutantGunEnd.transform.rotation); // or you might want to pass mutantGunEnd.transform.forward instead, it depends...
muzzleFlash.Play();
// your fancy logic ...
}
}
So, in this case you have only as many instance of ParticleEffect as you need and saving some resources. You could also create a universal generic pool for any type of object you want to recycle. (you want to recycle instead of instantiation, cuz Instantiation is cpu expensive).
M.b this is a bit overkill here, but i just wanted to share how would i think about this here

WaitForSeconds() Coroutine causes Game to freeze Indefinitely (Edited)

I have a game with four scenes, a menu scene, a loading scene, and two game scenes. All is well, when I am transitioning from my menu scene to my game scenes, but whenever I transition from my game scenes back to my menu scene or reload the game scene, the loading scene simply stops responding. I get a warning message that says "NetworkManager detected a script reload in the editor. This has caused the network to be shut down" only when I try to reload the currently active game scene. This issue is also present when I play in the build! I used print statements to trace down where my code stopped running, and I figured out that it was the Yield Return New WaitForSeconds() which caused the game to freeze. Why is that?
I have two scripts that controls transitioning. One simple script called on UIButtons for telling the second more complex script called in the preload scene to load the scene it's supposed to load and create animations. I have made sure that I was loading on to the correct scene, and that all of the scenes were added into my build settings.
The following pictures show the loading scene not responding. The first picture shows what happens when I try to reload the current game scene, and the second picture shows what happens when I try to load the menu scene:
My Loading Scene Script:
public class LoadingScreenManager : MonoBehaviour {
[Header("Loading Visuals")]
public Image loadingIcon;
public Image loadingDoneIcon;
public Text loadingText;
public Image progressBar;
public Image fadeOverlay;
[Header("Timing Settings")]
public float waitOnLoadEnd = 0.25f;
public float fadeDuration = 0.25f;
[Header("Loading Settings")]
public LoadSceneMode loadSceneMode = LoadSceneMode.Single;
public ThreadPriority loadThreadPriority;
[Header("Other")]
// If loading additive, link to the cameras audio listener, to avoid multiple active audio listeners
public AudioListener audioListener;
AsyncOperation operation;
Scene currentScene;
public static int sceneToLoad = -1;
// IMPORTANT! This is the build index of your loading scene. You need to change this to match your actual scene index
static int loadingSceneIndex = 1;
public static void LoadScene(int levelNum) {
Application.backgroundLoadingPriority = ThreadPriority.High;
sceneToLoad = levelNum;
SceneManager.LoadScene(loadingSceneIndex);
}
void Start() {
if (sceneToLoad < 0)
return;
fadeOverlay.gameObject.SetActive(true); // Making sure it's on so that we can crossfade Alpha
currentScene = SceneManager.GetActiveScene();
StartCoroutine(LoadAsync(sceneToLoad));
}
private IEnumerator LoadAsync(int levelNum) {
ShowLoadingVisuals();
yield return null;
FadeIn();
StartOperation(levelNum);
float lastProgress = 0f;
// operation does not auto-activate scene, so it's stuck at 0.9
while (DoneLoading() == false) {
yield return null;
if (Mathf.Approximately(operation.progress, lastProgress) == false) {
progressBar.fillAmount = operation.progress;
lastProgress = operation.progress;
}
}
if (loadSceneMode == LoadSceneMode.Additive)
audioListener.enabled = false;
ShowCompletionVisuals();
//THE PRINT STATEMENT WORKS FINE RIGHT HERE! The value of waitOnLoadEnd is only 1
yield return new WaitForSeconds(waitOnLoadEnd);
//THE PRINT STATEMENT STOPS RUNNING RIGHT HERE!
FadeOut();
yield return new WaitForSeconds(fadeDuration);
if (loadSceneMode == LoadSceneMode.Additive)
SceneManager.UnloadScene(currentScene.name);
else
operation.allowSceneActivation = true;
}
private void StartOperation(int levelNum) {
Application.backgroundLoadingPriority = loadThreadPriority;
operation = SceneManager.LoadSceneAsync(levelNum, loadSceneMode);
if (loadSceneMode == LoadSceneMode.Single)
operation.allowSceneActivation = false;
}
private bool DoneLoading() {
return (loadSceneMode == LoadSceneMode.Additive && operation.isDone) || (loadSceneMode == LoadSceneMode.Single && operation.progress >= 0.9f);
}
void FadeIn() {
fadeOverlay.CrossFadeAlpha(0, fadeDuration, true);
}
void FadeOut() {
fadeOverlay.CrossFadeAlpha(1, fadeDuration, true);
}
void ShowLoadingVisuals() {
loadingIcon.gameObject.SetActive(true);
loadingDoneIcon.gameObject.SetActive(false);
progressBar.fillAmount = 0f;
loadingText.text = "LOADING...";
}
void ShowCompletionVisuals() {
loadingIcon.gameObject.SetActive(false);
loadingDoneIcon.gameObject.SetActive(true);
progressBar.fillAmount = 1f;
loadingText.text = "LOADING DONE";
}
}
Script on UIButtons that call the above script:
public class LoadingSceneButton : MonoBehaviour {
public void LoadSceneWithLoadingScreen(int sceneNumber){
if (sceneNumber < 0 || sceneNumber >= SceneManager.sceneCountInBuildSettings) {
Debug.LogWarning ("Can't Load Scene, because It Doesn't Exist!");
}
LoadingScreenManager.LoadScene (sceneNumber);
}
}
(1) Don't use "print", please use this:
Debug.Log("fadeDuration is ....... " , fadeDuration.ToString("f4");
add that line of code just before you call FadeOut and also please add it inside FadeOut
(2) problems with CrossFadeAlpha
Please note that CrossFadeAlpha is extremely difficult to use! It's a real pain! It only works on UnityEngine.UI.Graphic, and it's tricky when used with coroutines.
public static void FadeOut(this Graphic g)
{
g.GetComponent<CanvasRenderer>().SetAlpha(1f);
g.CrossFadeAlpha(0f,.15f,false);
}
(3) problems with loading a scene in Unity5 !!!
Yes there is a
known issue
where it gets stuck on 0.9. Maybe this is the main problem at hand.
check out ... http://answers.unity3d.com/answers/1146173/view.html
and ... http://answers.unity3d.com/answers/1073667/view.html
Some basic working code example....
public void LaunchSoundboard()
{
StartCoroutine(_soundboard());
}
private IEnumerator _soundboard()
{
Grid.music.Duck();
yield return new WaitForSeconds(.2f);
AsyncOperation ao;
ao = UnityEngine.SceneManagement
.SceneManager.LoadSceneAsync("YourSceneName");
while (!ao.isDone)
{
yield return null;
}
// here, the new scene IS LOADED
SoundBoard soundBoard = Object.FindObjectOfType<SoundBoard>();
if(soundBoard==null) Debug.Log("WOE!");
soundBoard.SomeFunctionInSoundboardScript();
}
Note that you wait on ".isDone", rather than watch the float.
(4) #You actually have to have a scene called "preload", which ONLY preloads.
unfortunately the menu scene can not be your preload scene.
You have to actually have a separate preload scene which does nothing but that, "preload".
Note that any "game managers" you have must be on the preload scene. It's a bit annoying but that's how it is.
The purpose of the "preload" scene is to hold any game managers you have.
The only scene that you mark "don't destroy on load" must be only the "preload" scene. It's that simple.
It's a bit of a nuisance but very simple and reliable.

Is it possible to call a function on Unity Program Start?

I was wondering if there was a way in Unity that when I start my program on a scene it fires a function first, I should add that I want this one function to work regardless of what scene I'm in. So a simple Start function wont cut it. Not sure if this is possible in Unity?
public void ProgramBegins()
{
//FIRES FIRST ON ANY SCENE
//DO STUFF
}
RuntimeInitializeOnLoadMethodAttribute can help you :)
using UnityEngine;
class MyClass
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void OnBeforeSceneLoadRuntimeMethod()
{
Debug.Log("Before scene loaded");
}
}
Here you can find the execution order of all functions in unity: http://docs.unity3d.com/Manual/ExecutionOrder.html
Awake is the first function to be executed in your standalone application. For it to run you need to have a GameObject with a attached script containing the Awake function. This should be added to every scene if you want it to run regardless of the scene.
You still have to decide what is the startup scene of your game. So it is enough to add the GameObject there, if you actually want it to run just in the start of the program.
I use a prefab _AppStartup which is just an empty game object having a script AppStartup. Drag this in every scene and configure AppStartup to be executed as first like #maZZZu has stated.
AppStartup performs the following jobs:
Global initialisation tasks when the app is started
Switch to boot scene if have start an arbitrary scene in editor mode
Scene specific initialisation tasks
public class AppStartup : MonoBehaviour
{
const int bootSceneNo = 0;
public static bool veryFirstCallInApp = true;
void Awake ()
{
if (veryFirstCallInApp) {
ProgramBegins ();
if (Application.loadedLevel != bootSceneNo) {
// not the right scene, load boot scene and CU later
Application.LoadLevel (bootSceneNo);
// return as this scene will be destroyed now
return;
} else {
// boot scene stuff goes here
}
} else {
// stuff that must not be done in very first initialisation but afterwards
}
InitialiseScene ();
veryFirstCallInApp = false;
DestroyObject (gameObject);
}
void ProgramBegins()
{
// code executed only once when the app is started
}
void InitialiseScene ()
{
// code to initialise scene
}
}
So all you have to do is drag this prefab in every scene manually and give it -100 or whatever in the script execution order. Especially when the project grows and relies on a predefined scene flow it will save you al lot time and hassle.
Yes...
Decorate your method with [RuntimeInitializeOnLoadMethod].
It will be invoked as soon as the scene has finished loading (after Awake events).
[RuntimeInitializeOnLoadMethod]
static void OnRuntimeMethodLoad() {
Debug.Log("After Scene is loaded and game is running");
}
Documentation: https://docs.unity3d.com/ScriptReference/RuntimeInitializeOnLoadMethodAttribute.html
//try this it work in my case
public static bool isFirstLoad;
void Awake()
{
//your work that run only once even restart this scene
mainPanel.SetActive(!isFirstLoad);
if(!isFirstLoad)
{
isFirstLoad=true;
}
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(this.gameObject);
}
}

How do I access an updated variable between two scripts in Unity with C#?

Hopefully this isn't too much detail, I'm not used to asking programming questions.
I'm attempting to do the 3D Video Game Development with Unity 3D course that's on Udemy, though using C# instead of Javascript. I just finished up the tutorial that involves creating a space shooter game.
In it, a shield is created by the user when pressing a button. The shield has a "number of uses" variable that does not actually get used by the time the tutorial has finished. I'm trying to add it in, and have successfully managed to implement it so that with each use, we decrease the number of uses remaining, and no longer are able to instantiate the shield once that number is <=0.
This variable is stored on the player, and if I print it from the player, it returns the value I would expect.
However, I'm using a separate SceneManager.cs (this is where the tutorial placed the lives, and score, and timer variables ) where I print numbers into the GUI. My problem is that I cannot get my number of uses variable to stay current when I try to print it from the scene manager... it registers the initial value, but doesn't update after that.
Here is the Player Script
using UnityEngine;
using System.Collections;
public class player_script : MonoBehaviour {
// Inspector Variables
public int numberOfShields = 2; // The number of times the user can create a shield
public Transform shieldMesh; // path to the shield
public KeyCode shieldKeyInput; // the key to activate the shield
public static bool shieldOff = true; // initialize the shield to an "off" state
public int NumberOfShields
{
get{return numberOfShields;}
set{numberOfShields = value;}
}
// Update is called once per frame
void Update()
{
// create a shield when shieldKey has been pressed by player
if (Input.GetKeyDown (shieldKeyInput)) {
if(shieldOff && numberOfShields>0)
{
// creates an instance of the shield
Transform clone;
clone = Instantiate (shieldMesh, transform.position, transform.rotation) as Transform;
// transforms the instance of the shield
clone.transform.parent = gameObject.transform;
// set the shield to an on position
shieldOff = false;
// reduce the numberOfShields left
numberOfShields -=1;
}
}
print ("NumberOfShields = " + NumberOfShields);
}
public void turnShieldOff()
{
shieldOff = true;
}
}
when I run "print ("NumberOfShields = " + NumberOfShields);" I get the value I expect. (astroids trigger the turnShieldOff() when they collide with a shield.
Over in my Scene Manager however... this is the code I'm running:
using UnityEngine;
using System.Collections;
public class SceneManager_script : MonoBehaviour {
// Inspector Variables
public GameObject playerCharacter;
private player_script player_Script;
private int shields = 0;
// Use this for initialization
void Start ()
{
player_Script = playerCharacter.GetComponent<player_script>();
}
// Update is called once per frame
void Update ()
{
shields = player_Script.NumberOfShields;
print(shields);
}
// GUI
void OnGUI()
{
GUI.Label (new Rect (10, 40, 100, 20), "Shields: " + shields);
}
}
Any idea what I'm doing wrong that prevents shields in my SceneManager script from updating when NumberOfShields in my player_script updates?
I think you might have assigned a prefab into playerCharacter GameObject variable instead of an actual in game unit. In this case it will always print the default shield value of prefab. Instead of assigning that variable via inspector try to find player GameObject in Start function. You can for example give your player object a tag and then:
void Start() {
playerCharacter = GameObject.FindGameObjectWithTag("Player");
}

Categories