I'm making a simple game, one of the features is challenges which are going to be opened from a file, here is how one of the challenges look:
[System.Serializable]
public class Challenge10 : ChallegeInterface {
public bool isCompleted = false;
public string description = "test";
public int curCoins;
public override void giveReward (){
GameDataSingleton.gameDataInstance.game_data.addCoinsFromReward (7);
}
//Method to check if everything is completed
public override bool isComplete (){
if (!isCompleted) {
curCoins= GameDataSingleton.gameDataInstance.game_data.getCoinsThisGame ();
if (curCoins >= 1) {
isCompleted = true;
giveReward ();
return true;
}
}
return false;
}
}
The problem is that after I deserielize the file the string value(description) is null. Here is the code where the program is opening the challenges.
public void openChallenges(){
challenges = new ChallegeInterface[game_data.challenges.Length];
for (int i = 0; i < game_data.challenges.Length; i++) {
int curChallenge = game_data.challenges[i];
ChallegeInterface challenge = HddManager.HddManagerInstance.load<ChallegeInterface> ("challenges/challenge"+curChallenge);
Debug.Log (challenge.description);
challenges[i] = challenge;
}
}
Everything else seems fine, except the description. Am i missing something?
Edit:
This is how the program is serializing the object:
public void save<T>(T data, string fileName) where T : class{
if (fileName == "")
Debug.Log ("Empty file path");
FileStream file = null;
try{
if(fileName.IndexOf("/") > 0){
string[] strDirName = fileName.Split(new char[] {'/'});
string dirName = strDirName[0];
if(!Directory.Exists(Path.Combine(Application.persistentDataPath, dirName))){
Directory.CreateDirectory(Path.Combine(Application.persistentDataPath, dirName));
}
}
file = File.Create(Path.Combine(Application.persistentDataPath, fileName));
binFormatter.Serialize(file, data);
Debug.Log ("File saved succesfully" + fileName);
}catch(IOException e){
Debug.Log(e.ToString());
}finally{
if(file != null)
file.Close();
}
}
This is how the object is deserialized:
public T load<T> (string fileName) where T : class{ // T => object type
if (fileName == null) {
Debug.Log ("Empty path to file");
return null;
} else {
FileStream file = null;
try {
//Open the file;
file = File.Open (constructFilePath(fileName), FileMode.Open);
//To be removed
Debug.Log ("File loaded succesfully");
// Deserialize the opened file, cast it to T and return it
return binFormatter.Deserialize (file) as T;
} catch (IOException e) {
//To be removed
Debug.Log (e.ToString ());
// Saves the current object in case the file doesn't exist
// Use Activator to create instance of the object,
save (Activator.CreateInstance<T> (), fileName);
// calls the function again
return load<T> (fileName);
} finally {
//Close the file
if (file != null)
file.Close ();
}
}
}
By inspecting the following lines in the openChallenges() method:
ChallegeInterface challenge = HddManager.HddManagerInstance.load<ChallegeInterface> ("challenges/challenge"+curChallenge);
Debug.Log (challenge.description);
I assume ChallegeInterface is actually a class and not an interface since interfaces can not contain fields and you are accessing challenge.description when challenge is ChallegeInterface which means ChallengeInterface is a class.
In this case you are actually accessing the base class' (ChallegeInterface) field instead of the correct one (Challenge10). and that field is empty.
Important: keep clear and correct coding conventions, never name a class Interface, it's better to avoid naming types with programming terminology instead of indicative naming related to their usage.
P.S.: I've checked the serialization myself and inspected Challenge10's description and it works fine.
Related
I am new to stackoverflow and to Unity3D, so I am sorry if I am doing things wrong.
So currently, I am making a puzzle game. It has 50 different levels.
I need for each of them, to save 3 or 4 variables.
For example, when level 1 is cleared, I want it to store (int)hitCounts, (bool)cleared, (int)bestHitCounts.
I don't wanna use playerPrefs, as I don't want it to be readable from outside the box. I want it be converted to a binary file.
here is what I have :
#1 : made a static class TGameDat
[System.Serializable]
public class TGameDat
{
public int tGameDatInt;
public bool tGameDatBool;
public int tSceneIndex;
public TGameDat (TPlayer player)
{
tGameDatInt = player.tInt;
tGameDatBool = player.tBool;
tSceneIndex = player.tScene;
}
}
#2 : then made Tplayer(monobehaviour)
public class TPlayer : MonoBehaviour
{
public int tInt = 0;
public bool tBool = false;
public int tScene;
public List<TPlayer> TestGameDatList = new List<TPlayer>();
private void Start()
{
TSceneMaker();
}
public void TSceneMaker()
{
tScene = SceneManager.GetActiveScene().buildIndex;
}
public void TNextScene()
{
SceneManager.LoadScene(tScene + 1);
}
public void TPreviousScene()
{
SceneManager.LoadScene(tScene - 1);
}
public void TSaveVariables()
{
TSave.TSavePlayer(this);
TestGameDatList.Add(this);
Debug.Log("saved");
Debug.Log(tInt + " " + tBool + " " + tScene);
}
public void TLoadVariables()
{
List<TGameDat> data = TSave.TLoadPlayer(this);
Debug.Log("loaded. data count = " + data.Count + " tSceneIndex " + tScene);
tInt = data[0].tGameDatInt;
tBool = data[0].tGameDatBool;
tScene = data[0].tSceneIndex;
}
}
#3 : finally I created a save and load system :
public static class TSave
{
public static void TSavePlayer (TPlayer player)
{
BinaryFormatter formatter = new BinaryFormatter();
List<TGameDat> data = new List<TGameDat>();
string path = Application.persistentDataPath + "/Tsave_" + player.tScene + ".fun";
if(File.Exists(path))
{
FileStream stream = File.Open(path, FileMode.Open);
data.Add(new TGameDat(player));
formatter.Serialize(stream, data);
stream.Close();
}
else
{
FileStream stream = File.Create(path);
data.Add(new TGameDat(player));
formatter.Serialize(stream, data);
stream.Close();
}
}
public static List<TGameDat> TLoadPlayer(TPlayer player)
{
string path = Application.persistentDataPath + "/Tsave_" + player.tScene + ".fun";
if(File.Exists(path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = File.Open(path, FileMode.Open);
List<TGameDat> data = new List<TGameDat>();
data = formatter.Deserialize(stream) as List<TGameDat>;
stream.Close();
return data;
}
else
{
Debug.LogError("Save file not found in " + path);
return null;
}
}
}
So, here are my problems :
1 : in the current situation, each scene compiles a binary file. So at the end, it will have a bunch of binary files piled up... Like 50, as I have 50 scenes... isn't it too many?
2 : of course I tried the make a single save file using List, and each level would come to add its own variable data in it.
But instead of adding the data, it would simply replace the previous data. Then there is always only 1 index in the List.
Therefore, when I load, the variables are from the last played level! And when I try to play another level after playing the first level, because there is only 1 index in the list, I get out of range error.
How shall I approach this?
sorry for the long long text!
thank you for your inputs!
first thing first, do you really need every level to have its own save data? Because if you only need to store the data between one level and another I would suggest you use some kind of PlayerState class that stores the data of the previous level.
But if you really need to store the data of every level then I'll recommend you using a dictionary rather than a simple list.
Here is an example of how I would do it
Note: I haven't tested this code yet!
SaveGameManager class
public string SavePath => Application.persistentDataPath + "/save.dat";
public static SaveGameManager Instance; // Singleton pattern
private Dictionary<string, TGameDat> gameData;
private void Awake()
{
if (Instance != null)
{
Destroy(this.gameObject);
return;
}
// Singleton initialization
Instance = this;
// Keep the object when changing scenes
DontDestroyOnLoad(this.gameObject);
LoadGameData();
}
public TGameDat GetGameData(string key)
{
if (gameData.TryGetValue(key, out TGameDat data))
{
return data;
}
Debug.Log($"Unable to find saved data with key {key}");
return null;
}
public void SetGameData(string key, TGameDat data)
{
// Sets a value with given key and save it to file
gameData[key] = data;
SaveGameData();
}
public void SaveGameData()
{
Serializer.SaveBinaryFile(gameData, SavePath);
}
public void LoadGameData()
{
var savedData = Serializer.LoadBinaryFile(SavePath);
if (savedData != null)
{
gameData = (Dictionary<string, TGameDat>)savedData;
}
else
{
// Creating and saving new data because we can't found
// any that already stored in path
gameData = new Dictionary<string, TGameDat>();
SaveGameData();
}
}
And then, the Serializer class
public static void SaveBinaryFile(object data, string path)
{
using (var stream = new FileStream(path, FileMode.Create))
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, data);
}
}
public static object LoadBinaryFile(string path)
{
if (!File.Exists(path))
{
// Trying to load a file that does not exist
return null;
}
using (var stream = new FileStream(path, FileMode.Open))
{
var formatter = new BinaryFormatter();
return formatter.Deserialize(stream);
}
}
And then you can use it like this
public TGameDat data;
public void TSaveVariables()
{
SaveGameManager.Instance.SetGameData(level.id, this.data);
}
public void TLoadVariables()
{
var savedData = SaveGameManager.Instance.GetGameData(level.id);
if (savedData != null)
{
this.data = savedData;
}
else
{
// We don't have any save file for this level yet
savedData = new TGameDat();
}
}
You can change level.id to whatever identifier you wanted to use.
I'm new in VisualStudio, so I haven't got much experience with the unit testing in it.
I try to write a basic file reader class, wich has two variables: path and lines string list.
The path can be given to the constructor, or also can be set with a setter method.
There is a readIn function, which load up the list with the lines of the text file can be found on the path.
The function should chek if the path is not null or is it valid (is the path pointing to an existing file). If not, it throws a System.ArgumentNullException or a throw new System.ArgumentException.
When I try to test that the exceptions are thrown in case of the path is null or not valid, the VisualStudio always stuck and says that I have an unhadnled exception, however, I defined the expected exception before the test method.
Have you got any idea how should I do this in correct way?
Or if it is the correct way (I read about it also here and on the msdn page too, so it should work), what should I do to get it work?
The reader class:
using System;
using System.Collections.Generic;
using System.IO;
namespace LogParserConsole
{
public class FileReader
{
private string path;
private List<string> lines;
public FileReader()
{
path = null;
lines = null;
}
public FileReader(string path)
{
this.path = path;
if (isFileExists())
{
lines = new List<string>();
}
else
{
lines = null;
}
}
public void readIn()
{
if(path == null)
{
throw new System.ArgumentNullException("The path is not set. Please set a path first!");
}
else if(!isFileExists())
{
throw new System.ArgumentException("The given path is invalid, please chek it!");
}
string line;
System.IO.StreamReader file = new System.IO.StreamReader(path);
while ((line = file.ReadLine()) != null)
{
lines.Add(line);
Console.WriteLine(line);
}
file.Close();
}
public bool isFileExists()
{
return File.Exists(path);
}
public string getPath()
{
return this.path;
}
public List<string> getLines()
{
return this.lines;
}
public void setPath(string path)
{
this.path = path;
}
}
}
The unit test method for the exception:
[TestMethod]
[ExpectedException(typeof(System.ArgumentNullException))]
public void ReadInTest()
{
FileReader fr = new FileReader();
fr.readIn();
}
I also tried an example from the msdn blog page about the expected exceptions (https://blogs.msdn.microsoft.com/csell/2006/01/14/expectedexception-might-not-be-what-youve-expected/), but it also stopped at the exception and after the test failed.
It happened with this simple example code:
[TestMethod, ExpectedException( typeof ( ApplicationException ) ) ]
public void ut1()
{
throw new ApplicationException();
}
I think I should check my preferencies, but have no idea what should I search for.
Thanks for any advice!
I was unable to solve the following error. I'll appreciate your help.
I have the following class:
public class GB
{
private StreamReader sr;
public GB()
{
sr = new StreamReader(new FileStream("C:\\temp.txt", FileMode.Open, FileAccess.ReadWrite));
}
public int MultiCall()
{
if (sr.ReadLine() == "test1")
return 1;
else
return 0;
}
}
in my form, there is button with the following function;
void buttonClick()
{
myAssembly = Assembly.LoadFrom(dllpath); // dllpath is the dll file for GB class
myType = myAssembly .GetType("GB");
myObject = Activator.CreateInstance(myType);
myMethodInfo = myType .GetMethod("MultiCall");
returnValue = myMethodInfo.Invoke(myObject, null);
myObject = null;
}
Here is my question; When i click the button for the first time, Everything is OK. But when i click it again i get the following error;
The process cannot access the file 'C:\temp.txt' because it is being used by another process.
The object returned from activator.createinstance doesnot seem to be nulled after first call. Although i assign it to a null value by myObject = null i still get the same error. Any ideas?
thx.
The constructor of GB opens a ReadWrite stream to the file, but then never closes it. The subsequent invocation of GB attempts to open the same file, but this obviously fails.
What you need to do is implement IDisposable on GB, which disposes of the StreamReader, e.g.:
public class GB : IDisposable
{
private StreamReader sr;
private bool _isDisposed;
public GB()
{
sr = new StreamReader(new FileStream("C:\\temp.txt", FileMode.Open, FileAccess.ReadWrite));
}
public int MultiCall()
{
if (sr.ReadLine() == "test1")
return 1;
else
return 0;
}
~GB()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool isManaged)
{
if(_isDisposed)
return;
if(isManaged)
{
// Ensure we close the file stream
sr.Dispose();
}
_isDisposed = true;
}
}
Then dispose of your GB instance once you are done with it.
// Dispose of the GB instance (which closes the file stream)
var asDisposable = (IDisposable)myObject;
asDisposable.Dispose();
IDisposable exists for exactly this reason, to ensure unmanaged resources are successfully released.
Store the value returned by the file then check it value in the MultiCall method. This way you file gets closed and disposed for you.
public class GB
{
private string str;
public GB()
{
using (var sr = new StreamReader(new FileStream("C:\\temp.txt", FileMode.Open, FileAccess.ReadWrite)))
{
str = sr.ReadToEnd();
}
}
public int MultiCall()
{
if (str == "test1")
return 1;
else
return 0;
}
}
I have an object I want to store in the IsolatedStorageSettings, which I wan't to reuse when the application restarts.
My problem lies in that the code I have written for some reason does not remember the object when trying to access the key upon restarting it.
namespace MyNameSpace
{
public class WindowsPhoneSettings
{
private const string SelectedSiteKey = "SelectedSite";
private IsolatedStorageSettings isolatedStore = IsolatedStorageSettings.ApplicationSettings;
private T RetrieveSetting<T>(string settingKey)
{
object settingValue;
if (isolatedStore.TryGetValue(settingKey, out settingValue))
{
return (T)settingValue;
}
return default(T);
}
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
if (isolatedStore.Contains(Key))
{
if (isolatedStore[Key] != value)
{
isolatedStore[Key] = value;
valueChanged = true;
}
}
else
{
isolatedStore.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
public MobileSiteDataModel SelectedSite
{
get
{
return RetrieveSetting<MobileSiteDataModel>(SelectedSiteKey);
}
set
{
AddOrUpdateValue(SelectedSiteKey, value);
isolatedStore.Save();
}
}
}
}
I then instantiate WindowsPhoneSettings in App.xaml.cs and make a public getter and setter for it. To be able to access it in the whole application. Debugging this shows that the right object gets stored in the isolated store, but when closing the app and reopening it isolated store seems to be empty. I have tried this on both the emulator and a real device. As you can see I do call the save method when setting the object.
What am I doing wrong here?
I ended up saving the settings to a file in the isolated storage as IsolatedStorageSettings never seemed to work.
So my code ended up like this:
public class PhoneSettings
{
private const string SettingsDir = "settingsDir";
private const string SettingsFile = "settings.xml";
public void SetSettings(Settings settings)
{
SaveSettingToFile<Settings>(SettingsDir, SettingsFile, settings);
}
public Settings GetSettings()
{
return RetrieveSettingFromFile<Settings>(SettingsDir, SettingsFile);
}
private T RetrieveSettingFromFile<T>(string dir, string file) where T : class
{
IsolatedStorageFile isolatedFileStore = IsolatedStorageFile.GetUserStoreForApplication();
if (isolatedFileStore.DirectoryExists(dir))
{
try
{
using (var stream = new IsolatedStorageFileStream(System.IO.Path.Combine(dir, file), FileMode.Open, isolatedFileStore))
{
return (T)SerializationHelper.DeserializeData<T>(stream);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Could not retrieve file " + dir + "\\" + file + ". With Exception: " + ex.Message);
}
}
return null;
}
private void SaveSettingToFile<T>(string dir, string file, T data)
{
IsolatedStorageFile isolatedFileStore = IsolatedStorageFile.GetUserStoreForApplication();
if (!isolatedFileStore.DirectoryExists(dir))
isolatedFileStore.CreateDirectory(dir);
try
{
string fn = System.IO.Path.Combine(dir, file);
if (isolatedFileStore.FileExists(fn)) isolatedFileStore.DeleteFile(fn); //mostly harmless, used because isolatedFileStore is stupid :D
using (var stream = new IsolatedStorageFileStream(fn, FileMode.CreateNew, FileAccess.ReadWrite, isolatedFileStore))
{
SerializationHelper.SerializeData<T>(data, stream);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Could not save file " + dir + "\\" + file + ". With Exception: " + ex.Message);
}
}
}
And a settings class just containing the stuff I want to save. This could be:
class Settings
{
private string name;
private int id;
public string Name
{
get { return name; }
set { name = value; }
}
public int Id
{
get { return id; }
set { id = value; }
}
}
EDIT: Sample of how SerializationHelper could be implemented
public static class SerializationHelper
{
public static void SerializeData<T>(this T obj, Stream streamObject)
{
if (obj == null || streamObject == null)
return;
var ser = new DataContractJsonSerializer(typeof(T));
ser.WriteObject(streamObject, obj);
}
public static T DeserializeData<T>(Stream streamObject)
{
if (streamObject == null)
return default(T);
var ser = new DataContractJsonSerializer(typeof(T));
return (T)ser.ReadObject(streamObject);
}
}
Objects stored in IsolatedStorageSettings are serialised using the DataContractSerializer and so must be serializable. Ensure they can be or serialize (and deserialize) them yourself before adding to (and after removing from) ISS.
If the items aren't there when trying to retrieve then it may be that they couldn't be added in the first place (due to a serialization issue).
Here is the code I use to save an object to isolated storage and to load an object from isolated storage -
private void saveToIsolatedStorage(string keyname, object value)
{
IsolatedStorageSettings isolatedStore = IsolatedStorageSettings.ApplicationSettings;
isolatedStore.Remove(keyname);
isolatedStore.Add(keyname, value);
isolatedStore.Save();
}
private bool loadObject(string keyname, out object result)
{
IsolatedStorageSettings isolatedStore = IsolatedStorageSettings.ApplicationSettings;
result = null;
try
{
result = isolatedStore[keyname];
}
catch
{
return false;
}
return true;
}
Here is code I use to call the above -
private void SaveToIsolatedStorage()
{
saveToIsolatedStorage("GameData", GameData);
}
private void LoadFromIsolatedStorage()
{
Object temp;
if (loadObject("GameData", out temp))
{
GameData = (CGameData)temp;
}
else
{
GameData.Reset();
}
}
Note that the objects I save and restore like this are small and serializable. If my object contains a 2 dimensional array or some other object which is not serializable then I perform my own serialization and deserialization before using iso storage.
What if you changed RetrieveSetting<T> to this:
private T RetrieveSetting<T>(string settingKey)
{
T settingValue;
if(isolatedStore.TryGetValue(settingKey, out settingValue))
{
return (T)settingValue;
}
return default(T);
}
Notice that the object being fetched is being declared as type T instead of object.
I have a C# Windows forms application that runs a Trivia game on an IRC channel, and keeps the questions it asks, and the Leaderboard (scores) in Classes that I serialize to XML to save between sessions. The issue I have been having is best described with the flow, so here it is:
User X Gets entry in Leaderboard class with a score of 1. Class is saved to XML, XML contains one entry for user X.
User Y gets entry in Leaderboard class with a score of 1. Class is saved to XML, XML contains duplicate entries for User X, and one entry for User Y.
After running it for a week with under 20 users, I hoped to be able to write a web backend in PHP to help me use the scores. XML file is 2 megabytes.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
namespace IRCTriviaBot
{
[Serializable()]
public class LeaderBoard
{
[Serializable()]
public class Pair
{
public string user;
public int score;
public Pair(string usr, int scr)
{
user = usr;
score = scr;
}
public Pair() { }
}
private static List<Pair> pairs = null;
public List<Pair> Pairs
{
get
{
if (pairs==null)
{
pairs = new List<Pair>();
}
return pairs;
}
}
public LeaderBoard()
{
}
public void newScore(string usr)
{
bool found = false;
for (int i = 0; i < Pairs.Count && !found; ++i)
{
if (Pairs[i].user==usr)
{
found = true;
Pairs[i].score++;
}
}
if (!found)
{
Pairs.Add(new Pair(usr, 1));
}
}
public int getScore(string usr)
{
bool found = false;
for (int i = 0; i < Pairs.Count && !found; ++i)
{
if (Pairs[i].user == usr)
{
return Pairs[i].score;
}
}
if (!found)
{
return 0;
}
return 0;
}
}
}
Here's where the serialization and deserialization happens.
void parseMessage(string message, string user = "")
{
if (message == "-startgame-")
{
if (!gameStarted)
{
gameStarted = true;
openScores();
startGame();
}
}
else if (message == "-hint-")
{
if (!hintGiven && gameStarted)
{
sendMessage("Here's a better hint: " + Form2.qa.Answers[curQ].Trim());
hintGiven = true;
}
}
else if (message == "-myscore-")
{
sendMessage(user + ", your score is: " + leaderB.getScore(user));
}
else if (message.ToLower() == Form2.qa.Answers[curQ].ToLower())
{
if (gameStarted)
{
sendMessage(user + " got it right! Virtual pat on the back!");
leaderB.newScore(user);
saveScores();
System.Threading.Thread.Sleep(2000);
startGame();
}
}
else if (message == "-quit-")
{
if (gameStarted)
{
sendMessage("Sorry to see you go! Have fun without me :'(");
gameStarted = false;
}
else
{
sendMessage("A game is not running.");
}
}
else
{
if (gameStarted)
{
//sendMessage("Wrong.");
}
}
}
void saveScores()
{
//Opens a file and serializes the object into it in binary format.
Stream stream = System.IO.File.Open("scores.xml", FileMode.Open);
XmlSerializer xmlserializer = new XmlSerializer(typeof(LeaderBoard));
//BinaryFormatter formatter = new BinaryFormatter();
xmlserializer.Serialize(stream, leaderB);
stream.Close();
}
void openScores()
{
Stream stream = System.IO.File.OpenRead("scores.xml");
XmlSerializer xmlserializer = new XmlSerializer(typeof(LeaderBoard));
//BinaryFormatter formatter = new BinaryFormatter();
leaderB = (LeaderBoard)xmlserializer.Deserialize(stream);
stream.Close();
}
I think this has to do with pairs being marked static. I don't believe the XmlSerializer will clear a list before adding elements to it, so every time you call openScores() you will create duplicate entries rather than overwrite existing ones.
In general, I've observed that serialization and global variables don't play well together. For this purpose, "global variables" includes private statics, singletons, monostate classes like this, and thread-local variables.
It also looks like there's some waffle here between using XML and binary serialization. They are completely different beasts. XML serialization looks only at a class's public properties, while binary serialization looks only at a class's instance fields. Also, XML serialization ignores the Serializable attribute.