Наверняка вы уже слыхали нечто подобное в каком-либо чате или форуме, но если нет, то этот термин переводится примерно следующим образом - «движок, управляемый данными». Смысл термина заключается в том, что весь игровой процесс описывается и настраивается с помощью конфигурационных (здесь имеются ввиду не только *ini файлы, но и файлы с описанием игровых локаций, скрипты и т. д.) файлов. Т. е. движок спроектирован таким образом, что вся игровая механика вынесена за пределы exe'шника игры. Во время запуска главного исполняемого файла игры, осуществляется чтение настроек, хранящихся в конфигурационных файлах и по ним строятся локации, расставляются враги, препятствия, проигрываются всевозможные визуальные и звуковые эффекты. Именно благодаря такому подходу в создании игр стало возможным появление такого явления как mod-мэйкерство, когда группа энтузиастов берет игру, и, изменив некторые настройки, меняет геймплэй или добавляет в игру врагов, квесты, NPC и т.д.
Что же мы поимеем, решив создать data driven engine? К положительным моментам можно отнести следующее:
1. Положительную реакцию коммьюнити – фанаты будут рады получить дополнительные примочки к своей любимой игре в виде всевозможных модов. Возможно какой-нибудь мод сможет потягаться в интересности с оригинальной игрой (как это было в случае с CS'ом).
2. Облегчение жизни программистам – теперь для внесения изменений в игру не требуется их непосредственное участие.
3. Ускорение времени компиляции (для маленьких игр это не столь существенно, однако для игр чьи движки состоят из миллионов строк кода это время может быть весьма существенным), т.к. компилироваться будет только движок а не вся игра целиком.
Существенный отрицательный момент только один – существенное усложнение движка и увеличение сроков его разработки, что делает непригодным применение этой парадигмы для разработки маленьких игр (например шаровар).
Для хранения настроек игры был выбран XML (в настроечных файлах будут храниться настройки для всех менеджеров, настройки игры, GUI и уровней) по ряду причин – простота формата (по крайней мере нам не понадобятся все навороты этого формата, а только самые элементарные правила), наличие большого количества всевозможных редакторов с подсветкой тэгов, возможность управлять отображением информации с помощью XSLT шаблонов.
Существует целый ряд библиотек (tinyxml, MSXML), позволяющих загружать XML, однако можно написать и свой собственный парсер, что я и сделал. В результате появился следующий класс:
class CXMLTag{ protected: friend CXMLFile; // список вложенных тэгов std::vector< CXMLTag* > Tags; // список параметров текущего тэга std::vector< std::string > Params; // список значений текущего тэга std::vector< std::string > Values; int ProcessParams( CXMLTag *tag , std::vector< std::string > &Lexemmas , int &i ); bool IsClosingTag( CXMLTag *tag , std::vector< std::string > &Lexemmas , int &i ); void LoadXML( std::vector< std::string > &Lexemmas ); void LoadXML( CXMLTag *tag , std::vector< std::string > &Lexemmas , int &i ); void SaveXML( FILE * f_stream , char * tab_space ); void PrintXML( char * ); public: CXMLTag( void ){ } // имя текущего тэга std::string Name; // добавление вложенного тэга tag_name с одним параметром tag_attrib_name // который имеет значение tag_attrib_value void AddTag( char *tag_name, char *tag_attrib_name, char *tag_attrib_value); // добавление тэга с двумя параметрами void AddTag( char *tag_name , char *tag_attrib_name1 , char *tag_attrib_value1 , char *tag_attrib_name2 , char *tag_attrib_value2 ); // возвращает true если у тэга есть параметр param_name // в обратном случае возвращает false bool CheckParam( char *param_name); // возвращает true если существует вложенный тэг tag_name // иначе возвращает false bool CheckTag( char *tag_name ); // возвращает true если существует вложенный тэг tag_name с параметром param_name // иначе возвращает false bool CheckTagParam( char * tag_name , char * param_name ); // возвращает true если существует вложенный тэг tag_name с параметром param_name // и значением param_value // иначе возвращает false bool CheckTagParamValue( char * tag_name , char * param_name , char * param_value); // возвращает true если существует вложенный тэг с курсором // tag_cursor с параметром param_name // и значением param_value // иначе возвращает false bool CheckTagParamValue( int tag_cursor , char * param_name , char * param_value); // возвращает значение параметра текущего тэга std::string &GetParam( char * ); // возвращает значение параметра вложенного тэга std::string &GetParam( char * , char * ); // возвращает параметр текущего тэга в виде вещественно числа double GetParam_double( char * ); // возвращает параметр вложенного тэга в виде вещественного числа double GetParam_double( char * , char * ); // возвращает количество вложенных тэгов size_t NumOfChildTags( void ){ return( Tags.size() ); } // пробегаем по вложенным тэгам CXMLTag &operator[]( char * tag_name ); CXMLTag &operator[]( size_t tag_cursor ); // очистка объекта CXMLTag void Release( void ); }; // класс с которым программист непосредственно работает // в классе реализованы методы для оперирования с файлами – загрузка, // сохранение, вывод на экран class CXMLFile:public CXMLTag{ public: CXMLFile( void ){} CXMLFile( char * file_path ){LoadXML( file_path );} void LoadXML( char * ); void PrintXML( void ){ CXMLTag::PrintXML( " " ); } void SaveXML( char * file_name ); };
Класс для ведения логов, существенно проще, поэтому привожу его целиком с минимальными комментариями:
class CLogStream{ public: CLogStream( void ){} CLogStream( char * ProjectName , char * ProjectVersion , char * EngineVersion ); CLogStream &operator<<( char * str ); CLogStream &operator<<( std::string & str ); CLogStream &operator<<( float f ); }; // при создании объекта старый лог очищается и заводится новый в котором // пишется версия проекта, движка и название проекта // далее для вывода в лог можно использовать либо созданный объект // либо анонимный экземпляр класса CLogStream::CLogStream( char * ProjectName , char * ProjectVersion , char * EngineVersion ){ __time64_t ltime; FILE *LogFile; LogFile = fopen( "c:\\system.log" , "wt" ); fprintf( LogFile , "===========================================================================\n\n" ); fprintf( LogFile , "New logging session started at : " ); fprintf( LogFile , "%s", _ctime64( <ime ) ); fprintf( LogFile , "Project name : %s\n" , ProjectName ); fprintf( LogFile , "Project version : %s\n" , ProjectVersion ); fprintf( LogFile , "Enginen version : %s\n" , EngineVersion ); fprintf( LogFile , "\n===========================================================================\n" ); fclose( LogFile ); } // вывод в файл C-строки CLogStream &CLogStream::operator<<( char * str ){ FILE *LogFile; LogFile = fopen( "c:\\system.log" , "at" ); fprintf( LogFile , "%s" , str ); fclose( LogFile ); return( *this ); } // вывод в файл STL строки CLogStream &CLogStream::operator<<( std::string & str ){ FILE *LogFile; LogFile = fopen( "c:\\system.log" , "at" ); fprintf( LogFile , "%s" , str.c_str() ); fclose( LogFile ); return( *this ); } // вывод в файл вещественного числа CLogStream &CLogStream::operator<<( float f ){ FILE *LogFile; LogFile = fopen( "c:\\system.log" , "at" ); fprintf( LogFile , "%f" , f ); fclose( LogFile ); return( *this ); }
Вот собственно и все на сегодня.