Уроки: 3ds Max

Кушаем геймдевелоп

КУШАЕМ ГЕЙМДЕВЕЛОП

Здравствуйте! Всем тем, кто меня уже знает и тем, кто еще нет.

Сейчас я учусь на 2 курсе Иркутского Государственного Технического Института. Все еще).

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

И вот, теперь я пришел с уроком больше по геймдеву, но все так же затрагивающую 3dsMax.

И так – поехали!

Часть 0. Что и почему.

Ретроспектива.

Даа.... Много времени утекло с тех пор как я учился в лицее, когда только изучал GLScene, да и делфи.

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

Второй мой приход на рендер со статьей был посвящен 3ds файлам и возможности их чтению из OpenGl приложения, очень сложная тема, которая меня сильно помучила в свое время. Напрашивается вопрос - почему же я не продолжил работу с ОГЛ, все просто, опять же слабое знание с++, малое количество уроков и то, что ОГЛ довольно неудобный, если сравнить с DX, просто первый пишут как открытый проект, само собой он не может так часто обновляться и быть удобнее, я не говорю что на нем нельзя делать игры, само собой можно, но на мой опыт и взгляд я думаю начать лучше с DX.

Кстати, наверняка есть такие люди, у кого возник вопрос – что же этот урок делает на рендере.ру, ведь сайт «как бы не об этом»? Многое изменилось в этом сайте за последние годы, вот и к девелопу рендер.ру повернулся таки лицом и теперь мы видим тут и некоторые новости для игроманов, да и уроки были в таком направлении, может и не было ТАКОГО урока, но все случается впервые, ведь так?) Впрочем, про 3dsMax  в уроке будет так же как всегда написано.

Почему DirectX?

Вообще переход на DX был долго не возможен изза того что скачать с сайта Microsoft я 460 метров не мог (ну не было у меня ни мобилы с gprs  ни денег, те что выиграл проапгрейдили комп, это святое;)) В начале этого лета я купил нормальную мобилу и буквально сразу закачал SDK DirectX9, сделать это можно с сайта производителя. Обновления они выкладывают стабильно, мне в sdk достался 10dx, но висты у меня нет так что я просто поглядел скрины:) Вообще интересно то что самый свежий dx скачать можно даже на пиратской винде, но только самый новый, остальные никак... Ну так вот, отдалились от темы, за лето я вник в суть, купил несколько книг, и собрав все знания в голове, в силу своих возможностей стал кодить, начал с модернизаций примеров с sdk, что довольно полезно для разгона.

Что еще, ну я все собирал в MVC++8.0, но примеры с инета качал в основном под 7.1 и даже 6.0, так что тут проблем не должно быть, мой выбор обусловлен удобствами :)

Если хотите просто посмотреть примеры – убедитесь, что directX у вас стоит новый. Желательно этого лета.

Как будем изучать.

Исходники писать в самой статье не стану, только код местами, чтоб не засорять урок. Все исходники можно скачать в части «полезности», в конце урока.

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

Как тему для первых моделей я отчего-то выбрал космическую тему, ее было проще смоделить)…

