Load scene with param variable Unity - unity3d

In my game there is a map view that contains a 50x50 grid of tiles. When you click on the tile you get sent to that tiles view and attack things, etc. The only difference between these "tiles" as far as the code will be concerned is the tiles ID, aka. which number on the grid. That number will be passed to the server on init to handle the rest.
Obviously, with that being the only difference in tiles it would be a mistake to create a scene "1", scene "2"...scene "2500" and call SceneManager.LoadScene to switch to the specific tile view.
I could use DontDestroyOnLoad(); when the tile is clicked to preserve the tile ID on scene switch but 1) it only accepts gameobjects not just an int variable 2) I don't need/want to preserve that variable for anything more than the init in tile view. So while it could work it seems like overkill.
Is there a better practice for essentially just passing a parameter to a scene load?

You can make a static class that holds the information for you. This class will not be attached to any GameObject and will not be destroyed when changing scene. It is static which means there can only be ONE of it; you can't write StaticClassName scn = new StaticClassName() to create new static classes. You access them straight through StaticClassName.SomeStaticMethod() for example and can be accessed from anywhere. See this example on how to store a value in a variable, change scene and then use it in that scene (please note the added using UnityEngine.SceneManagement;):
A normal Unity script attached to a GameObject in Scene "Test":
using UnityEngine;
using UnityEngine.SceneManagement;
public class TestingScript : MonoBehaviour
{
void Start()
{
StaticClass.CrossSceneInformation = "Hello Scene2!";
SceneManager.LoadScene("Test2");
}
}
A new static class (not inheriting from monobehaviour) that holds information:
public static class StaticClass
{
public static string CrossSceneInformation { get; set; }
}
A script attached to a game object in scene "Test2":
using UnityEngine;
public class TestingScript2: MonoBehaviour
{
void Start ()
{
Debug.Log(StaticClass.CrossSceneInformation);
}
}
You don't need to have the entire class static (if you for some reason need to create more instances of it). If you were to remove the static from the class (not the variable) you can still access the static variable through StaticClass.CrossSceneInformation but you can also do StaticClass sc = new StaticClass();. With this sc you can use the class's non-static members but not the static CrossSceneInformation since there can only be ONE of that (because it's static).

Just wanted to share a premade solution for this as the question is already answered, so others or maybe even the OP use it.
This script uses a key-value approach for storing and modifying variables inside a static class that means it is available across scenes so you can use it as a cross-scene persistent storage, so all you need to do is import the script to your Unity project and use the API (check below for an example):
using System.Collections.Generic;
/// <summary>
/// A simple static class to get and set globally accessible variables through a key-value approach.
/// </summary>
/// <remarks>
/// <para>Uses a key-value approach (dictionary) for storing and modifying variables.</para>
/// <para>It also uses a lock to ensure consistency between the threads.</para>
/// </remarks>
public static class GlobalVariables
{
private static readonly object lockObject = new object();
private static Dictionary<string, object> variablesDictionary = new Dictionary<string, object>();
/// <summary>
/// The underlying key-value storage (dictionary).
/// </summary>
/// <value>Gets the underlying variables dictionary</value>
public static Dictionary<string, object> VariablesDictionary => variablesDictionary;
/// <summary>
/// Retrieves all global variables.
/// </summary>
/// <returns>The global variables dictionary object.</returns>
public static Dictionary<string, object> GetAll()
{
return variablesDictionary;
}
/// <summary>
/// Gets a variable and casts it to the provided type argument.
/// </summary>
/// <typeparam name="T">The type of the variable</typeparam>
/// <param name="key">The variable key</param>
/// <returns>The casted variable value</returns>
public static T Get<T>(string key)
{
if (variablesDictionary == null || !variablesDictionary.ContainsKey(key))
{
return default(T);
}
return (T)variablesDictionary[key];
}
/// <summary>
/// Sets the variable, the existing value gets overridden.
/// </summary>
/// <remarks>It uses a lock under the hood to ensure consistensy between threads</remarks>
/// <param name="key">The variable name/key</param>
/// <param name="value">The variable value</param>
public static void Set(string key, object value)
{
lock (lockObject)
{
if (variablesDictionary == null)
{
variablesDictionary = new Dictionary<string, object>();
}
variablesDictionary[key] = value;
}
}
}
And you can use it on a script that is inside your Main Menu scene let's say:
public class MainMenuScript : MonoBehaviour
{
void Start()
{
// Load a sample level
LoadLevel(12);
}
public void LoadLevel(int level)
{
GlobalVariables.Set("currentLevelIndex", level);
UnityEngine.SceneManagement.SceneManager.LoadScene("Game");
}
}
And the other one is inside Game scene:
public class GameScript : MonoBehaviour
{
void Start()
{
int levelIndex = GlobalVariables.Get<int>("currentLevelIndex");
Debug.Log(levelIndex); // Outputs 12
}
}
So, the data is assigned in the Main Menu scene is accessible from the Game or any other scene.
Learn more about the script with usage examples >

Maakep! Perfect and easy code!
But your method to load scene not working.
You can use another method:
UnityEngine.SceneManagement.SceneManager.LoadScene("Vuforia-4-Spheric");

Related

Why using getComponent to call objects in Unity?

I am new in unity, I'm just wondering why we call the player object like this :
private void OnTriggerEnter(Collider other)
{
// if the player hit the enemy then destroy the ememy
if (other.tag == "PLAYER" ) {
// like this
other.transform.GetComponent<Player>().damage();
}
while we can make the damege method static and call it like this :
private void OnTriggerEnter(Collider other)
{
// if the player hit the enemy then destroy the ememy
if (other.tag == "PLAYER" ) {
//other.transform.GetComponent<Player>().damage();
Player.damage();
}
why we call the object like this what the point
other.transform.GetComponent<Player>().damage();
does it effect performance ? or is it just another available way to call objects
I will try to explain this the best I can. This explanation isn't perfect, but I worded it so it makes the most sense.
A class is a file that contains code. There are two "types" of classes, static and non-static. Static classes can be accessed anywhere, but there is only ever 1 copy. For example, use UnityEngine.Mathf. That is a static class, so you can just do Mathf.(whatever).
The non-static class can have multiple copies. Non-static classes can have static methods and fields, which can be accessed like you would a static class. For most basic applications, these classes just use normal methods. These methods are instance-dependent.
For example, take a non-static Player class.
public class Player : MonoBehaviour {
//This is accessed using Player.instance
public static Player instance;
//This function only runs on the script you place on a GameObject
public void Start(){
//Sets the static instance to this instance
instance = this;
}
}
You can then access the active player instance with any other script (as long as the instance variable was set before)
public Player GetPlayer() {
//As you can see, you don't need a reference to a Player; you can just use the static instance.
Player instance = Player.instance;
return instance;
}
In conclusion, using Player.Whatever references the static version. Using playerInstance.Whatever references a runtime instance. If you will only have one runtime instance of a class, you can use the example above to allow other classes to access the instance.

Trying to record level time data and print to screen at the end Unity 2D

So I have a 2D platformer parkour game which holds a timer when player starts level. I have 5 levels and at the end of each level, I want to keep the last time value and display them at the end of the game.
So far, I tried to hold the last time value when player triggers the End portal and store them in an array in the code below. Here are the scripts:
Time Data Manager Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class timeDataManager : MonoBehaviour
{
public string[] timeDataArr;
void Start(){
}
void Update(){
Debug.Log(timeDataArr[SceneManager.GetActiveScene().buildIndex-1]);
}
public void AddTimeData(string timeData, int levelBuildIndex){
timeDataArr[levelBuildIndex-1] = timeData.ToString();
}
}
End Portal Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class EndPortal : MonoBehaviour
{
public AudioSource aSrc;
private int sceneNumber;
public GameObject bgAudio;
public Text scoreText;
string textData;
public timeDataManager tDManager;
void Start(){
sceneNumber = SceneManager.GetActiveScene().buildIndex;
tDManager = GameObject.FindGameObjectWithTag("TimeDataManager").GetComponent<timeDataManager>();
}
void OnTriggerEnter2D(Collider2D col){
if (col.gameObject.tag == "Player"){
aSrc.Play();
Destroy(bgAudio);
textData = scoreText.text;
Debug.Log(textData);
Debug.Log(sceneNumber);
tDManager.AddTimeData(textData,sceneNumber);
SceneManager.LoadScene(sceneBuildIndex:sceneNumber+1);
}
}
}
As I said before, I tried to keep all timer values at the end of each level and store them in an array in my timeDataManager script. But it's not working.
Any ideas on how to fix? Or do you have any other ideas? Thanks a lot for your time.
Issylin's answer is a better solution for your needs but i want to mention couple of things about your code.
One thing about your timeDataArr. If you don't touch it in inspector, you need to initialize it first. So let's say, if you want to hold 5 levels of time data, you need to do something like;
public string[] timeDataArr = new string[5];
or
public class timeDataManager : MonoBehaviour
{
public string[] timeDataArr;
public int sceneAmount_TO_HoldTimeData = 5;
void Start()
{
timeDataArr = new string[sceneAmount_TO_HoldTimeData];
}
}
or
public class timeDataManager : MonoBehaviour
{
public string[] timeDataArr;
public int sceneAmount_NOT_ToHoldTimeData = 1;
void Start()
{
timeDataArr = new string[SceneManager.sceneCountInBuildSettings - sceneAmount_NOT_ToHoldTimeData];
}
}
Another thing is you need to be careful about levelBuildIndex-1. If you call that in first scene,
SceneManager.GetActiveScene().buildIndex
will be 0 and levelBuildIndex-1 will be -1 or in Debug part it will be timeDataArr[-1] but array indexes must start with 0. So it will throw an error.
One more thing, this is not an error or problem. Instead of this part
tDManager = GameObject.FindGameObjectWithTag("TimeDataManager").GetComponent<timeDataManager>();
you can do
tDManager = FindObjectOfType<timeDataManager>();
There are several way to achieve what you're trying to do.
The easier would be to use a static class for such a simple data like the one you're carrying through your game.
using System.Collections.Generic;
public class Time_Data
{
/// TODO
/// Add the members
/// e.g. your time format as string or whatever,
/// the scene index, etc
}
public static class Time_Manager
{
private static List<Time_Data> _times;
public static void Add_Time(in Time_Data new_time)
{
_times.Add(new_time);
}
public static List<Time_Data> Get_Times()
{
return _times;
}
public static void Clear_Data()
{
_times.Clear();
}
}
Use it like any other static methods within your OnTriggerEnter2D call.
Time_Manager.Add( /* your new data here */ );
The other way, if you intend to stay with a game object within your scenes, would be to use the DontDestroyOnLoad method from Unity so your game object which has your script timeDataManager would remain.
Note that in your code, using string[] might be unsafe. For your use of it, consider using a List<string>.

How to reference a gameObject inside a prefab? Using GameObject.Find does not work

I am trying to use GameObject.Find within a prefab to reference an object that exists in the scene. The name of the object is exactly the same as the actual object.
LoseLives loseLives;
GameObject loseLivesing;
private void Awake()
{
loseLivesing = GameObject.Find("MasterManager");
}
private void Start()
{
loseLives = loseLivesing.GetComponent<LoseLives>();
target = Waypoints.waypoints[0];
}
loseLives.LoseLife();
Null Reference
I get a null reference when "loseLives.LoseLife();" runs.
Any help with solving the no reference problem would be greatly appreciated!
If you think there is a better way to create the connection between a script on a prefab and a script on a in-scene gameObject could you please explain it or point me to a tutorial, because in all my research I did not have any luck.
Thank you!
It seems like your LoseLives is a singular, constant object.
First Method
Simply making use of the inspector by exposing the fields.
public class YourScript : MonoBehaviour {
[SerializeField]
private LoseLives loseLives;
// ..
private void Start() {
loseLives.LoseLife();
}
// ..
}
It comes with the problem of needing to constantly drag your constant object into the inspector whenever you need it.
For a Prefab, it works if the target object is part of the Prefab, or the Prefab is generated in the same scene as the targeted object.
Second Method
Using singletons.
LoseLives.cs
public class LoseLives : MonoBehaviour {
public static LoseLives Instance {
get => Instance;
}
private static LoseLives instance;
private void Start() {
instance = this;
}
public void LoseLife() {
// ...
}
}
YourScript.cs
public class YourScript : MonoBehaviour {
// ..
private void Start() {
LoseLives.Instance.LoseLife();
}
// ..
}
This works if you have a global, singular instance of LoseLives object.
Even though it might not be the best approach, I found a workaround for my issue.
I am using a static variable that resides in the other script (CurrencyManager), calling it from the LoseLives script.
Instead of:
loseLives.LoseLife();
I do:
CurrencyManager.playerHealth -= 1;
Where CurrencyMananger is the other script and playerHealth is a static variable that is in the CurrencyManager script. This works because a static variable does not require the objects to be referenced in the inspector.
From now on, when I have a problem where I want to reference a gameObject within a script that is on a prefab, I will consider using static variables in my solution.

How to access Cinemachine Basic MutliChannel Perlin Noise?

how can I access the component of the "CinemachineFreeLook Camera's CinemachineBasicMultiChannelPerlin' for me to change some variables in the C#?
Like changing the Amplitude Gain or Frequency Gain
From the CinemachineVirtualCamera script, I understand that the components of the virtual cameras are attached on a hidden child gameobject
/// <summary>This is the name of the hidden GameObject that will be created as a child object
/// of the virtual camera. This hidden game object acts as a container for the polymorphic
/// CinemachineComponent pipeline. The Inspector UI for the Virtual Camera
/// provides access to this pipleline, as do the CinemachineComponent-family of
/// public methods in this class.
/// The lifecycle of the pipeline GameObject is managed automatically.</summary>
public const string PipelineName = "cm";
So I suggest you to get the component like this:
using UnityEngine;
using Cinemachine;
public class YourScript: MonoBehaviour
{
[SerializeField]
private CinemachineVirtualCamera m_vcam = null;
[System.NonSerialized]
private CinemachineBasicMultiChannelPerlin m_noiseComp = null;
void Start()
{
m_noiseComp = m_vcam.GetComponentInChildren<CinemachineBasicMultiChannelPerlin>();
if(noiseComp == null)
Debug.LogError("No MultiChannelPerlin on the virtual camera.", this);
else
Debug.Log($"Noise Component: {noiseComp}");
}
}

Global Scriptable Object

Suppose I have this ScriptableObject to represent a list of cards:
using UnityEngine;
using System.Collections;
[CreateAssetMenu(fileName = "CardList", menuName = "Inventory/List", order = 1)]
public class CardList : ScriptableObject {
public string[] cardName;
public Sprite[] cardSprite;
}
Then I create the ScriptableObject and fill it with all my cards information.
Is there a way to modify this code so that any script have access to it statically? For example, is it possible to give CardList a singleton behaviour?
I don't want to create another class, it would be easy to create a ScriptableObjectManager that could reference CardList. I'd rather call something like CardList.instance.cardName[i] directly.
Definitely, you can create it as singleton so for this.
Suppose that we have Unity Project that has file hierarchy like
UnityProject
- Assets (directory)
Scripts
SampleScriptableObject.cs
- Resources (directory)
SampleScriptableObject.asset
- Project
- ..etc
in "SampleScriptableObject" class will be like
public class SampleScriptableObject<T> : ScriptableObject where T : ScriptableObject
{
private static T _instance;
public T GetInstance()
{
if (_instance == null)
{
_instance = Resources.Load(typeof(T).Name) as T;
}
return _instance;
}
}
you can use scriptable object as singleton as you want.