netlib.narod.ru< Назад | Оглавление | Далее >

Блокирование пути барьерами

В главе 8 я объяснил основы обнаружения столкновений. Вы знаете, как обнаружить, когда ходящий по карте персонаж сталкивается со стенами и обеспечить, чтобы он твердо стоял на земле. Но как быть с такими объектами, как двери, загораживающие путь вашему персонажу? Поскольку дверь не является частью ландшафта, я не включил двери, когда конструировал код обнаружения столкновений. Пришло время исправить эту ситуацию.

Объекты, мешающие свободному передвижению персонажа, называются барьерами. Барьеры могут находиться в двух состояниях: открытом (выключенном) или закрытом (включенном). Персонажам разрешено проходить через барьеры, когда они открыты, но не могут проходить через закрытые барьеры.

Вы можете рассматривать барьеры почти так же, как триггеры. Вы определяете барьер таким же способом, как определяете триггеры на карте. Можно определять сферические, кубические, цилиндрические и треугольные барьеры. У барьеров также есть состояние включения, где TRUE означает, что барьер блокирует перемещение персонажа, а FALSE означает, что проход через барьер свободен.

Большое отличие барьеров от триггеров в том, что у барьеров есть связанные с ними сетки и анимации. Это освобождает вас от бремени рисования барьеров и возлагает эту работу на движок барьеров. Все, что вам надо сделать, — назначить сетки и анимации.

Начнем использование барьеров с объявления класса барьера (он находится в файлах Barrier.h и Barrier.cpp, расположенных на CD-ROM в каталоге к главе 13), которое выглядит очень похожим на объявление класса триггера. Заметьте, что я также определяю перечисление и структуру (sBarrier), используемую для хранения данных каждого барьера:

enum BarrierTypes { Barrier_Sphere = 0, Barrier_Box,
                    Barrier_Cylinder, Barrier_Triangle };

typedef struct sBarrier {
    long Type;    // Сфера, куб и т.д.
    long ID;      // ID барьера
    BOOL Enabled; // Флаг включения

    float XPos, YPos, ZPos; // Координаты
    float XRot, YRot, ZRot; // Вращение

    float x1, y1, z1; // Координаты 1
    float x2, y2, z2; // Координаты 2
    float x3, z3;     // Координаты 3
    float Radius;     // Радиус границы

Здесь сходство между триггерами и барьерами завершается. Барьеру необходимо графическое представление (трехмерная сетка), так что в последующем коде добавляется объект графического ядра cObject, который используется для хранения данных сетки и анимации барьера:

    cObject Object; // Графический объект

Возвращаясь к сходству классов триггеров и барьеров, отметьте указатели для поддержки связанного списка, а также конструктор и деструктор структуры sBarrier:

    sBarrier *Prev, *Next; // Связанный список

    sBarrier() { Prev = Next = NULL; }
    ~sBarrier() { delete Next; Next = NULL; Object.Free(); }
} sBarrier;

Сходства между триггерами и барьерами продолжают проявляться и в объявлении класса барьера:

class cBarrier
{
  private:
    cGraphics *m_Graphics;      // Родительский объект cGraphics
    long      m_NumBarriers;    // Кол-во барьеров в связанном списке
    sBarrier  *m_BarrierParent; // Связанный список барьеров

    long GetNextLong(FILE *fp); // Получение следующего long в файле
    float GetNextFloat(FILE *fp); // Получение следующего float в файле

    // Создание структуры sBarrier и вставка ее в список
    sBarrier *AddBarrier(long Type, long ID, BOOL Enabled,
                         float XPos, float YPos, float ZPos,
                         float XRot, float YRot, float ZRot);

Переключите ваше внимание на аргументы, которые получает функция AddBarrier. Помимо позиции, в которой располагается барьер (используются аргументы XPos, YPos и ZPos), функция AddBarrier получает значения вращения, используемые для рисования сетки барьера (аргументы XRot, YRot и ZRot, представляющие угол поворота в радинанах по осям X, Y и Z, соответственно).

Обратите внимание на добавленные по всему классу барьера значения вращения, а также на добавление дополнительного трио координат, определяющего местоположение сетки в мире. Поскольку вы будете наталкиваться на эти дополнительные значения, я должен был упомянуть их.

Продолжим рассматривать объявление класса cBarrier:

