Leveling in Unity - c#

I'm new at programming and don't know how to make a level counter that can be accessed in every scene.
I created a 3D game. When the player has completed level 1, then I want level 2 to be unlocked using this method:
When the player has completed the level, the value of the level counter changes to 2 and the trigger can access that level counter.
The below is not my code, but is just there to make it more clear:
var level = "level counter which is 2 in this case"
if level == 3
active the trigger (trigger teleports the player to level 3)
else {
floatMessage("You haven't finished level (?) yet.")
}

Looks like you need a persistance system.
There are many ways to save your game state. The easiest and most accessible remains the PlayerPrefs, although it is not optimal for more complex saves.
You could save your amount of levels unlocked (assuming it is incremental) by using this call :
PlayerPrefs.SetInt("levelsUnlocked", 3);
and then you can retrieve the value by using :
levelsUnlocked = PlayerPrefs.GetInt("levelsUnlocked", 0);
Note : The second parameter here is a default value assigned in case you haven't saved before.
Hope it helps. If you're looking to upgrade your save game, consider writing your own save file class and serialize it using JSON. https://docs.unity3d.com/Manual/JSONSerialization.html
The former option remains convenient for a simple project.

There's a few ways to handle this. My recommendation is to store game progress data to a file or database outside the scope of the scene (a good file-based DB solution is SQLite - here's a wiki post I made on how I implemented SQLite in one of my recent projects).
However, if you only wish game progress to persist during the life of the game (it always starts at level 1 when you start it), then Singletons and DontDestroyOnLoad are also a good solution, as #BugFinder mentioned.
public class LevelManager : MonoBehaviour
{
private static LevelManager instance;
public static LevelManager Instance { get => instance; }
int levelNum = 1;
public int LevelNum { get => levelNum; }
private void Awake()
{
//Check to see if an instance of the LevelManager already exists.
if(instance != null && instance != this)
{
Destroy(this.gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this.gameobject);
}
public void LevelComplete()
{
levelNum++;
}
}
As long as the above code is attached to a gameobject in your scene, you can call the following from another gameobject's script. You will notice that I encapsulate levelNum and instance. This is to enforce good practice in my other code. I know that the only script that can modify levelNum or instance is LevelManager, so I won't accidentally break something by changing one of those values in another script accidentally.
Be careful to only put the above script on a gameobject that is allowed to persist between scenes. I like to create an empty "Singletons" gameobject that just holds my singletons.
Example usage when defeating the final boss (if that's a way to beat your levels):
void BossDefeated()
{
LevelManager.Instance.LevelComplete();
}
Example usage when checking to see if a portal can be opened:
int portalNum = 3;
bool CheckPortal()
{
if(LevelManager.Instance.LevelNum >= portalNum)
return true;
return false;
}

Could you just use a Static int if you just want something quick:
public static int level;
Then from anywhere, call the class name i.e. LevelManager.level;
If you want to have it trigger something, you are better off having a callback of some sort, and making it private with a public get, which in turn calls something like a UnityEvent. This answer will probably not be very popular, but it is quick to do :)

Related

How to control multiple GameObjects

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();

Unity How to have only one source audio?

I have a source audio with this code:
public class Sound : MonoBehaviour {
Static Sound instance;
void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = this;
DontDestroyOnLoad(gameObject);
}
}
So when i change scenes the audio starts playing again. How do i limit it to having only 1?
Edit: How do i fix that? i get this error
1 - Unexpected symbol instance' in class, struct, or interface member declaration 2 - Unexpected symbol ;' in class, struct, or interface member declaration
Option 1:
The Singleton Pattern is likely to be your best choice when it comes to things like this. The intent of the Singleton Pattern is to restrict a class to only one Instance of itself. To read more about the pattern, see the Wiki on it.
It looks like you are already doing this (after your first edit) but the thing you're going to want to change is the capitalization of Static in your code. Static and static are two different things. For your sake, you are looking for static. This should fix your problem/error and allow you to use the Singleton Pattern correctly.
Option 2:
Also, another option if you want to avoid the typical Singleton Pattern would be to search through all GameObjects for any that have you Sound Class/Component on it. Then if there is already on in scene, have the GameObject that is trying to instantiate, destroy itself. You can do this with the following code:
public class Sound : MonoBehaviour
{
void Awake()
{
int numSoundInstances = FindObjectsOfType<Sound>().Length;
if (numSoundInstances > 1)
{
Destroy(this.gameObject);
}
else
{
DontDestroyOnLoad(this.gameObject);
}
}
}
In my opinion, this is way less efficient then using the Singleton Pattern. This is because using FindObjectsOfType forces the code to check every GameObject that is currently in the game. Depending on the amount of GameObjects in the game, this can be very taxing on the system.

int variable resetting when loading another scene in unity