Что будем делать.

  • Начнем писать движок, в нем будем грузить модели из файлов, читая файл карты.
  • Будем вводить возможность передвигаться с видом от первого лица.
  • Введем основы физики, но только для персонажа (камеры) и только на столкновения параллелепипедов и сфер.

  Создание чего в этом уроке НЕ будет:

  • Создание  шейдеров.
  • Физики rigid body`s и физики с произвольными поверхностями.
  • Скриптов.
  • И.И. и прочих продвинутых вещей, которые я планирую рассматривать в следующих уроках.

Шаги по sdk пакету.

Шаг первый.

Внимание! - в sdk примеры c проектами только для msvc++ 7.1 и 8.0. Скачайте sdk, без него никак. Выкладывать его не буду, урок ограничен 5 метрами, научитесь подходить к серьезному делу серьезно. Если хотите серьезно поработать, то приготовьтесь к тому, что не все это так просто и придется много изучать, прежде чем начнет получаться. Тут нет места идеям типа – «а сделаю как я за недельку (месяц, даже два), классную игруху». Терпение и целеустремленность – залог успеха. Я серьезно, в конце приведу несколько писем из сотни пришедших мне за 2 года, такие мысли частое явление и я даже понимаю их, сам начинал с этого.

Шаг второй.

Устанавливаем пути к библиотекам и заголовочным файлам (\DirectX SDK\Include; \DirectX SDK\Lib) или копируем из них файлы в папки самого компилятора (lib и include).

Шаг третий.

Для начала зайдите в "\Samples\C++\Direct3D\Bin\x86" и позапускайте примеры (на них всех естественно есть исходники, хотя они громоздкие, но изучать их интересно), примеры довольно хорошие, и даже само чтение их приносит много пользы.

Так же примеры можно рассматривать через "\Samples\SampleBrowser\SampleBrowser.exe". Потом можете зайти в папку "\Samples\C++\Direct3D\Tutorials". Именно с них я и начну с них введение.

Часть 1. Изучаем sdk.

О чем?

Рассмотрим то какими уроками наполнен сам сдк и попробуем создать чего-нибудь свое.

Что-нибудь такое вы сможете сделать после урока этой части:

Разглядываем примеры.

Построение примеров из «Tutorials» sdk пакета  напоминает снежный ком, где каждый следующий файл это модификация предыдущего (всего их 6).

Первый пример показывает, как в созданное окно инициализировать DirectX. Думаю его объяснять не надо, тут все предельно понятно.

Второй пример показывает, как создавать vertex buffer и заносить в него точки для построения плоскостей. Вообще об этом можно прочитать в интернете много статей (очень много:) несколько сайтов в конце урока), но немного и я скажу.

В программах по 3d плоскость образуют только 3 точки и модель загружаемая из макса, если она polymesh, все равно преобразуется и квадраты разбиваются на треугольники, поэтому лучше перед экспортом (об этом подробнее будет далее по уроку) сетку преобразовать в mesh. Но это не обязательно если модель не анимируется, в обратном случае будет сбита нумерация точек сетки и анимация будет потеряна.

Третий пример про матрицы, которые преобразуют объекты из трехмерного мира на наш двумерный экран. Опять же статей в нете много и матрицы проходят все в институте, однако тут глубокие познания почти не нужны. В директе существуют три глобальных матрицы D3DTS_WORLD - отвечает за объекты,D3DTS_VIEW - для камер(например установки точки просмотра) и D3DTS_PROJECTION - для перспективных искажений. Так же для каждого объекта можно создать матрицу проекций, но об этом не сейчас. Основы видно исходнике, но немного прокомментирую.

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

    D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/3, 1.0f, 1.0f, 100.0f );   

Функция для поворота координат по определенной оси. (timeGetTime() – возвращает текущее время выполнения программы, меняется с течением времени, зависит от времени в винде)

    D3DXMatrixRotationY( &matWorld, timeGetTime()/1000.0f );

Векторы с параметрами камеры:  

    D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );//точка где находится камера

    D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );//точка куда смотрит камера

    D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );//вектор для поворота по оси просмотра камеры

Пример четвертый показывает как ввести простое освещение. Впринципе для включения самого простого освещения надо только вписать следующюю строку, например в InitD3D():

g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff ); //Объекты осветит со всех сторон.

Если хотите создать точечный свет (вроде omni в 3dmax) введите следующее:

   //в начале кода, где объявляем переменные

   D3DLIGHT9 light;

   //в  InitD3D(), выделяем память под свет

   ZeroMemory(&light, sizeof(D3DLIGHT9));

   //устанавливаем тип свет, если выбирете DIRECTLIGHT то не забудьте указать точку куда он направлен,  

   //например - light.Direction = D3DXVECTOR3(0.0f,0.0f,0.0f);

   light.Type = D3DLIGHT_POINT;

   //выбираем точку расположения света

   light.Position.x = 15.5f;

   light.Position.y = 15.5f;

   light.Position.z = 0.5f;

  //устанавливаем яркость

   light.Diffuse.g = light.Diffuse.b = light.Diffuse.a= light.Diffuse.r = 0.1f;

   //можно ограничить дальность света

   //light.Range = 20.0f;

   //назначаем на индекс, в этом случае 0 (для всех источников света он должен быть разный!)

   g_pd3dDevice->SetLight(0,&light);

   //включаем свет

   g_pd3dDevice->LightEnable(0,true);

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

Именно шестой пример мы будем модифицировать в дальнейшем.

Среди переменных сразу отметим следующие:

LPD3DXMESH                          g_pMesh                = NULL;      //  переменная для  хранения сетки

D3DMATERIAL9*                   g_pMeshMaterials  = NULL;     // переменная для  хранения материалов нашей сетки

LPDIRECT3DTEXTURE9*     g_pMeshTextures   = NULL;     // текстуры для сетки (аналогично 3dsmax`у)

