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

Использование автоматических карт

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

Вам необходимо отображать только те части карты, которые игрок исследовал. Места, которые еще ни разу не были посещены, показывать не надо, то есть их следует показывать только тогда, когда они обнаружены игроками. Таким образом, игроки смогут оглянуться назад, где они уже были, и, возможно, взглянуть вперед и наметить пути для будущих экспедиций. Это волшебство называется автоматической картой.

Автоматические карты в действии

Одна из моих любимых игр, Phantasy Star Online, выпущенная Sega, использует автоматические карты цельным образом. Посмотрите на рис. 13.5, который показывает рабочую автоматическую карту в верхнем правом углу экрана.


Рис. 13.5. Phantasy Star Online использует плоскую двухмерную версию уровня, как показано выше

Рис. 13.5. Phantasy Star Online использует плоскую двухмерную версию уровня, как показано выше


В Phantasy Star Online главный игрок и другие важные персонажи игры отображаются на автоматической карте как небольшие стрелки. Когда главный игрок ходит вокруг, карта прокручивается, чтобы показывать область вокруг игрока. Когда игрок посещает новую комнату (область), она появляется на автоматической карте.

Возможность автоматического картографирования, подобно многим другим возможностям, показанным в этой книге, легко воссоздать для ваших собственных игровых проектов.

Большие карты, малые карты

Главный вызов здесь — преобразовать ваш большой игровой уровень в маленькую карту, подходящую для отображения в вашей игре. На рис. 13.6 показан снимок экрана демонстрационной программы Mapping. Обратите внимание на карту в верхнем правом углу экрана. Она использует альфа-смешивание (обратитесь к главе 2 за дополнительной информацией по этой теме), чтобы было видно игровое действие под ней.


Рис. 13.6. Демонстрационная программа Mapping использует автоматическое картографирование, чтобы отображать области карты, которые игрок уже посетил

Рис. 13.6. Демонстрационная программа Mapping использует автоматическое картографирование, чтобы отображать области карты, которые игрок уже посетил


Простейший способ создать уменьшенную версию игрового уровня — перейти в редактор трехмерной графики и загрузить тот уровень, уменьшенную карту которого вы собираетесь сконструировать. На рис. 13.7 показан пример уровня, загруженный в MilkShape 3D и готовый для работы.


Рис. 13.7. Пример уровня, загруженный в редактор MilkShape 3D и готовый для преобразования в уменьшенную карту

Рис. 13.7. Пример уровня, загруженный в редактор MilkShape 3D и готовый для преобразования в уменьшенную карту


 

ВНИМАНИЕ!
MilkShape 3D неспособен показывать большие сетки, поэтому, чтобы рассмотреть сразу весь уровень, вам может потребоваться уменьшить масштаб сетки для работы, а затем вернуть прежний масштаб, когда вы будете записывать результат.

Для начала выберите все полигональные грани, нажав Ctrl+A. Щелкните по вкладке Groups и выберите Regroup для создания единой сетки. Затем щелкните по вкладке Materials и щелкайте Delete, пока не будут удалены все материалы. В результате вы должны получить единую сетку, которая не использует наложение текстур.

Теперь сложная часть: пройдитесь по всей сетке и удалите несущественные полигоны. В их число входят полигоны, которые никогда не будут видимы при взгляде сверху, которые используются для украшения, или представляют стены. Вам надо оставить только те полигоны, из которых состоит земля. Чтобы удалить грани, выберите ее (вам надо щелкнуть Select, а затем Face) и нажмите клавишу Delete.

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


Рис. 13.8. Перекрывающиеся полигоны на малой карте при визуализации с альфа-смешиванием могут создавать странные цветовые артефакты

Рис. 13.8. Перекрывающиеся полигоны на малой карте при визуализации с альфа-смешиванием могут создавать странные цветовые артефакты


После небольшой переработки, как вы можете видеть на рис. 13.9, моя загруженная сетка уровня сократилась только до тех полигонов, которые доступны персонажам. Я добавил несколько новых полигонов, которые будут полезны при разделении сетки на несколько областей.


Рис. 13.9. Малая карта содержит значительно меньше полигонов. Полигоны готовы к группировке в отдельные секции