I created a football score calculator. I have a button that changes the scene, but when I change the scene and reopen it the score value is reset to 0. Here is the code:
public class Main : MonoBehaviour {
public Text plusUp;
public int value = 0;
public void button(int sc)
{
SceneManager.LoadScene(sc);
}
public void plus()
{
value++;
plusUp.text = value.ToString();
}
}
1. You can use the method Object.DontDestroyOnLoad on the object that hold the variable you want to save. It will keep the GameObject this script is attached too alive when another scene is loaded:
void Awake()
{
DontDestroyOnLoad(this.gameObject);
}
See the documentation for more informations
You can also make a Singleton but this design pattern is a little more complex as Unity will destroy it when you load another scene. You still have to use DontDestroyOnLoad see how to implement this pattern on their GitHub page
2. Or you can save the value on the disk before loading another scene and then load the value with PlayerPrefs helpers methods:
public int value = 0;
void Awake()
{
//Load the saved score (this value will be saved even if you restart the app)
value = PlayerPrefs.GetInt("Score");
}
public void button1(int sc)
{
//Save your value before you load the scene
PlayerPrefs.SetInt("Score", value);
SceneManager.LoadScene(sc);
}
See the documentation for more informations on the types.
There's a few core concepts that would prove useful to understand this issue.
Scope: Each scene operates in it's own scope. Any variables, objects, or changes that occur in one scene do not automatically transfer to another scene. When you start a scene, all objects in the scene are instantiated and initalized, and their Awake()/Start() methods are called if they are Monobehaviours.
Initialization - When an object is instantiated, it is initialized with constructors or default values. Monobehaviours do not have constructors, so any variables will defer back to default values.
Data persistence - When you change scenes, all game objects in the previous scene are destroyed, while all objects in the new scene are instantiated and initialized. Because all objects in the previous scene are destroyed, any values set on those objects disappear. You can prevent a GameObject from being destroyed with DoNotDestroyOnLoad(), but that does not overwrite the objects defined in the new scene. It is usually not advised to use DoNotDestroyOnLoad() as a core part of your game logic, as it often results in scenes being dependent on one another ("scene 1 has to define the values of a GameObject and pass it to scene 2 to be usable" = bad practice).
Solving your problem
It looks like you want score to persist as a value regardless of scene. Since all GameObjects and Monobehaviours are scoped within the scene, you can:
Force the object to be scene-analagous using the Singleton pattern.
Store score data to a file whenever it changes, and read from that file in the Start() method.
My recommended approach: Use a ScriptableObject to hold the score, and refrence that object when changing the score and updating your gameObjects. ScriptableObjects are scoped at the project level, so they automatically persist between scenes.

Checking a private value in another class [C#]

I'm making a simple dart game in the console for an assignment where I am to practice using private lists and variables everywhere. The basic flow of the program for some context is as follows:
User lands in a menu
User chooses from 1-4. (1 = Add player, 2 = Add CPU, 3 = Start game, 4 = Quit)
Game starts. Players manually add their 3 throws per turn, and CPU gets theirs randomly.
When a player or CPU reaches 301 score, the loop ends and you now see every throw made by the winner.
UML diagram for class structure context: https://i.imgur.com/bL5pZV5.png
Everything is pretty much complete. I've made the program to such an extent that both players and CPUs are getting random values (are treated as CPU players), it prints out everything correctly and follows the flow to the end.
My issue now is that I want to be able to reach the is_CPU variable which is private in the Player class from the Game class and use it in an IF check, directing whether or not the values are manually added or randomly generated.
Pseudo-code:
FOREACH (var player in player_list)
IF (is_CPU == TRUE)
THEN Assign random values
ELSE
THEN Manually enter values
I tried messing around with the get-set stuff, but I don't fully understand how to use them and how they work. I have looked around on here and still don't see how I should be using them in this case, if at all.
I can think of one way to work around this and that is by making a method just for this where it checks that value and returns true/false, but that seems like a 'lazy' or improper way to do this, and comes with several downsides. I feel like there should be a better way to do this, one that won't come back to bite me in the ass later. Hopefully there is, and I can learn it by asking here.
EDIT: The variables and lists HAVE to be private. It is part of the exercise where we learn how to handle these.
I think you just want a get property on your player class.
public bool IsCpu { get { return is_CPU; }}
See also c# properties
In order to access private members of a class instance, you either have to define properties on that class with a public getter, as follows:
public class Player
{
private Boolean m_IsCPU;
public Boolean IsCPU
{
get { return m_IsCPU; }
}
// ...
}
or to change these members in order to make them public, as follows:
public class Player
{
public Boolean IsCPU;
// ...
}
Whatever you choose (I suggest you to go for the first approach), in any part of your code in which you have to check the IsCPU property/member for each instance of the Player class, you can just do as follows:
foreach (Player player in players)
{
if (player.IsCPU)
// Do Something...
else
// Do Something Else...
}
Some interesting links:
Access Modifiers
C# Properties
Why prefer Properties to public variables?
Redesign your app like this:
Class Game
List<IPlayer> Players
ShowMenu()
AddPlayer()
StartGame()
IsGameOver(): boolean
Interface IPlayer
Turn() : Score
CpuPlayer: IPlayer
Player: IPlayer
Split your logic into two different classes: you dont need to check. Treat every player the same in the game. Later if you come up with 'NetworkPlayer', 'AIPlayer', 'SuperPlayer' you can easily add to your system.
In your menu:
switch (userInput) {
case AddUser:
AddPlayer(new Player());
break;
case AddCpuPlayer:
AddPlayer(new CpuPlayer());
break;
In your gameplay:
while (!IsGameOver)
{
var nextPlayer = ... next player
nextPlayer.Turn() ...
}

Duplicates because of DontDestroyOnLoad()

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

Categories

Resources