The question is: if I want a simple file format to describe the objects in my
game level, what would it look like?
First, I'd recommend using XML. There are many good parsers out there,
including my simple and fast XMLSCAN library.
If you don't want XML, then you can define a file format that just lists the
types and names of objects, and the parameters for each object.
Here's the typical parser loop for reading objects:
size_t size;
char * text = read_file( "level.txt", &size );
char * del = text;
char * end = size+text;
while( text < end ) {
get_object( text );
}
delete[] del;
|
The helper function to read a file into memory looks like this:
char * read_file( char const * fn, size_t * osize )
{
FILE * f = fopen( fn, "rb" );
if( !f ) throw std::runtime_error( "Can't find file" );
fseek( f, 0 , 2 );
*osize = ftell( f );
fseek( f, 0, 0 );
char * ret = new char[*osize+1];
fread( ret, 1, *osize, f );
ret[*osize] = 0;
fclose( f );
return ret;
}
|
"getting" and "object" from a text file means parsing the file. There are a
number of ways to write a parser; the simplest is a recursive-descent parser
("RD").
// assuming syntax is:
// obj-type obj-name {
// parameter value;
// parameter value;
// }
void get_object( char * & text )
{
std::string type = get_token( text );
std::string name = get_token( text );
if( get_token( text ) != '{' ) throw std::runtime_error( "syntax error; '{' expected" );
std::list< std::pair< std::string, std::string > > params;
while( true ) {
std::string param = get_token( text );
if( tok == '}' ) break; // done
std::string value = get_token( text );
if( get_token( text ) != ';' ) throw std::runtime_error( "syntax error; ';' expected" );
params.push_back( std::pair< std::string, std::string >( param, value ) );
}
register_object( type, name, params );
}
|
OK, all we need to know now is how to read a token from a character pointer. In
the simplest case, we assume that tokens are non-whitespace, non-semicolon and
non-brace characters, separated by semicolons, braces, or whitespace. To
support strings with spaces in them, let's use double-quote for that, and
backslash-doublequote to quote a quote.
std::string get_token( char const * & text )
{
while( *text && isspace( *text ) ) ++text;
if( !*text ) throw std::runtime_error( "get_token() encountered end of file" );
if( *text == '{' || *text == '}' || *text == ';' ) { ++text; return std::string(text[-1], 1); }
bool quote = false;
bool backslash = false;
std::string ret;
while( *text ) {
if( !quote && !backslash ) {
if( *text == '{' || *text == '}' || *text == ';' || isspace( *text ) ) break;
if( *text == '"' ) quote = true;
else if( *text == '\\' ) backslash = true;
else ret.push_back( *text );
}
else if( backslash ) {
ret.push_back( *text );
backslash = false;
}
else {
if( *text == '"' ) quote = false;
else ret.push_back( *text );
}
++text;
}
if( quote || backslash ) throw std::runtime_error( "get_token() found end of file within string value" );
return ret;
}
|
It's really not that complicated. Fancification of this method may include
supporting datatypes other than strings for parameters (vectors, quaternions,
integers, etc), and maybe even supporting sub-objects. Typically, you'll
implement a variant type to use instead of "string" for the values in this
case.
So, what do you do with the parsed objects that you get into register_object()?
Well, that's where the specifics of your program comes in. If you want to store
all the parameters as-is and use them for runtime look-up, you probably want to
use a std::multi_map instead of a std::list. Else, you can just parse through
the list of parameter values, and stuff the given value into fields of your
manufactured objects inside register_object(). Other approaches are possible
too; it really depends on what your game is doing!
| |
|