[ AmberSkyNet VR ]

Трёхмерные предметы у нас вертятся и летают, и это, конечно, хорошо.. Но например, если мы захотим написать изометрическую игру, или даже совсем двумерную, то не обязательно в ней использовать трёхмерные модельки.. Можно вполне обойтись плоскими картинками, т.е. спрайтами. Реализацией класса, который бы отрисовывал на сцене спрайты мы сейчас и займёмся. Возможно, для двумерной или изометрической игры понадобится специализированный класс мира (интерфейс IWorld), но им мы займёмся несколько позже, если надо будет...

CNodeSprite

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

Сразу предусмотрим возможность анимации, чтобы не писать отдельный класс NodeSpriteAnimate, т.к. в общем-то отрисовка анимации делается простым смещением текстурных координат.
Так же сразу предусмотрим возможность спрайта оставаться всё время повёрнутым к камере (billbord) для реализации всяких эффектов типа огня, взрывов, облаков...

class CNodeSprite: public CNode {
public:
    CNodeSprite(IEngine* engine);
    ~CNodeSprite();

virtual bool LoadResource(); // загружаем ресурсы
virtual bool Draw(); // рисуем
virtual char Update(float tms);// изменяем состояние спрайта на время tms

// "перехватываем" установку параметров, характерных только для спрайта 
virtual void SetParam(const char* param_name,const char* param_value);

protected:

    UINT current_frame; //текущий анимационный кадр

    UINT Repeat; // число повторений анимации, 0 - бесконечно
    UINT Anim_Count_X; // количество кадров, умещающихся в текстуре по X
    UINT Anim_Count_Y; // количество кадров, умещающихся в текстуре по Y

    UINT Start_Anim; // номер кадра, с которого начинается анимация
    UINT Count; // число кадров в анимации
    
    bool Billbord; // если true - всегда повёрнут к камере
    
    float lifeTime; // "время жизни" (используется для анимации)
    float *dataBuffer; // буфер точек и текстурных координат

    IDrawObject *DrawSprite; // объект для отрисовки спрайта граф.менеджером

    IMaterial *DrawMaterial; // "материал" спрайта

};

Функция LoadResource предназначена для загрузки ресурсов, которые использует класс спрайта для отрисовки. Первым делом проверяется значение параметра "Texture" (которое устанавливается извне через функцию INode::SetParam() ). Если параметр не установлен - функция возвращает true, т.к. попытка загрузки ресурсов закончилась ошибкой.
Если имя текстуры уже задано, функция обращается к графическому менеджеру с просьбой создать для неё динамический объект для отрисовки ( IDevice::AddDrawObject() ). Динамический, т.к. текстурные координаты в нём будут постоянно меняться в разные моменты времени для разных объектов.
Далее создаётся массив данных, который заполняется значениями, предназначенными для отрисовки одного прямоугольника, на который натянута текстура. Впоследствии значения текстурных координат этого массива будут изменяться в функции Update().
Потом в полученном указателе на созданный менеджером графики обьект отрисовки IDrawObject устанавливаются указатель на этот массив, смещения в нем текстурных координат, итп.. Но это не всё.. надо подготовить текстуру :)
Пытаемся получить от менеджера графики IDevice материал с именем, совпадающим с именем используемой текстуры ( IDevice->GetMaterial() ). Если такой материал уже кто-то создал - запоминаем указатель на него в переменной DrawMaterial, если нет - создаём его ( DEVICER->AddMaterial( TextureName ) ) и устанавливаем в нём текстуру и параметры...
Дальше устанавливаем начальные значения переменных отвечающих за отрисовку спрайта и вызываем функцию Update(), чтобы она расчитала нам первоначальные значения в массиве данных Возвращем false, т.е. загрузка ресурсов прошла без ошибок.

Функция Draw устанавливает матрицу вида и отрисовывает спрайт. Если установлен признак Billbord, то координаты точек в буфере dataBuffer пересчитываются так, чтобы отрисовываемый прямоугольник был всегда перпендикулярен лучу обзора, исходящему из камеры.

Функция Update увеличивает переменную LifeTime на величину tms, и если результат больше параметра "Speed", то уменьшает величиную LifeTime на Speed (т.е. возвращаемся на начало анимации) и производит пересчёт текстурных координат с учётом заданных параметров анимации и номера текущего кадра.
Если мы отрисовали полный цикл анимации положенное число раз, то происходит удаление данного класса..

Базовые объекты

В предыдущем шаге мы научили класс мира загружать и сохранять элементы сцены в виде XML-файлов. Доработаем его немного - введём возможность описания базовых объектов.
Зачем нужны базовые обьекты? Сейчас поясню... Скажем, нам надо нарисовать сцену из 10 колонн. Без базовых объектов нам придётся у каждой из них описывать размеры, текстуру, характеристики материала.. А с базовым объектом мы один раз (у базового объекта) зададим характеристики общие для всех 10 колонн, а при формировании сцены просто напишем 10 раз ссылку на базовый объект и текущее положение колонны. При этом сами базовые обьекты на сцене не рисуются! Их вообще не существует в виде экземпляров классов. Они представляют собой всего лишь что-то вроде словаря, по которому получаем полное описание обьекта при его создании.

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