DWORD                                 g_dwNumMaterials = 0L;          // число созданных материалов

Уже знакомая нам InitD3D() инициализирует все параметры dx и создает ambient-освещение.

И вот перед нами функция InitGeometry() которая и создает объект. Посмотрим на сам код и мои комментарии:

HRESULT InitGeometry()

{  //создаем текстурный буфер, который нужен для создания материалов для объекта (он их перехватит)

    LPD3DXBUFFER pD3DXMtrlBuffer;

    //грузим сетку из файла  Tiger.x в переменную g_pMesh. подробнее о флагах функции можете узнать в хелпе

    if( FAILED( D3DXLoadMeshFromX( L"Tiger.x", D3DXMESH_SYSTEMMEM,

                                  g_pd3dDevice, NULL,

                                  &pD3DXMtrlBuffer, NULL, &g_dwNumMaterials,

                                   &g_pMesh ) ) )

    {  // так как компилятор создает ехе в папке debug программа проверяет файл в папке выше по иерархии

        if( FAILED( D3DXLoadMeshFromX( L"..\\Tiger.x", D3DXMESH_SYSTEMMEM,

                                   g_pd3dDevice, NULL,

                                   &pD3DXMtrlBuffer, NULL, &g_dwNumMaterials,

                                   &g_pMesh ) ) )

        {

            MessageBox(NULL, L"Не найден tiger.x", L"Meshes.exe", MB_OK);

            return E_FAIL;

        }  }

 

    // Теперь мы получаем информацию о материалах и текстурах из pD3DXMtrlBuffer в который

   // они отловились когда мы грузили сетку

    D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();

    //создаем массив материалрв и проверяем не произошло ли переполнения памяти

    g_pMeshMaterials = new D3DMATERIAL9[g_dwNumMaterials];

    if( g_pMeshMaterials == NULL )

        return E_OUTOFMEMORY;

    //создаем массив текстур и проверяем не произошло ли переполнения памяти

    g_pMeshTextures  = new LPDIRECT3DTEXTURE9[g_dwNumMaterials];

    if( g_pMeshTextures == NULL )

        return E_OUTOFMEMORY;

 

    //цикл загружает в память сам текстуры по перехваченным путям к ним

    for( DWORD i=0; i<g_dwNumMaterials; i++ )

    {

        // Копируем материал

        g_pMeshMaterials[i] = d3dxMaterials[i].MatD3D;

 

        // Установим ambient цвет для материала т.к D3DX этого не делал

        g_pMeshMaterials[i].Ambient = g_pMeshMaterials[i].Diffuse;

 

        g_pMeshTextures[i] = NULL;

        if( d3dxMaterials[i].pTextureFilename != NULL &&

           lstrlenA(d3dxMaterials[i].pTextureFilename) > 0 )

        {

            //создаем текстуру по параметрам из буфера, опять же в хелпе можно глянуть возможные флаги

            if( FAILED( D3DXCreateTextureFromFileA( g_pd3dDevice,

                                               d3dxMaterials[i].pTextureFilename,

                                               &g_pMeshTextures[i] ) ) )

            { //так как компилятор создает ехе в папке debug программа проверяет файл в папке выше по иерархии

                const CHAR* strPrefix = "..\\";

                CHAR strTexture[MAX_PATH];

                StringCchCopyA( strTexture, MAX_PATH, strPrefix );

                StringCchCatA( strTexture, MAX_PATH, d3dxMaterials[i].pTextureFilename );

              

                if( FAILED(D3DXCreateTextureFromFileA( g_pd3dDevice,

                                                   strTexture,

                                                   &g_pMeshTextures[i] ) ) )

               {

                   MessageBox(NULL, L"не найдена текстура", L"Meshes.exe", MB_OK);

               }   }   }  }

    //освобождаем D3DX буфер

   pD3DXMtrlBuffer->Release();

    return S_OK;}

