Saving & loading data on level selection screen, XML, XNA - c#

I am making a basic platformer, my first ever game. I've run into a bit of a problem. So far the game only has one level, and it is loaded from a .txt file. However I'd like to have a sort of an Angry Birdish world/level selection screen.
The plan is to have an icon for each level visible, but only so far completed levels and the next one accessible. Also for the completed levels the score (stars, whatever) would be displayed under the icon.
I do not wish to load the levels from XML, at least not yet. Only the persistent world data that needs to be read AND written. I assume the easiest way is to load even the formatting of the level selection screen from XML, and not use the method i currently use (text files).
I could do this with text files, I suppose, but I really do not relish the idea of writing and sorting through the file. I then discovered that XML-files should be a bit less problematic in this regard. However additional problem rises from the fact tht I've never ever worked with XML-files before.
Could someone point me in a direction of a tutorial for this sort of things, or some sample you might have come accross that accomplishes at least relatively similar results. I don't expect anyone to do the coding for me, but if you have pointers or time and patience to provide a sample, I'd appreciate it a lot.
After some further digging and fumbling with tutorials for older XNA versions I managed to produce following save/load class:
namespace SaveLoadXML
{
class SaveLoad
{
public LevelInfo Load (int id)
{
LevelInfo level;
// Get the path of the save game
string fullpath = "World.xml";
// Open the file
FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate,
FileAccess.Read);
try
{
// Read the data from the file
XmlSerializer serializer = new XmlSerializer(typeof(LevelInfo));
level = (LevelInfo)serializer.Deserialize(stream);
}
finally
{
// Close the file
stream.Close();
}
return (level);
}
public void Save (LevelInfo level, int id)
{
// Get the path of the save game
string fullpath = "World.xml";
// Open the file, creating it if necessary
FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate);
try
{
// Convert the object to XML data and put it in the stream
XmlSerializer serializer = new XmlSerializer(typeof(LevelInfo));
serializer.Serialize(stream, level);
}
finally
{
// Close the file
stream.Close();
}
}
}
}
Now I started to think, is there a way to target a specific part of the XML-file, or is the writing always just from the start? Almost all of the examples I saw had a condition at the start: if the file exists, delete it and then write.
I assume I could (or even should?) make a list of LevelInfo objects and just load them all at once, as there is no real need to load a single LevelInfo anyway. On the saving however, do I need to load the previous state (old list) and then manipulate the list regarding the certain indexes involved, and then delete te file, and save it again.
This might open an easy way for the system to fail if something goes wrong in the saving or power fails for example. The whole file would be lost or corrupt. I suppose this ould be countered with using back-up file and then checking the integrity of the main file, but now it's starting to feel like quite a mountain to climb for a beginner like me.
Having tried this question on GameDev, I'll just clarify the main question here:
1) Can I save only info about one or two levels in the XML-file containing info for all levels? ie. can I use some indexing to point the write operation to a particular section that would then be overwritten/replaced.
2) If not, is there any way to safely load all info from file, delete file, save all info after modifying it where needed.
After some looking into this Json stuff, I've managed to successfully serialize test level information. However, de-serialization fails as I have a rectangle as a part of the object. Error is as follows:
Error converting value "{X:1 Y:1 Width:1 Height:1}" to type 'Microsoft.Xna.Framework.Rectangle'. Path '[0].Rectangle', line 6, position 46.
class LevelInfo
{
public int ID { get; set; }
public Vector2 Dimensions { get; set; }
public Vector2 Position { get; set; }
public Rectangle Rectangle { get; set; }
public int Stars { get; set; }
public string Text { get; set; }
}
class SaveLoadJSON
{
public static List<LevelInfo> Load()
{
List<LevelInfo> levels = new List<LevelInfo>();
using (StreamReader file = File.OpenText("World.json"))
{
JsonSerializer serializer = new JsonSerializer();
levels = (List<LevelInfo>)serializer.Deserialize(file, typeof(List<LevelInfo>));
}
return levels;
}
public static void Save(List<LevelInfo> levels)
{
if (File.Exists("World.json"))
{
File.Delete("World.json");
}
using (FileStream fs = File.Open("World.json", FileMode.CreateNew))
using (StreamWriter sw = new StreamWriter(fs))
using (JsonWriter jw = new JsonTextWriter(sw))
{
jw.Formatting = Formatting.Indented;
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(jw, levels);
}
}
}
Is there a way to work around this? Preferably a relatively simple way for a simple beginner like me.
Or alternatively, is there a way to omit the rectangle information to begin with, and maybe add it later? If I input nothing to the rectangle, it still is added to Json-file with 0 values. I do need the rectangle info for the drawing.

