Войти
K5EngineСтатьи

Графическая система движка - ключевые элементы.

Автор:

Введение.

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

Спрайты.

Основным графическим объектов в движке является спрайт (TSprite), заголовочный файл которого находится по пути [K5Engine][Core][GraphicSystem][Sprite][Sprite.h].
TSprite наследуется от TBaseGraphicObject, который в свою очередь наследуется от TBaseSceneObject. Далее по порядку разберём каждый класс.

Класс TBaseSceneObject ( путь к заголовочному файлу: [K5Engine][Core][BaseSceneObject.h]):

class TBaseSceneObject
{
  protected:
    static unsigned long IdCounter;
    unsigned long        Id;

    wstring  Name;
  public:
    TPoint Pos;
    TPoint Center;
    TValue Angle;
  public:
    TBaseSceneObject();
    ~TBaseSceneObject();

    unsigned long GetID() const;

    void    SetName(const wstring &Val);
    wstring GetName() const;
};
Разберём параметры данного класса.
Id - Уникальный индекс класса , фактически он есть у всех ключевых классов движка.
Задавать его нельзя, он задаётся автоматически для каждого объекта. Получить его можно при помощи метода GetID.
Name — имя объекта, задаётся и берётся при помощи соответствующих методов  SetName и  GetName.
Pos — позиция объекта на сцене, является экземпляром класса TPoint.
Center — центр вращения, является экземпляром класса TPoint.
Angle — угол наклона объекта, является экземпляром класса TValue.

Далее идёт базовый графический объект:

class TBaseGraphicObject:public TBaseSceneObject
{
  protected:
    bool  View;
    pTBaseDevice  Device;

    enTextureFilter TexMinFilter;
    enTextureFilter TexMagFilter;
  protected:
    void SetDefParams(const TBaseGraphicObject *Val);

    virtual void ToSetDevice() = 0;
    virtual void ToDraw() = 0;
  public:
    TBaseGraphicObject();
    TBaseGraphicObject(const TBaseGraphicObject &Val);
    virtual ~TBaseGraphicObject();

    void operator=(const TBaseGraphicObject &Val);

    void SetView(const bool &Val = true);
    bool GetView() const;

    void SetDevice(const pTBaseDevice &Val);
    pTBaseDevice GetDevice();

    void SetMinFilter(const enTextureFilter &Val);
    void SetMagFilter(const enTextureFilter &Val);
    void SetFilter(const enTextureFilter &Val);

    enTextureFilter GetMinFilter() const;
    enTextureFilter GetMagFilter() const;

        void Draw();
};
Он имеет такие параметры:
View — флаг отображения объекта, по умолчанию он true. Используется в методе Draw(). Если флаг  View равен false, то не вызывается виртуальный метод  ToDraw.
Флаг можно задать и получить соответствующими методами  SetView и  GetView.
Device — указатель на класс устройства TBaseDevice. Задаётся и берётся соответственно  SetDevice и  GetDevice. При задании устройства вызывается виртуальный метод  ToSetDevice.
Суть всего этого — при задании устройства у наследников от  TBaseGraphicObject в методе  ToSetDevice происходит инициализация данных, которые зависят от контекста рисования.
Например в спрайте создаются класс для рисования меша и класс для управления позицией и масштабом меша.
TexMinFilter и TexMagFilter — параметры для фильтрации текстуры, наложенной на объект. В данный момент не используются.

Рассмотрев базовые классы, можно перейти к TSprite:

class TSprite: public TBaseGraphicObject
{
  protected:
    pTBaseViewMatrixWorker ViewMatrix;
    pTSpriteVertexData     VertexData;
  protected:
    void ToSetDevice();
    void ToDraw();
  public:
    TColor         Color;
    TPoint         Size;
    TPointArray    Mesh;
    TSpriteTexture Texture;
  public:
    TSprite();
    TSprite(const TSprite &Val);
    virtual ~TSprite();

    void operator=(const TSprite &Val);
};
Сначала разберём защищённые элементы класса:
ViewMatrix — матрица отображения объекта, которая зависит от контекста рисования. При помощи неё изменяется размер, позиция и наклон спрайта.
VertexData — данные точек, которые зависят от контекста рисования, это позиция в сетке, цвет отдельной точки и текстурные координаты.
ToSetDevice и ToDraw — переопределённые виртуальные методы из базового класса. В ToSetDevice при не NULL указателе на TBaseDevice создаются ViewMatrix и VertexData,
если же Device равен  NULL, то произойдёт очистка данных.

Далее — открытые элементы класса:
Color — цвет спрайта, экземпляр класса TColor, компоненты цвета изменяются от 0 до 1;
Size – размер спрайта, экземпляр класса TPoint, используются только X(ширина) и Y(высота) координаты для задания размеров, Z игнорируется.
Mesh — абстрагированные от типа визуализации точки спрайта, экземпляр класса TPointArray, массив точек сторон спрайта.
Всего используется четыре точки, обходятся по часовой стрелке начиная с нижней правой точки.
Texture — экземпляр класса TSpriteTexture, содержит в себе указатель на объект текстуры, абстрагированные от типа визуализации координаты текстуры и режимы наложения текстуры.
Можно задавать отражение текстуры по X или Y координатам, количество повторов или задать использование части текстуры(текстурный атлас).

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