Надеюсь, с этим мы разобрались, осталось отобразить модель в функции render(), которая вызывается в цикле сообщений нашего окна.

        // В файле сетки могло находится несколько объектов и текстур к ним, так что в цикле

        //мы на них наносим текстуры и все отображаем в цикле

        for( DWORD i=0; i<g_dwNumMaterials; i++ )

        {   g_pd3dDevice->SetMaterial( &g_pMeshMaterials[i] );

            g_pd3dDevice->SetTexture( 0, g_pMeshTextures[i] );       

            //Отрисовываем Subset (один из объектов сцены из *.х)

            g_pMesh->DrawSubset( i ); }

 

Итак, мы изучили этот пример. Для интереса можете попробовать «поиграть»  с примером. Например, можно создать в максе файл, в котором будет целая сцена, например космическая станция, вокруг сфера вывернутая наизнанку с текстурой космоса. Теперь вы можете загрузить ее в пример и посмотреть на довольно интересную картинку (хотя все зависит от того, что туда занесли :)). Для того чтобы не было проблем с размерами, пока вы не знаете про масштабирование, занесите в сцену в 3dsMax файл «tiger.x» из примера (про то, как и чем, это сделать посмотри в конце урока).

Разделение.

Когда я рассматривал этот пример, я сразу решил, что когда и directx функции и функции окон находятся в одном файле, это не удобно, просто если их разделить, то лишние функции не будут мешать, а файл с функциями окон будет надолго отложен в сторону. Я перенес все функции графики в graphic_dx.cpp, в graphic_dx.h объявил используемые функции, а в файле meshes.cpp оставил все функции для окон. Хочу отметить, что разные программисты делают сейчас по-разному разделение на файлы *.h и *.cpp, например многие вообще оставляют  только один *.cpp, что вполне можно реализовать. Я делал оба варианта. Так же надо не забывать добавлять в новый файл следующие строки:

Например, у нас “файл geometry.h”

 

#ifndef  geometry _h

#define  geometry _h

//далее ваш код

#endif

 

Это избавит вас от ошибок в файлах *.obj проекта.

Часть 2. Зарождение двига.

О чем?

Теперь мы создадим класс объекта сцены и научимся его отображать, а так же крутить. Сделаем что-то такое, тут планета и вокруг нее, по орбите, крутятся корабли:

Объектное ориентирование.

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

Для отображения множества объектов в сцене я решил создать класс объектов. В программе будет создан массив указателей на объекты созданные в памяти. Сам файл примера  берите >тут<. Теперь рассмотрим класс объекта:

Переменные:

LPDIRECT3DDEVICE9              *pd3dDevice;   // Our rendering device

D3DXMATRIXA16                          GOMatrix;   // матрица проекций обекта, изначально клон мировой матрицы

D3DXMATRIX GOMatrixDef;

LPD3DXMESH                                       pMesh;   // собственно сетка объекта

D3DMATERIAL9*                  pMeshMaterials;   // материалы объекта

LPDIRECT3DTEXTURE9*     pMeshTextures;   // текстуры объекта

DWORD                                  dwNumMaterials;  // число материалов объекта

Функции:

InitGeometry(LPCSTR filename) не претерпела изменений, она только стала получать параметр – имя файла. У меня он сразу с путем. То как подставить путь к имени файла видно из примера с загрузкой текстуры.

GORender() вобрала в себя код  отображения объекта и его частей с нанесением текстур уже знакомый нам.

rotateYL(float timeFactor) просто поворачивает матрицу объекта, для привнесения некоторой динамики в сцену. Функцией D3DXMatrixRotationY( &GOMatrix, timeGetTime()/timeFactor ); мы устанавливаем угол на timeGetTime()/timeFactor, при этом значение timeGetTime() обеспечивает увеличение угла с течением времени.

Обратим внимание на конструктор:

CGObject_static(LPDIRECT3DDEVICE9 *Device,D3DXMATRIXA16* matrix)

//нам при создании объекта нужно передать в него мировую матрицу и 3d девайс

