Sign in to follow this  
Virii

RPG Character Saving

Recommended Posts

So I'm creating a text RPG, and I want to have multiple characters that are able to be saved in the games memory, or to a text file or something. My questio nis, what is the best way to save it all and what is the best way to read the information back into the game to continue their adventure? Right now my plans are to have a text file hold everything, from what items they are holding, to HP and level plus character name. My only problem is figuring out how to read all that into the game and properly assign it all to the right variables. Also, how can I create multiple characters and save them all to the same file and then later.. read back JUST the characters name and have the user select it, and then read all the data to the right variables. Does all this make sense?

Share this post


Link to post
Share on other sites
I just used a simple text file with a {descriptor}={value} pair for almost everything:

i.e.
hp=40
maxhp=60
name=Bob

and then just used some simple parsing crap to break each line into tokens for reading back into my program. I wrote an accessor library for these files, and had a getInt(descriptor)/ getBool(descriptor)/ getString(descriptor) method that would step through the file, find that descriptor, parse its associated value and return it.

You could probably use TinyXML or something, it would be a little bit easier.

Share this post


Link to post
Share on other sites
Could you reword that so that someone who is really new to C++/programming could understand it a little better?

Did you create a text file with the information with something=value, and then how did you parse it all through so that it matches up with te different character names?

Also, someone told me fread/fwrite/fopen/fclose are bad to use, is this true and why? If it's true, what is the better way of read/writing things to a file?

Share this post


Link to post
Share on other sites
Firstly, the methods for saving and reloading depend on what language you are using.

As to the rest of your question, saving it all as one text file would probably work fine. I would suggest the first line of the file holding the number of characters. Then you can loop through as many characters there are, each time getting all the information you need. With only reading the names, and nothing else, you could have the first line of each new character be the name, so for each character there would be:
Name
HP
Level
Armor
etc...
So depending on how many things you had for each character, you could skip that many lines, so you would only get the names (first line for each character).

This might be confusing, as I'm kind of in a rush, but your file could look something like this:
3 (Number of characters)
NameChar1
HPChar1
LevelChar1
NameChar2
HPChar2
LevelChar2
NameChar3
HPChar3
LevelChar3

If you tell me what language you are using, I can show you how to load all the information.

Hope this helps.

Share this post


Link to post
Share on other sites
I'm using C++.

Heres a pastebin of all coding I have done so far. Feel free to also critic me on my coding.

http://pastebin.com/425268

Share this post


Link to post
Share on other sites
Quote:
Original post by Virii
Could you reword that so that someone who is really new to C++/programming could understand it a little better?

Did you create a text file with the information with something=value, and then how did you parse it all through so that it matches up with te different character names?

Also, someone told me fread/fwrite/fopen/fclose are bad to use, is this true and why? If it's true, what is the better way of read/writing things to a file?


I created a text file with the pairs in it when I was saving my stuff to disk, which isn't exactly perfect but it works well.