  public:
    cBarrier();  // Конструктор
    ~cBarrier(); // Деструктор

    // Функции для загрузки и записи списка барьеров
    BOOL Load(char *Filename);
    BOOL Save(char *Filename);

    // Функции для установки сетки и анимации барьера
    BOOL SetMesh(long ID, cGraphics *Graphics, cMesh *Mesh);
    BOOL SetAnim(long ID, cAnimation *Anim,
                 char *Name, long Time);

Когда вам надо назначить сетку барьеру, используйте функцию SetMesh, передав идентификационный номер барьера, а также объекты графического ядра cGraphics и cMesh. Для установки анимации барьера вы передаете идентификационный номер барьера, объект cAnimation, имя используемой анимации и время анимации (используя функции таймера, такие как timeGetTime).

После того, как вы назначили сетку и анимацию, можно визуализировать барьер на экране используя следующую функцию Render. Функция Render получает в качестве аргумента текущее время для обновления анимации (снова используем timeGetTime) и пирамиду видимого пространства, применяемую для отсечения тех барьеров, которые находятся вне нашего поля зрения:

    // Визуализируем барьеры, используя
    // заданную пирамиду видимого пространства
    BOOL Render(unsigned long Time, cFrustum *Frustum);

Когда дело подходит к началу добавления барьеров в связанный список, класс cBarrier предлагает несколько предназначенных для этого функций, как это делал cTrigger. Взгляните на прототипы этих функций (я покажу вам, как работает каждая из них после того, как продемонстрирую объявление класса cBarrier целиком):

    BOOL AddSphere(long ID, BOOL Enabled,
                   float XPos, float YPos, float ZPos,
                   float XRot, float YRot, float ZRot,
                   float CXPos, float CYPos, float CZPos,
                   float Radius);

    BOOL AddBox(long ID, BOOL Enabled,
                float XPos, float YPos, float ZPos,
                float XRot, float YRot, float ZRot,
                float XMin, float YMin, float ZMin,
                float XMax, float YMax, float ZMax);

    BOOL AddCylinder(long ID, BOOL Enabled,
                     float XPos, float YPos, float ZPos,
                     float XRot, float YRot, float ZRot,
                     float CXPos, float CYPos, float CZPos,
                     float Radius, float Height);

    BOOL AddTriangle(long ID, BOOL Enabled,
                     float XPos, float YPos, float ZPos,
                     float XRot, float YRot, float ZRot,
                     float x1, float z1,
                     float x2, float z2,
                     float x3, float z3,
                     float CYPos, float Height);

    BOOL Remove(long ID);
    BOOL Free();

    long GetBarrier(float XPos, float YPos, float ZPos);

    BOOL GetEnableState(long ID);
    BOOL Enable(long ID, BOOL Enable);

    long GetNumBarriers();
    sBarrier *GetParentBarrier();
};

И снова, класс барьера очень похож на класс триггера, который вы видели в разделе «Создание класса триггера», так что я не буду зря тратить место, приводя здесь полный код класса cBarrier. Вместо этого обратитесь к классу cTrigger за спецификой большинства функций, и продолжайте чтение, чтобы увидеть устройство функций, являющихся особенностями класса барьера.

cBarrier::SetMesh и cBarrier::SetAnim

В дополнение к графическому объекту, вам надо назначить сетку и анимацию. У каждого барьера есть выделенный cObject, используемый для ориентации, но сперва должна быть назначена сетка через функцию SetMesh. Далее следует установка анимации с помощью функции SetAnim. Взгляните на эти функции, отвечающие за установку сеток и анимаций:

BOOL cBarrier::SetMesh(long ID,
                       cGraphics *Graphics, cMesh *Mesh)
{
    sBarrier *BarrierPtr;
    long Count = 0;

    // Перебираем все барьеры, ища указанный ID
    if((BarrierPtr = m_BarrierParent) != NULL) {
        while(BarrierPtr != NULL) {

            // Если ID совпадает, устанавливаем сетку
            if(BarrierPtr->ID = = ID) {
                BarrierPtr->Object.Create(Graphics, Mesh);
                Count++;
            }

            // Переход к следующему барьеру
            BarrierPtr = BarrierPtr->Next;
        }
    }

    // Возвращаем TRUE, если установлена
    // какая-нибудь сетка
    return (Count) ? TRUE : FALSE;
}