{              pd3dDevice = Device;                       // получаем 3д девайс

    GOMatrix = *matrix;                                  // получаем мировую матрицу проекции

                D3DXMatrixIdentity(&GOMatrix);  // обрабатываем созданную матрицу

};

Создаем объекты.

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

 

//прописав новую переменную

CGObject_static object(d3dDevice,&matWorld);

//Инициализировав в объекте геометрию (в функции InitGeometry())

object.InitGeometry("models\\модель.x");

//отрисовав объект(в Render())

object.GORender();

//возможно включив ему вращение

object.rotateYL(8500.0f);

Не забывайте убирать из памяти объекты и переменные созданные в процессе работы программы через указатели, например через деструктор (в Cleanup()) иначе вы можете потом удивляться странным результатам работы программы или полюбоваться на экстренную перезагрузку компа :)

object.~CGObject_static();

Клавиатура и мышь.

В принципе можно было использовать directinput, однако, чтобы не заморачиваться, я использовал более простой подход взятый из примера с opensource.net

Кажется, тут все понятно и не вижу смысла подробно объяснять, учитывая, что впереди еще много чего я объясню, а если бы я стал останавливаться здесь, то укоротилась бы другая тема, а она вам надо?)

Иллюзия освещения.

В моем примере (пример 5) создается ощущение, что на планете тень, однако это не так, просто я установил у источника света дальность освещения, а ambient-свет сделал темно синем и слабым. Вообщем эффектно, но просто)

Итог.

Сцена, конечно, получилась не великая, но когда я сам ее сделал, то я был в шоке! Ведь то, что я делал раннее было не так прикольно, а ведь все писалось мной самим, а не чьими-то библиотеками, когда делаешь что то своими руками это всегда приятно). Еще интересен тот момент, что объекты можно позиционировать в 3dsMax и использовать его как редактор карт, как и делают многие, что будет описано далее, в соответствующей части.

Часть 3. Уровни.

О чем?

Как создать массив из классов объектов и как создать на основе этого класса объекты разных видов и массивы к ним.

Файл уровня.

 Довольно интересный пункт, где есть много вариантов решения задачи, но мне понравились два – чтение XML и чтение файла с подобной структурой. Первый вариант я не реализовал вначале из-за того, что не умел работать с этим форматом, а искать, чьи либо библиотеки противоречило моему плану работать самому полностью. Вообще я создал довольно слабый (что уж скрывать), но стабильно работающий класс для загрузки уровня. Суть в том, что чтение файла идет построчно и циклически. Когда находим тег «<object>» начинаем считывать теги параметров объекта до тега «/object». Список тегов можно посмотреть в файле объекта. Собрав в структуру (буфер) мы создаем объект, с учетом того какой он (из тегов мы это считали), в него передаем все нужные параметры и добавляем единицу в счетчик объектов.

Вот и все тут… читаем код, там все довольно понятно…

Часть 4. Физика.

О чем?

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

Кубический мир.

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

Самое простое это просчитать столкновение точки и куба, при таком просчете мы просто должны проверить, не в кубе ли точка (чистая математика:)) Но в играх конечно персонажи не точки, а что? Частое заблуждение в том что предполагается что ваш персонаж взаимодействует всей своей моделью с окружающим миром, но это, по сути, нереальная нагрузка на процессор, проверить столкновение каждой точки сетки объекта, а ведь просчет идет тогда несколько раз за секунду(каждый кадр). Самый простой способ это заключить персонажа в сферу, а точнее в две сферы (тогда при приседание одна сфера убирается, что очень удобно). Сфера, конечно, это не сетка из точек, а радиус и точка центра. Остается только математически просчитать столкновение сферы и бокса например в момент нажатия клавиши вперед, тогда в функцию проверки предается массив объектов для физики(в примере у таких объектов есть две доп. Координаты описывающих коробку вокруг объекта полученную функцией директа - D3DXComputeBoundingBox, которая вызывается в создании такого объекта в классе объектов, в функции «getMeshBounding»). Вообщем далее классическая проверка на пересечение сферы и куба, перенесенная на формат программы, хотя ее пришлось вывести самому (не смог вспомнить) посидев немного с ручкой и листочком… Похожие принципы у цилиндра и сферы, но они проще.

