Greetings! The following is my entire method for saving and loading data in and out of a running program.
Firstly, I’m using the method for saving and loading information in and out of my RPG, so asides from the fact that this isn’t all I will be using during the entire program’s flow, I built it with the idea in mind that it will be used within an RPG to handle character data so the method might not be applicable to all game genres and a different method might work better for you. However the actual code will work regardless so if you need to know how to save and load data in and out of your program this might help.
K, first thing I did was to make a class with a set amount of data elements which I know will make up all my world objects, not just world characters but everything. I did this because all objects within my world are going to be treated as world characters and interact using a unique statistical character interaction system called Ability Based Character Development (ABCD), which I created to, in short, blow D20 out of the water.
Alright, every aspiring RPG games designer makes that claim so ignore that bit if you want but that is why I did it this way.
Anyway, the class I created is as follows:
#pragma once
class Object
{
public:
Object(void);
~Object(void);
// - Character ATRIBUTES -
// ID
char* Name ;
// Graphics
char* ImageSet ;
char* ImageName1 ; // The filename of the first Character image, add more for animation
int SpriteNumber ; // The number of the sprite used by the Character
int RenderOrder ; // The order in which to render the Character for depth simulation
int XPos, YPos ; // World position of Character
int QuadNum ; // Quadrent of World which contains the Character for area specfic rendering
// Collision
float H1Top, H1Bottom, H1Left, H1Right ; // Collision Data of Character for Height 1 (H1)
// ABCD System
int PHY, MEN, DEX ; // Prime ABCD Stat Types
};
This contains the basics character data which I have come up with thus far:
Name – Holds the name of the character
Imageset – Holds the name of the set of images which will be loaded into the following variables according to designation, i.e. if this says “Human_Male” then the following images will be loaded with multiple images which make up a Male Human’s appearance which can be rotated for animation.
SpriteName1 – Is the first of the images of the characters Imageset. I only have one image per character at the moment as I am currently not concerned about the characters animation.
SpriteNumber – Holds for various reasons what the number of the sprite pertaining to the character information (this is a 2D game btw).
RenderOrder – Holds the value for when the sprite should be rendered. I added this mainly for transparency purposes.
XPos and YPos – Holds the characters X Position and Y Position on the screen. These are used for movement and will be important in the functional code I show later as it is currently set to move and record the characters new position and then replace them back on the screen post deletion.
QuadNum – Is something to do with my rendering sequencer which will allow me to have a continues 2D environment so unlike other 2D games I won’t have just 2D maps for each level but rather a landscape where you can walk from one end of the environment to the other.
H1Top, H1Bottom, H1Left and H1Right – Are used to record the characters collision boundaries. At the moment in time these are set but unused within my program as I have only one object.
PHY, MEN and DEX – Are the primary stats of my Ability Based Character Development system and these are totally unused at the moment so I won’t go into their functionality.
Right then, the next thing I did was create the Maestro class which currently holds the functions for creating and loading objects into the world:
#include "Uber Header.h" // Uber Header
Maestro::Maestro(void)
{
}
Maestro::~Maestro(void)
{
}
void Maestro::Create_Object ( Object *Obj, char* Name, char* ImageSet )
{
// ID
Obj->Name = Name ;
// GRAPHICS
Obj->ImageSet = ImageSet ;
if ( Obj->ImageSet == "Void" )
{
Obj->ImageName1 = "Void.bmp" ;
}
else if ( Obj->ImageSet == "Mage" )
{
Obj->ImageName1 = "Mage.bmp" ;
}
// SPRITE
Obj->SpriteNumber = 1 ;
Obj->RenderOrder = 0 ;
// POSITION
Obj->XPos = 0 ;
Obj->YPos = 0 ;
Obj->QuadNum = 0 ;
// COLLISION
Obj->H1Top = 0 ; // Top
Obj->H1Bottom = 0 ; // Bottom
Obj->H1Left = 0 ; // Left
Obj->H1Right = 0 ; // Right
// STATS
Obj->PHY = 0 ;
Obj->MEN = 0 ;
Obj->DEX = 0 ;
}
void Maestro::Load_Object ( Object *Obj )
{
// IMAGE / SPRITE
dbLoadImage ( Obj->ImageName1 , 1 ) ; // Load the sprites image
dbSprite ( Obj->SpriteNumber, Obj->XPos, Obj->YPos, 1 ) ; // Create the sprite
dbOffsetSprite ( Obj->SpriteNumber, dbSpriteWidth ( Obj->SpriteNumber )/2, dbSpriteHeight ( Obj->SpriteNumber )/2 ) ; // Offset the sprite
}
The create object function takes in an object type object from the main, the world characters Name and ImageSet. It then sets the object’s name varible to the name arguement and the Imageset is set likewise. I am using a ripped sprite from Ragnarok Online as an image marker as I would like it’s graphics on my game eventually.
The imageset of “Mage” sets ImageName1 to “Mage.bmp” which allows the following function within Maestro to load the image into the program. The other varibles of the passed object are simply set to 0 as most of them either aren’t used or need setting to 0 at this point.
The load object function takes only one arguement; the same object from the main. It uses the values set by the create object function to make the sprite. Of course if the object object from the main is set to different values by the load command then it can load a saved object.
Alright, now for the good stuff:
// Object Declaration
fstream *Scribe ;
Maestro *Deus ;
Object *Player ;
The code above creates all the objects which are used during saving and loading within the program. One fstream for saving and loading data, one Maestro for creating and loading objects (see above) and one object of type Object by name of Player which is designed to handle all of the world characters by way of loading and saving of player data from saved files.
I like the names of these personally. If you don’t get it, check the meanings of ‘Scribe’ and ‘Deus’ (Deus Ex Machina) and then check their functionality.
// Object Initilization
Scribe = new(fstream) ;
Mover = new(Movement) ;
Deus = new(Maestro) ;
Player = new(Object) ;
The above simply initilizates each object becuase I declare my objects as pointers. Not sure why, it’s just the way I learned to do stuff.
// Global Varibles
int loc[15] ;
char buf[15][30] ;
for ( int i = 0; i <= 14; i++ ) { loc[i] = 0 ; }
for ( int i = 0; i <= 14; i++ ) { memset( buf[i], '�', 30 ) ; }
fstream::pos_type size ;
char* memblock = "" ;
This is my global varibles list which contains the data location (loc) array which is used later to find data elements within the extracted datafile, the buffer (buf) multidemenshional array used to store the data elements post extraction, two for loops for voiding both loc and buf and finally two varibles for setting the size of memblock to the size of the data file and for containing the data from the datafile after extraction.
Alright then, first you call this:
Deus->Create_Object ( Player, "Void", "Void" ) ;
To set the contents of the player object to nothing...alright SpriteNumber is set to 1 but that dosent really matter.
Deus->Create_Object ( Player, "Ludovic", "Mage" ) ;
You then call this to create the basic structure of a player, in this case it’s name is Ludovic and the image set it should be using is ‘Mage’.
Scribe->open ( Player->Name, ios::out | ios::binary | ios::beg ) ; // Open the file and apply any new data to the start of the file
// Write->Name
Scribe->write( Player->Name, strlen ( Player->Name ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->ImageSet
Scribe->write( Player->ImageSet, strlen ( Player->ImageSet ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->ImageSet
Scribe->write( Player->ImageName1, strlen ( Player->ImageName1 ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->SpriteNumber
Scribe->write( dbStr( Player->SpriteNumber ), strlen ( dbStr ( Player->SpriteNumber ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->RenderOrder
Scribe->write( dbStr( Player->RenderOrder ), strlen ( dbStr ( Player->RenderOrder ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->XPos
Scribe->write( dbStr( Player->XPos ), strlen ( dbStr ( Player->XPos ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->YPos
Scribe->write( dbStr( Player->YPos ), strlen ( dbStr ( Player->YPos ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->QuadNum
Scribe->write( dbStr( Player->QuadNum ), strlen ( dbStr ( Player->QuadNum ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->H1Top
Scribe->write( dbStr( Player->H1Top ), strlen ( dbStr ( Player->H1Top ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->H1Bottom
Scribe->write( dbStr( Player->H1Bottom ), strlen ( dbStr ( Player->H1Bottom ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->H1Left
Scribe->write( dbStr( Player->H1Left ), strlen ( dbStr ( Player->H1Left ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->H1Right
Scribe->write( dbStr( Player->H1Right ), strlen ( dbStr ( Player->H1Right ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->PHY
Scribe->write( dbStr( Player->PHY ), strlen ( dbStr ( Player->PHY ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->MEN
Scribe->write( dbStr( Player->MEN ), strlen ( dbStr ( Player->MEN ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
// Write->DEX
Scribe->write( dbStr( Player->DEX ), strlen ( dbStr ( Player->DEX ) ) ) ; //
Scribe->write( "¬", 1 ) ; //
Scribe->close() ;
This lot still needs putting into a function but I am currently looking for a way to simplfiy it into a loop or something or at least make it a bit shorter. It’s basic function is to create and open a file named Ludovic and write to it all the different factors about a player character from it’s name to world position and stats. What it gives you is a file which looks like this that can be accessed with notepad:
Ludovic¬Mage¬Mage.bmp¬1¬0¬1040¬620¬0¬0¬0¬0¬0¬0¬0¬0¬
You will see why is formtted like this a second.
Scribe->open( "Ludovic", ios::in | ios::binary | ios::beg ) ;
size = dbFileSize ( "Ludovic" ) ;
memblock = new char [size] ;
Scribe->read ( memblock, size ) ;
Scribe->close() ;
string s = memblock ;
for ( int i = 0; i <= 14; i++ )
{
if ( i == 0 )
{
// Data Element 1
loc[i] = s.find( "¬" , i );
s.copy( buf[i], loc[i], i );
}
else if ( i >= 1 )
{
int F = i ;
int S = i - 1 ;
// Data Element 2-14
loc[F] = s.find( "¬" , loc[S]+1 );
s.copy( buf[F], (loc[F]-(loc[S]+1)), loc[S]+1 );
}
}
Player->Name = buf[0] ;
Player->ImageSet = buf[1] ;
Player->ImageName1 = buf[2] ;
Player->SpriteNumber = dbVal ( buf[3] ) ;
Player->RenderOrder = dbVal ( buf[4] ) ;
Player->XPos = dbVal ( buf[5] ) ;
Player->YPos = dbVal ( buf[6] ) ;
Player->QuadNum = dbVal ( buf[7] ) ;
Player->H1Top = dbVal ( buf[8] ) ;
Player->H1Bottom = dbVal ( buf[9] ) ;
Player->H1Left = dbVal ( buf[10] ) ;
Player->H1Right = dbVal ( buf[11] ) ;
Player->PHY = dbVal ( buf[12] ) ;
Player->MEN = dbVal ( buf[13] ) ;
Player->DEX = dbVal ( buf[14] ) ;
The first line opens the Ludovic file by the simple expident of using the characters name. I plan on changing this to a character reference number later though but it will work exsactly the same way.
The next three lines just gets the file size, set memblock to that file size and then transfer the contents of the file into the memblock char*. The file is then closed. The for loop, which runs the same number of times as the quantity of data elements minus 1 becuase arrays start at 0 not 1, scans how many characters are sitting between each ‘¬’ character and places it’s contents into a slot on the multidemenshional char* buffer array, one layer of 30 characters for each slot on a Object Object, most of which won’t ever equal 15 and can be limited quite easily.
After the for loop the data stored within the buffer multidemenshional char* is transfered into the relevent slots on the Object class object, i.e. Player.
Deus->Load_Object ( Player ) ;
The maestro function Load_Object can then use the data stored within Player to actually create the object. Then with...
if ( dbUpKey ( ) ) { Player->YPos += -10 ; dbSprite ( Player->SpriteNumber, Player->XPos, Player->YPos, 1 ) ; }
if ( dbDownKey ( ) ) { Player->YPos += 10 ; dbSprite ( Player->SpriteNumber, Player->XPos, Player->YPos, 1 ) ; }
if ( dbLeftKey ( ) ) { Player->XPos += -10 ; dbSprite ( Player->SpriteNumber, Player->XPos, Player->YPos, 1 ) ; }
if ( dbRightKey ( ) ) { Player->XPos += 10 ; dbSprite ( Player->SpriteNumber, Player->XPos, Player->YPos, 1 ) ; }
You can move the object around be editing the X and YPos varibles on the Player Object and calling the sprite command. If you then reuse the save command to save the updated X and YPos you can then deleate the object from the game world thus...
Player->SpriteNumber ;
dbDeleteImage ( 1 ) ;
...And then void the player object, reload the updated saved data back into player, call the load object maestro function again and get the character back into the game world in the same position it was in previously.
Using this method you could create loads of objects and characters with the same set of stats and control varibles within something like a map maker and then use game specfic functionality to control what those characters do and respond to within the game world.
Currently I’m now working on creating an second object for the first to collide with and reorganising the movement functions and that. Afterwards I can move onto implementing my ability based character development system using the above method for storing object and character data. Of course the above will need a lot of reorganization and placement into a User Interface designed to manage the stored files but I don’t see that as being a problem.
...If I’m not mistaken I think they used this method for making the monsters in Spore too...sheer accident that mines the same...*shrug*.
We are here and it is now.