netlib.narod.ru | < Назад | Оглавление | Далее > |
Скрипты в этих нескольких последних главах повсюду высовывают свою голову, и вырисовывается истина: скрипты играют главную роль, когда имеешь дело с персонажами. Скрипты работают с общением, заклинаниями, перемещением персонажей и многим другим.
Что вам надо сейчас, так это четко определить метод обработки игровых скриптов. Лучший способ сделать это состоит в создании класса сплетающего воедино вашего персонажа и другую прикладную обработку.
Вместо того, чтобы разрабатывать класс обработки скриптов раньше в этой книге, я подождал, пока в нем не возникнет настоящая необходимость. В главе 10, «Реализация скриптов», вы узнали как легко работать с системой Mad Lib Script (MLS) и как просто обрабатывать скрипты, созданные с использованием MLS Editor. Однако, выполнение скриптов становится еще проще, когда вы помещаете обработку скриптов целиком в класс. Здесь вам будет представлен класс cScript, определенный в файлах Script.cpp и Script.h (вы найдете их в каталоге \BookCode\Chap12\Chars на CD).
class cScript { private: long m_NumActions; // Количество загруженных действий скрипта sScript *m_ScriptParent; // Связанный список скрипта // Перегружаемые функции для подготовки к обработке скрипта // и когда обработка завершена virtual BOOL Prepare() { return TRUE; } virtual BOOL Release() { return TRUE; } // Обработка отдельного действия скрипта virtual sScript *Process(sScript *Script) { return Script->Next; } public: cScript(); // Конструктор ~cScript(); // Деструктор BOOL Load(char *Filename); // Загрузка скрипта BOOL Free(); // Освобождение загруженного скрипта BOOL Execute(char *Filename=NULL); // Выполнение скрипта };
Обманчиво маленький класс cScript обладает недюжинной мощью. Загрузка скрипта выполняется через функцию Load. После загрузки вы можете выполнить скрипт с помощью вызова Execute. Если вы не хотите возиться с загрузкой скрипта перед его выполнением, вызов функции Execute с указанием файла скрипта загрузит его и выполнит в одном вызове функции (плюс освободит скрипт по завершении выполнения).
Способ обработки скрипта классом cScript весьма оригинален. В действительности вы должны выполнить наследование от класса cScript для разбора и выполнения каждого действия скрипта. Это цель функции Process. Когда скрипт загружен, функция Process вызывается для обработки каждого действия скрипта.
Через указатель на скрипт запрашивается номер действия скрипта и вы должны решить, что делать с этим действием. Затем вам надо обновить указатель на скрипт, вернув указатель на следующее действие скрипта в связанном списке. (Обратитесь к главе 10, за информацией о том, как обрабатываются скрипты.)
Полный код класса cScript был показан в главе 10, так что давайте теперь повернемся к рассмотрению того, как выполнить наследование класса, используемого для обработки скрипта.
Я предполагаю, что к этому моменту вы можете свободно работать с шаблонами действий и скриптами. В качестве примера использования наследуемого класса скрипта будет рассмотрен следующий шаблон действий:
"End" "If flag ~ equals ~ then" INT 0 255 BOOL "Else" "EndIf" "Set flag ~ to ~" INT 0 255 BOOL "Print ~" TEXT
Теперь, используя представленный шаблон действий, напишем следующий скрипт (я привожу его в текстовой форме, чтобы он был проще для понимания):
If flag 0 equals TRUE then Print "Flag is TRUE" Set flag 0 to FALSE Else Print "Flag is FALSE" Set flag 0 to TRUE EndIf
Краткий просмотр показывает, что представленный скрипт отображает сначала сообщение «Flag is FALSE» (поскольку всем флагам скрипта при инициализации присваивается FALSE); когда он выполняется снова, скрипт отображает «Flag is TRUE».
Следующий шаг для обработки скрипта — наследование класса от cScript:
class cGameScript : public cScript { private: BOOL m_Flags[256]; // Внутренние флаги // Прототипы функций скрипта sScript *Script_End(sScript*); sScript *Script_IfFlagThen(sScript*); sScript *Script_Else(sScript*); sScript *Script_EndIf(sScript*); sScript *Script_SetFlag(sScript*); sScript *Script_Print(sScript*); // Перегруженная функция обработки sScript *Process(sScript *Script); public: cGameScript(); };
Показанный здесь наследуемый класс (cGameScript) использует массив значений BOOL, представляющий внутренние флаги, которые скрипт может использовать. Следом за единственным объявлением переменной идет список прототипов функций.
Прототипы функций скрипта — это средства к существованию обработчика скрипта. У каждого действия скрипта есть назначенная функция, которая вызывается из функции Process. Как вы увидите чуть позже в этом разделе, функция Process переопределяется, чтобы вызывать именно эти функции скрипта.
Помимо этих закрытых функций есть конструктор, который очищает массив m_Flags, присваивая всем значениям FALSE:
cGameScript::cGameScript() { // Сброс всех внутренних флагов в FALSE for(short i = 0; i < 256; i++) m_Flags[i] = FALSE; }
Вернемся немного назад и взглянем на переопределенную функцию Process. Как видно из последующего кода, cGameScript::Process получает тип текущего действия скрипта и передает управление соответствующей функции. При возврате из каждой функции действия возвращается указатель на следующее действие скрипта. Если возвращается значение NULL, выполнение скрипта останавливается.
sScript *cGameScript::Process(sScript *Script) { // Переход к функции на основании типа действия switch(Script->Type) { case 0: return Script_End(Script); case 1: return Script_IfFlagThen(Script); case 2: return Script_Else(Script); case 3: return Script_EndIf(Script); case 4: return Script_SetFlag(Script); case 5: return Script_Print(Script); } return NULL; // Ошибка выполнения }
Теперь, когда вы переопределили функцию Process (и заполнили инструкцию switch вызовами функций действий), можно продолжить и запрограммировать все функции действий, как показано ниже:
sScript *cGameScript::Script_End(sScript *Script) { // Принудительная остановка обработки скрипта return NULL; } sScript *cGameScript::Script_IfFlagThen(sScript *Script) { BOOL Skipping; // Флаг для условия if...then // Смотрим, соответствует ли флаг второму элементу if(m_Flags[Script->Entries[0].lValue % 256] == Script->Entries[1].bValue) Skipping = FALSE; // Не пропускаем следующие действия else Skipping = TRUE; // Пропускаем следующие действия // Сейчас Skipping установлена, если действия скрипта // надо пропустить, согласно условной инструкции if...then. // Дальнейшие действия обрабатываются, если skipped = FALSE, // при этом смотрим не появится ли else для переключения // режима пропуска или endif для завершения условного блока Script = Script->Next; // Переходим к обработке следующего действия while(Script != NULL) { // Если это else, переключаем режим пропуска if(Script->Type == 2) Skipping = (Skipping == TRUE) ? FALSE : TRUE; // Прерывание в конце if if(Script->Type == 3) return Script->Next; // Обрабатываем функции скрипта в условном блоке // обеспечивая пропуск действий, когда условие не выполняется if(Skipping == TRUE) Script = Script->Next; else { if((Script = Process(Script)) == NULL) return NULL; } } return NULL; // Достигнут конец скрипта } sScript *cGameScript::Script_Else(sScript *Script) { return Script->Next; // Переходим к следующему действию скрипта } sScript *cGameScript::Script_EndIf(sScript *Script) { return Script->Next; // Переходим к следующему действию скрипта } sScript *cGameScript::Script_SetFlag(sScript *Script) { // Установка логического значения m_Flags[Script->Entries[0].lValue % 256] = Script->Entries[1].bValue; return Script->Next; // Переходим к следующему действию скрипта } sScript *cGameScript::Script_Print(sScript *Script) { // Отображаем текст в окне сообщений MessageBox(NULL, Script->Entries[0].Text, "Text", MB_OK); return Script->Next; // Переходим к следующему действию скрипта }
Чтобы проверить класс cGameScript, создайте его экземпляр и запустите пример скрипта, показанный мной ранее в разделе «Создание наследуемого класса скрипта». Если предположить, что вы сохранили скрипт в файл с именем test.mls, функциональность класса скрипта вам покажет следующий пример:
cGameScript Script; Script.Execute("test.mls"); // Печатает сообщение Flag is FALSE // Теперь внутренние флаги скрипта поддерживаются, // так что следующий вызов учитывает новое состояние флага Script.Execute("test.mls"); // Печатает сообщение Flag is TRUE
Хотя этот пример наследуемого класса cGameScript сляпан на быструю руку, он в действительности не слишком отличается от законченного анализатора скриптов, использующего огромный шаблон действий. Вам просто надо добавить в класс каждую функцию обработки действия, и вызывать их через функцию Process. Повсюду в оставшейся части книги вы будете видеть применение этого класса скрипта; фактически, он формирует основу многих проектов.
netlib.narod.ru | < Назад | Оглавление | Далее > |