Итак, переменные, которые я использовал далее:

Newpos – векторная переменная, содержит координату в которую переместится тело если там ничего нет.

BoundingBoxMin – векторная переменная содержащая ближнюю нижнюю координату бокса (коробки) объекта.

BoundingBoxMax – векторная переменная содержащая дальнюю верхнюю координату бокса объекта.

Radius – радиус сферы

Down – расстояние между сферами.

Chk – булева переменная, указывает произошло ли пересечение.

 

//sphere 01

if((( abs(newpos.x-BoundingBoxMin.x)< radius || abs(newpos.x-BoundingBoxMax.x)< radius)

   && newpos.y>BoundingBoxMin.y-radius && newpos.y<BoundingBoxMax.y+radius

   && newpos.z>BoundingBoxMin.z-radius && newpos.z<BoundingBoxMax.z+radius)       ||             

 

  (( abs(newpos.y-BoundingBoxMin.y)< radius || abs(newpos.y-BoundingBoxMax.y)< radius)

&& newpos.x>BoundingBoxMin.x-radius && newpos.x<BoundingBoxMax.x+radius

&& newpos.z>BoundingBoxMin.z-radius && newpos.z<BoundingBoxMax.z+radius)          ||             

 

 (( abs(newpos.z-BoundingBoxMin.z)< radius || abs(newpos.z-BoundingBoxMax.z)< radius)

&& newpos.x>BoundingBoxMin.x-radius && newpos.x<BoundingBoxMax.x+radius

&& newpos.y>BoundingBoxMin.y-radius && newpos.y<BoundingBoxMax.y+radius))       chk= true;

 

//sphere 02

if(((abs(newpos.x-BoundingBoxMin.x)< radius || abs(newpos.x-BoundingBoxMax.x)< radius)

&& newpos.y-down>BoundingBoxMin.y-radius && newpos.y-down<BoundingBoxMax.y+radius

&& newpos.z>BoundingBoxMin.z-radius && newpos.z<BoundingBoxMax.z+radius)          ||             

 

 (( abs(newpos.y-down-BoundingBoxMin.y)< radius || abs(newpos.y-down-BoundingBoxMax.y)< radius)

&& newpos.x>BoundingBoxMin.x-radius && newpos.x<BoundingBoxMax.x+radius

&& newpos.z>BoundingBoxMin.z-radius && newpos.z<BoundingBoxMax.z+radius)          ||             

 

((abs(newpos.z-BoundingBoxMin.z)< radius || abs(newpos.z-BoundingBoxMax.z)< radius)

&& newpos.x>BoundingBoxMin.x-radius && newpos.x<BoundingBoxMax.x+radius

&& newpos.y-down>BoundingBoxMin.y-radius && newpos.y-down<BoundingBoxMax.y+radius)) chk= true;                                                

Используя эту функцию можно реализовать и физику для камеры, создав функцию, которая на вызове будет пытаться увеличить текущую скорость физического движения объекта (скорость это вектор, чтобы можно было легко менять направление гравитации). Функцию я добавил в класс физики и вызвал ее в render (>пример тут<). И вот появилась проблема, если отрисовка кадра занимает больше времени, знаяит число вызовов функции физики сократится, так же это влияет на вызов проверки пересечений по нажатию клавиш. Причем если в первом случае физика работает рывками, то во втором мы просто движемся медленнее… На мощном компе это не заметно, но на среднем уже все хорошо видно. Вот для этого и нужен еще процессор, он и будет отдельно работать на физику. Но пока эра таких видеокарт не пришла, нужно искать альтернативу. Можно работать с потоками, чтобы функция физики была не в рендере и не в цикле сообщений формы, а вне этого. Но есть и еще вариант, более простой, можно отслеживать то, за сколько было произведено отображение кадра и  в зависимости от этого менять значение коэффициента скорости, конечно проблему прыжков и падений это не решит, рывки так не уберешь, но вот на замедленности передвижения камеры по уровню этим можно повлиять.

Новое в классе модели.

D3DXVECTOR3 vecBoundingBoxRMin;

D3DXVECTOR3 vecBoundingBoxLMax;

D3DXVECTOR3 vecBoundingBoxMin;