So here comes the promised answer.
Personally I'd prefer using JSon for storing data, since it's a lot easier to work with than XML, and takes up less storage. What you're going to want to do, is make Data Models of your player, enemy, items, scene objects, etc.
Then, you'll want to JsonConvert.SerializeObject() a parent data model, which will contain all those things.
Save this in any file, and Deserialize it again upon load, and reconstruct all objects from scratch.
Alternatively, just have all properties in the classes you're working with already, be public. That way, JsonConvert will be able to actually serialize the entire model. Keep in mind, if you do this runtime, it will make more of a complete snapshot of the Levels current state. Aka. where the enemies are located, the health remaining and whatever else you may have.
I hope this answers your question.

Related

C# / WPF - How should I save user-typed data all at once after a submit button click?

I'm learning WPF/C# via self-teaching and am currently building a small WPF app for practice instead of following tutorials, and so far it's been great; however, I have hit a roadblock that I've spent days on.
I have a WPF User Control with multiple TextBoxes where a user can type in a title, ingredients, tools, steps, notes, and add an image of what they made. After all of that is filled in, I would like for them to click a 'Submit' button and have that data saved and able to be accessed at another time.
Now for one, I don't know how I should go about doing that, I have seen mention of XML, JSON, SQLite, etc. I did try the XML method like so:
private void SubmitButton_Click(object sender, RoutedEventArgs e)
{
Recipe recipe = new Recipe();
recipe.Title = TitleBox.Text;
recipe.Step1 = StepBox1.Text;
recipe.Step2 = StepBox2.Text;
recipe.Step3 = StepBox3.Text;
recipe.Step4 = StepBox4.Text;
recipe.Step5 = StepBox5.Text;
recipe.Step6 = StepBox6.Text;
recipe.Ingredients = IngredientBox.Text;
recipe.Tools = ToolBox.Text;
recipe.Notes = NoteBox.Text;
CreateViewModel.SaveData(recipe, TitleBox.Text);
}
In my ViewModel, I have this typed up:
public static void SaveData(object obj, string filename)
{
XmlSerializer sr = new XmlSerializer(obj.GetType());
TextWriter writer = new StreamWriter(filename);
sr.Serialize(writer, obj);
writer.Close();
}
This does end up "working" (without the image of course) in that I see the file and can access it manually via the explorer (loading the file in the program is a whole other thing I'm going to tackle another day), but I feel that this isn't the way to go about it from what I've seen on my searches. If there's a better strategy here that I'm missing, I would love to use and learn from it.
Thank you
EDIT
Alright so I downloaded and am currently using PostgreSQL thanks to #Jonathon Willcock (watched a long video on it to get the gist, and am currently diving deep into it even more as I implement it into my small app), so far I think I've been successful in saving the data into a table with the necessary values as I see it there currently, but I'm clueless when it comes to saving an image. I will continue to work at it and try to figure it out, but if anyone has any advice for me on how I could go about doing that, I would greatly appreciate it!

Azure Form Recognizer only analyzes the first file in a stream

I am testing some AI Document analysis stuff, and am currently trying to allow users to Upload Files to a WebApp, which in turn sends them to Azure Form Recognizer and processes the results.
I am however not able to do so in a single Request.
This is how the Files are represented:
[BindProperty] public List<IFormFile> Upload { get; set; }
I can iterate over these and get the expected results, but this makes the operation take quite long. I would like to just send all of the files in one request (as shown below), but it only ever analyzes the first one. I am using Azure.AI.FormRecognizer.DocumentAnalysis, so the client and StartAnalyzeDocument Method is from there.
using (var stream = new MemoryStream())
{
foreach (IFormFile formFile in Upload)
{
formFile.CopyTo(stream);
}
stream.Seek(0, SeekOrigin.Begin);
AnalyzeDocumentOperation operation = client.StartAnalyzeDocument(modelId, stream);
operation.WaitForCompletion();
Console.WriteLine("This many documents were analysed: " + operation.Value.Documents.Count);
result = operation.Value;
};
"result" is what I process later on. I am quite stumped on this, as I would have expected the appended stream to just work. If anyone has a solution or could point me in the right direction, it would be much appreciated.
Form Recognizer does not yet support processing multiple documents in a single analyze operation for prebuilt-invoice and custom models. Furthermore, most file formats cannot just be appended together to concatenate the content.
One way to speed up the analysis of multiple files in a batch is to call the analyze operation in parallel. Here is a sketch.
var results = Upload.AsParallel().ForAll(formFile =>
{
using (var stream = formFile.OpenReadStream())
{
var operation = client.StartAnalyzeDocument(modelId, stream);
operation.WaitForCompletion();
return operation.Value;
}
}).ToArray();

How to serialize and deserialize tabcontrol c#

I am using a tabcontrol which I want to serialize and save . I am using this code but it gives that tabcontrol class is not marked as serializable . How to mark it serializable as I am not able to override the class . How to do it ?
using (Stream stream = File.Open("data.dat", FileMode.Create))
{
BinaryFormatter bin = new BinaryFormatter();
bin.Serialize(stream,tabControl1);
}
It gives this error
System.Windows.Forms.TabControl not marked as serializable
Why don't serialize Controls and is there an alternative?
If you serialize a control, there are some problems:
You can't do it because System.Windows.Forms.TabControl not marked as serializable like you have seen.
If you will do it and only if it is allowed, are there a lot of properties and classes, interfaces, events etc. that are serialized with it, inherited from the classes above and that is not what you will.
The only way you could do it is by made a new class, bind all the values you will save with the properties and serialize that class.
[Serializable] // don't forget this! It will mark your class so you can serialize it.
public class BindingClass // p.s.: give this a better name!
{
public string Text { get; set; } // Bind whit a control of your tab control.
public float Number { get; set; }
public string ImageLocation { get; set; } // used for the image
public IEnumerable<object> ListOfString { get; set; } // used for a list
}
Code example
Text and numbers
Well for text and numbers it is easy. You can made an intense of your class and you can bind that. After it you can serialize it. An example:
BindingClass bc = new BindingClass();
bc.Text = textBox1.Text;
bc.Number = numbericUpDown.Value;
using (Stream stream = File.Open("data.dat", FileMode.Create))
{
BinaryFormatter bin = new BinaryFormatter();
bin.Serialize(stream, bc);
}
Images
For images is it a little bit complex. You can serialize an image but it is also a bad thing to do that. Better is to save the image in your bin/debug folder of your project and serialize the path of that image. An example:
string imageLocation = Application.StartupPath + #"\myImage.jpg"
pictureBox1.Image.Save(imageLocation, ImageFormat.Jpeg);
// declare bc like code above.
bc.ImageLocation = imageLocation;
// serialize bc.
If the image already exists in the file, you can override it. But if you will work with histories, not a good thing... You can solve it by use the current date time as filename! Change your code with this:
string imageLocation = Application.StartupPath +
DateTime.Now.ToString("yyyyMMddHHmmss") + ".jpg"
Note: You can also use a blob service like Azure and Amazon (not free) or upload images to Imgur, Flickr or 9gag (patical free). Remark that there must be an internet connection between the client and server. You can upload by searching on Google how to do it.
List of string
For list of strings you can use this:
bc.ListOfString = comboBox1.Items;
Note
I haven't test the code. So if you have a problem with one the examples comment it and I will look at it, but try also to look on Google for a solution for your problem. Try it yourself, best way to learn...
Alternative for serialize (update 16-Jun-16)
Serializing is a save way to make your code unreadable for people. However that can give problems if you scale your application. The problem is also happen by Microsoft Word. The old .doc files are also serialized code, the new .docx files are zipped xml files and now it's easier to make .docx files.
Good alternatives are Json or XML.

C# Collection was modified error with garbled text reading from file

I'm new to C# and have been asked to write a custom task in a plugin for our deployment software, but I just can't wrap my head around this. I'm simply trying to log some data that is in a certain directory on my deployment server to the output log, but I only get the first file logged (and then even the text is garbled, I think it's loading the bytes wrong somehow) before getting strange errors about "Additional information: Collection was modified; enumeration operation may not execute."
Here is the code I have so far:
class Clense : AgentBasedActionBase
{
public string dataPath { get; set; }
protected override void Execute()
{
IFileOperationsExecuter agent = Context.Agent.GetService<IFileOperationsExecuter>();
GetDirectoryEntryCommand get = new GetDirectoryEntryCommand() { Path = dataPath };
GetDirectoryEntryResult result = agent.GetDirectoryEntry(get);
DirectoryEntryInfo info = result.Entry;
// info has directory information
List<FileEntryInfo> myFiles = info.Files.ToList();
foreach (FileEntryInfo file in myFiles)
{
Byte[] bytes = agent.ReadFileBytes(file.Path);
String s = Encoding.Unicode.GetString(bytes);
LogInformation(s);
// myFiles.Remove(file);
}
}
}
Does anyone know what I can do to try to fix this?
Update
Removing the myFiles.Remove() fixed the error (I thought it would loop too many times but it doesn't) and it looks like I'm getting one log entry per file now, but the messages are still garbled. Does anyone have any idea why this is happening?
You can either iterate the myFiles collection in the reverse direction (so that you don't corrupt the collection when you remove each individual file), or you can simply clear the collection when you are done iterating it (which would accomplish the same thing).
You are modifying the collection with
myFiles.Remove(file);
Delete that line (since it's the cause).
In his comment, Blorgbeard is almost certainly correct with regards to the encoding used to read the files on disk. Remember that Encoding.Unicode is actually UTF16, which is somewhat confusing, and if I had to guess, probably not the encoding your files were created with.
For completeness, I will add the BuildMaster-idiomatic way to handle your scenario using the ReadAllText() extension method on IFileOperationsExecuter:
protected override void Execute()
{
var agent = this.Context.Agent.GetService<IFileOperationsExecuter>();
var entry = agent.GetDirectoryEntry(new GetDirectoryEntryCommand() { Path = dataPath }).Entry;
foreach(var file in entry.Files)
{
string contents = agent.ReadAllText(file.Path);
this.LogInformation(contents);
}
}
The ReadAllText() method will internally assume UTF8 encoding, but there is an overload that accepts a different encoding if necessary.

Small Simple Local Data Store for keeping user settings

I have this tiny C# winforms application that will NOT grow larger in any way.
It's just two input fields and a button.
I want to know if you guys have knowledge of a way to store the values that a user inputs in a local datastore. You may consider 10 records to be a lot of data for this scenario.
Requirements
It shouldn't require any setup from the database side. (table creation, etc)
I should be able to just give it an object and it should store it, I don't want to waste time on that.
The data needs to be fairly easily retrievable.
I want to be able to reuse this thing for every small app I create like this.
My Ideas
A POCO object that will be XML-Serialized and saved to the Local Settings folder. Upon loading of the app, this file is deserialized back into the POCO object.
An OODBMS: I have no experience with these but I always thought they consisted of a single dll so it would be easy to package them with the program.
I once, a long long time ago, built an application that stored user settings inside the registry. Don't know if that is still appreciated though.
What do you think is the best approach?
Code samples are very much appreciated!
I've taken both answers into account and built the following:
public static class IsolatedStorageExtensions
{
public static void SaveObject(this IsolatedStorage isoStorage, object obj, string fileName)
{
IsolatedStorageFileStream writeStream = new IsolatedStorageFileStream(fileName, FileMode.Create);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(writeStream, obj);
writeStream.Flush();
writeStream.Close();
}
public static T LoadObject<T>(this IsolatedStorage isoStorage, string fileName)
{
IsolatedStorageFileStream readStream = new IsolatedStorageFileStream(fileName, FileMode.Open);
BinaryFormatter formatter = new BinaryFormatter();
T readData = (T)formatter.Deserialize(readStream);
readStream.Flush();
readStream.Close();
return readData;
}
}
A wrapper POCO object that contains that data to be serialized:
[Serializable]
internal class DataStoreContainer
{
public DataStoreContainer()
{
UserIDs = new List<int>();
}
public List<int> UserIDs { get; set; }
}
To consume these extensions:
private IsolatedStorageFile _isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);
private DataStoreContainer _data = new DataStoreContainer();
private const string FILENAME = "MyAppName.dat";
And in any method where you want to get the data :
_data = _isoStore.LoadObject<DataStoreContainer>(FILENAME);
To save the data:
_isoStore.SaveObject(_data, FILENAME);
Have you looked at Isolated Storage? It stores data in a local file, specific to that user (or to the application, depending on how you specify). You can easily serialize objects to and from the store because it's stream-based. It sounds like the perfect solution for your problem.
Since you state 10 items would be a lot I would vote for #1 or a variation of #1, Binary serialized... you don't seem to indicate that being able to read the data is important and binary data should give you smaller file sizes, though if 10 is a lot this still shouldn't be important.
That being said I enjoy what I've seen of db4objects.

Categories