bool SetBaseObject(const char *ObjName, const char *InitString);
const char *GetBaseObjectParam(const char *ObjName, const char *ParamName);

Функция SetBaseObject задаёт классу IWorld имя базового объекта и его характеристики. Пока в качестве характеристики я использовал строку инициализации, но никто не мешает подвесить XML-парсер и туда.. Это позволит нам создавать группу базовых объектов вызовом одной функци createNode. Но это возможно в будущем, если возникнет такая потребность. Пока же - строка инициализации, которая выглядит так:

Параметр=Значение;Параметр=Значение;...Параметр=Значение;

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

<?xml version="1.0" ?>
<file Name="FileName" />
...
<BaseObject >
    <BaseName Type="BaseType" ParamName="ParamValue" ... >
    ...
</BaseObject >
<World>
    <NodeType ParamName="ParamValue" ... >
    ...
</World>

  • file - если парсер встречает этот узел xml-дерева, то пытается загрузить файл с именем FileName. Таким образом одним вызовом функции LoadWorld мы можем подгружать все необходимые файлы для сцены.

    Общий список базовых обьектов мира мы можем вынести в отдельный XML-файле, который будем подключать к разным сценам. Не обязательно подключать этот список "вручную", можно прописать в XML-файле описания сцены строчку <file Name="FileName" />, где FileName - имя файла, в котором описаны наши базовые объекты. При разборе XML-файла нашей карты парсер обнаружит эту строчку и подгрузит в мир данные из файла с именем FileName. А некоторые базовые объекты будут общими для всех наших миров (например - логотипы SDL, OpenGL и AmberSkyNetVR ).

  • BaseObject - секция описания базовых объектов.
    BaseName - имя базового объекта, при помощи которого на него будут ссылаться в файле сцены
    Type - название класса базового объекта. Экземпляр класса с таким именем попытается создать наш менеджер плагинов.
    Имена остальных параметров различны для разных классов...

    Например вот так выглядит описание логотипа AmberSkyNet в базовой секции:

    <LogoAmberSkyNet Type="NodeSprite" Texture="AmberSkyNet.png" 
    	  Size="5 5 5" Billbord="1" Repeat="0" />

  • World - секция описания непосредственно самого мира. Возможно, впоследствии эта секция будет разделена на сцены, но пока у нас мир представляет собой одну сцену...
    NodeType - либо название базового объекта, либо название экземпляра базового класса, который будет создан менеджером плагинов. При разборе XML-файла парсер сначала ищет данное имя в списке своих базовых обьектов. Если в списке базовых объектов такое название отсутствует, то парсер воспримет NodeTypr как имя класса узла сцены и вызовет у менеджера плагинов функцию создания класса с таким именем.
    Например, если мы захотим в сцене увидеть логотип AmberSkyNet в точке 100,100,100 мы можем написать в секции описания мира строчку:
       <LogoAmberSkyNet Pos="10 10 10" />

    Интерфейс менеджер звуков ISound и его реализация CSoundSDL_mixer

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

    
    class ISound {
    public:
        virtual ~ISound(){}
    
        virtual bool PlayFX(const char *FxName)=0;
        virtual bool PlayMusic(const char *FxName)=0;
        virtual bool StopMusic()=0;
        
    
    };
    

    Функция PlayFX запускает воспроизведение звукового эффекта.

    Функция PlayMusic начинает воспроизведение фоновой музыки.

    Функция StopMisuc останавливает воспроизведение фоновой музыки.

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

    Изменения в исходниках

    include/: Убран интерфейс менеджера плагинов, который теперь скрыт в недрах IEngine. Создание объектов теперь производится через вызов IEngine->CreateObject.
    Добавлен интерфейс менеджера звуков ISound, изменён интерфейс IWorld c учётом реализации базовых объектов.
    Добавлен класс CBoundBox.h (для вычисления границ нод и проверки на столковения узла сцены с отрезком).

    src/asnCSoundSDL_mixer/: Новый плагин - менеджер звука.

    src/asnDeviceGl/: В плагин добавлена поддержка текстурных шрифтов, а так же отслеживание событий выхода/входа курсора мышки в пределы окна, сворачивание окна, итп... А еще функция MakeScreenshot заработала - теперь можно делать скриншоты формата bmp.
    Добавлен код из библитеки Борескова (3dsteps.narod.ru) по загрузке DDS.

    src/asnWorld/: Доработаны процедуры загрузки/сохранения сцены с учётом базовых объектов.
    Добавлена функция getNodesByPos(Start, End), выдающая список нод, через BoundBox'ы которых прошёл отрезок. Это нам пригодится для выбора кликом мышки предмета, летающего по сцене.

    src/asnMain/: Небольшая аркадная демка с падающими предметами, на которые надо быстренько кликнуть мышкой пока они не упали. Модели по-прежнему позаимствованными из проекта S.C.O.U.R.G.E.

    Исходники этого шага выложены в SVN. Скачать их можно набрав команду:

    svn co https://svn.sourceforge.net/svnroot/ambernet/tags/AmberSkyNet-0.10 ambernet_0.10
    
  • Powered by: SourceForge.net Logo