Очередь рисования.

Так как спрайты на сцене нужно располагать по глубине рисования (например фон, второй план, первый план), а в ручную управлять порядком рисования неудобно,
то была введена очередь рисования, которая автоматически рисует спрайты по порядку их расположения по координате Z.
Чем больше координата Z, тем глубже на сцене располагается графический объект.
Объект очереди рисования — TRenderQueue, заголовочный файл находится по пути [K5Engine][Core][GraphicSystem][Base][RenderQueue.h].
Его описание:

class TRenderQueue
{
    protected:
    TExceptionGenerator Exception;
    vector<pTRenderQueueCell> Elems;
    bool View;
    public:
    TRenderQueue();
    ~TRenderQueue();

    void Draw ();
    void Clear();
    void Sort ();

    void Add(const pTRenderQueueCell   &Cell);
    void Add(const pTBaseGraphicObject &Object, const bool &CellView = true);

    pTRenderQueueCell Get(const int &Index);

    void Del(const unsigned long &Id);
    void Del(const pTBaseGraphicObject &Object);

    void SetCellDraw(const unsigned long &Id, const bool &DrawFlag);
    bool GetCellView(const unsigned long &Id) const;

    void SetView(const bool &Val);
    bool GetView() const;

    int GetSize() const;
};
По своей сути это динамический список, объекты которого можно сортировать по глубине их расположения на сцене. Это выполняется методом Sort.
Для рисования объектов очереди используется метод  Draw. Удалить все элементы из очереди -  Clear. Добавление ячейки очереди — методы  Add для созданной TRenderQueueCell и для отдельного объекта.
Удалить ячейку можно методами Del, которые получают или указатель на графический объект, или его уникальный идентификатор. Количество объектов, помещённых в очередь рисования,
можно узнать при помощи метода  GetSize.
Можно управлять видимостью как всей очереди (SetView и GetView), так и отдельной ячейки очереди ( SetCellDraw и GetCellView ).
Вообще отдельный флаг рисования для ячейки сделан для того, что бы можно было скрывать или отображать объекты группами, не меняя их собственный флаг отображения.
Методика работы с очередью: добавить объекты в неё, отсортировать, отображать. При добавлении новых объектов или при значительном изменении координаты Z
уже добавленных объектов так же желательно проводить сортировку очереди.
При возникновении ошибки очередь генерирует исключение TException. 

Списки графических объектов.

Когда в сцене графических объектов становится много, управлять каждым по отдельности становится неудобно.
для их хранения и обработки в движке имеется специализированный шаблонный список TBaseGraphicObjectList.
Его описание можно найти по пути [K5Engine][Core][GraphicSystem][Base][BaseGraphicObjectList.h].
Класс шаблонный, то есть его код полностью открыт. Он него уже наследуются TSpriteList и TTextList. При возникновении ошибки очередь генерирует исключение TException. 
Рассмотрим ключевые группы методов класса.
SetName и GetName, соответственно задание и взятие имени списка.
SetView и GetView, установка  и взятие флага рисования списка. При вызове SetView, если задана очередь рисования и в списке есть объекты, то для них будет изменён флаг их ячеек рисования.
Группа методов Get и переопределённых операторов operator[] позволяет получить указатель на графический объект по его индексу в списке, по уникальному идентификатору или по имени.
Методы IfExist позволяют проверить, есть ли в списке объект с таким именем или идентификатором.
Методы GetIfExist позволяют получить указатель на объект, если он содержится в списке. В противном случае вернётся NULL.
Методы GetIndex возвращают индекс(позицию) объекта в списке по его имени или уникальному идентификатору.
Методы SetDevice получают указатель на устройство или само устройство и запоминают его.
Если список не пустой, сохранённый указатель будет передам всем элементам списка. Соответственно для получения указателя на устройство используются GetDevice.
Методы SetRenderQueue получают указатель на очередь рисования или саму очередь. Далее если ранее очередь была задана, из неё будут извлечены элементы списка
и перемещены в новую очередь рисования.
Для получения указателя на очередь рисования — GetRenderQueue.
Для добавления объектов в список есть два метода Add. Один получает экземпляр объекта, второй — указатель на предварительно созданный объект.
Работают они по разному, при передаче экземпляра объекта создаётся его копия и уже она помещается в список как указатель. При помещении объекта в список, 
если нужно ему задаётся устройство и объект помещается в очередь рисования.
Удаление графических объектов производится при помощи методов Del и DelIfExist. Удаляются по имени и уникальному идентификатору а так же при Del по позиции в списке.
Для извлечения объектов из списка есть методы Extract, получающие позицию объекта в списке или его уникальный идентификатор или имя.
При этом объект так же будет извлечён из очереди рисования, если она была задана ранее.
Для удаления всех помещённых объектов есть метод Clear. Так же он убирает объекты из очереди рисования, если она была задана. Метод так же вызывается при разрушении самого списка.
Для удаления указателей на объекты без их удаления есть метод ClearElems. Но во первых, он очень редко используется, во вторых, если уж вдруг понадобился,
пользуйтесь с крайней осторожностью, так как небрежное применение чревато утечками памяти.
Для рисования объектов без задания очереди рендера есть метод Draw.
Для сортировки списка применяется метод Sort. При этом будет отсортированы только элементы списка, если была задана очередь, то для её сортировки используется SortRenderQueue.
Так же можно переместить графические объекты из одного списка в другой. Для этого есть метод Merge, получающий указатель на целевой список или сам целевой список.
Так же у списка объектов переопределён оператор присваивания, который позволяет скопировать параметры и объекты из одного списка в другой.
Но прим этом указатели на очередь рисования и устройство не присваиваются.

