| 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 | < Назад | Оглавление | Далее > |