Войти
ПрограммированиеСтатьиОбщее

Применение иерархии объектов в играх.

Автор:

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

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

Начнем-с :

Для представления игровой сцены мы будем использовать некоторую иерархию объектов. Т.е. наша сцена будет представлять собой дерево (tree) с неограниченным количеством родительских объектов (parent), каждый из этих объектов может иметь один или несколько дочерних объектов (children). Дочерние объекты, в свою очередь, также могут иметь свои дочерние объекты, для которых они уже будут являться родительскими(parent). Любой дочерний объект, при просчете иерархии, унаследует некоторые свойства родительского объекта, который находится в вершине иерархии. Например, мировую матрицу трансформации (для правильного позиционирования в 3D мире) или Render States.

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


Изображение

Итак, в основе сцены находится некий объект "Root", который является родительским для ВСЕХ остальных, к нему будут подсоединяться остальные объекты (я для этих целей использую объект класса CScene см. исходники). Объекты, обозначенные на диаграмме "SomeObject 1", "SomeObject 2", "SomeObject 3" являются дочерними для объекта "Root". Объекты "BestObject 1", "Best Object 2", "BestObject 3" также являются дочерними объектами, но для них родительским будет объект "SomeObject 1".

Как же это все организовать? В этом нам поможет всеми любимое объектно-ориентированное программирование, точнее некоторые из его свойств, а именно наследование (inheritance) и полиморфизм (polymorphism).

Наследование позволяет объектам расширять возможность базового (base) класса, потомками которого они являются(derived).

Полиморфизм дает возможность вызывать методы класса-потомка, которые были созданы с директивой virtual в основном классе.

Также, для создания иерархии, будем использовать STL (Standart Template Library), а именно контейнер vector. В интернете существует уйма документации по STL, если кто не знаком с этой библиотекой, рекомендую почитать о ней, пригодится. Почитать можно, например, по адресу: http://www.sgi.com/tech/stl/

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

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

void CreateObject(char* strFileName, char* strObjectName)
{
     CObject* pObject = new CStaticMesh(strFileName, strObjectName);
     pSampleSceneRoot->AttachChild(pObject);
}

Итак, CObject - это наш основной класс, от которого будут происходить все наши остальные классы. CStaticMesh - класс, который представляет статичный 3D объект, т.е. без анимации, например, дом. pSampleSceneRoot представляет из себя основу нашей сцены. К этому объекту будут цепляться все остальные и для обработки всей сцены необходимо будет только пройтись по всем его дочерним объектам.

Рассмотрим более подробно реализацию класса CObject

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

// список дочерних объектов, лучше эти переменные сделать 
// private членами класса
vector<CObject*> m_childrenList;
// Указатель но родительский объект
CObject* m_pParent;

Также должна быть некая функция, с помощью которой мы могли бы установить родительский объект:

void SetParent(CObject* pParent)
{
  m_pParent = pParent;
}

Введем возможность добавления дочерних объектов:

void AttachChild(CObject* pChild)
{
  pChild->SetParent(this);
  m_ChildrenList.push_back(pChild);
}

Ну, и, наконец, каждый объект должен уметь возвращать список дочерних объектов:

vector<CObject*>* GetChildrenList()
{
  return &m_ChildrenList;
}

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

virtual void Render(float fElapsedTime) = 0;
virtual void Update(float fElapsedTime) = 0;

Вот собственно и все. Некоторые специфические добавления каждый может сделать сам.

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

Теперь рассмотрим подробнее, как мы будем рендерить наши объекты.

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

Например, в игре MS Monster Track Madness (гонки на грузовиках с огромными колесами), кузов машины представляет собой родительский объект типа CStaticMesh, а колеса, также являющиеся отдельными объектами типа CStaticMesh, являются его дочерними объектами. Каждое колесо может скакать по ухабам отдельно, но, тем не менее, зная свое положение относительно кузова грузовика, они будут правильно отображены в сцене.

Рассмотрим пример, как можно реализовать проход по иерархии нашей сцены. Мы будем использовать рекурсию, т.е. будем вызывать функцию из самой себя.

void UpdateScene(CObject* pObject, float fElapsedTime)
{
  if (pObject==NULL) return;
  else
  {
    pObject->Update(fElapsedTime);

    // теперь проходимся по всем дочерним объектам
   vector<CObject*>::iterator It = pObject->GetChildrenList()->begin();
    for (; It!=pObject->GetChildrenList()->end(); It++)
    UpdateScene(*It,  fElapsedTime);
  }
}

Здесь, в качестве примера я выбрал функцию обновления объекта, таким же образом можно реализовать и рендеринг.

На основе класса CObject можно построить любой игровой объект и "подсоединить" его к сцене.

В исходниках есть пример реализации класса CObject и класса CStaticMesh, в readme.txt написано несколько замечаний.

Надеюсь, что все вышесказанное было Вам полезно.

Have more fun.
Enjoy yourself.

#игровые объекты

12 июня 2002 (Обновление: 28 окт 2009)

Комментарии [34]