Moving beyond Arcade Style Games

Started by
16 comments, last by Norman Barrows 8 years, 10 months ago

Speaking strictly as a hobbyist, after completing several games along the area of Space Invaders, I want to move onto RPG games. I am starting out with Turn Based Strategy RPGs, such as military conflict where there are different phases, such as Movement, Attacks and Results.

Where I am currently have a mental block, is in the Save Game option. I have done the Save High Scores into a Sequential Text File and then reloaded those scores when the game is started.

My question is.... "What is the best way to write the <Save Game> file ?"

Do you write all your Game Pieces 1 data element at a time ? EXAMPLE : Writeln(filename,GamePiece.LocationY);

Writeln(filename,GamePiece.LocationX);

Or do you / can you write the entire Structure as 1 ? EXAMPLE: Writeln(filename,GamePiece);

And how do you achieve this ? I am using C++ and an Example code block would be helpful or at least point me in the right direction or reference a web site.

Thank you.

Your Brain contains the Best Program Ever Written : Manage Your Data Wisely !!

Advertisement

The key search term you're looking for is serialization. https://www.google.com/#q=c%2B%2B+serialization+library

But it is never that easy. Start simple, and build it up.

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

i write out individual fields of a struct one at a time to a binary file. a fixed read / write order precludes the need for key:value pair parsing. by writing individual fields, adding new fields to a struct doesn't automatically break your load and save routines. new fields can be added to load and save as the struct changes. by initializing data structures to default values, then reading in over them until EOF, you can load older formats, and use default values for remaining variables - giving you built-in automatic conversion to newer file formats.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Reading and writing structs one element at a time is a bit laborious, and you haven't explained how you deal with loading an old file into a newer version of the program after the format has changed.


by initializing data structures to default values, then reading in over them until EOF, you can load older formats, and use default values for remaining variables - giving you built-in automatic conversion to newer file formats.

Are you saving one struct per file? Otherwise, I don't know how your system could work.

A more robust method would be to write a version and/or size for each struct. Then you have the benefit of being able to add more members to a struct without needing to do much else, and you also have the flexibility to completely change a struct (re-order members, change their meaning) and just write a little temporary (if this is still in development) conversion code when loading the old version of the struct..

Don't reinvent the wheel unless you have a really good reason.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]


Are you saving one struct per file? Otherwise, I don't know how your system could work.

i'm saving every variable in the game pretty much - more or less the entire state - missiles in flight and all. in a single binary file.

think of it this way:

time to save the game, so you're going to write some data to disk. a bunch of variables. some might be members of objects, some might be members of structs, some might be stand alone variables.

all i do is write them all out to a binary file, one at a time, in a fixed order, then read them in, in the same order.

the fixed read /write order means you know what variable to read next, and thus its type - and its size. no key:value parsing required.

now, time to add a new variable to a class declaration. and we want to load and save this new variable as part of a savegame.

so first you do all the usual things, declare, init, change, and use it. then comes time to load and save. this means your savegame file format is going to change. the old format won't have the new variable, the new format will.

now, how to change the format? well, you could just insert it into the existing load and save code right after the previous variable in that class, but that would change the existing load and save order, and any games saved with the old order could no longer be read. and you want to be able to read the old AND new formats, as well as save in the new format.

so what you do is this:

1. when loading a savegame, init all loaded vars to default values first, then read in saved values over them. if the savegame file ends before you read all the variables, any unread variables will retain their default values.

2. to add new variables to the savegame format, you always add the load and save code at the end of the routine, IE at the end of the list of variables to load/save. this preserves the loading order of existing variables. so now you can add code to the end of your load and save routines for the new vars, load in an old format file, and the new vars will take default values, and when you save, it'll save in the new format - automatic conversion with no special routine or utility. and backward compatibility all the way back to the original format.

3. to remove existing variables, you just read and write junk values to maintain the read order (and size). if you add a new variable of that type later, you can use that "empty slot" for the new variable, instead of using a junk value. this can safely be done if both your junk and new default values are zero. this saves space, but then very old formats with the deprecated value in them will no longer be compatible with the new format. i find it handy since i only really have the one in-house long term playtest savegame file to convert at the moment, and variables continue to be added to and removed from the format as the game evolves. no sense reading and writing a bunch of junk values in the Caveman 3.0 release version. it doesn't have to be backward compatible with anything. but if i remove variables in the version 3.1 release, i'd need to use junk values, and not reuse them, for backward compatibility with v3.0.

