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

Общение между персонажами

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

Говорящие куклы

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

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

Управляемые скриптами говорящие куклы

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

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

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

If flag 0 is TRUE then
    Print message "Я вижу, вы посетили Грэнуолл на юге!"
Else
    Print message "Вы должны сходить на юг в Грэнуолл."
Endif

Как видите, в показанном выше скрипте отслеживается равно ли значение флага (flag 0) TRUE или FALSE (флаг становится равным TRUE, всякий раз, когда игрок посещает город Грэнуолл).

Управляемые скриптами говорящие куклы относительно просты для создания и работы с ними, и я использую этот метод общения в оставшейся части книги. В идущем далее разделе «Демонстрация персонажей в программе Chars» и в главе 16 вы увидите обработку скриптов в действии и как использовать управляемые скриптами говорящие куклы в вашей игре.

Показ диалогов и другого текста

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

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

Чтобы все было проще, я разработал систему (класс текстового окна, названный cWindow), которая может визуализировать текстовое окно любого размера в любом месте экрана. Окно может быть в любой момент перемещено и может содержать произвольную строку текста, которую вы назначите. В качестве дополнительного приза текстовое окно может действовать как вариант «текстового пузыря» с указателем на говорящий персонаж. На рис. 12.6 показан класс текстового окна в действии.


Рис. 12.6. Класс текстового окна cWindow

Рис. 12.6. Класс текстового окна cWindow позволяет вам открыть окно любой ширины и высоты для отображения любого текста (особенно текста беседы)


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

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

В реальности вы можете использовать текстовое окно и для чего-нибудь другого. Скажем, вы можете открыть окно для показа изображения, используя класс текстового окна и объект текстуры. Это становится вопросом рисования сначала одного, а потом другого. В главе 16 вы увидите, как класс текстового окна находит применение для вещей, отличных от общения персонажей.

Класс cWindow

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

class cWindow
{
  private:
    typedef struct sVertex { // Свои вершины
        float x, y, z; // Координаты в экранном пространстве
        float rhw;     // Значение RHW
        D3DCOLOR Diffuse; // Рассеиваемый цвет
    } sVertex;
    #define WINDOWFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

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

Затем вы определяете ряд указателей на родительские объекты cGraphics и cFont. У текстового окна для работы должны быть заранее инициализированный объект графического устройства и объект шрифта. Также вы объявляете экземпляр объекта буфера вершин текстового окна.

    cGraphics *m_Graphics;    // Родительский объект cGraphics
    cFont *m_Font;            // Объект шрифта
    cVertexBuffer m_WindowVB; // Буфер вершин для окна
    char *m_Text;             // Отображаемый текст
    D3DCOLOR m_TextColor;     // Цвет для рисования текста

Текстовая строка содержится внутри класса (то есть в выделенном буфере char), вместе с соответствующим цветом, используемым для рисования текста. После этого определения текстовой строки следуют координаты и размеры окна и отдельная переменная, указывающая надо ли рисовать указатель текстового пузыря (как определено последующим вызовом позиционирования окна).

    long m_XPos, m_YPos;    // Координаты окна
    long m_Width, m_Height; // Размеры окна
    BOOL m_DrawTarget;      // Флаг для рисования указателя пузыря

Затем идут объявления открытых функций класса. (Я рассмотрю детали описания прототипов функций после их показа. С этого момента я буду показывать код каждой функции отдельно.)

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

    // Функции создания/освобождения текстового окна
    BOOL Create(cGraphics *Graphics, cFont *Font);
    BOOL Free();

    // Устанавливаем текст и координаты/размеры/цвет окна
    BOOL SetText(char *Text, D3DCOLOR TextColor = 0xFFFFFFFF);

    // Перемещаем окно
    BOOL Move(long XPos, long YPos, long Width,long Height=0,
              long TargetX = -1, long TargetY = -1,
              D3DCOLOR BackColor = D3DCOLOR_RGBA(0,64,128,255));
    long GetHeight(); // Получаем высоту окна после установки

    // Визуализация окна и отображение текста
    BOOL Render(char *Text = NULL);
};
cWindow::cWindow и cWindow::~cWindow

Конструктор и деструктор малы и по сути только очищают и освобождают ресурсы класса:

cWindow::cWindow()
{
    // Очистка данных класса
    m_Graphics = NULL;
    m_Font = NULL;
    m_Text = NULL;
    m_TextColor = 0xFFFFFFFF;
}

