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 с указанием файла скрипта загрузит его и выполнит в одном вызове функции (плюс освободит скрипт по завершении выполнения).

ПРИМЕЧАНИЕ
Использование функции Load для загрузки скрипта полезно, когда скрипт выполняется несколько раз, поскольку вы не будете освобождать его между использованиями. Загрузка скрипта через функцию 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

 

ВНИМАНИЕ!
Помните, что пример скрипта показан здесь в текстовой форме, но когда используется Mad Lib Script формат основан на значениях. Например, действие if...then представлено значением 1, а действие Endif — значением 3.

Краткий просмотр показывает, что представленный скрипт отображает сначала сообщение «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< Назад | Оглавление | Далее >

Сайт управляется системой uCoz