To parse, I use ifstream::getline and strtok (although you shouldn't use strtok; it's one of those C functions that isn't necessarily safe. Someone else can probably prescribe a better solution for that).

Effectively, I go through the file from the top, reading each line and tokenizing it (so I get the "descriptor" before the "=") and then check to see if that's the one I'm looking for. If so, I take what's left on the line (after the =) and turn that into an integer/string/etc.

The C file functions aren't too bad, but you should probably use the C++ functions in fstream.

Share this post


Link to post
Share on other sites
Id say something like this would be perfect for saving your characters


FILE* out;
out=fopen("Save.sav","wb");
fwrite(&character,sizeof(character),1,out);
fclose(out);

Share this post


Link to post
Share on other sites
Well, if you're using C++, you can do something like this:

char Name[32];
int NumChars;
int MaxHP;
int HP;
std::ifstream CharFile("CharFile.dat", std::ios::binary);
CharFile >> NumChars; // Get number of characters
for(int i = 0; i < NumChars; i++) // Loop through all the characters and get the info
{
CharFile >> Name;
CharFile >> MaxHP;
CharFile >> HP;
}

I think that would work. It uses fstream, rather than the FILE structure, but it should be fairly easy to change to suit your needs.

Hope this helps.

Share this post


Link to post
Share on other sites
As you can see, there's definately more than one way to skin a cat. My solution would be more object oriented, and from looking at your code I think the procedural way is better suited for you.

It's just a matter of what best fits you.

Personally here's what you could consider...

write functions like:

ReadInt, WriteInt, ReadFloat, WriteFloat, ReadStr, WriteStr... etc.

Then you can get the data in and out easily. All you have to worry about is the format.

Share this post


Link to post
Share on other sites
You'd want to do it this way because using a FILE pointer (C way) has no idea of what an object is; thus, bugs can roll in quickly. A better approach is the C++ way. This should get you started.


#include <iostream>
#include <string>
#include <fstream>
using namespace std;

class Player
{
public:
string name;
int age;
}

int main()
{
Player PC;
PC.name = "foo";
PC.age = 23;

// save character
ofstream out("data.dat", ios::binary);
out.write((char *)&PC, sizeof (class Player));
out.close();

// read in character
ifstream in("data.dat", ios::binary);
in.read((char *)&PC, sizeof (class Player));
in.close();

return 0;
}

Share this post


Link to post
Share on other sites
Could you explain to me what the following does:

// save character
ofstream out("data.dat", ios::binary);
out.write((char *)&PC, sizeof (class Player));
out.close();

// read in character
ifstream in("data.dat", ios::binary);
in.read((char *)&PC, sizeof (class Player));
in.close();

Also, how could I do something like this but to make it in this format:
name=Bob
hp=40
maxhp=50
items=armor,heavy_sword,health_potion

Then read in all the information when a character is brought back from being saved.

Share this post


Link to post
Share on other sites
You said:
Quote:
Original post by Xero-X2
Id say something like this would be perfect for saving your characters


FILE* out;
out=fopen("Save.sav","wb");
fwrite(&character,sizeof(character),1,out);
fclose(out);

Our survey says:
Uh-uh (X).

Will sometimes work but as soon as character is a non-POD type or contains a pointer all bets are off. You should also be using what is idiomatic in C++ (streams) when using C++ since C file IO may not be familiar to other people reading your code.

You said:
Quote:
Original post by Harryu
Well, if you're using C++, you can do something like this:

char Name[32];
int NumChars;
int MaxHP;
int HP;
std::ifstream CharFile("CharFile.dat", std::ios::binary);
CharFile >> NumChars; // Get number of characters
for(int i = 0; i < NumChars; i++) // Loop through all the characters and get the info
{
CharFile >> Name;
CharFile >> MaxHP;
CharFile >> HP;
}

I think that would work. It uses fstream, rather than the FILE structure, but it should be fairly easy to change to suit your needs.

Hope this helps.

Our survey says:
Uh-uh (X).

Better, but if Name is ever longer than 31 characters in length all bets are off (and you know it's going to happen sooner or later). In C++ text is represented by std::strings, not evil null-terminated char arrays.

You said:
Quote:
Original post by dxFoo
You'd want to do it this way because using a FILE pointer (C way) has no idea of what an object is; thus, bugs can roll in quickly. A better approach is the C++ way. This should get you started.


#include <iostream>
#include <string>
#include <fstream>
using namespace std;

class Player
{
public:
string name;
int age;
}

int main()
{
Player PC;
PC.name = "foo";
PC.age = 23;

// save character
ofstream out("data.dat", ios::binary);
out.write((char *)&PC, sizeof (class Player));
out.close();

// read in character
ifstream in("data.dat", ios::binary);
in.read((char *)&PC, sizeof (class Player));
in.close();

return 0;
}

Our survey said:
Uh-uh (X).

Almost full circle. Saving a non-POD type or type containing a pointer in that manner will not work (although it may appear to sometimes). You are not saving the name of the player to disk, you are saving a pointer to the name of the player. Load the file up again in the same instance of the program and it is likely that the players name still exists at that location and things will appear to work. Save the file in one program and load it in another and at best your players name will be an empty string or a string of random characters. More likely your program will just crash.

I say:
class Character
{

public:

// public interface to Character

private:

std::string name_;
unsigned int health_;
unsigned int level_;
std::vector< Item > inventory_;
// other stuff

// the following lines declare two "friend" functions. In C++
// "friend"s can access each others "privates". So the following
// functions, being declared as friends of this class, are allowed
// to access the private data of this class
friend std::ostream & operator<<(std::ostream & stream, Character const & character);
friend std::istream & operator>>(std::istream & stream, Character & character);

};

// this is an overloaded insertion operator. Whenever you call "X << c", where
// X is an output stream (i.e. std::cout, a std::ofstream, etc.) and c is an
// instance of the above Character class, this function will be called.
std::ostream & operator<<(std::ostream & stream, Character const & character)
{
// write the members in "key=value" form
stream << "name=" << character.name_ << '\n';
stream << "health=" << character.health_ << '\n';
stream << "level=" << character.level_ << '\n';

// inventory is a bit more difficult. I write this in
// "key={count,value,value,value}" form. The count is not stricly
// necessary, but provides a bit of extra safety. Here I assume that
// the Item class has an operator<<, just like this one
stream << "inventory={" << character.inventory_.size();
for (std::vector< Item >::const_iterator item = character.inventory_.begin(); item != character.inventory_.end(); ++item)
{
stream << ',' << *item;
}
stream << "}\n";
return stream;
}

// a helper function for reading a "key=value" pair from an input stream (i.e.
// std::cin, an instance of std::ifstream, etc.) where value is expected to be
// a string
bool read(std::istream & stream, std::string const & item, std::string & string)
{
std::string label;
// read everything from the stream upto the first '='
std::getline(stream, label, '=');
// if what we read is not the label we expected then return failure
if (label != item)
{
return false;
}
// read the rest of the line
std::getline(stream, string);
// return success
return true;
}

// a helper function for reading a "key=value" pair from an input stream where
// value is expected to be an unsigned integer
bool read(std::istream & stream, std::string const & item, unsigned int & value)
{
std::string label;
// read everything from the stream upto the first '='
std::getline(stream, label, '=');
// if what we read is not the label we expected the return failure.
// if what we read is the label we expected then try and read an unsigned
// integer from the stream. If that fails then return failure.
if (label != item || !(stream >> value))
{
return false;
}
// ignore the rest of the line (everything upto and including the next '\n').
// don't worry about the std::numeric_limits< std::streamsize >::max(),
// it's a rigourous form of "everything"
stream.ignore(std::numeric_limits< std::streamsize >::max(), '\n');
// return success
return true;
}

// a helper function for reading a "key=value" pair from an input stream where
// value is expected to be a vector of Items
bool read(std::istream & stream, std::string const & item, std::vector< Item > & vector)
{
std::string label;
// read everything from the stream upto the first '='
std::getline(stream, label, '=');
unsigned int count;
// if what we read is not the label we expected the return failure.
// if what we read is the label we expected but the "label=" is not
// followed by a '{' then return failure
// if what we read is the label we expected and the "label=" is followed
// by a '{' then try and read an unsigned integer from the stream that
// represents the number of items in the vector. If that fails then
// return failure.
if (label != item || stream.get() != '{' || !(stream >> count))
{
return false;
}
// make sure the vector doesn't contain any old items
vector.clear();
// while we still have items to get
while (count > 0)
{
Item item;
// if the next character in the stream is not a ',' then return
// failure
// if the next character in the stream is a ',' then try and read
// an item from the stream. Here I assume that the Item class
// has an operator<<. If that fails then return failure
if (stream.get() != ',' || !(stream >> item))
{
return false;
}
// append the newly read item into the vector
vector.push_back(item);
// we have one less item left to read
--count;
}
// if the next character in the stream is not a '}' then the count was
// wrong. Return failure
if (stream.get() != '}')
{
return false;
}
// ignore the rest of the line (everything upto and including the next '\n').
// don't worry about the std::numeric_limits< std::streamsize >::max(),
// it's a rigourous form of "everything"
stream.ignore(std::numeric_limits< std::streamsize >::max(), '\n');
// return success
return true;
}

// this is an overloaded extraction operator. Whenever you call "X >> c", where
// X is an input stream (i.e. std::cin, a std::ifstream, etc.) and c is an
// instance of the above Character class, this function will be called.
std::istream & operator>>(std::istream & stream, Character & character)
{
// try to read the name
if (!read(stream, "name", character.name_))
{
// failed, throw an exception indicating the problem
throw FileError("Failed reading characters name from file");
}
// try to read the health
if (!read(stream, "health", character.health_))
{
// failed, throw an exception indicating the problem
throw FileError("Failed reading characters health from file");
}
// try to read the level
if (!read(stream, "level", character.level_))
{
// failed, throw an exception indicating the problem
throw FileError("Failed reading characters level from file");
}
// try to read the inventory
if (!read(stream, "inventory", character.inventory_))
{
// failed, throw an exception indicating the problem
throw FileError("Failed reading characters inventory from file");
}
return stream;
}

int main()
{
Character player;
Character enemy;
std::cout << player; // print to screen
std::ofstream file("save.dat");
file << player; // print to file
file.close();
std::ifstream reader("save.dat");
reader >> enemy; // read from file (and create evil clone of player)
}

Enigma

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this