cWindow::~cWindow()
{
    Free(); // Освобождение данных класса
}
cWindow::Create и cWindow::Free

Вы используете функции Create и Free для подготовки класса к использованию (путем назначения объектов cGraphics и cFont, применяемых для визуализации) и освобождения внутренних данных класса:

BOOL cWindow::Create(cGraphics *Graphics, cFont *Font)
{
    Free(); // Освобождаем предыдущие данные класса

    // Проверка ошибок
    if((m_Graphics = Graphics) == NULL || (m_Font = Font)== NULL)
        return FALSE;

    // Создаем новый буфер вершин
    // (с 11 вершинами для использования)
    m_WindowVB.Create(m_Graphics, 11, WINDOWFVF, sizeof(sVertex));

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

BOOL cWindow::Free()
{
    m_WindowVB.Free(); // Освобождаем буфер вершин
    delete [] m_Text; // Удаляем текстовый буфер
    m_Text = NULL;
    return TRUE;
}

Буфер вершин окна использует 11 вершин для хранения двух прямоугольников окна и графического указателя на местоположение цели на экране. Вы увидите, как этот буфер создается и используется немного позже в разделе «cWindow::Move». Что касается текста окна, достаточно только выделить массив и скопировать текст в него, так что освобождение класса включает удаление используемого массива.

cWindow::SetText

Как я только что упомянул, вы храните текст окна, создавая массив байтов и копируя текст окна в этот массив. Установка текста окна — это предназначение функции SetText, которая получает два параметра — используемый текст (char *Text) и цвет, используемый для рисования текста (D3DCOLOR TextColor).

BOOL cWindow::SetText(char *Text, D3DCOLOR TextColor)
{
    // Удаление предыдущего текста
    delete [] m_Text;
    m_Text = NULL;

    m_Text = strdup(Text); // Копирование текстовой строки
    m_TextColor = TextColor; // Сохранение цвета текста
    return TRUE;
}

Для эффективности используется функция strdup, которая сразу выделяет память и копирует текстовую строку. Функция strdup получает в своем аргументе текстовую строку и возвращает указатель на выделенный буфер, который содержит текст из запроса (вместе с завершающим символом NULL, который отмечает конец текста). Теперь текст готов к использованию в классе, и вы в любой момент можете поменять текст, просто вызвав SetText еще раз.

cWindow::Move

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

BOOL cWindow::Move(long XPos, long YPos,
                   long Width, long Height,
                   long TargetX, long TargetY,
                   D3DCOLOR BackColor)
{
    sVertex Verts[11];
    long i;

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

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

    // Сохраняем координаты и вычисляем если надо высоту
    m_XPos = XPos;
    m_YPos = YPos;
    m_Width = Width;

    if(!(m_Height = Height)) {
        RECT Rect;
        Rect.left = XPos;
        Rect.top = 0;
        Rect.right = XPos + Width - 12;
        Rect.bottom = 1;

        m_Height = m_Font->GetFontCOM()->DrawText(m_Text, -1,
                           &Rect, DT_CALCRECT | DT_WORDBREAK,
                           0xFFFFFFFF) + 12;
    }

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

Используя ранее сохраненные местоположение и размеры, окно конструируется, как показано на рис. 12.7.


Рис. 12.7. Два окна, каждое из которых использует четыре вершины

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


Кроме того, буфер вершин может содержать еще три вершины, которые образуют указатель в верхней или в нижней части текстового окна. Присваивание аргументам TargetX и TargetY значений, отличных от –1, информирует функцию Move о необходимости рисовать указатель. Вот код для установки вершин окна:

    // Очищаем данные вершин
    for(i = 0; i < 11; i++) {
        Verts[i].z = 0.0f;
        Verts[i].rhw = 1.0f;
        Verts[i].Diffuse = 0xFFFFFFFF;
    }

    // Устанавливаем белый бордюр
    Verts[0].x = (float)m_XPos;
    Verts[0].y = (float)m_YPos;
    Verts[1].x = (float)(m_XPos+m_Width);
    Verts[1].y = (float)m_YPos;
    Verts[2].x = (float)m_XPos;
    Verts[2].y = (float)(m_YPos+m_Height);
    Verts[3].x = (float)(m_XPos+m_Width);
    Verts[3].y = (float)(m_YPos+m_Height);

    // Устанавливаем текстовое окно
    Verts[4].x = (float)m_XPos+2.0f;
    Verts[4].y = (float)m_YPos+2.0f;
    Verts[4].Diffuse = BackColor;
    Verts[5].x = (float)(m_XPos+m_Width)-2.0f;
    Verts[5].y = (float)m_YPos+2.0f;
    Verts[5].Diffuse = BackColor;
    Verts[6].x = (float)m_XPos+2.0f;
    Verts[6].y = (float)(m_YPos+m_Height)-2.0f;
    Verts[6].Diffuse = BackColor;
    Verts[7].x = (float)(m_XPos+m_Width)-2.0f;
    Verts[7].y = (float)(m_YPos+m_Height)-2.0f;
    Verts[7].Diffuse = BackColor;

    // Устанавливаем указатель цели
    if(TargetX != -1 && TargetY != -1) {
        m_DrawTarget = TRUE;

        if(TargetY < m_YPos) {
            Verts[8].x = (float)TargetX;
            Verts[8].y = (float)TargetY;
            Verts[9].x = (float)(m_XPos+m_Width/2+10);
            Verts[9].y = (float)m_YPos;
            Verts[10].x = (float)(m_XPos+m_Width/2-10);
            Verts[10].y = (float)m_YPos;
        } else {
            Verts[8].x = (float)(m_XPos+m_Width/2-10);
            Verts[8].y = (float)(m_YPos+m_Height);
            Verts[9].x = (float)(m_XPos+m_Width/2+10);
            Verts[9].y = (float)(m_YPos+m_Height);
            Verts[10].x = (float)TargetX;
            Verts[10].y = (float)TargetY;
        }
    } else {
        m_DrawTarget = FALSE;
    }

    m_WindowVB.Set(0, 11, &Verts); // Устанавливаем вершины
    return TRUE;
}
cWindow::GetHeight

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

long cWindow::GetHeight()
{
    return m_Height;
}
cWindow::Render

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

BOOL cWindow::Render(char *Text, D3DCOLOR Color)
{
    // Проверка ошибок
    if(m_Graphics == NULL || m_Font == NULL)
        return FALSE;

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

    // Рисуем окно
    m_WindowVB.Render(0, 2, D3DPT_TRIANGLESTRIP);
    m_WindowVB.Render(4, 2, D3DPT_TRIANGLESTRIP);

    // Рисуем указатель, если надо
    if(m_DrawTarget)
        m_WindowVB.Render(8, 1, D3DPT_TRIANGLELIST);

    // Рисуем текст, только если высота > 12
    if(m_Height > 12) {
        // Рисуем текст
        if(Text == NULL)
            m_Font->Print(m_Text, m_XPos+6, m_YPos+6,
                          m_Width-12,m_Height-12,
                          m_TextColor, DT_WORDBREAK);
        else
            m_Font->Print(Text, m_XPos+6, m_YPos+6,
                          m_Width-12,m_Height-12,
                          Color, DT_WORDBREAK);
    }
    return TRUE;
}

У Render есть два необязательных аргумента. Первый аргумент, Text, позволяет переопределить текст, заданный внутри класса, который был установлен с использованием функции SetText. Переопределение рисуемого текста замечательно подходит для динамического обновления того, что нужно показать. Второй аргумент, Color, задает цвет, который вы хотите использовать для рисования отображаемого текста.

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

Чтобы быстро продемонстрировать использование cWindow, позвольте мне показать как отобразить короткое сообщение (подразумевается, что вы уже инициализировали объект cGraphics):

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

// Создание используемого шрифта
Font.Create(&Graphics, "Arial", 16);

// Подготавливаем класс для использования
Window.Create(&Graphics, &Font);
Window.SetText("The cWindow class in action!");
Window.Move(4, 4, 632);

Graphics.BeginScene(); // Начинаем сцену
Window.Render();       // Рисуем окно и текст
Graphics.EndScene();   // Завершаем сцену
Graphics.Display();    // Отображаем сцену

Window.Free(); // Освобождаем данные класса окна

 

ВНИМАНИЕ!
Сейчас вы должны жестко задавать размер шрифта; шрифт должен быть 16 пикселей, иначе могут возникнуть проблемы с межстрочными интервалами. Я оставляю изменение кода для вас, но во всех последующих примерах я настоятельно рекомендую придерживаться использования шрифта Arial размером 16.

Полезность класса cWindow будет полностью видна позже, в разделе «Демонстрация персонажей в программе Chars», когда вы будете отображать диалоги, так что пока отложите общение и текстовые окна на второй план.


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

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