D3DXVECTOR3  vecBoundingBoxMax;

 

вид уровня из 3dsMax:

Часть 5. Пути развития.

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

 

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

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

Часть 6. Про 3dsMax.

Как же без него, родимого).

Пример из папки «дополнительно» и его вид из 3dsmax:

Модели.

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

Текстуры.

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

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

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

Моделирование.

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

При моделировании следует учесть, что при экспортировании одного и того же объекта в poly или в mesh, нумерация вершин будет разной. (Видеокарта работает всегда с треугольниками, так что при экспорте в Х все квадраты будут разбиты на треугольники) При работе со статичными объектами это значения не имеет, но когда дело дойдет до анимации лучше не конвертировать одно в другое.

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

Часть 7. Полезности.

Код:

1.Как вывести текст на экран? (например информация о загруженных моделях)

В глобальных -

     char buffer[_CVTBUFSIZE];

В инициализации -

// Create a font for statistics and help output

hr = D3DXCreateFont(g_pd3dDevice, nHeight, 0, FW_BOLD, 0, FALSE,

                 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,

                 DEFAULT_PITCH | FF_DONTCARE, TEXT("Arial"),

                 &g_pd3dxFont );

if( FAILED( hr ) )

MessageBox(NULL,"Call to D3DXCreateFont failed!", "ERROR",MB_OK|MB_ICONEXCLAMATION);

В рендере -

RECT destRect1;

    _gcvt( “Что выводить”, 8, buffer ); // C4996

   SetRect( &destRect1, 10, 10, 0, 0);   //куда

  g_pd3dxFont->DrawText( NULL,"Camera position.X", -1, &destRect1, DT_NOCLIP,D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f) );

2.Как вывести прозрачную текстуру?

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

(*pd3dDevice)->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );

(*pd3dDevice)->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );

(*pd3dDevice)->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

(*pd3dDevice)->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );

for( DWORD i=0; i<dwNumMaterials; i++ )

  {

    // Set the material and texture for this subset

   (*pd3dDevice)->SetMaterial( &pMeshMaterials[i] );

   (*pd3dDevice)->SetTexture( 0, pMeshTextures[i] );

    // draw subset

    pMesh->DrawSubset( i );

   }//->for

(такая текстура есть в примере с космической станцией)

 

Ссылки:

Для конвертации моделей из форматов 3dsMax  в Х я советую использовать программу -  Deep Exploration фирмы Right Hemisphere , она так же очень удобна для смены параметров модели, текстур, и вообще множества других настроек модели. Еще ее удобно использовать для быстрого просмотра моделей.

http://www.righthemisphere.com/dexp.htm

Вот списокнескольких хороших на мой взгляд сайтов, посвященных разработке игр:

www.gamedev.com

www.gamedev.ru

www.csportal.ru

www.codesampler.com

и много других, для поиска которых идите на гугл)…

Вот все примеры и прочие файлы к уроку.

chast01.rar

chast02.rar

chast04.rar

other.rar

Часть 8. Забавности.

За два года мне приходило огромное количество писем, я как мог старался на них отвечать, но иногда меня просто ставило в тупик их содержание. Это же относится и к вопросам по аське. Вот несколько примеров:

По аське недавно спрашивали: “Где искать программистов? Какими способами их найти?”

(все знают ориентировку на  программистов?))

Очень часто приходило письмо с текстом вроде этого:

Здравствуйте!

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

(Таких писем я насчитал 24! А ведь сколько удалял я их…)

Иногда даже просили: «

45824 Автор:
Актуальность: 356
Качество: 339
Суммарный балл: 695
Выбор Публики
Голосов: 106 оценки

Отзывы посетителей:

2 | След.
аватар
 
Whiskas 1 0
Спасибо за урок, прочитав эту статью нетрудно узнать с какими проблемами можно столкнуться в разработке игр.
аватар
 
crol 168 0
AlexModeste:
честно говоря непонятно как ты так читаешь :
"Шаг первый.
Внимание! - в sdk примеры c проектами только для msvc++ 7.1 и 8.0."

Расшифрую для тех кто не умеет пользоваться google - Microsoft Visual studio for C++ , среда разработки приложений на языке с++.
аватар
 