BOOL cBarrier::SetAnim(long ID, cAnimation *Anim,
                       char *Name, long Time)
{
    sBarrier *BarrierPtr;
    long Count = 0;

    // Перебираем все барьеры, ища указанный ID
    if((BarrierPtr = m_BarrierParent) != NULL) {
        while(BarrierPtr != NULL) {

            // Если ID совпадает, устанавливаем анимацию
            if(BarrierPtr->ID = = ID) {
                BarrierPtr->Object.SetAnimation(Anim, Name, Time);
                Count++;
            }

            // Переход к следующему барьеру
            BarrierPtr = BarrierPtr->Next;
        }
    }

    // Возвращаем TRUE, если установлена
    // какая-нибудь анимация
    return (Count) ? TRUE : FALSE;
}

После того, как вы загрузили или создали барьеры, назначьте сетки, используя идентификационные номера соответствующих барьеров. Обратите внимание, что функция SetAnim похожа на функцию cObject::SetAnimation — у вас есть имя анимации и стартовое время анимации.

Обе функции просто сканируют связанный список барьеров, ища соответствие идентификационного номера, после чего класс записывает сетку или устанавливает анимацию, и идет дальше по оставшейся части связанного списка.

cBarrier::Render

Другой эксклюзивной функцией cBarrier (в противоположность cTrigger) является Render, получающая значение времени, используемое для обновления анимации барьера и пирамиду видимого пространства, применяемую для отбрасывания невидимых объектов барьеров. Взгляните на код функции Render:

BOOL cBarrier::Render(unsigned long Time, cFrustum *Frustum)
{
    sBarrier *BarrierPtr;
    float Radius;

    // Контроль ошибок
    if(Frustum = = NULL)
        return FALSE;

    // Перебираем все барьеры
    if((BarrierPtr = m_BarrierParent) != NULL) {
        while(BarrierPtr != NULL) {

            // Получаем радиус и проводим проверку
            // пирамиды видимого пространства
            BarrierPtr->Object.GetBounds(NULL, NULL, NULL, NULL,
                                         NULL, NULL, &Radius);
            if(Frustum->CheckSphere(BarrierPtr->XPos,
                                    BarrierPtr->YPos,
                                    BarrierPtr->ZPos, Radius)) {

                // Позиционирование объекта
                BarrierPtr->Object.Move(BarrierPtr->XPos,
                                        BarrierPtr->YPos,
                                        BarrierPtr->ZPos);
                BarrierPtr->Object.Rotate(BarrierPtr->XRot,
                                          BarrierPtr->YRot,
                                          BarrierPtr->ZRot);

                // Обновление анимации
                BarrierPtr->Object.UpdateAnimation(Time, TRUE);

                // Визуализация объекта
                BarrierPtr->Object.Render();
            }

            // Переходим к следующему барьеру
            BarrierPtr = BarrierPtr->Next;
        }
    }

    return TRUE;
}

В показанной выше функции Render сканируется связанный список барьеров и, для каждого барьера, выполняется проверка попадания в пирамиду видимого пространства. Если какая-либо часть барьера находится внутри пирамиды видимого пространства (даже если она скрыта другими объектами), обновляется соответствующая анимация и визуализируется сетка.

Добавление барьеров с cBarrier

Класс барьера отмечает области на карте, используя геометрические фигуры, точно так же, как и класс триггера, но при этом класс барьера еще и позиционирует сетки. Взглянув снова на объявление класса cBarrier, обратите внимание, что каждая из функций добавления барьера — AddSphere, AddBox, AddCylinder и AddTriangle — имеет набор координат для позиционирования и вращения сетки барьера перед началом визуализации.

Чтобы определить, где будет расположена сетка, установите аргументы XPos, YPos и ZPos функции добавления барьера, указав где вы хотите визуализировать сетку. Вам также надо установить аргументы XRot, YRot и ZRot, указав значения поворота для рисования сетки.

Скажем, например, вы хотите добавить сферический барьер, которому уже назначена сетка. Барьер размещается по координатам 10, 20, 30 (с радиусом 40), а сетка помещается в 10, 0, 30 и не использует значения вращения. Для добавления барьера вы вызываете AddSphere следующим образом:

cBarrier::AddSphere(1, TRUE,
                    10.0f, 0.0f, 30.0f, 0.0f, 0.0f, 0.0f,
                    10.0f, 20.0f, 30.0f, 40.0f);

Вы лучше разберетесь с добавлением и использованием барьеров в следующем разделе.

Использование класса барьера

Использовать класс барьера не сложно; это очень похоже на использование класса триггера. Главное отличие в том, что вы добавляете к файлу данных барьера информацию о размещении объекта и назначаете соответствующие сетки и анимации.

Создание файла данных барьеров

Файл данных барьеров организован также, как файл данных триггеров, но здесь определение каждого барьера содержит идентификационный номер, тип, флаг включения, координаты размещения (x, y, z) и поворот (поворот по x, поворот по y и поворот по z) для размещения графического объекта барьера. Заканчивается каждое определение данными, зависящими от типа барьера.

В следующем примере определяются два используемых барьера (находящихся в файле с именем test.bar). Координаты размещения и значения поворота барьера выделены полужирным шрифтом:

1 1 1 -900 0 0 0 0 0 -1154 0 10 -645 100 -10
2 1 0 0 0 -900 0 1.57 0 -10 0 -1154 10 100 -645

Здесь два барьера и оба имеют форму прямоугольного параллелепипеда. Графический объект первого барьера располагается в координатах –900, 0, 0 и имеет значения вращения 0, 0, 0. Первый параллелепипед охватывает область от –1154, 0, 10 до –645, 100, –10.

Графический объект второго барьера располагается в координатах 0, 0, –900 и имеет значения вращения 0, 1.57, 0. Второй барьер охватывает область от –10, 0, –1154 до 10, 100, –645.

Загрузка данных барьеров

Чтобы загрузить и использовать файл данных барьеров, создайте экземпляр класса cBarrier, загрузите файл данных и соответствующие сетки и назначьте сетки:

// Graphics = ранее инициализированный объект cGraphics

cBarrier Barrier;

// Загрузка файла данных барьеров
Barrier.Load("test.bar");
// Загрузка используемых сеток и анимаций
cMesh Mesh;
cAnimation Anim;
Mesh.Load(&Graphics, "barrier.x");
Anim.Load("barrier.x", &Mesh);

// Назначение сеток и анимаций обеим загруженным барьерам
Barrier.SetMesh(1, &Graphics, &Mesh);
Barrier.SetMesh(2, &Graphics, &Mesh);
Barrier.SetAnim(1, &Anim, "AnimationName", 0);
Barrier.SetAnim(2, &Anim, "AnimationName", 0);

Проверка столкновений с барьерами

Чтобы увидеть, заблокирована ли область карты, вызовите GetBarrier с координатами персонажа. Если возвращается значение TRUE, проход заблокирован, и вы должны предпринять соответствующие действия. Возьмем следующий пример, который сравнивает координаты персонажа со всеми барьерами, загруженными из списка барьеров.

Вы используете трио значений, представляющих перемещение персонажа по каждой из осей, чтобы предварительно определить, заблокировано ли передвижение барьером. Скажем, персонаж перемещается на 10 единиц вдоль оси Z, и это значит, что в входящей переменной ZMove будет установлено значение 10. Эта переменная ZMove прибавляется к текущему местоположению персонажа, и, если произошло пересечение с барьером, переменная ZMove очищается, запрещая таким образом данное передвижение вдоль оси, как показано ниже:

// XPos, YPos, ZPos = координаты персонажа
// XMove, YMove, ZMove = значения перемещения персонажа
if(Barrier.GetBarrier(XPos+XMove, YPos+YMove, ZPos+ZMove) == TRUE) {
    // Проход запрещен, очищаем переменные перемещения
    XMove = YMove = ZMove = 0.0f;
}

Визуализация барьеров

Теперь вам надо только вызвать cBarrier::Render, чтобы нарисовать все объекты барьеров в поле зрения:

// Frustum = ранее инициализированный объект cFrustum
Barrier.Render(timeGetTime(), &Frustum);

netlib.narod.ru< Назад | Оглавление | Далее >

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