Менеджер графических объектов и список указателей.

Так как иногда одних списков графических объектов не хватает, есть менеджеры графических объектов TBaseGraphicObjectListManager и наследники TSpriteListManager и TTextListManager. 
По своей сути это список списков. Он обладает функционалом, подобным TBaseGraphicObjectList (но с определёнными особенностями),
это так же шаблонный класс и его код можно найти по пути [K5Engine][Core][GraphicSystem][Base][BaseGraphicObjectListManager.h].
Весь функционал менеджеров разбирать не будем, остановимся только на некоторых отличиях:
Нет оператора присваивания и методов для слияния элементов менеджеров.
Метод SetView не меняет флаги у ячеек очереди рисования, он влияет только на собственный Метод Draw менеджера.
Это нужно учитывать, если нужно изменить значение флага отображения для всех списков, которые находятся в менеджере.
Есть специальные методы для получение указателя на список, в котором находится графический объект, методы для получения, добавления или удаления отдельного графического объекта.
Лучше всего просмотреть код базового менеджера, что бы понимать, как он работает и что делает.

Иногда нужно хранить указатели на группу графических объектов, которые уже находятся в списках. Для этого есть шаблонный класс TBaseGraphicObjectPointerList,
код которого находится по пути [K5Engine][Core][GraphicSystem][Base][BaseGraphicObjectPointerList.h].
В целом его функционал повторяет  TBaseGraphicObjectList с некоторыми нюансами. Например нет оператора присваивания и методов для слияния.
В работе список указателей востребован достаточно редко. От  TBaseGraphicObjectList наследуются TSpritePointerList и  TTextPointerList. 

Камера.

Заголовочный класс камеры находится по пути [K5Engine][Core][GraphicSystem][Camera.h].
Работать с ней достаточно просто, у неё есть два публичных параметра — смещение (Shift) и масштаб (Scale), которые являются экземплярами TPoint.
Для задействования трансформации камеры по отношению к объектам вызывается её метод Run, для прекращения работы — Stop.
Инициализация камеры происходит при передаче ей указателя или экземпляра объекта TBaseDevice (методы SetDevice).
Для пересчёта локальных координат экрана в координаты сцены с применением камеры есть методы RecountPoint для точек и RecountEvent для события системы.
Код работы с камерой будет выглядеть примерно так:

Camera.Run();
GameRender.Draw();
Camera.Stop();

Текстуры.

Работа с текстурами происходит через указатель на класс-интерфейс TTexture. Вообще на прямую с текстурами лучше не работать, чревато возникновением ошибок.
Для загрузки текстуры из файла применяется класс TTextureLoader, заголовочный файл которого можно найти по пути [K5Engine][Cover][TextureLoader.h].
Он является наследником базового класса — интерфейса TBaseTextureLoader. Инициализация загрузчика происходит как и у многих других классов — при передаче указателя на устройство.
агрузить текстуру можно при помощи методов Run, которые принимают путь к файлу и имя будущей текстуры.
Так же есть возможность загрузить только часть изображения в качестве текстуры и создать маску текстуры из файла (метод Mask). Работа с загрузчиком выглядит примерно так:

TTextureLoader Loader(&Device); // инициализация загрузчика
pTTexture Texture = Loader.Run(TexturePath, L"TextureName"); // загрузка текстуры 
После завершения работы с текстурой нужно отчистить её данные и отчистить память:
Texture->Del();
delete Texture;

Так как при росте числа текстур в ручную управлять ими становится неудобно, для них были введены списки текстур и менеджеры.
Фактически их функционал аналогичен спискам графических объектов, но с поправкой на особенности работы с текстурами.
Список текстур — класс TTextureList, заголовочный файл находится по пути [K5Engine][Core][GraphicSystem][TextureList.h].
Менеджер списков текстур — TTextureListManager, заголовочный файл находится по пути [K5Engine][Core][GraphicSystem][TextureListManager.h].
Так же есть список указателей на текстуры, аналог TBaseGraphicObjectPointerList. 

Итог.

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

3 января 2012