rather surprising - or perhaps not - you don't get much of a performance hit writing individual variables one at a time using fwrite nolock (or whatever its called) - ie the good old fashioned bare bones down to the metal no error checks c-type runtime library code. i'm saving something like 73 meg in 4-5 seconds flat.

i find this method gives me a number of advantages:

1. no key-value pair parsing required

2. easy to mod: wi(intname) wf(floatname) ws(stringname) - write int, write float, write string. ri(intname), rf(floatname), rs(stringname) - read int, read float, read string.

just add a line of code to the end of load and save for each new var - all cut and paste boilerplate stuff. you can also save chunks of vars/data with blockread and write, since its binary format. but that means the chunks can't change without automatically breaking the load and save code. the thing about writing out individual members vs a whole struct is you can change a struct definition without automatically breaking the existing load and save code.

3. automatic backward compatibility

4. automatic format conversion.

5. only have to save the members you want out of a struct.

i was looking for a savegame system and file format that was

1. easy to use

2. fast

3. backwardly compatible

my savegame format for Caveman has probably changed six times since i implemented the system. no more re-creating or converting long term playtest games from old to new formats. and i have a long term playtest game i started almost two years ago now.

over the long term, you end up with a save routine something like this:

init vars to default

open file

load original format vars

load file format 2 additional vars

load file format 3 additional vars
(...)
load file format N additional vars
close file
save is identical, with write instead of read calls.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

It doesnt realy matters for small stuff, big stuff you Always write blocks if you can anyways.

I think you dont notice the difference with 1 write to disk, or more writes to disk.

S T O P C R I M E !

Visual Pro 2005 C++ DX9 Cubase VST 3.70 Working on : LevelContainer class & LevelEditor

It doesnt realy matters for small stuff, big stuff you Always write blocks if you can anyways.

I think you dont notice the difference with 1 write to disk, or more writes to disk.

i used to think that way too. write out each member of each struct individually? instead of blockwriting the whole array of structs? naw! it would never work! too slow!

but once you get down to the bare bones fwrite_nolock routines, its actually doable. i'm saving 72 meg in about 5 seconds flat.

the whole problem with writing blocks is that if the definition of a block changes in the code, you can't read existing savegames without converting them first.

by writing out blocks one variable at a time, you can change the definition of a block, and still read existing savegames. by only adding new variables at the end of the file format, and initializing new variables to default values before loading, you can convert old format savegames to new format just by loading and saving them.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php


i find this method gives me a number of advantages:
1. no key-value pair parsing required
2. easy to mod: wi(intname) wf(floatname) ws(stringname) - write int, write float, write string. ri(intname), rf(floatname), rs(stringname) - read int, read float, read string.
just add a line of code to the end of load and save for each new var - all cut and paste boilerplate stuff. you can also save chunks of vars/data with blockread and write, since its binary format. but that means the chunks can't change without automatically breaking the load and save code. the thing about writing out individual members vs a whole struct is you can change a struct definition without automatically breaking the existing load and save code.
3. automatic backward compatibility
4. automatic format conversion.
5. only have to save the members you want out of a struct.

i was looking for a savegame system and file format that was
1. easy to use
2. fast
3. backwardly compatible

Hold on a sec... how do you handle variable numbers of objects? Like, what if you save a game and there are 100 active animals, and you save another time and there are 150 animal objects? Or do you have fixed numbers of each type of game object? What if you add a property to animal? You have to write the new properties for all animals at the end of your save function? What if then if you increase the fixed number of animals? You'd need to write the first n animals with m properties, then later on n animals with the 1 new property, then later on more animals with (m + 1) properties.

If I'm understanding correctly, that's an incredibly inflexible solution that I wouldn't recommend to anybody. It sounds like an unmaintainable mess. It may "work", but there are much better ways to do this.

This topic is closed to new replies.

Advertisement