Design question: how to handle saves & text

Started by
5 comments, last by SmkViper 8 years, 9 months ago

Hello fellow game devs,

I recently started doing some design on a game that will be my first experience beyond "simple games".

what I mean is, to this point I've only made the conceptually basic games, such as scrolling shooters, platformers, tic-tac-toe, checkers, etc.. basic arcade games and emulation of board games....

But, now I'm pursuing the design of a game that hopefully will give me more useful/practical skills in game developement. One with story and heavy use of text and branched dialogs. One that will have a "complete Time" long enough to merrit a save-system, rather then the code-system I was using in my platformer. But I really don't have any experience in either of these fields OR know exactly how to search for these sorts of things online.

What I'm looking for help with:

1) how does one go about making "save files", obviously I could do a basic text or xml file with the important data, but even then how is a good practice for structuring it? Also what methods provide more "secure" save files, something the user couldnt just open in notepad+ and edit to change their game? any file types that are used? or possibly encoding methods I should know?

2) how does one deal with large ammounts of text in a game? I understand that, again, yes I could use an XML file and look up blocks of text by an IDtag or something, but again nothing is stopping the player from opening that file in Notepad and spoiling the story, or finding out which conversation decisins lead to what rewards, etc... So I'm asking what a good way to do large quantities of text is?

**I'm doing this game in C# in Unity for now, so if anything here is needing platform/language specific addressing thats my tools. But honestly I would be happy with pseudocode, links to guides/tutorials, or general discussion on theory and practices.

TL;DR - I need guidance on how to do save files, and how to deal with large ammounts of text/dialog that I feel shouldnt be ghardcoded into a game. looking for pseudocode, guides/tutorials, or discussion on theories and practical structures

Thanks and sorry for the long post!

Advertisement
First off: Don't bother encrypting or making things "hard to read" for the end user. If they want in, they'll get in, and nothing you do will stop them. If you want to get extra kudos from your users, include the editor with the game and label them "modding tools" and they'll love you smile.png

To more directly answer your questions:

For save files there are a lot of ways to do them, and it will depend on what kind of game you're making and how you want to save. If you're making a visual novel style game, then you really only have to save the player's current position in the story, and any choices they've made on the way. If you're doing a checkpoint or save-point style system like old JRPGs, you only need to save the player's current stats, inventory, and location. Maybe a few story choices or flags. However if you're doing a save-anywhere game you're pretty much going to have to tease out all of the state that your game needs from every single object that has state that can change. Stuff like animation time, position, velocity, sound file position, etc.

