I've been trying to implement a savegame function into my game. When I load the game from the save, it doesn't load the position of the player.
I have 2 main scenes: The scene in which the game is taking place, and the main menu scene. The main menu makes use of my load function, which is supposed to read my save file and put the player at a given position, however, it just loads the scene at the default position. No errors are thrown, no warning messages. Here is all of my code:
This is my Save game system:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public static class SaveSystem
{
public static void SavePlayer (Player player)
{
BinaryFormatter formatter = new BinaryFormatter();
string path = Application.persistentDataPath + "player.fun";
FileStream stream = new FileStream(path, FileMode.Create);
PlayerData data = new PlayerData(player);
formatter.Serialize(stream, data);
stream.Close();
}
public static PlayerData LoadPlayer ()
{
string path = Application.persistentDataPath + "/player.fun";
if (File.Exists(path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(path, FileMode.Open);
PlayerData data = formatter.Deserialize(stream) as PlayerData;
stream.Close();
return data;
}
else
{
Debug.LogError("Save file not in " + path);
return null;
}
}
}
Container for player data:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class PlayerData
{
public int level;
public int health;
public float[] position;
public int stamina;
public PlayerData (Player player)
{
level = player.level;
health = player.health;
stamina = player.stamina;
position = new float[3];
position[0] = player.transform.position.x;
position[1] = player.transform.position.y;
position[2] = player.transform.position.z;
}
}
My Scene Changing Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneChanger : MonoBehaviour
{
public static bool Isload = false;
public void gotoWelwardLoad()
{
SceneManager.LoadScene("Welward");
bool Isload = true;
}
public void gotoWelward()
{
SceneManager.LoadScene("Welward");
}
public void gotomainmenu()
{
SceneManager.LoadScene("Menu");
}
}
My Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Player : MonoBehaviour
{
public int health = 1;
public int stamina = 1;
public int level = 1;
public void SavePlayer ()
{
SaveSystem.SavePlayer(this);
}
public void LoadPlayer ()
{
SceneManager.LoadScene("Welward");
PlayerData data = SaveSystem.LoadPlayer();
level = data.level;
health = data.health;
stamina = data.stamina;
Vector3 position;
position.x = data.position[0];
position.y = data.position[1];
position.z = data.position[2];
transform.position = position;
}
public static void gotomenu ()
{
SceneManager.LoadScene("Menu");
}
public static void Welward()
{
SceneManager.LoadScene("Welward");
SaveSystem.LoadPlayer();
}
}
Link with full unity project files:
https://drive.google.com/open?id=1mFH5aNklC0qMWeJjMT4KD0CbTTx65VRp
As BugFinder correctly points out, your save path is different than your loading path.
Try changing Application.persistentDataPath + "player.fun"; to Application.persistentDataPath + "/player.fun"; to match your loading code. Or you could move that string path variable up into the class as a const then reference it since it will be guaranteed to match.
After that however you will also need to call Player.LoadPlayer() somewhere since in your existing project you do not do that anywhere I can find, and when you call it currently it will reload the scene (which probably isn't the behaviour you want either). I would remove SceneManager.LoadScene("Welward"); from that method.
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public static class SaveSystem
{
// This is a way to make sure your path is the same, and not about to get overwritten
public static string Path => Application.persistentDataPath + "/player.fun";
public static void SavePlayer (Player player)
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(Path, FileMode.Create);
PlayerData data = new PlayerData(player);
formatter.Serialize(stream, data);
stream.Close();
// It's helpful to print out where this is going
Debug.Log($"Wrote to {Path}");
}
public static PlayerData LoadPlayer ()
{
if (File.Exists(Path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(Path, FileMode.Open);
PlayerData data = formatter.Deserialize(stream) as PlayerData;
stream.Close();
// It's also helpful to print out that it worked, not just log errors if it fails
Debug.Log($"Successfully read from {Path}");
return data;
}
else
{
Debug.LogError("Save file not in " + Path);
return null;
}
}
}
One thing thats noticeable is in save you use Application.persistentDataPath + "player.fun"; and in load you use, Application.persistentDataPath + "/player.fun"; So, perhaps therefore it doesnt find the file to load it. As you thought. Personally Path.Combine is a good option, platform non specific etc. So try replacing both with Path.Combine(Application.persistentDataPath,"player.fun");
From the code you provided I don't see the part where you call Player.LoadPlayer(). So add it to Player:
void Start(){
LoadPlayer();
}
Hope it helps.
Related
I'm working in Unity to make a game and I'm having some problems with a Save System.
What I want is to save variables from more than 1 script.
In [GameManager.cs] i'm saving things like Level, Currency and Scores, all of that in one file.
In [MenuManager.cs] I want to save the user name IN ANOTHER file (in case he didn't did it yet) using the same saving system.
Acces those variables in other scripts. Example: I want to Log the variables saved in point 1) (GameManager.cs) in point 2) (MenuManager.cs). Or ... is possible to load(READ them from the saved file) them from [MenuManager.cs] using the same script from [GameManager.cs]? (I managed to load them only from [GameManager.cs] using the LoadPlayer() function).
Here is how 1st application run look like: https://imgur.com/a/IqoQjpX and the second: https://imgur.com/a/QpienFQ
The question is why in second photo it outputs blank? Why the save system works perfectly fine for [GameManager.cs] and for [MenuGame.cs] it doesn't?
Here s how the script looks:
[PlayerData]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class PlayerData
{
public int Score;
public int BestScore;
public string UserName;
// 1st Constructor
public PlayerData(GameManager Player)
{
BestScore = Player.BestScore;
}
// 2nd Constructor
public PlayerData(MenuManager User)
{
UserName = User.UserName;
}
}
[SaveSystem.cs]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public class SaveSystem
{
public static void SavePlayer(GameManager Player)
{
BinaryFormatter formatter = new BinaryFormatter();
string Path = Application.persistentDataPath + "/player.xd";
FileStream Stream = new FileStream(Path, FileMode.Create);
PlayerData Data = new PlayerData(Player);
formatter.Serialize(Stream, Data);
Stream.Close();
}
public static void SavePlayer(MenuManager Player)
{
BinaryFormatter formatter = new BinaryFormatter();
string Path = Application.persistentDataPath + "/name.xd";
FileStream Stream = new FileStream(Path, FileMode.Create);
PlayerData Data = new PlayerData(Player);
formatter.Serialize(Stream, Data);
Stream.Close();
}
public static PlayerData LoadPlayer()
{
string Path = Application.persistentDataPath + "/player.xd";
if (File.Exists(Path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream Stream = new FileStream(Path, FileMode.Open);
PlayerData Data = formatter.Deserialize(Stream) as PlayerData;
Stream.Close();
return Data;
}
else
{
return null;
}
}
public static PlayerData LoadName()
{
string Path = Application.persistentDataPath + "/name.xd";
if (File.Exists(Path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream Stream = new FileStream(Path, FileMode.Open);
PlayerData Data = formatter.Deserialize(Stream) as PlayerData;
Stream.Close();
return Data;
}
else
{
return null;
}
}
}
[MenuManager.cs]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class MenuManager : MonoBehaviour
{
public string UserName = "whereIsTheLogic";
public InputField input;
public void Start()
{
// Here I checked if the user saved his name
PlayerData Data = SaveSystem.LoadName();
if (Data == null)
{
Debug.Log("User didn't saved his name");
/* so ... We jump to SaveButton() */
}
else
{
Debug.Log("User saved his name");
/* The file exists ... so we assume this isn't his first application use, right? */
/* Now what I want is to simply log the name ... just log already 4 god sake!! */
UserName = Data.UserName;
Debug.Log("User saved name is: " + UserName);
/* BUT it outputs blank NOT null or the inserted name) */
}
}
public void SaveButton()
{
string userName = input.text;
SaveSystem.SavePlayer(this);
Debug.Log("The user name: " + userName); /* yes ... it outputs the correct user answer */
}
public void PlayGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
[GameManager.cs] (only things that matter ...)
public class GameManager : MonoBehaviour
{
public int Score = 0;
public int BestScore = 0;
void Start()
{
/* Here I load saved variables (Score/Best Score)
and it loads them correctly
*/
LoadPlayer();
}
public void SavePlayer()
{
// Here I save the above mentioned variables and ... yes it works correctly
SaveSystem.SavePlayer(this);
Debug.Log("Salvat");
}
public void LoadPlayer()
{
PlayerData Data = SaveSystem.LoadPlayer();
Debug.Log("Loaded");
BestScore = Data.BestScore;
}
}
So the main issue comes from
string userName = input.text;
SaveSystem.SavePlayer(this);
Debug.Log("The user name: " + userName);
You make your Log dependend on the local variable userName, but you never update any field value of this (= MenuManager).
What you probably rather wanted would be updating the existing field UserName:
UserName = input.text;
SaveSystem.SavePlayer(this);
Debug.Log("The user name: " + UserName);
In general it is quite confusing to me that you have two separate files name.xd and player.xd, both storing serialized data for the same class PlayerData. And then each file only actually uses very specific fields of that class.
You should rather either have the two files but serialize actually the data you need or - what I would do - have one file serializing everything like
public class SaveSystem
{
private PlayerData _data = new PlayerData();
private string _path = Path.Combine(Application.persistentDataPath, "player.xd");
public static void SavePlayer(GameManager Player)
{
using(var stream = new File.Open(_path, FileMode.Create))
{
var formatter = new BinaryFormatter();
// instead of creating it new only update the field in the existing data
_data.BestScore = Player.BestScore;
formatter.Serialize(Stream, Data);
}
}
public static void SavePlayer(MenuManager Player)
{
using(var stream = new File.Open(_path, FileMode.Create))
{
var formatter = new BinaryFormatter();
// instead of creating it new only update the field in the existing data
_data.UserName = User.UserName;
formatter.Serialize(Stream, Data);
}
}
public static bool LoadPlayer(out PlayerData playerData)
{
playerData = default;
// Since you seem to anyway load only exactly ONCE per app start
// You could simply call this only ONCE
if(_data != null)
{
playerData = _data;
return true;
}
if (!File.Exists(path))
{
return false;
}
using(var stream = File.Open(path, FileMode.Open))
{
var formatter = new BinaryFormatter();
_data = formatter.Deserialize(Stream) as PlayerData;
}
playerData = _data;
return true;
}
}
and don't need the constructors so only
[System.Serializable]
public class PlayerData
{
public int Score;
public int BestScore;
public string UserName;
}
And you now would rather use e.g.
public class MenuManager : MonoBehaviour
{
public string UserName = "whereIsTheLogic";
public InputField input;
private void Start()
{
if(!SaveSystem.LoadPlayer(out var playerData))
{
Debug.Log("User didn't saved his name");
/* so ... We jump to SaveButton() */
}
else
{
Debug.Log("User saved his name");
UserName = playerData.UserName;
Debug.Log("User saved name is: " + UserName);
}
}
public void SaveButton()
{
UserName = input.text;
SaveSystem.SavePlayer(this);
Debug.Log("The user name: " + UserName);
}
public void PlayGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
and accordingly
public class GameManager : MonoBehaviour
{
public int Score = 0;
public int BestScore = 0;
void Start()
{
LoadPlayer();
}
public void SavePlayer()
{
SaveSystem.SavePlayer(this);
Debug.Log("Salvat");
}
public void LoadPlayer()
{
if(SaveSystem.LoadPlayer(out var playerData))
{
Debug.Log("Loaded");
BestScore = playerData.BestScore;
}
}
}
You are right about the local variable :) Looked in your script a while, I managed to fix the errors (probably cause we have different compilers)
but the saving system doesn't work with your method. Thanks for your time and help but I think your script is a bit way too much for me (at least for now).
The things I had to change to your code to be able to compile the program: https://pastebin.com/umHpLJmL
I managed to save both score and name in one single file but with a non religious method.
Even if my game isn't about optimizing or things like that, I still want to get better results and to improve my knowledge.
So, down below I made the following modifications:
[SaveSystem.cs]
public class SaveSystem
{
static string Path = Application.persistentDataPath + "/Save";
public static void SavePlayer(GameManager Player)
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream Stream = new FileStream(Path, FileMode.Create);
// FileStream Stream = File.Open(Path, FileMode.Open);
PlayerData Data = new PlayerData(Player);
formatter.Serialize(Stream, Data);
Stream.Close();
}
public static void SavePlayer(MenuManager Player)
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream Stream = new FileStream(Path, FileMode.Create);
// FileStream Stream = File.Open(Path, FileMode.Open);
PlayerData Data = new PlayerData(Player);
formatter.Serialize(Stream, Data);
Stream.Close();
}
public static PlayerData LoadPlayer()
{
if (File.Exists(Path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream Stream = new FileStream(Path, FileMode.Open);
PlayerData Data = formatter.Deserialize(Stream) as PlayerData;
Stream.Close();
return Data;
}
else
{
return null;
}
}
}
[GameManager.cs]
And here's the thing: in order not to lose any variables while saving the score, I made a load just before the new saving and then I saved the username in a global GameManager variable.
So now I can easily save the game and after restarting it both name and score are saved.
public void SavePlayer()
{
PlayerData Data = SaveSystem.LoadPlayer();
UserName = Data.UserName;
SaveSystem.SavePlayer(this);
Debug.Log("Salvat " + Data.UserName);
}
public void LoadPlayer()
{
PlayerData Data = SaveSystem.LoadPlayer();
Debug.Log("Loaded");
BestScore = Data.BestScore;
}
[PlayerData.cs]
[System.Serializable]
public class PlayerData
{
public int Score;
public int BestScore;
public string UserName;
public PlayerData(GameManager Player)
{
BestScore = Player.BestScore;
UserName = Player.UserName;
}
public PlayerData(MenuManager Player)
{
UserName = Player.UserName;
}
}
So my question is, how can I do it JUST better? I mean, isn't there an option to save things in file in a specific section and not to modify the other things which are in the file?
Example:
[Level="18"]
[Name="Andrei"]
And to be able to modify only the parameter I choose to.
Another solution which came in my mind is (don't know if is possible to implement, just asking):
public static void SavePlayer(GameManager obj1, MenuManager obj2)
{
// Loading both global scene variables
PlayerData _data1 = new PlayerData(obj1);
PlayerData _data2 = new PlayerData(obj2);
// Copying the variables into global SaveSystem variables
Score = _data1.Score;
UserName = _data2.UserName;
// Saving global SaveSystem variables into document
formatter.Serialize(Stream, this);
}
There are two problems:
Calling the function: SaveSystem.SavePlayer(GameManager, MenuManager);
I used to make the calls with this. With what should I replace it? Isn't it a refference to current class name?
formatter.Serialize(Stream, this);
What I want in point 2) is to save the global class variables into document. But how? Using this doesn't make the refference to the class.
[Picture]I am developing a game and am trying to make a save and load button. I believe that I have coded correctly, but my problem is that the player that I wish to access does not get found. I suspect that it has something to do with the way I am coding it.
Due to having multiple levels I have used the code:
GameObject player = GameObject.FindWithTag("Player"); to try and find the given player in the scene, but I do not think it is working.
I did not set the player as a public variable as it changes throughout each scene.
For example, I have attempted to access the players current health with the following lines of code, which is saved in a GameController empty game object in a script called Gamecontrol.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using UnityEngine.SceneManagement;
public class GameControl : MonoBehaviour {
public static GameControl controller;
int shooterHealth;
int shooterScore;
//public GameObject player;
//public GameObject gun;
int level;
// Use this for initialization
void Awake() {
GameObject player = GameObject.FindWithTag("Player");
shooterHealth = player.GetComponent<PlayerHealth>().currentHealth;
shooterScore = player.GetComponent<Gun>().score;
level = player.GetComponent<PlayerHealth>().level;
if(controller == null) {
DontDestroyOnLoad(gameObject);
controller = this;
}
else if(controller != this) {
Destroy(gameObject);
}
}
public void SaveGame() {
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath + "/shooter.dat");
PlayerData data = new PlayerData();
data.shooterHealth = shooterHealth;
data.shooterScore = shooterScore;
data.level = level;
bf.Serialize(file, data);
file.Close();
}
public void LoadGame() {
if(File.Exists(Application.persistentDataPath + "/shooter.dat"))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/shooter.dat", FileMode.Open);
PlayerData data = (PlayerData)bf.Deserialize(file);
file.Close();
GameObject player = GameObject.FindWithTag("Player");
player.GetComponent<PlayerHealth>().currentHealth = data.shooterHealth;
player.GetComponent<Gun>().score = data.shooterScore;
level = player.GetComponent<PlayerHealth>().level;
SceneManager.LoadScene(level);
}
}
}
[Serializable]
class PlayerData
{
public int shooterHealth;
public int shooterScore;
public int level;
}
The error that I get is:
NullReferenceException: Object reference not set to an instance of an object
GameControl.Awake () (at Assets/GameControl.cs:20)
Does anybody know a way around this? Thank you in advance:)
The error is in shooterScore = player.GetComponent<Gun>().score;
You are trying to access the component Gun, but the Player GameObject doesn't have a Gun component attached. Maybe your forgot to attach it or you it has another name.
Maybe you attach the Gun component at runtime. In that case check the script execution order to verify that the GameControl script is executed after the script that attaches the Gun component at runtime.
So. I'm trying to make a save profile but I have the error shown in the picture and have no idea how to fix it as I have been following tutorials on YouTube and this error never occurred. Can anyone help me work out what I should do.
using UnityEngine;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class SavingControl : MonoBehaviour
{
public static SavingControl control;
public int cash = PlayerBetting.instance.bankBal;
public int difficulty = 2;
void Awake ()
{
if (control = null)
{
DontDestroyOnLoad(gameObject);
control = this;
}
else if (control != this)
{
Destroy(gameObject);
}
}
public void Save()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath + "/playerInfo.dat");
PlayerData data = new PlayerData();
data.BankBal = cash;
data.Difficulty = difficulty;
bf.Serialize(file, data);
file.Close();
}
public void Load()
{
if (File.Exists(Application.persistentDataPath + "/PlayerInfo.dat"))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath = "/PlayerInfro.dat", FileMode.Open);
PlayerData data = (PlayerData)bf.Deserialize(file);
file.Close();
cash = data.BankBal;
difficulty = data.Difficulty;
}
}
}
[Serializable]
class PlayerData
{
public int BankBal;
public int Difficulty;
}
Change the = to a +:
FileStream file = File.Open(Application.persistentDataPath + "/PlayerInfro.dat", FileMode.Open);
Better yet use Path.Combine:
FileStream file = File.Open(Path.Combine(Application.persistentDataPath, "PlayerInfro.dat"), FileMode.Open);
Im working on a Game. I have a ProgressControl Class that tracks the Players Progress, and save and load that to a Binary File. Serializing Gold and Points works. Now I want to add a Hashtable to save the Points for every Level, to Limit the maximum Points a Player can get on every Level. But the Hashtable in the Serializable Class never gets assigned and stays null. So no Points are saved.
How can I get that Hashtable serialized? What am I missing?
using UnityEngine;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Collections;
public class ProgressControl : MonoBehaviour
{
public static ProgressControl progress;
public int gold;
public int points;
public int level;
public Hashtable h;
void Awake()
{
h = new Hashtable();
if (progress == null)
{
DontDestroyOnLoad(gameObject);
progress = this;
}
else if (progress != this)
{
Destroy(gameObject);
}
}
public void save()
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath + "/playerInfo.dat");
PlayerData data = new PlayerData();
data.gold = gold;
data.points = points;
data.h = h;
bf.Serialize(file, data);
file.Close();
}
public void load()
{
if (File.Exists(Application.persistentDataPath + "/playerInfo.dat"))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/playerInfo.dat", FileMode.Open);
PlayerData data = (PlayerData)bf.Deserialize(file);
file.Close();
gold = data.gold;
points = data.points;
h = data.h;
}
}
public void saveLevel()
{
level = TargetScript.activeLevel;
Debug.Log("Level Saved");
}
public void savePointsToHashMap(int LevelKey, int points)
{
if (h.ContainsKey(LevelKey))
{
object value = h[LevelKey];
int compare = int.Parse(value.ToString());
if (compare < points)
{
h.Add(LevelKey, points);
Debug.Log("Overwrite and Save Points");
}
}
else{
Debug.Log("Save Points");
h.Add(LevelKey, points);
}
save();
}
}
[Serializable]
class PlayerData
{
public int gold;
public int points;
public Hashtable h;
}
Im trying to save some enums in my code. Saving and converting to the string is going well. But with the loading there r some problems. I mean. I just cant load it). Im doing all that in unity. I thought to convert enum to string and after convert it back. Conversion to string works fine. But what happens with back-convestion i dont know. Thanks.
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class GameControl : MonoBehaviour {
public static GameControl control;
public float health;
public float experience;
public string stateString;
public enum State { start_0, start_1, start_3, start_4 };
public State myState;
void Awake () {
if(control == null)
{
DontDestroyOnLoad(gameObject);
control = this;
}
else if(control != this)
{
Destroy(gameObject);
}
}
public void Save()
{
stateString = myState.ToString(); //Converts ENUM to STRING
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath +
"/playerInfo.dat");
PlayerData data = new PlayerData();
data.health = health;
data.experience = experience;
data.stateString = stateString;
bf.Serialize(file, data);
file.Close();
}
public void Load()
{
if(File.Exists(Application.persistentDataPath + "/playerInfo.dat"))
{
State loadedState = (State) Enum.Parse(typeof(State), stateString);
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath +
"/playerInfo.dat", FileMode.Open);
PlayerData data = (PlayerData)bf.Deserialize(file);
file.Close();
health = data.health;
experience = data.experience;
stateString = data.stateString;
}
}
}
[Serializable]
class PlayerData
{
public float health;
public float experience;
public string stateString;
public enum State { start_0, start_1, start_3, start_4 };
public State myState;
}