AlexModeste 1 0
честно говоря непонятно с самого начала, на чем все это делается..... скачал сдк, дык чем все это оробатывается? каким образом я посмотрю уроки в папке директа.....
аватар
 
crol 168 0
Адрес сайта автора, где можно узнать дальнейшее развитие создания данного игрового движка и о том в каком проекте он будет использован.
Так же там есть материалы и ссылки по данной тематике.
darkzirigon.at.ua/forum/
аватар
 
Haze 1 0
очень хороший урок,побольше б таких 5/5
аватар
 
crol 168 0
число голосов-25!... мне кажется или эта тема никому не интересна? самое странное это то что у того урока что лидирует-70 голосов, для меня это загадка. Вроде просмотров не мало, но уже месяц никто не ставил оценок... Выходит это совсем не популярно. Удивлен.
аватар
 
ZbVld 1 0
Panda DX Exporter для 3D Max 2008 32 и 64 бит http://depositfiles.com/files/2634782
аватар
 
Ed 2 0
На всякий случай:

Исходный код книги + экспортер Panda : www.wordware.com/files/dx9graphics
аватар
 
Ed 2 0
Пересечение плоскости с шаром:

Из книги Алена Торна:

FLOAT D3DXPlaneDotCoord
{
CONST D3DXPLANE *pP,
CONST D3DXVECTOR3 *pV
};

В примере:

D3DXVECTOR3 Point(0.0f, 0.0f, 0.0f);

FLOAT result = D3DXPlaneDotCoord(&Plane, &Point);


if(result == 0) // Принадлежит плоскости
if(result > 0) // Перед плоскостью
if(result < 0) // За плоскостью

Осталось только добавить в код проверки радиус шара.
аватар
 
Ockonal 1 0
Крутой урок!!! Сколько всего полезного!
Мне он очень помог!!!
аватар
 
Ed 2 0
LPD3DXMESH g_pMesh = NULL; // переменная для хранения сетки

D3DMATERIAL9* g_pMeshMaterials = NULL; // переменная для хранения материалов нашей сетки

LPDIRECT3DTEXTURE9* g_pMeshTextures = NULL; // текстуры для сетки (аналогично 3dsmax`у)

А почему не D3DXMESHCONTAINER ?

typedef struct D3DXMESHCONTAINER {
LPSTR Name;
D3DXMESHDATA MeshData;
LPD3DXMATERIAL pMaterials;
LPD3DXEFFECTINSTANCE pEffects;
DWORD NumMaterials;
DWORD * pAdjacency;
LPD3DXSKININFO pSkinInfo;
D3DXMESHCONTAINER * pNextMeshContainer;
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;
аватар
 
crol 168 0
DAG: формул? наверное это ты про С++ код:) STINGER: спасибо за хвалу), а комменты действительно некоторые писал для смеха, некоторые тянутся еще с момента когда я сам мало что знал:) а про тот коммент что ты привел я даже написал в самом уроке...
СПАСИБО всем,всем,всем,кто меня похвалил! Я старался:)
аватар
 
crol 168 0
Дорогие читатели моего урока, недавно я столкнулся с одной математической проблемой и пока не нашел решения, а тут еще учеба придавила... ну так вот: мне нужно проверить пересекает ли шар поверхность (шар-точка и радиус, поверхность-плоскость-три точки задают и ограничивают). надеюсь кто либо из моих читателей найдет ответ и подскажет мне решение:) заранее благодарен.
ЗЫ: расстояние между точками естественно может превышать радиус, так что одной проверкой нахождения точек в шаре тут не обойдешься :\
аватар
 
DAG 2 0
я чуть глаза не сломал от избытка формул))))
аватар
 
KJS 7 0
Урок супер )))))))) афтор жжот :D комменты в исхожниках прекрепленных снизу меня налили интузиазмом ))) есть что впитать в мозг и в самих пояснениях, в целом урок на 5, мне подкинуло пару идей даже кое-что)))
но это немагу не процетировать :
#endif// это от самого начала тянется, не тронь, убъет!) повеселило :D
аватар
 
Иван Ветошкин 220 0
Соведущий Dominance War
ниасилил :)
2 | След.
Зарегистрируйтесь, чтобы добавить комментарий.
Эту страницу просмотрели: * уникальных посетителей