The way I've usually structured save games is to start off with some kind of simple header containing a short identifier so I know I'm loading a save (usually four characters long, just cause that's easy to load), a version number so I can load older save versions in newer code, an object table containing each object saved and its type, and then all the object data in a table - pointers to other objects are saved as indices into the object table.

Whether it's binary or XML or whatever is up to you, it doesn't really matter, but binary formats will generally save and load faster than text-based ones like XML or JSON.

Example in XML:

<CoolGameSave>
  <Version>1</Version>
  <ObjectTable>
    <Object Type="Player"\>
    <Object Type="Sword"\>
    <Object Type="Box"\>
    <Object Type="Enemy"\>
  </ObjectTable>
  <Player>
    <Health>10</Health>
    <Inventory>
      <Object id="2"/> <!-- indicates the second object in the table, the "sword" -->
    </Inventory>
  </Player>
  <Sword>
    <Name>Sword of Doom</Name>
    <Damage>15</Damage>
  </Sword>
  <Box>
    <Name>Mysterious Treasure Chest</Name>
    <Inventory/>
  </Box>
  <Enemy>
    <Health>5</Health>
    <Speed>10</Speed>
    <Name>Scary Skeleton</Name>
  </Enemy>
</CoolGameSave>
As for text, nothing really special to it. Only gets complex when you have to deal with localization, especially with languages that might require multi-byte characters or special layouts (i.e. right-to-left reading order vs left-to-right).

This text you generally want to store outside the game in an easy to edit format (like XML or JSON) so that you can fix typos easily, or hand them off to translators who may not need your entire game editor to do their work. Then, in your UI or wherever you need text, you can use specially tagged "keys" that index into the localization file so it can be replaced with the real text at runtime.

Example:

<Strings>
  <String Key="Title">Super cool game</String>
  <String Key="Copyright">Copyright (c) 2015 Best Studio Ever</String>
  <String Key="New_Game">New Game...</String>
  <String Key="Continue">Continue</String>
  <String Key="Settings">Settings</String>
  <String Key="Quit">Quit Game</String>
</Strings>
Then you might access the strings like so (using a mythical XML based UI layout system):

<MainMenu>
  <Title HAlign="Center" VAlign="Top">$Title\n$Copyright</Title>
  <Option Action="NewGame">$New_Game</Option>
  <Option Action="Continue">$Continue</Option>
  <Option Action="Settings">$Settings</Option>
  <Option Action="Quit">$Quit</Option>
</MainMenu>
Note the $ in front of each entry, which you simply replace with the appropriate text from your string file. This has the added bonus for if you forget to make a string or make a typo, it will be obvious, even if you don't know the language, cause the string will have "$" in front which will look wrong in most places in the UI.
OK! This is a lot to absorb, but all good stuff!

Yeah it's a pretty text heavy "narrative puzzle" game, so along the lines of Phoenix wright, professor Layton, and Zero Escape. I figured it would be the ideal stepping stone between beginner games and a big expansive project.. Sorta uses my previous experience with small games and puzzles, and wraps it in a narrative with some new stuf f :) I think because the game design of these sorts of games is "episodic" in nature it shouldn't be to hard to just catalog all the flags/switches that have been performed, and the level :P...

You mentioned in the save file discussion that, while acceptable, XML files load slower then a binary file. I doubt you mean a ".bin" file, as ive only ever seen/used those for command line stuff.. So would you mind explaining more on this?

Thanks again!

Binary files are just files were you write the memory representation of a int to the file instead of changing this to readable ascii text first. If you are using C++ STL streams you do this by specifying ios_base::binary to the open_mode parameter of the stream.open function.

In the case of using a FILE* you use the fwrite functions to write data to the file.

As an example in code to convert an int to a char buffer


char* buffer = new char[128];//just a piece of memory, char is 1 byte large, so this gives a chunk of memory that is 128 bytes.
int test = 0;
 
//Write code
*(int*)buffer = test; //This will write the int to the buffer
buffer += sizeof(int); //This will advance the buffer pointer with the size of an integer so that you wont overwrite the int you just written
 
 
//Read code
test = *(int*)buffer; //This will read an int from the buffer
buffer += sizeof(int); //So that the next time you read from this you will read the next value not the int you just read.

The trick here is that I am working around the C++ type system and am explicitly telling the pointers how a piece of memory should be interpreted when I write or read the integer from/to memory.

In the case of the Write operation we tell C++ to assign the data of a type to a char buffer. In this case we cast the buffer pointer to a pointer of the type we want to write to it. Then we dereference the memory area that pointer is pointing at, which gives us a variable of our type and copy the data we want to into that area. We then add the amount of bytes (this is what sizeof() does) we have written to the buffer pointer, so that the next element wont overlap with the one we just written.

The case of the read operation it works the other way round we assign from the char buffer to the memory that has our type. This is done through telling the char buffer pointer, that the thing it is actually pointing at is a type of our choice(in this case int*), we then deference that memory area so that we dont read the pointer but the actual data as that type and assign to a variable of that type.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

OK, as I'd said above I'm using C# in Unity, so I'm sure there's a similar method. I guess I just didn't realize a "plain" file was a binary file thanks!

OK, as I'd said above I'm using C# in Unity, so I'm sure there's a similar method. I guess I just didn't realize a "plain" file was a binary file thanks!

For C# there is a binary stream writer I believe. This is the class I believe will help you on your way in C#: https://msdn.microsoft.com/en-us/library/system.io.binarywriter%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

I missed the C# reference before.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

The basic difference between binary and text documents is when I write the number 10 to a binary file, it writes the number 10 (which can be stored in a single byte).

If I write 10 to a text document it has to figure out how to represent 10 (stored in binary) as decimal, and then convert the decimal number to a series of characters, finally writing out the character '1' and then the character '0'. For ASCII files this is two bytes of information - twice the storage space of writing the number directly, and when you read and write it you have to convert it. (And if you're writing in a Unicode text format it could be even larger in size)

Text makes things very easy to edit for a human, binary makes things quicker to load smile.png

If you're clever about how you read and write your data, you can even swap out a binary stream for a text stream for debugging purposes - writing out text to make sure your code works, and then switching to a binary stream for speed.

This topic is closed to new replies.

Advertisement