I want a restart menu that works for all my levels, as I load level 1 there is this script setting an integer to 1 on an empty game object.
using UnityEngine;
using System.Collections;
public class SetRestart : MonoBehaviour {
public int Setrestart = 1;
void awake ()
{
DontDestroyOnLoad (this);
}
}
When you fail the level you get to the next scene called: LostMenu. You have the option to restart that level you were playing or quit. So here I made a button to restart and attached this script to it:
using UnityEngine;
using System.Collections;
public class RestartButton : MonoBehaviour
{
public int Setrestart;
void Start()
{
if (Setrestart == 1) {
Application.LoadLevel("Main");
}
}
}
(Plan is to make 50 levels and also 50 if statements, this is just the first one for the first level called "Main", every level will have its own number).
The problem is that nothing happens when I click on the button (Screenshot: http://prntscr.com/9tf4dd) and that when I load the LostMenu screen nothing happens to the int and it stays at 0 in the inspector while in the level scene called: "Main" I said to it to give it 'int = 1.' The number 1 stays in the "Main" scene and doesn't go to the menu scene.
Level 1 is scene: "Main".
The menu to restart when you lose is scene: "LostMenu".
Am I clear? Sorry for my bad English and thank you in advance.
I use a static 'Globals' class to persist data between scenes in Unity.
You could use something like this to store the name of the last level that you played.
public static class Globals
{
public static string LastLevel {get; set;}
}
Then you could just write to this string at the start of your level and read it in your restart button handler.
In order for RestartButton to know about the Setrestart value stored in SetRestart you need to have a reference to the GO with the SetRestart component. Right now RestartButton is checking its own Setrestart value, which defaults to 0. This is why it doesn't work.
Instead you need this:
public class RestartButton : MonoBehaviour
{
GameObject persistentObject;
void Start()
{
persistentObject = GameObject.Find("NameOfGameObject");
int shouldReset = (persistentObject.GetComponent<SetRestart>() as SetRestart).Setrestart;
if (shouldReset == 1) {
Application.LoadLevel("Main");
}
}
}
GameObject.Find isn't great, performance wise, but for a once-off lookup, it's fine. There are also other ways of getting the reference, but in the case of objects that persist across scene changes, this is the only way (note that I'm saving the GameObject reference to a field, while not neccessary here it is good practice so you aren't GO.Find()ing all the time).
Related
I'm trying to store the current music that is playing in the current scene that I'm on and then in the next scene change it for another one.
Here are my scripts.
AudioManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour {
public static AudioManager Instance;
private MusicList musicList;
[SerializeField] private AudioSource _effectsSource, currentMusic;
private void Awake() {
if (Instance == null) {
Instance = this;
DontDestroyOnLoad(this);
}
else Destroy(this);
Instance = this;
musicList = GetComponentInChildren<MusicList>();
}
public void PlayMusic(MusicId id) {
AudioSource musicToPlay = musicList.GetMusicSource(id);
musicToPlay.Play();
currentMusic = musicToPlay;
}
public void ChangeMusic(MusicId newMusicId) {
currentMusic.Stop();
AudioSource musicToPlay = musicList.GetMusicSource(newMusicId);
musicToPlay.Play();
currentMusic = musicToPlay;
}
public void PlaySound(AudioClip clip) {
_effectsSource.PlayOneShot(clip, 0.1f);
}
}
MusicList is a children of the AudioManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicList : MonoBehaviour
{
public Music[] musicList;
private void Awake()
{
foreach (Music music in musicList) {
music.source = gameObject.AddComponent<AudioSource>();
music.source.clip = music.clip;
music.source.volume = music.volume;
music.source.loop = true;
}
}
public AudioSource GetMusicSource(MusicId id) {
foreach (Music music in musicList) {
if (music.id == id) return music.source;
}
return null;
}
}
Music
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Music
{
public MusicId id;
public AudioClip clip;
[HideInInspector]
public AudioSource source;
public float volume;
}
public enum MusicId {
Menu,
Gameplay
}
When I debug in the first scene the current track playing is stored, but when I change the scene and try to access the currentMusic value it's null.
Thank you for the help!
You have at least two approaches here. One of them is using proprietary scene data (not a technical term), meaning you use whatever info the current scene has, while the other method is using data persistence (this is a technical term) to "save" data in (normally) transient objects after scene changes. There is a third way to persist data, but it's not recommended for your use-case.
There are use-cases for all of them, each having different aims. Let's attack them:
scene info
This is straight forward: you have a menu, a tutorial, 2 levels, a boss fight level then the "end game + credits". This would mean 6 different songs (assuming each scene, including the end-credits has a different theme). Once you switch from menu to level and from level to level and then to "the end", a new song pops-up on the audio manager.
The simplest approach here is to have a GameObject in each scene (preferably with the same name and same components, only the song being different). Let's call it "SceneInfo". On this GO you can attach any info necessary to your level, like some description of the level, objectives, difficulty or song. You can use the Find() function to locate this GO then just access the component you need (like a component that saved the Music, then just pop-it on the AudioSource and Play() it).
Minor note: the scene GO must NOT be set as DontDestroyOnLoad(). Because you must not "carry" it in a different scene since each scene has its own stuff. And, of course, this should be different from your AudioManager GO.
This is prolly what you want for your approach.
data persistence
This is normally used to "carry" player-related stuff like the player icon, player name, health, inventory etc. from an earlier scene to this one. You can "carry" the music too, I guess, assuming it's a custom playlist OR if you want to continue the song. Otherwise, I don't recommend this for music if the songs are always different between levels.
This is basically what you did with your AudioManager.
storage (not recommended for what you need)
There would be a third option related to storage, by using PlayerPrefs, but I think this is overkill just for a song. But feel free to use this if you find it easier. As I said, it all depends on your use-case.
This option refers mostly to game preferences like volume, graphics settings etc. It's basically the "memory" of game settings.
Minor caveats:
I would set currentMusic; as public.
Your Awake() function has a weird flow. else Destroy(this); is missing a return. It's not wise to set the Instance to this since you're destroying the object.
Try this instead:
private void Awake()
{
if (Instance == null)
{
DontDestroyOnLoad(this);
Instance = this;
}
else if (Instance != this)
{
Destroy(this);
return;
}
if (musicList == null)
musicList = GetComponentInChildren<MusicList>();
}
You said: MusicList is a children of the AudioManager. I think you mean component instead of children, if the gameObject it's attached to is called "AudioManager", right? Else it doesn't quite make sense. A child class is something else.
Then the code in PlayMusic() is identical to ChangeMusic() (apart from stopping the previous song to update it). That means if you want to change something in the code for PlayMusic() or ChangeMusic(), you'll always have to do it twice (in each function). This is better:
public void PlayMusic(MusicId id) {
AudioSource musicToPlay = musicList.GetMusicSource(id);
musicToPlay.Play();
currentMusic = musicToPlay;
}
public void ChangeMusic(MusicId newMusicId) {
currentMusic.Stop();
PlayMusic(newMusicId);
}
... which can be simplified even more to this:
public void PlayMusic(MusicId id) {
currentMusic = musicList.GetMusicSource(id);
currentMusic.Play();
}
public void ChangeMusic(MusicId newMusicId) {
currentMusic.Stop();
PlayMusic(newMusicId);
}
And now there is no temp variable. It complicated the code for no reason.
(of course, assuming the id is always guaranteed to be in the list, which, of course, it should be). This way you only have one place to change your code or fix potential bugs.
While you can "force" serialization of C# objects, it's better to use Unity objects (anything from UnityEngine.object, but mostly rely on ScriptableObjects, Components and Prefabs).
The music init workflow is also weird. Instead of initializing the AudioSource on each music wrapper, you do it in a loop in the list. It's a bit "backwards", but not necessarily a bad thing. I just wouldn't do it.
I have a homework assigned and I need the make a sound and music volume thing and I want it to be used in other scripts too.
What I mean is :
enter image description here
So when I drag the slider value to 0.2 for example I want the audio source on the other scene to have volume 0.2, but I have no idea how thats made.
Thanks.
(I only have a plan but no code)
Also does anyone know why does it take forever to load when you save a script and go to unity:
enter image description here
A great way to do this is to use static variables that are actually defined for the class And can hold variables between scenes.
public class AudioManager
{
public static float MusicVolume = 1f;
public static float SoundVolume = .5f;
public void SetVolume(float value) => MusicVolume = value;
}
To call them, you just need to write the full name of the class before the variable name.
public class Player : MonoBehaviour
{
public AudioClip AudioClip;
public void Shot()
{
AudioSource.PlayClipAtPoint(AudioClip, transform.position, AudioManager.SoundVolume);
}
}
Remember that these are class variables will set in all instances of the same class. Also if you want your variables to be loaded after re-running the game. I suggest using PlayerPrefs for saving them.
To do this you would write a singleton script AudioManager that is set to DontDestroyOnLoad
It's just a script holding your AudioSources and that doesn't get destroy when you switch scenes.
Something like this
public class AudioManager : MonoBehaviour
{
private static AudioManager instance;
[Header("AudioSources")]
[SerializeField] private AudioSource musicSource;
[SerializeField] private AudioSource soundSource;
private void Awake()
{
// If you have AudioManager in every scene, you want to only keep the main one (the first one)
if (instance != null && instance != this)
{
Destroy(gameObject);
}
else
{
instance = this;
DontDestroyOnLoad(this); // This line will tell Unity to keep this gameobject when switching scenes
}
}
}
Then you can alter your audio sources as you wish, they won't get destroy after switching scene.
Okay Its nice that I have a lot of things to do but okay the script that you guys sent me, so if I put it on the audio Gameobject and I still don't get how I can change parameters from 1 scene to other (I'm a beginner and I'm 13 years old so I might not get what audio ATM's are but yes.)
For short I need this:
Scene1.findgameobject.name = blah blah = audiogameobject in menu
I'm new to coding and Unity, I'm working on a simple click style game to learn the basics of both. I've created several scenes: MainMenu, UI, 1st level and 2nd level. After pressing 'Start' in main menu i'm loading UI and 1st level additively.
In UI layer I have a shop UI and other bits that I never want to unload. On the 1st and 2nd level i have the bits that i want to have only on those scenes.
So, what I'm trying to do is when i purchase an item or upgrade for the 1st level i want a GameObject (sprite) to be set as active.
What I've tried to do is to call a function in one script that is attached to GameObject in 1st level from script that is attached to a purchase button in UI scene, but from what I was able to understand from messing with it will set that game object active if it's assigned in the UI scene - so the whole thing is basicly pointless and i can do it much easier.
Code in script attached to GameObject in 1st level scene
public class Work_Button : MonoBehaviour
{
public GameObject hubert12;
public void Huberd()
{
hubert12.SetActive(true);
}
}
Code in script attached to GameObject in UI scene
public class Shop : MonoBehaviour
{
public GameObject buyHubertOnebutton;
public GameObject test;
public void UnlockHubert1()
{
if (Global_Cash.CashCount >= 20)
{
Global_Cash.CashCount -= 20;
buyHubertOnebutton.GetComponent<UnityEngine.UI.Button>().interactable = false;
Work_Button sn = test.GetComponent<Work_Button>();
sn.Huberd();
}
}
}
If you have any remarks anout how i've spit scenes or anything else they will be more than welcome!
Thanks!
There's something you need to be aware of, and that is that you can't reference an object from one scene, in another. You CAN reference the same Prefab though, but that's a slightly different issue.
One way I find effective is to do something like this:
public class Work_Button : MonoBehaviour
{
public GameObject hubert12;
public void Awake ( )
{
Shop.Register ( this );
}
public void Huberd ( )
{
hubert12.SetActive ( true );
}
}
Then you can have a Shop manager that has instance and static components.
public class Shop : MonoBehaviour
{
// Static variables
private static Work_Button _workButton;
// Instance variables
public GameObject buyHubertOnebutton;
public static void Register ( Work_Button workButton )
{
_workButton = workButton;
}
public void UnlockHubert1 ( )
{
if ( Global_Cash.CashCount >= 20 )
{
Global_Cash.CashCount -= 20;
buyHubertOnebutton.GetComponent<UnityEngine.UI.Button> ( ).interactable = false;
if ( _workButton != null )
_workButton.Huberd ( );
}
}
}
This works by having your button register with the shop. And because we're using the static variable here, we don't need to "find" the Shop object. The instance method UnlockHubert1 will check to make sure there is a button registered.
This represents just one of many ways to accomplish this. It's also the most basic of basic implementations. But as of right now I'm fond of this method. Extending this, you could store the registered items in a Dictionary collection, and you could do some cleanup/deregister when a button is destroyed (i.e. OnDestroy ).
The assumption here is that there is only one Work_Button in the scene, otherwise you'll need a way to differentiate them (i.e. a Dictionary with am identifier as the key).
I have a car game, you define in one scene all settings, like the speed, after that you can press play, the new scene shows up.
The code of the first Scene is this, it is attached to an empty GameObject:
using UnityEngine;
using UnityEngine.UI;
public class Data : MonoBehaviour {
static public float speed=10f;
public InputField speedField;
void Update()
{
speed = float.Parse(speedField.text);
Debug.Log(speed);
}
}
In the next scene I have to work with speed in a script called Driving, which is attached to the cars. And I have to destroy the Data-Script, because in the next scene I don't have an Input-Field. How can I access to speed?
I tried a couple of hours and I'm not able to find a solution for this easy question. Thanks.
There are many ways you can solve this problem what I will suggest is to use the static script for holding data only, but you cant assign it to game object. It is a simple approach as you don't need to use a singleton.
public static class Data{
private static int speed
public static int speed{
get
{
return speed;
}
set
{
speed = value;
}
}
If you want to assign your script to game object then use DontDestroyOnLoad(gameObject); Or use playerprefs to save data.
In Unity C# script I have a singleton game controller object in which the game variables are stored but I am getting odd behaviour when accessing them in a member function. It prints the initialized value, not the current one. In the update function however it prints the correct value each frame. I summarized the class below. The controller class has a static reference to itself. If you need to know additional details you can ask. I am new to C# and Unity so I might be lacking some obvious answer.
Thanks
public class controller : MonoBehaviour {
public int[] star = new int[64];
void Start(){ /* calls another function to set 0 for each star index */ }
void Update(){ // during gameplay star[0] gets a value of 1
print(star[0]); // prints correct value which is 1
}
public void checkValue(){
print(star[0]); // prints 0 incorrectly which should be 1
}
}
I've set up a little example. I created a button and an empty gameobject GameController. I added this code to the GameController:
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour {
public static GameController instance;
[System.NonSerialized] public int[] star = new int[64];
private void Awake()
{
if(instance == null)
instance = this;
else if(instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
}
private void Start()
{
StartCoroutine(SetAllTo(1));
}
// for simulating some changes during the game
private IEnumerator SetAllTo(int value)
{
yield return new WaitForSeconds(2.0f);
for(int i = 0; i < star.Length; i++)
star[i] = value;
Debug.Log("Setting done");
}
public void PrintFirst()
{
Debug.Log(star[0]);
}
}
Now I added an OnClick event to the button, dragged the GameController gameobject into the slot and picked PrintFirst.
I start the game and click the button once before the Coroutine log and once after and the console gives the following:
0
Setting Done
1
Edit:
The gameobject for the OnClick event must be in the scene, it can't be a prefab in the assets folder.
It seems that's the call to the start function may also occurs in your external calls before the call for ckeckValue
Try debugging by putting breakpoint in each of these functions and you can understand what is going on.
The start function runs once in the beginning of the game and not again.
For that you have pattern - Singleton
The second thing why to call external function to initialize class member? do it in your default contractor.
- C# default constractor already initialize 0 in int[].
if you still what to run Start from other scope in your code make it public (c# makes variable/methods as private by default)