I'm making a game in Unity3D with C# for mobile devices and can't figure out how to check which scene was loaded before the current scene. I need to check this to change the spawn point from the player gameobject. First I added a simple script to my buttons (loadnextscene and loadprevscene)
public class SwitchScene : MonoBehaviour {
public int sceneNumber;
public void LoadScene(int sceneNumber) {
Application.LoadLevel(sceneNumber);
}
}
A second scripts handles the touch input from the user and changes the movement of the player object.
So, for example: If the player clicks on the "load previous scene" button in the second Level to switch to the first level again, I want to set the spawn point of the player object on the right half on the screen and not on the left side like when the game was started the first time.
I tried it with Singleton and PlayerPrefs, but it did not work out.
You need to save the scene number to some variable before LoadScene, then check it after the scene loaded.
The only problem is that this variable will be destroyed after the new scene is loaded. So, to prevent it, you can use DontDestroyOnLoad. Here is what you do:
First, create a new empty game object, and attach the following script to it:
using UnityEngine;
using System.Collections;
public class Indestructable : MonoBehaviour {
public static Indestructable instance = null;
// For sake of example, assume -1 indicates first scene
public int prevScene = -1;
void Awake() {
// If we don't have an instance set - set it now
if(!instance )
instance = this;
// Otherwise, its a double, we dont need it - destroy
else {
Destroy(this.gameObject) ;
return;
}
DontDestroyOnLoad(this.gameObject) ;
}
}
And now, before you load, save the scene number in the Indestructable object:
public class SwitchScene : MonoBehaviour {
public int sceneNumber;
public void LoadScene(int sceneNumber) {
Indestructable.instance.prevScene = Application.loadedLevel;
Application.LoadLevel(sceneNumber);
}
}
And last, in your scene Start() check Indestructable.instance.prevScene and do your magic accordingly.
More info here:
http://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
*I did not compile the code, so there may be some errors, but this is the general idea.
Why did the PlayerPrefs approach did not work?
I think its the easiest way to solve your problem.
public class FirstLevel : MonoBehaviour {
public void Start() {
PlayerPrefs.SetString("SceneNumber", SceneManager.GetActiveScene().name);
}
}
And then in the second scene simply read the saved PlayerPrefs
public class SecondLevel : MonoBehaviour {
string PrevScene;
public void Start() {
PrevScene = PlayerPrefs.GetString("SceneNumber");
// if there will be a third scene, etc.
PlayerPrefs.SetString("SceneNumber", SceneManager.GetActiveScene().name);
}
public void GoToPrevScene() {
SceneManager.LoadScene(PrevScene);
}
}
You can solve this problem with a single static member variable in the SwitchScene class. No need for the singleton pattern or DontDestroyOnLoad.
public class SwitchScene : MonoBehaviour
{
public int sceneNumber;
private static int previousScene;
private int oldPreviousScene;
void Start()
{
oldPreviousScene = previousScene;
previousScene = sceneNumber;
}
public void HandleLoadPrevButtonClick()
{
SceneManager.LoadScene(oldPreviousScene);
}
}
Related
I am creating a game where the first scene will have a generated map saved as a 2D array, then the next scene takes care of all the combat. Once that's done, user needs to go back to the first scene and see the same map. I have followed Unity's tutorial on Data persistence, and as you can see in the code below I am checking twice if an Instance is not null and destroying the object if its not.
The problem is, that every time I go back from combat scene to the map scene, it creates another instance of WorldMapManager, and generates another map on top of the existing one.
Where am I going wrong to stop creation of unnecessary extra copies of WorldMapManager object?
public class WorldMapManager : MonoBehaviour
{
public static WorldMapManager Instance { get; private set; }
public static int _mapSizeX = 4;
public static int _mapSizeY = 4;
public static int _playerScore;
public static int _playerCells;
public static int _enemyScore;
public static int _enemyCells;
public static GameObject[,] _map;
public static GameObject _startingPoint;
public static Vector2Int _playerBase;
// Awake is called before Start
void Awake()
{
if (WorldMapManager.Instance != null)
{
Destroy(gameObject);
}
else
{
DontDestroyOnLoad(gameObject);
}
SceneManager.sceneLoaded += OnLevelFinishedLoading;
}
// Initialize the map
public void InitMap()
{
// Map Generation happens here
}
// OnSceneLoaded is called when a scene is loaded
void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode)
{
if (scene.name == "WorldMap")
{
if (WorldMapManager.Instance != null)
{
Destroy(this);
} else
{
InitMap();
}
}
}
}
I have been stuck on this for several days trying different approaches, but I'm all out of ideas, so thank you very much for any help.
By the looks of your code it seems you never ever set the WorldMapManager.Instance. So it will always be null and always go on to DontDestroyOnLoad(gameObject);.
So add the assignment to Instance too.
Instance = this;
DontDestroyOnLoad(gameObject);
For the same reason in void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode) the InitMap() will be executed. No need to set anything to Instance here ofcourse.
So for some reason when I try to access variables from another script the text is showing what I put but when I change things in the editor the accrual values don't change. even if in the code I put playerPoints to 9 when I run it its 0 during a collision. I have all the scripts and objects connected up, and when I try to flip where the variables are, make them in the destroy script, for some reason, it doesn't work. it wont let me use public BallsText bt; it will return an error even though it works fine for the other. I'm sorry if all of these are really basic questions but I've looked as far as I can on the internet and I cant find anything, so any help would be appreciated.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BallsText : MonoBehaviour
{
public Destroy destroy;
// public int playerPoints = 0;
//public int enemyPoints = 0;
//int playerPoints = 0;
//int enemyPoints = 0;
public Text playerPointsText;
public Text enemyPointsText;
void Update()
{
playerPointsText.text = destroy.playerPoints.ToString();
enemyPointsText.text = destroy.enemyPoints.ToString();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Destroy : MonoBehaviour
{
public int playerPoints = 9;
public int enemyPoints = 0;
void OnCollisionEnter2D(Collision2D col)
{
if (this.name == "Destroy.cs")
{
Debug.Log("i have a doughnut");
}
if (col.gameObject.name == "Player")
{
Debug.Log(playerPoints);
playerPoints++;
Debug.Log(playerPoints);
Debug.Log("at least im a dounut");
Destroy(this.gameObject);
}
else if (col.gameObject.name == "Enemy Zone")
{
enemyPoints++;
Destroy(this.gameObject);
}
}
}
You can hide them from the inspector's view with HideInInspector while still being public.
[HideInInspector] public int playerPoints = 9;
You can also use the reset script to return the setting of public numbers to the first state.
Like Everts said, you had the value at zero and created an instance of the script. Now when you change the script Unity will serialize (save) all the public/serialized fields, then reload the script, then deserialize (load) all the public/serialized fields it previously saved. This means your values are locked into the instance that you previously used. If you make a new instance, you get the current values, and if you reset the script you'll get the current values.
This has also bitten me enough times that I don't set default values when variables are declared anymore. If you have a value that you want to use, set those values in Start() or Awake().
The advantage here is that whatever is in the script will get overwritten when play mode starts. The disadvantage is that you can't customize those values on a per-instance basis anymore, because all instances of the script will all load the same default values when play mode starts. If this matters to you, and you want to be able to customize those values, then unfortunately you'll need to go to each script and change those values manually.
If you use a property with an automatic backing field then you won't be able to see it in the editor
public class Destroy : MonoBehaviour
{
public int playerPoints{get; set;} = 9; // Can't see this in the editor
public int enemyPoints{get; set;} = 0; // Can't see this in the editor
If you use a property with an explicit backing field then you can expose the backing field to the editor with the [SerializeField] tag, but then you've got the same problem you've got now - the editor will serialize that field and subsequent changes to the script won't affect instances:
public class Destroy : MonoBehaviour
{
[SerializeField]
private int playerPoints_ = 9; // Instances will "lock in" values and later changes to the script here won't take effect
[SerializeField]
private int enemyPoints_ = 0; // Instances will "lock in" values and later changes to the script here won't take effect
public int playerPoints
{
get=>playerPoints_;
set{playerPoints_ = value;}
}
public int enemyPoints
{
get=>enemyPoints_;
set{enemyPoints_ = value;}
}
If you keep the fields public (and thus exposed to the editor) but set the values at runtime in Awake() or Start() then you can see the values in the editor but the editor values for all instances will be overridden when play mode starts:
public class Destroy : MonoBehaviour
{
public int playerPoints; // Doesn't matter what the value is here on instantiation becuase you'll override it on Awake()
public int enemyPoints; // Doesn't matter what the value is here on instantiation becuase you'll override it on Awake()
public void Awake()
{
playerPoints = 9; // Will override *every* instance's values with this.
enemyPoints = 0; // Will override *every* instance's values with this.
}
:EDIT:
I'll add too that repeatedly polling is wasteful. If you use events then you can subscribe and get notifications when there's something to see. Consider instead:
public class Destroy : MonoBehaviour
{
private int playerPoints = 9;
private int enemyPoints = 0;
public System.EventHandler<int> OnPlayerPointsChanged;
public System.EventHandler<int> OnEnemyPointsChanged;
void OnCollisionEnter2D(Collision2D col)
{
if (this.name == "Destroy.cs")
{
Debug.Log("i have a doughnut");
}
if (col.gameObject.name == "Player")
{
Debug.Log(playerPoints);
playerPoints++;
OnPlayerPointsChanged?.Invoke(this, playerPoints);
Debug.Log(playerPoints);
Debug.Log("at least im a dounut");
Destroy(this.gameObject);
}
else if (col.gameObject.name == "Enemy Zone")
{
enemyPoints++;
OnEnemyPointsChanged?.Invoke(this, enemyPoints);
Destroy(this.gameObject);
}
}
}
Now Destroy has two public events that fire when the public or enemy points change. Anyone subscribing to those events will get notified when the points change, and part of the event notification is the current point value.
Then your other script subscribes to the events and does whatever they need to when they receive that event. Here they'll convert the points .ToString() and update the Text values:
public class BallsText : MonoBehaviour
{
public Destroy destroy;
public Text playerPointsText;
public Text enemyPointsText;
private void Start()
{
destroy.OnPlayerPointsChanged += PlayerPointsChanged;
destroy.OnEnemyPointsChanged += EnemyPointsChanged;
}
public void PlayerPointsChanged(object sender, int points)
{
playerPointsText.text = points.ToString;
}
public void EnemyPointsChanged(object sender, int points)
{
enemyPointsText.text = points.ToString();
}
}
Last note here is that your Destroy script increments enemyPoints but then also immediately destroys the gameObject, so I don't see the point in incrementing enemyPoints unless there's something else accumulating enemy points. With the subscription model that's totally doable - you could have something else subscribing to the Destroy script models and they'll get notifications before the script self-destructs.
I finished watching a couple tutorials on state machines in unity and I'm now trying to figure out how I can actually use one with my player movement code, but I'm a bit stuck. The tutorial I followed had shown to do a StateManager script like this
public class PlayerStateManager : MonoBehaviour
{
PlayerBaseState currentState;
public PlayerJumpState jumpState = new PlayerJumpState();
public PlayerIdleState idleState = new PlayerIdleState();
public PlayerRunState runState = new PlayerRunState();
void Start()
{
currentState = idleState;
currentState.EnterState(this);
}
void Update()
{
currentState.UpdateState(this);
}
public void SwitchState(PlayerBaseState state)
{
currentState = state;
state.EnterState(this);
}
}
And now I assumed all I would need to do to use this was this
public class TestMovement : MonoBehaviour
{
PlayerStateManager playerState;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
playerState.SwitchState(playerState.jumpState);
}
}
}
But this clearly isn't correct since I keep getting an error saying "NullReferenceException: Object reference not set to an instance of an object" at the line with SwitchState.
In TestMovement, do you have a Start or Awake method which assigns something to the playerState field? If not, that field is never assigned and is therefore always null.
Another thing you could do is make it public PlayerStateManager playerState; and assign something to it in the editor.
playerState is private by default so you have to make public or [SerializeField] and introduce it in the game engine
So I wanted to make a counter that counts how many times the player hitted a "player_grower". But the problem I'm getting that it isn't counting futher then 1.
public class Collision_Player_grower : MonoBehaviour
{
public GameObject Player_grower;
public float times_player_grower;
void Start()
{
times_player_grower = 0;
}
void OnTriggerEnter(Collider collision)
{
if (collision.gameObject.tag == "Player")
{
print("we hit an playergrower");
Destroy(Player_grower);
times_player_grower = times_player_grower + 1;
print("times hit =" + times_player_grower);
}
}
void Update ()
{
}
}
The result this gives me is that it prints: "times hit = 1", even when I hit two or more of those objects. I believe this happens because it doesn't save the value but I'm not sure.
How can I fix this?
Each of your Collision_Player_grower instances have their own respective field
public float times_player_grower;
so this is an individual counter for each of them.
You probably would make it static so it is "shared" among them all
// I also think an int would be probably more appropriate for your use case
public static int times_player_grower;
Or as an alternative - and cleaner in my eyes - would be to rather let the player itself track the collisions and count.
You either keep your current code but let the player store according value:
// Script on your player object
public class PlayerHitCountController : MonoBehaviour
{
public int times_player_grower;
}
and then do
public class Collision_Player_grower : MonoBehaviour
{
public GameObject Player_grower;
void OnTriggerEnter(Collider collision)
{
if (collision.TryGetComponent<PlayerHitCountController>(out var hitCounter))
{
print("we hit an playergrower");
Destroy(Player_grower);
hitCounter.times_player_grower += 1;
print("times hit =" + hitCounter.times_player_grower);
}
}
}
Or and that would be my actual approach go the total other way round.
// If Player_grower is the same as the object this script is attached to than probably you wouldn't need it
public class Collision_Player_grower : MonoBehaviour
{
// does nothing but holding the information
public GameObject Player_grower;
}
// Script on your player object
public class GrowCollisionChecker : MonoBehaviour
{
public int times_player_grower;
void OnTriggerEnter(Collider other)
{
if (other.TryGetComponent<Collision_Player_grower>(out var grower))
{
print("we hit an playergrower");
Destroy(grower.Player_grower);
// Or if Player_grower is the same object as this is attached to anyway
//Destroy(grower.gameObject);
times_player_grower += 1;
print("times hit =" + times_player_grower);
}
}
}
This way your player has the full control over the counter and can e.g. also reset it where needed.
Most probably you are trying to count the triggers per Player_grower. What is happening is the following: let's say you have 5 Player_grower objects. 1 of them gets triggered. It is calling the OnTriggerEnter() on that particular object setting times_player_grower to 1 and then immediately destroying the object. Even though the Destroy statement is before the incremente and print statments Unity still executes everyting before destroying the object (calling Destroy only marks the object to get destroyed at the end of the frame, until then everything runs). So this is why it always prints 1.
Now to answer your problem - times_player_grower variable exists on every grower but you need to have it declared only 1 time, like a "global" variable instead of "local" for every object so that it will keep counting and not get destroyed together with the GameObject. Now there is 2 ways of achieving this:
The first one is to make the variable static like this: public static float times_player_grower; what static is doing here is declaring a shared variable across all instances of the Collision_Player_grower. All the objects of class Collision_Player_grower will have a common variable called times_player_grower. This way the variable does not get destroyed when the object gets destroyed and you can do your count there. You MUST research static variables if you don't know what they are.
Second one is keeping a count in a separate GameObject that does not get destroyed duing the trigger event.
Good luck
I have singleton( which is monobehavior also same question for non-monobehavior) which is created at scene 1 , and it created without dontDestroyOnLoad. im calling this singleton from scene 2 , and getting/using the info inside without any problem.I have read something about ghost GameObjects in this case but couldnt find detailed info.
In Scene 1
using UnityEngine;
public class RefreshAccount : MonoBehaviour
{
public static RefreshAccount refreshAccount;
public string aString = "aaaaaaaa";
void Awake()
{
if (!refreshAccount) refreshAccount = this;
else Destroy(this.gameObject);
// it is not labeled as DontDestroyOnLoad
}
(...)
}
Scene 2
using UnityEngine;
using UnityEngine.SceneManagement;
public class testnewscen : MonoBehaviour
{
private void Start()
{
Debug.Log(RefreshAccount.refreshAccount.aString );
}
}
So will it cause any problem/error in the future of this app ?
Will there be any memory problem or performance problem?
If you use this solution, you cannot run scene 2 without run scene 1 before
If you dont need to set serialize variable / game object / prefabs to your singleton (RefreshAccount) I prefer to use non-monobehaviour singleton instead like
public class RefreshAccount {
private static RefreshAccount instance
public static RefreshAccount Instance {
get {
if(instance == null) {
instance = createInstance();
}
return instance;
}
}
}
If you need to use for read some serialize value (variable, config, gameobject, etc.) without behaviour ( awake, update, fix update )
You can use SerializableObject
SerialzpizeObject is similar static class or prefab but you need to use RESOURCE.LOAD to read it