Рис. 13.9. Малая карта содержит значительно меньше полигонов. Полигоны готовы к группировке в отдельные секции


И, наконец, карту надо разделить на меньшие фрагменты (которые, по сути, являются отдельными комнатами на карте), которые открываются по мере того, как персонаж обнаруживает их. Начнем с отмены выделения (нажмите Shift+Ctrl+A). Теперь щелкните вкладку Model, щелкните Select и выберите Face.

Щелкайте по тем граням, которые вы хотите включить в один фрагмент карты. Как видно на рис. 13.10, я выбрал группу граней в верхнем левом углу сетки. Эти грани будут образовывать первую область карты.


Рис. 13.10. Выбор граней - первый этап построения отдельных областей карты

Рис. 13.10. Выбор граней — первый этап построения отдельных областей карты


Когда все нужные грани выбраны, снова щелкните по вкладке Groups и выберите Regroup. Обратите внимание, что создана новая группа (рис. 13.11).


Рис. 13.11. Создана новая группа граней

Рис. 13.11. Создана новая группа граней


Поздравляю! Вы создали вашу первую область карты. Щелкните по группе Regroup01, снимите выбор со всех граней, и начинайте выбирать грани для следующего фрагмента карты. Продолжайте выбор граней и их перегруппировку, пока вся карта не будет разделена на различные группы.

Для карты примера я разбил сетку на четыре индивидуальных группы, представляющих верхний левый, верхний правый, нижний левый и нижний правый углы уровня. Итоговый файл карты вы найдете в примере программы из этой главы. Пойдемте дальше, запишем вашу карту и экспортируем ее в файл .X (используйте экспортер файлов .X, созданный мной и поставляемый на диске с этой книгой).

Загрузка и отображение автоматической карты

Ну вот, уменьшенная карта создана и ждет использования. Сейчас вам надо загрузить файл .X и получить отдельные сетки, которые он содержит. Для загрузки сетки замечательно подходит использование объекта cMesh графического ядра.

Теперь вы конструируете массив буферов вершин — по одному буферу для каждой сетки автоматической карты. Вы заполняете каждый буфер вершин данными треугольных граней каждой сетки в объекте cMesh. Трюк здесь в том, что когда вы копируете данные вершин из сетки в буфер вершин, координата Y отбрасывается, чтобы получившийся буфер вершин сетки был плоским, обеспечивая двухмерный вид автоматической карты.

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

Хотя концепция кажется простой, перейдем к существу, взглянув на некоторый рабочий код.

Создание класса автоматической карты

Класс автоматической карты, который я разработал для этой книги, загружает объект cMesh и сжимает его в плоскую версию карты. Плоская карта хранится в наборе буферов вершин. Эти буферы вершин используют только координаты X, Y и Z каждой вершины, плюс отдельный рассеиваемый цвет. Значит, карта будет компактной и простой для визуализации. Это также означает, что вы можете использовать альфа-смешивание, чтобы карта накладывалась на экран не скрывая происходящих важных игровых событий.

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

class cAutomap
{
  private:
    typedef struct {
        float x, y, z; // 3-D координаты
    } sGenericVertex;

    typedef struct {
        float x, y, z; // Координаты
        D3DCOLOR Diffuse; // Цвет карты
    } sVertex;

    #define AUTOMAPFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)

    cGraphics *m_Graphics; // Родительский объект cGraphics

    long m_NumSections; // Кол-во фрагментов карты
    char *m_Visible;    // Видимость фрагментов

    cVertexBuffer *m_MapVB;    // Буфер вершин карты
    cVertexBuffer m_PointerVB; // Буфер вершин указателя

    D3DVIEWPORT9 m_Viewport; // Область для рисования карты
    cCamera m_Camera;        // Камера, используемая для рисования карты

    float m_Scale; // Масштаб для рисования карты

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

    // Функции для создания и освобождения карты
    BOOL Create(cGraphics *Graphics, char *Filename,
                long Color = D3DCOLOR_RGBA(64,64,64,255));
    BOOL Free();

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

    // Возвращает количество фрагментов карты
    long GetNumSections();

    // Включение/выключение флага видимости фрагмента карты
    BOOL EnableSection(long Section, BOOL Enable);

