Hi I am new to Unity game engine and I am creating a 3d shooting game. In the level 1 I want to shoot 5 enemies in certain amount of time let's say 30sec. After completing level 1, I want to go to level 2 where my total enemies are 10 and want to kill it in 60sec and if fail there will be game over. I wrote some script for it, it works a little bit but it is not perfect because after starting level 2 the game become slow and after game over the level 2 restart again but not with the default value of 10 enemies, rather it starts from the no. which reaches at the time of game over. needs some idea and good logic and script for my game. here is my code.
public class Status : MonoBehaviour
{
public static int TotalZombies=5;
public static float timeLeft=25.0f;
// destry this game object.
Destroy (this.gameObject);
TotalZombies--;
}
and here is my other script where I am handling my levels and time etc.
using UnityEngine;
using System.Collections;
public class Generate : MonoBehaviour {
public GUIText Zombiesobject;
public string zombiesscore;
public GUIText countdown;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
zombiesscore = "Zombies Left: " + Status.TotalZombies.ToString ();
Zombiesobject.text = (zombiesscore);
Status.timeLeft -= Time.deltaTime;
if (Status.timeLeft <= 0.0f && Status.TotalZombies > 0)
{
countdown.text = "Game Over";
Application.LoadLevel(Application.loadedLevel);
}
else if (Status.timeLeft <= 10.0f && Status.TotalZombies > 0)
{
countdown.text = "Time left = " + (int)Status.timeLeft + " seconds" + " \n You are running out of time!";
}
else if (Status.timeLeft > 0.0f && Status.TotalZombies <= 0) {
countdown.text = "You win!";
Application.LoadLevel("level 2");
Status.TotalZombies=10;
Status.timeLeft=59.0f;
}
else
{
countdown.text = "Time left = " + (int)Status.timeLeft + " seconds";
}
}
}
Your problem is that you use static variables. static's are exactly like global variables. The only difference is that you need to access those through a Class name.
So what does that exactly mean? When you load your game, and when the class itself gets loaded the static variables are created and initialised. In your case it is TotalZombies set to 5 and timeLeft to 25f.
But those variables persist and never gets re-initialized as long as your game runs. Even if you do a Application.LoadLevel these variables and their values persists.
That means if you change those variables, and reload your level TotalZombies and timeLeft still have their last values.
Because of this i encourage to never use static variables. They easily introduce hard to spot bugs. Let's for assume a simple fix to your code.
You additionally add an initialization to your Start() Method. For example in your Status class you add.
void Start() {
TotalZombies = 5;
timeLeft = 25.0f;
}
In your case it could solve the problem completely, but you also could say this is just by accident or luck.
In Unity there don't exists an order in which Start() is called. It could for example still happen that the Start method in your Generate class is called first on loading a Scene. If you used Status.TotalZombies or Status.timeleft in Start to initialize something in Generate you still have your bug that your initializiation is wrong because it uses the variables from the previous run. The problem is Unity could sometimes first execute Status.Start() before Generate.Start() sometimes the other way around. That would lead to a bug that just sometimes occur and is extremely hard to debug.
If you knew the above you could also put your initialization in the Awake method. Because Awake methods will be called before any Start method. So this will be a better fix.
But there exists other problems. For example lets look into your Generate.Update() method. You for example directly do a Status.timeLeft -= Time.deltaTime; in your Update method. But when you for example have multiple GameObjects in your game that has the Generate Component it means timeLeft will be decreased multiple times in a single frame. If you have two Generate Components it means your time will run out twice as fast.
So even putting an initialization into Start or Awake can fix some bugs, but you still have different problems with statics
That is a reason why i encourage not to use static at all. So how do you fix also this problem? Instead of having static you should create Attributes of a class. And on top of that you should make all your attributes only set-able only from your own class. That also has an impact on other code. For example you could not reduce the timeLeft attribute anymore from Generate. That sounds like a disadvantage, but it forces to think you about how to change timeLeft correctly. In your case you do not really want that any class from everywhere can change timeLeft. It is a time that should be constantly reduced and it is just an error to reduce it multiple time. The result of that is. Your Status class should only change the timeLeft in the Update. The same goes for TotalZombies. It would be better to just have a method like IncrementTotalZombies and DecrementTotalZombies instead of doing Status.TotalZombies++ and so on. For example your Status class should now look like
public class Status : MonoBehaviour {
public int TotalZombies { get; private set; }
public float TimeLeft { get; private set; }
void Awake() {
this.TotalZombies = 5;
this.TimeLeft = 25f;
}
void Update() {
this.TimeLeft -= Time.deltaTime;
}
public void IncreaseTotalZombies() {
this.TotalZombies++;
}
public void DecreaseTotalZombies() {
if ( this.TotalZombies <= 0 ) {
throw new ApplicationException("Cannot decrease TotalZombies. Already 0. Possible Bug in your code.");
}
this.TotalZombies--;
}
}
Now the IncreaseTotalZombies or DecreaseTotalZombies sounds like overhead, but you can do a lot of extra checking here. For example check if the counter never gets smaller than zero. Because when it does, you have a bug somewhere in your code. For example Increasing your TotalZombies accidentally by two, or somewhere else decreasing it by two and so on. You also could implement a MaxTotalZombies attribute that ensures that you never get more Zombies as defined. And if that happens it will throw an Exception pointing it to your code directly where it happened.
It is also easier to identify bugs. Because Increasing it twice in a row looks wrong.
status.IncreaTotalZombies();
status.IncreaTotalZombies();
where following code can just look right
Status.TotalZombies += 2;
But if you do the above changes you will see your current Status.TotalZombies will not work anymore. You also have to change how to get an instance of your Status class. For this lets assume you create a GameObject in Unity named Status. Then in your Generate class you should add the following.
private Status status;
void Awake() {
this.status = GameObject.Find("Status").GetComponent<Status>();
}
Now you can replace the Status.TotalZombies++ and so on with status.IncreaseTotalZombies(). If you just want to get the values, you still just can write status.TimeLeft but setting the value status.TimeLeft -= Time.deltaTime will now throw an error. And you don't need to set it anymore because that is a behaviour that the Status class already handle in his Update Method.
Now additonally in your Generate class you had code like this.
Application.LoadLevel("level 2");
Status.TotalZombies=10;
Status.timeLeft=59.0f;
This didn't work as expected. Because when you call Application.LoadLevel() your new Scene gets called and the lines behind it was was never called. You could fix this be changing the order.
Status.TotalZombies=10;
Status.timeLeft=59.0f;
Application.LoadLevel("level 2");
Because your Status where static the value persists through loading. But the whole approach is still not really good. The Problem is you hardcode values in your code. And it seems you want different amount of Zombies and Time for every level. If you want that you can just add Attributes to your Status class that initialize your variables, And those variables are set-able through your Unity IDE. For example add the following attributes to your Status class.
public int _StartZombies = 5;
public float _StartTime = 25f;
If you add this to your Status class now in your IDE two TextBoxes will appear named Start Zombies and Start Time. In this boxes you can now enter how many Zombies or how much Start time your Level should have. The default values are 5 and 25 for those values. But those values didn't get applied on loading your level. To also apply those values when your level gets loaded change your Awake method to.
void Awake() {
this.TotalZombies = this._StartZombies;
this.TimeLeft = this._StartTime;
}
Now this.TotalZombies and this.TimeLeft always get the values that you have configured in your IDE. The only thing you now need to do is to write.
Application.LoadLevel("SomeLevel");
And you just can configure the amount of Zombies and time through your IDE! It also means you now have reusable Components. And you configure things where it belongs!
You also described that you want different conditions to loading a new Level. For example if a user is able to kill all the Zombies in a specific amount of time he directly jumps to Level 3 instead of Level 2 and so on. So how can you add this, without creating a lot of special classes?
At first you need a class on its own that just hold data. In your case you want a specific time and a definition which level gets loaded. So you could write something like this.
[System.Serializable]
public class LoadLevelData {
public float TimeLeft;
public string LoadLevel;
}
But in my opinion that logic belongs to the Status class, so what you now do is add the following to this class.
public LoadLevelData[] _NextLevels;
As soon as you add that to your code. In the Unity IDE you will see "Next Levels" with a "Cursor". You now can expand this cursor and a Size field will appear. You now can for example write 2 into it and it gives you Element 0 and Element 1. So Unity gives you the ability to create an Array of objects and you can create as many entries as you want from the IDE with any values that you want!
Now you could write a LoadNextLevel Method in such a way.
public void LoadNextLevel() {
foreach ( var level in this._NextLevels ) {
if ( level.TimeLeft > this.TimeLeft ) {
Application.LoadLevel(level.LoadLevel);
}
}
}
Now you can configure in the Unity IDE
Element 0:
Time Left -> 20
Next Level -> "Level 3"
Element 1:
Time Left -> 10
Next Level -> "Level 2"
You only need to call status.LoadNextLevel() when your game finished. And you can configure everything from the IDE. Also note. The order in which you fill your _NextLevel Array is important. In this case "Time Left" -> 20 must be before "10".
Related
Im in my 1st year in game design / dev ,
and my project is to make a simpler "binding of isaac" type of game 3D
I managed to make Dungeon Generation (with a lot of help from YouTube lol)
And now I'm working on a system that will detect in each room if all the enemies have died so that the doors in that room will be in "open" mode [bool] .
my idea was to create a C# script in the prefab of each room.
and it goes something like this > Timer [after 5sec to check if all enemies in rooms is dead] and loop until they are.
1- now the problem is if they are all dead in room X how to keep room Y closed ? [ because its the same prefab for the Dungeon generation]
2 - and what is the best way to check if they are all dead? [the easiest way for me is just by using: game tag "enemies" ] but using colliders / tags is very costly...
using Unity/c# btw
Any kind of help would be greatly appreciated
:)
Instead of using a timer to check periodically (if the player kills the last monster 0.01s after the previous check, they have to wait 4.99s before the doors open), have the room manage a collection of monsters in it, and when a monster dies have the monster notify the room. Psuedo code for the room:
private bool DoorsOpen = true;
private List<GameObject> MonstersInMe { get; set; }
private void GenerateMonster(GameObject monsterPrefab)
{
var monster = Instantiate(monsterPrefab);
monster.transform.SetParent(this.transform);
MonstersInMe.Add(monster);
}
public void RemoveMonster(GameObject monster)
{
MonstersInMe.Remove(monster);
if (MonstersInMe.Count == 0)
{
OnClear();
}
}
private void OnClear()
{
DoorsOpen = false;
// do whatever else
}
The room shouldn't be dependent on the Prefab after its been instantiated, so you shouldn't have to worry about room X confusing room Y due to being the same prefab.
In general instead of poll checking values or existence of things to be more efficient you want to make your code as event driven as possible.
I would have a dedicated component on the monsters so you can make all your code event based and track when one is destroyed:
public class Monster : MonoBehaviour
{
// event to be invoked when this monster is destroyed
public UnityEvent<Monster> whenDestroyed;
private void OnDestroy()
{
whenDestroyed.Invoke(this);
}
}
Then in your rooms you could do something like e.g.
public class BasicRoom : MonoBehaviour
{
// the default prefab to spawn, can be different for each room instance
[SerializeField] protected Monster defaultMonsterPrefab;
private readonly HashSet<Monster> monsters = new ();
// event to be invoked when this room is cleared
// per default needs at least one monster to have existed
public UnityEvent whenCleared;
// making this virtual - maybe different rooms do different things
// also adding an optional prefab parameter to allow to overrule the default fallback prefab
public virtual void SpawnMonster(Monster monsterPrefab = null)
{
// if monsterPrefab is not passed in use the defaultMonsterPrefab as fallback instead
if(monsterPrefab == null)
{
monsterPrefab = defaultMonsterPrefab;
}
// create the monster instance from the given prefab
// optionally as a child of he room
var instance = Instantiate(monsterPrefab, transform);
// keep track of the created monsters
monsters.Add(monster);
// and react when it gets destroyed
instance.whenDestroyed.AddListener(OnMonsterDestroyed);
}
// make this virtual - maybe different rooms do different / additional things
protected virtual void OnMonsterDestroyed(Monster monster)
{
monsters.Remove(monster);
CheckAllDestroyed();
}
// again making this virtual - maybe there is a special room that has different condition
// also make this public so you could still check it from somewhere else if needed
public virtual bool CheckIsCleared()
{
if(monsters.Count == 0)
{
whenCleared?.Invoke();
return true;
}
return false;
}
}
Now in the whenAllDestroyed field you can either assign callbacks via the Unity Inspector (like e.g. for Button.onClick) or via code like
someRoom.whenCleared.AddListener(OpenDoor);
By having some methods virtual you could have different room implementations with different behavior. One could e.g. not have monsters at all but simply play some sequence or whatever and automatically call whenCleared after X seconds
I'm making a 2D game where you're in the middle of the screen and you move round an endless green (screen) world and white cubes spawn randomly around you, and I have finished the game mechanics and a main menu and game over screens. The one thing I'm trying to add now is a high score. I did a bit of research and found PlayerPrefs is probably the way to do it. I have a seperate scene for my main menu and my gameplay level (which includes the game over screen). I have no error messages. I have created a HSSetter (High Score Setter) script on the high score text in the main menu screen:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Start is called before the first frame update
void Start()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
// Update is called once per frame
void Update()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
}
and in my score script which is in my actual game level, here's the bit where I try to create the high score:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class score : MonoBehaviour
{
public int scoreCount = 0;
public int highScoreIFA;
void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
void Update()
{
if (scoreCount >= highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
}
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
}
}
}
In AddToScore(), I increment scoreCount.
Through some debugging, I have found that everything in the HSSetter script works - when I change the highScoreText.text, the text on screen changes, which led me to believe the issue might be with the change of scenes? Thanks!
Multiple things you should do here
The first you already updated in your question afterwards: You had the condition wrong and always updated only
if(highScoreIFA > scoreCount)
which would almost always be the case.
Now you have changed it to
if(scoreCount >= highScoreIFA)
which still is not good since if the score is equal there is no reason to update it, yet.
I would rather use
if(scoreCount > highScoreIFA)
so only really update it when needed.
Secondly in both scripts do not use Update at all! That is extremely inefficient.
I would rather use event driven approach and only change and set stuff in the one single moment it actually happens.
You should only one single class (e.g. the score) be responsible and allowed to read and write the PlayerPrefs for this. I know lot of people tent to use the PlayerPrefs for quick and dirty cross access to variables. But it is exactly this: Quick but very dirty and error prone.
If you change the keyname in the future you'll have to do it in multiple scripts.
Instead rather let only the score do it but then let other scripts reference it and retrieve the values directly from that script instead
And finally you should use
PlayerPrefs.Save();
to create checkpoints. It is automatically done in OnApplicationQuit, bit in case your app is force closed or crashes the User would lose progress ;)
Might look like
public class score : MonoBehaviour
{
public int scoreCount = 0;
// Use an event so every other script that is interested
// can just register callbacks to this
public event Action<int> onHighScoreChanged;
// Use a property that simply always invoked the event whenever
// the value of the backing field is changed
public int highScoreIFA
{
get => _highScoreIFA;
set
{
_highScoreIFA = value;
onHighScoreChanged?.Invoke(value);
}
}
// backing field for the public property
private int _highScoreIFA;
private void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
// Only update the Highscore if it is greater
// not greater or equal
if (scoreCount > highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
// Save is called automatically in OnApplicationQuit
// On certain checkpoints you should call it anyway to avoid data loss
// in case the app is force closed or crashes for some reason
PlayerPrefs.Save();
}
}
}
}
Then your other script only listens to the event and updates its display accordingly. It is even questionable if both scripts should not rather simply be one ;)
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Reference your Score script here
[SerializeField] private score _score;
private void Awake ()
{
// Find it on runtime as fallback
if(!_score) _score = FindObjectOfType<score>();
// Register a callback to be invoked everytime there is a new Highscore
// Including the loaded one from Start
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnDestroy()
{
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnHighScoreChanged(int newHighScore)
{
highScoreText.text = $"High Score: {newHighScore}";
}
}
Please look at example - http://www.mathplayground.com/mancala.html
Can anyone suggest the logic to :
1) spawn objects at positions
2) Pick up all objects on click and distribute them one by one.
3) Is it better to create all objects or instantiate them on the fly. ?
I tried code below but it just instantiates all objects at once.
if (HoleHandler.gemCount_Hole1_Update_Flag == true)
{
foreach (GameObject g in gemList1)
{
Destroy(g);
//want to add a time delay of 2 secs here
}
if (gemCount_Hole1 > 0)
{
for (int i = 0; i < gemCount_Hole1; i++)
{
int Gem_prefabIndex = UnityEngine.Random.Range(0, 9);
gemList1.Add(Instantiate(Gem_prefabList[Gem_prefabIndex], new Vector2((xPos_Hole1 + (Random.Range(-20, 20))) * 2.0F, (-229 + (20 * i))), Quaternion.identity));
}
}
}
I'm not 100% sure of what you're trying to achieve but I will answer as best I can.
For a start, any gameobject you are going to be instantiating (spawning) at run time should ideally be done so from a prefab.
Secondly, to spawn them at random intervals you want to be checking if they should be spawned at different time frames. This can be achieved through a co-routine or the Update function. I would recommend Update if this is new to you.
Update is called every frame.. and it's with this that you can achieve timed events. You can use a variety of helper methods to determine the time since the last frame or the real time elapsed.
For example
public class MyGameObject : Monobehaviour {
void Start() {
//This is called first, use it to set up whatever you want.
}
void Update() {
//This will be called every frame.
//Each frame or time lapse will determine if I should spawn
// a new gameobject.
}
}
Update
After looking at the game you have linked in your post I can offer the following advice.
Something like the following may point you in the right direction.
public int[] gemsInCups = new int [] {4,4,4,4,4,4,0,4,4,4,4,4,4,0};
public void Distribute(int position){
int gems = gemsInCups[position];
for(int i = position + 1; gems > 0; i++){
gemsInCups[position] ++;
gems --;
//Check the end of the array has not been reached.
//If it has, start distributing again from the first position provided
// there are still gems to distribute.
}
}
You will need some additional logic to finish this.
What you should remember is, I usually find it much more manageable keeping my data and my view (gameobjects) under different scopes... but the view will change to reflect the data and does not directly alter it. Now you know how many gems there are in each cup, you can simply update this each frame.
This has me utterly confused. I am using unity3d and c# for the scripts and it seems as if the code is running twice per frame. However on button down I have a sprite change position and it only changes once at least I think it does.
I added the Debug in and I am getting results like this:
score 1 at 3.569991 at frame 168
score 2 at 3.57414 at frame 168
score 3 at 3.818392 at frame 183
score 4 at 3.820178 at frame 183
and so forth carrying on. I am not updating the score in any other scripts. There is more to this script but it is just printing the score out on screen.
Is there any reason why a script may run like this?
full script:
using UnityEngine;
using System.Collections;
public class Score : MonoBehaviour {
public static int highScore;
public static int myScore;
public static bool allowScore;
public GUIText myText;
public static bool WhichScene;
//only allows score to start when first object has passed player object
void OnTriggerEnter2D(Collider2D collisionObject) {
allowScore = true;
Debug.Log ("allowScore is true");
}
void Start () {
highScore = PlayerPrefs.GetInt("highScore");
int scale = Screen.height / 20;
myText.fontSize = scale;
}
//add 1 to score every switch
void Update () {
// need to stop score counting
if (DeathOnImpact.dead == true) {
allowScore = false;
} if (Input.GetMouseButtonDown (0) && allowScore == true && WhichScene == true) { // added SpawnerObjectMovement.WhichScene == true
//Input.GetMouseButtonDown (0)
//Input.GetKeyDown("space")
myScore = myScore + 1;
Debug.Log ("My score is " + myScore + " point(s)" + " at time:" + Time.realtimeSinceStartup + " at frame:" + Time.frameCount);
} if (myScore > highScore) {
highScore = myScore;
PlayerPrefs.SetInt("highScore", highScore);
}
myText.text = myScore.ToString ();
//myText.text = "Score: " + myScore.ToString ();
if (Score.WhichScene == false) {
int scale = Screen.height / 40;
myText.fontSize = scale;
myText.text = "practice mode";
}
}
}
The script is attached to a TriggerObject, a sprite, and a Gui Text
WhichScene referes to which button I pressed, 'play' for normal play or 'practice mode' for an easier version. Score is disabled for 'practice mode'.
UPDATE: I have just edited out everything that I have added since the problem arose and it has not been fixed. Im going to check all unity setting to see if anything has changed. It seems in build setting an old Scene which I deleted around when the problem arose is not selcted but just 'shadowed' so I cant select it. The Scene was an exact copy of the PlayScene. Is this a sign of the problem?
SOLUTION: It appears that separating the script into two smaller scripts has solved the issue. I am still unsure why it has only arisen now since it was working before as one, but oh well I guess. Thank you.
Based on your comments, where you have said that your script is attached to 3 GameObjects, that means that the Update() method is getting called 3 times per frame.
public class Score : MonoBehaviour {
public static int myScore;
You have declared myScore as a static int. This functionally means it will be shared by all instances of the Score script that run.
The Update() method of MonoBehaviour is called once per frame for every GameObject that has this script attached. You have 3 GameObjects with this script attached. Therefore, each will call Update() on their individual instance of the Score script.
I'm not sure what you exactly intend to happen, so it's hard for me to give any advice beyond pointing out the problem.
I think that you need to split this script into multiple scripts. This script is doing too much. It's violating the Single Responsibility Principal (SRP). This is one of the most important principles to follow in programming. I'd suggest splitting this into at least 3 scripts. I'd probably make the following scripts:
PlayerStatistics - Attach this script to your player object (I'm assuming that is the sprite you mentioned). It will simply hold the statistics, including score, for your player. There should only be one attached to each player.
ScoreBoard - Attach this script to your GUI component. It will take a reference to the PlayerStatistics. Notice this is a reference to the single instance that is on your Player. Your ScoreBoard script will only read the value of the score from the PlayerStatistics script.
ScoringTrigger - Attach this to your TriggerObject. It would also have a reference to the PlayerStatistics script. It would have the code that checks to see if scoring should be done, and updates the value of the PlayerStatistics script.
To avoid some events to run twice a frame use Events to get imput actions:
if(Event.current.type == EventType.MouseDown){
if(Event.current.button == 0 && allowScore && WhichScene) {
// do it once!
}
}
After reading and trying many different examples, I am still stuck with this fairly simple problem on getting a score from one level to the next in Unity.
I have a C# script that handles my game logic for a level:
First, I set my level variables:
public class GameLogic : MonoBehaviour {
public GUIText countText;
public GUIText targetCity;
public GUIText gameOverText;
public GUIText endScoreText;
public GUIText timerText;
public Texture2D bgImage;
private int count;
public GameObject cityPrefab;
List<MyCity> mycities;
public float finalScore; // I Want this value to be available when my next scene loads
private float startTime;
After this, my level code executes fine, until the GameOver condition is met (time is up). Then, this code executes:
public void GameOver ()
{
gameOverText.text = "time is up!";
endScoreText.text = "You have found " + count.ToString() + " cities. Good wrok!";
Destroy (GameObject.FindWithTag("EmptyCity"));
Destroy (GameObject.FindWithTag("City"));
Destroy (GameObject.FindWithTag("TargetCity"));
finalScore = count; // SO AT THIS STAGE, MY FINAL SCORE IS SET AND READY TO BE PASSED TO THE NEXT SCENE. QUESTION IS HOW?
StartCoroutine(Wait());
}
IEnumerator Wait() {
yield return new WaitForSeconds(7);
Application.LoadLevel (3);
}
So, I end my level with an updated value in public float 'finalScore'. So far so good. Now my problem starts: Level 3 loads and all gameobjects from level 2 are destroyed. In my game, level 3 is a simple Unity scene where I congratulate the player on his performance. I want to have access to that public float finalScore from my previous scene (level2).
I know I have to use Dontdestroyonload somewhere. But I don't know how. How and where do I create a GameObject that has the public float 'finalScore' in it? How do I call that GameObject in my new level so that I can do something like this in my new level:
public GUIText ContratsOnScore;
void SetContratsText() {
CongratsOnScore = "Congratulations, you scored" + (finalScoreFloatValue from Previous level).ToString();
The DontDestroyOnLoad method is callable on an object, not on a single variable. It represents the best possible approach for a value of that kind, that needs to be "exported" from your actual scene.
However, you can also use PlayerPrefs. They are intended for informations at a "higher" level of persistency, like flags for unlocked levels, final scores for a ranking system, or attributes of a customizable character. That's why the DontDestroyOnLoad way is better suited (for your "temporary" score), but you can use this one also. Basically, PlayerPrefs are information stored in the registry of your operative system.
Before exiting from the actual scene, save the score this way:
PlayerPrefs.SetInt("Player Score", finalScore);
And, when next scene starts, initialize score's variable by retrieving that information:
finalScore = PlayerPrefs.GetInt("Player Score");
(here a similar post)
Someone else said, and I shamelessly quote, use a static variable for your data in a C# class, and there you go...
(Still I'd prefer to pass parameters to LoadLevel itself, but that's another story)