    // Определение области для рисования карты
    BOOL SetWindow(long XPos, long YPos,
                   long Width, long Height);

    // Визуализация карты на экране
    BOOL Render(cCamera *OldCamera,
                float MXPos, float MYPos, float MZPos,
                float NumPositions,
                float *XPos, float *ZPos, float *Angle);
};

В начале вы видите определения двух структур данных вершин. Первую из них, GenericVertex, вы используете для доступа к координатам вершин исходной сетки. Вторая, sVertex, применяется для хранения фрагментов карты.

За структурами данных вершин следует набор переменных. Обратите внимание на объект cGraphics, используемый для загрузки сеток, количество используемых фрагментов карты, массив буферов вершин, массив переменных char, используемых для отметки видимых фрагментов карты, структуру данных порта просмотра, cCamera, переменную коэффициента масштабирования и буфер вершин указателя.

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

Например, карта размером 1024 единицы в ширину и глубину масштабируется до 256 единиц в ширину и глубину. Фактически, карта масштабируется до размерв 256 × 256, независимо от ее размера в X-файле.

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

Помимо закрытых переменных класса вы также имеете дело с функциями.

cAutomap::cAutomap и cAutomap::~cAutomap

Конструктор и деструктор класса cAutomap гарантируют, что все данные будут приведены в известное исходное состояние и что все используемые ресурсы будут освобождены. Конструктор только очищает некоторые переменные и ориентирует камеру, чтобы она была направлена вниз. Позднее вы используете эту камеру для визуализации карты. Деструктор вызывает функцию Free для освобождения всех используемых ресурсов. Взгляните на код этих функций:

cAutomap::cAutomap()
{
    m_Graphics = NULL;

    // Установка данных фрагментов и буфера вершин
    m_NumSections = 0;
    m_Visible = NULL;
    m_MapVB = NULL;

    // Направляем камеру вниз
    m_Camera.Rotate(1.57f, 0.0f, 0.0f);

    // Устанавливаем окно по умолчанию для отображения карты
    SetWindow(0,0,100,100);
    m_Scale = 1.0f; // Устанавливаем масштаб по умолчанию
}

cAutomap::~cAutomap()
{
    Free();
}

cAutomap::Create и cAutoMap::Free

Глубоко вздохните, приступая к этой части. Функция Create самая большая из всех. Она загружает файл .X и преобразует каждую сетку из него в отдельный буфер вершин. Начнем с объявления ее переменных, исследуя переменные по одной, чтобы лучше понимать, что будет дальше:

BOOL cAutomap::Create(cGraphics *Graphics,
                      char *Filename, long Color)
{
    cMesh Mesh;                // Загружаемый файл .X
    sMesh *MeshPtr;            // Указатель на сетки в cMesh
    ID3DXMesh *IMeshPtr;       // Указатель на сетку Direct3D
    sGenericVertex *GenVert;   // Источник вершин
    sVertex Vertex, *VBPtr;    // Местоназначение вершин
    long i, j, Num;
    long VertexSize, NumFaces;
    unsigned short *IndexPtr;  // Указатель на буфер индексов сетки
    char *VertexPtr;           // Указатель на буфер вершин сетки
    float Radius;              // Радиус всех сеток в .X

    // Определение буфера вершин указателя
    sVertex PointerVerts = {
        {  0.0f, 0.0f,  10.0f, D3DCOLOR_RGBA(128,64,0,255) },
        {  5.0f, 0.0f, -10.0f, D3DCOLOR_RGBA(128,64,0,255) },
        { -5.0f, 0.0f, -10.0f, D3DCOLOR_RGBA(128,64,0,255) }
    };

    // Освобождаем предыдущую автоматическую карту
    Free();

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

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

Пойдем дальше, начав с кода, который загружает сетку карты, используемую для создания буферов вершин:

    // Пытаемся загрузить сетку
    if(Mesh.Load(Graphics, Filename) == FALSE)
        return FALSE;

    // Получаем указатель на сетку
    if((MeshPtr = Mesh.GetParentMesh()) == NULL) {
        Mesh.Free();
        return FALSE;
    }

    // Получаем размер вершин в исходной сетке
    VertexSize = D3DXGetFVFVertexSize(MeshPtr->m_Mesh->GetFVF());

    // Получаем ограничивающий радиус для масштабирования
    Mesh.GetBounds(NULL, NULL, NULL, NULL, NULL, NULL, &Radius);
    m_Scale = 128.0f / Radius;

    // Получаем количество фрагментов в сетке карты
    if(!(m_NumSections = Mesh.GetNumMeshes())) {
        Mesh.Free();
        return FALSE;
    }

Первая задача состоит в загрузке файла .X с диска. Первая структура sMesh захватывается из объекта cMesh (вспомните из описания графического ядра, что класс cMesh хранит сетки в связанном списке структур sMesh).

Затем вы вычисляете размер структуры данных вершин, используемой в файле .X, и рассчитываете коэффициент масштабирования для изменения загруженной сетки. В конце вы сохраняете количество фрагментов карты в переменной класса. Заметьте, что количество фрагментов карты совпадает с числом сеток в файле .X.

Двигаясь дальше вы выделяете память для массива переменных char, каждый элемент которого сообщает, видим ли фрагмент карты. У каждого фрагмента карты есть соответствующий ему элемент массива. Вы также создаете массив буферов вершин (используя класс графического ядра cVertexBuffer). Эти буфера вершин будут использоваться для хранения фрагментов карты. Взгляните на код, который создает эти массивы и начинает сканирование списка сеток:

    // Выделяем буфер видимости и очищаем его
    m_Visible = new char[m_NumSections];
    ZeroMemory(m_Visible, m_NumSections);

    // Выделяем буферы вершин
    m_MapVB = new cVertexBuffer[m_NumSections]();

    // Перебираем каждую сетку в объекте cMesh
    // и конструируем соответствующий буфер вершин.
    // Убедитесь, что начинаете с последнего фрагмента карты,
    // чтобы скомпенсировать для связанного списка
    // упорядочение сеток в cMesh
    Num = m_NumSections - 1;

    while(MeshPtr != NULL) {
        // Получаем указатель на сетку
        IMeshPtr = MeshPtr->m_Mesh;

Вспомните, что сетки хранятся в связанном списке структур. Сейчас пришло время перебрать все структуры в связанном списке и запросить у каждой структуры указатель на реальный объект Direct3D ID3DXMesh, который содержит информацию сетки для отдельного фрагмента карты.

Затем вы блокируете буферы вершин и индексов (также как в главе 8) и начинаете извлекать данные вершин:

        // Блокируем буферы индексов и вершин
        IMeshPtr->LockIndexBuffer(D3DLOCK_READONLY,
                                  (void**)&IndexPtr);
        IMeshPtr->LockVertexBuffer(D3DLOCK_READONLY,
                                   (void**)&VertexPtr);

        // Создаем буфер вершин
        NumFaces = IMeshPtr->GetNumFaces();
        m_MapVB[Num].Create(Graphics, NumFaces*3,
                            AUTOMAPFVF, sizeof(sVertex));

        // Блокируем буфер вершин
        m_MapVB[Num].Lock(0, 0);
        VBPtr = (sVertex*)m_MapVB[Num].GetPtr();

Создаваемый буфер вершин соответствует количеству полигональных граней в исходной сетке. Буфер вершин блокируется и будет получен указатель для начала сохранения вершин:

        // Извлекаем вершины и конструируем список вершин
        for(i = 0; i < NumFaces; i++) {
            for(j = 0; j < 3; j++) {
                // Получаем указатель на вершину
                GenVert = (sGenericVertex*)
                           &VertexPtr[VertexSize * (*IndexPtr++)];

                // Создаем новые вершины
                Vertex.x = GenVert->x * m_Scale;
                Vertex.y = 0.0f;
                Vertex.z = GenVert->z * m_Scale;
                Vertex.Diffuse = Color;

                memcpy(VBPtr++, &Vertex, sizeof(sVertex));
            }
        }

Два цикла перебирают каждую полигональную грань в исходной сетке и три вершины каждой грани копируются в буфер вершин карты. Обратите внимание, что вы используете только координаты X и Z, а координата Y устанавливается равной 0 (снова, чтобы сделать карту плоской). И, наконец, вы устанавливаете рассеиваемый цвет равным предоставленному цветовому значению (используется для визуализации карты).

        // Разблокируем буфер вершин
        m_MapVB[Num].Unlock();

        // Разблокируем буферы
        IMeshPtr->UnlockVertexBuffer();
        IMeshPtr->UnlockIndexBuffer();

        // Переходим к следующей сетке
        Num--;
        MeshPtr = MeshPtr->m_Next;
    }

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

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

Функция Create завершается созданием буфера вершин указателя и копированием в него определенных ранее данных описаний вершин. Исходная сетка освобождается и управление возвращается вазывавшему.

    // Создание буфера вершин указателя персонажа
    m_PointerVB.Create(Graphics, 3, AUTOMAPFVF, sizeof(sVertex));
    m_PointerVB.Set(0, 3, &PointerVerts);

    Mesh.Free(); // Освобождаем загруженную сетку

    return TRUE;
}

Для удаления фрагментов карты из памяти вы предоставляете функцию Free, которая освобождает все выделенные ресурсы и подготавливает класс к загрузке другой карты:

BOOL cAutomap::Free()
{
    long i;

    // Освобождаем буферы вершин карты
    if(m_MapVB != NULL) {
        for(i = 0; i < m_NumSections; i++)
            m_MapVB[i].Free();

        delete [] m_MapVB;
        m_MapVB = NULL;
    }

    m_NumSections = 0;   // Сбрасываем кол-во фрагментов
    delete [] m_Visible; // Освобождаем массив видимости
    m_Visible = NULL;
    m_PointerVB.Free();  // Освобождаем буфер вершин указателя

    return TRUE;
}

cAutomap::Load и cAutomap::Save

Вспомните, что вы должны включить каждый фрагмент карты, чтобы он был видим при визуализации. Массив m_Visible отслеживает видимость каждого фрагмента карты; если значение элемента массива равно 0, соответствующий фрагмент карты не отображается. Если элемент установлен в 1, фрагмент карты рисуется.

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

BOOL cAutomap::Load(char *Filename)
{
    FILE *fp;
    long Num;
    BOOL ReturnVal = FALSE;

    // Открываем файл
    if((fp = fopen(Filename, "rb")) == NULL)
        return FALSE;

    // Получаем количество фрагментов
    fread(&Num, 1, sizeof(long), fp);

    // Проверяем соответствие и загружаем флаги видимости
    if(m_NumSections == Num && m_Visible != NULL) {
        fread(m_Visible, 1, Num, fp);
        ReturnVal = TRUE;
    }

    fclose(fp);

    return ReturnVal;
}

BOOL cAutomap::Save(char *Filename)
{
    FILE *fp;

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

    // Создаем файл
    if((fp = fopen(Filename, "wb")) == NULL)
        return FALSE;

    // Записываем количество фрагментов
    fwrite(&m_NumSections, 1, sizeof(long), fp);

    // Записываем флаги видимости
    fwrite(m_Visible, 1, m_NumSections, fp);

    fclose(fp); // Закрываем файл

    return TRUE; // Возвращаем флаг успеха
}

Формат хранения массива видимости простой: файл начинается с переменной long, указывающей количество элементов в массиве. Следом записывается весь массив видимости карты. Для загрузки массива видимости считывается количество элементов, проверяется, что оно соответствует загруженной в данный момент карте, после чего загружается массив.

cAutomap::GetNumSections и cAutomap::EnableSection

Эти две функции возвращают количество фрагментов загруженной карты и позволяют вам установить видимость каждого фрагмента карты. Фрагменты карты нумеруются последовательно в порядке их хранения в файле .X. Использование аргумента TRUE для Enable обеспечивает видимость фрагмента карты, а использование FALSE гарантирует, что фрагмент карты не будет отображаться. Держите это в уме, когда будете читать следующий код:

long cAutomap::GetNumSections()
{
    return m_NumSections;
}

BOOL cAutomap::EnableSection(long Section, BOOL Enable)
{
    if(Section >= m_NumSections || m_Visible == NULL)
        return FALSE;

    m_Visible[Section] = (Enable == TRUE) ? 1 : 0;

    return TRUE;
}

cAutomap::SetWindow и cAutomap::Render

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

BOOL cAutomap::SetWindow(long XPos, long YPos,
                         long Width, long Height)
{
    m_Viewport.X = XPos;
    m_Viewport.Y = YPos;
    m_Viewport.Width = Width;
    m_Viewport.Height = Height;
    m_Viewport.MinZ = 0.0f;
    m_Viewport.MaxZ = 1.0f;

    return TRUE;
}

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

BOOL cAutomap::Render(cCamera *OldCamera,
                      float MXPos, float MYPos, float MZPos,
                      float NumPositions,
                      float *XPos, float *ZPos, float *Angle)
{
    cWorldPosition Pos;
    D3DVIEWPORT9 OldViewport;
    long i;

    // Контроль ошибок
    if(m_Graphics == NULL || !m_NumSections ||
               m_MapVB == NULL || m_Visible == NULL)
        return FALSE;

    // Перемещение камеры
    m_Camera.Move(MXPos*m_Scale, MYPos, MZPos*m_Scale);
    m_Graphics->SetCamera(&m_Camera);

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

Затем вы создаете порт просмотра визуализации (параметры старого порта просмотра сохраняются для последующего восстановления). Вы устанавливаете режимы визуализации (нет Z-буферизации и нет текстур) и матрицу преобразования для центрирования автоматической карты в мире:

    // Поучаем старый порт просмотра и устанавливаем новый 
    m_Graphics->GetDeviceCOM()->GetViewport(&OldViewport);
    m_Graphics->GetDeviceCOM()->SetViewport(&m_Viewport);

    // Устанавливаем режимы визуализации и текстуру
    m_Graphics->EnableZBuffer(FALSE);
    m_Graphics->SetTexture(0, NULL);

    // Визуализируем буферы вершин
    m_Graphics->SetWorldPosition(&Pos);

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

    for(i = 0; i < m_NumSections; i++) {
        if(m_Visible[i])
            m_MapVB[i].Render(0, m_MapVB[i].GetNumVertices()/3,
                              D3DPT_TRIANGLELIST);
    }

    // Выключаем альфа-смешивание для визуализации указателей
    m_Graphics->EnableAlphaBlending(FALSE);

    // Рисуем местоположение персонажа
    if(NumPositions) {
        for(i = 0; i < NumPositions; i++) {
            Pos.Move(XPos[i] * m_Scale, 0.0f, ZPos[i] * m_Scale);
            Pos.Rotate(0.0f, Angle[i], 0.0f);
            m_Graphics->gSetWorldPosition(&Pos);
            m_PointerVB.Render(0, 1, D3DPT_TRIANGLELIST);
        }
    }

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

    // Восстанавливаем старую камеру, если передана
    if(OldCamera != NULL)
        m_Graphics->SetCamera(OldCamera);

    // Восстанавливаем старый порт просмотра
    m_Graphics->GetDeviceCOM()->SetViewport(&OldViewport);

    return TRUE;
}

Использование cAutomap

Демонстрационная программа к этой главе содержит замечательный пример использования класса автоматической карты, но для того, чтобы дать вам ясное представление о его использовании, вот небольшой пример. Начнем с создания экземпляра класса cAutomap и вызова функции Create для загрузки файла .X:

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

Automap.Create(&Graphics, "Map.x", D3DCOLOR_RGBA(64,64,64,255));

Теперь карта загружена и готова к использованию. Для визуализации карты используется темно-серый цвет (это причина появления макроса D3DCOLOR_RGBA). Чтобы начать визуализацию карты вы должны сперва задать позицию окна, в котором будет выполняться визуализация:

Automap.SetWindow(0, 0, 200, 200); // Используем для карты окно от 0,0 до 200,200

Затем вы отмечаете фрагмент карты как видимый:

Automap.EnableSection(0); // Делаем видимым 1-й фрагмент

И осталось только визуализировать карту:

Automap.Render(NULL, 0.0f, 200.0f, 0.0f, 0, NULL, NULL, NULL);

Показанный вызов помещает камеру в точку с координатами 0, 200, 0 (200 единиц над картой) и визуализирует единственный видимый фрагмент карты. А что насчет других фрагментов карты? Как ваша игра будет узнавать, какие фрагменты карты отметить как видимые? Используя триггеры, вот как!

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

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


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

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