Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.15 Объекты в игре
Программируем 3D-шутер от первого лица (FPS) (Win32, C++, DirectX9)

1.15 Объекты в игре


В этой Главе:

  • Изучим понятие "игровой объект" а также то, как они применяются в играх.
  • Исследуем принципы движения объектов с использованием векторов и мировой матрицы (world matrix).
  • Внедрим в движок 3 базовых типа объектов, встречающиеся в каждой FPS-игре.


В предыдущей части мы разработали систему поддержки 3D-мешей, которая позволяет загружать 3D-меши и рендерить их на экране. В то же время, нам так и не удалось сдвинуь их с места (т.к. в предыдущем примере двигалась сама камера). Представь себе игру, в которой все меши статичны и не могут двигаться по игровой сцене либо быть анимированными. Любой игрок хочет видеть в FPS-игре 3D-меши игроков могут бегать по игровой карте, меши оружия и аптечек, которые появляются, непрерывно вращаются на одном месте до тех пор, пока их не подберёт игрок а затем вновь респаунятся (от англ. "respawn(external link)" - дословно "перерождение") на том же месте через определённый промежуток времени. В этой Главе мы разработаем необходимую инфраструктуру для поддержки всех этих фич, и вдохнём жизнь в наши 3D-меши.

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

В большинстве игр по игровой сцене разбросаны различные штуки, с которыми игрок может взаимодействовать. На деле персонаж игрока также представлен неким 3D-объектом, который может перемещаться по 3D-окружению. Например, объект игрока может быть представлен в виде маленького космического корабля, летающего по экрану и уничтожающего другие корабли. Также в игре моуть быть т.н. "аптечки" (power-ups) для восстановления жизней или энергии, которые летают вокруг объекта игрока, а тот их собирает. Кроме того в игре могут быть препятствия, которые также двигаются и игрок должен избегать столкновения с ними. Все вышеперечисленные объекты обладают рядом общих свойств. Во-первых все они (или почти все) обязательно представлены на экране в виде изображения или 3D-меша. Во вторых, у каждого из них есть набор физических свойств, которые позволяют им двигаться по экрану и сталкиваться друг с другом. Как видим, все эти объекты очень схожи друг с другом. Именно их обычно и называют игровыми объектами (game objects) или для краткости просто объектами.

Рис.1 Ветвление игровых объектов
Рис.1 Ветвление игровых объектов

Создание игровых объектов (далее - объектов) это та сфера, где активно применяются наследование и полиморфизм. Мы обсуждали эти понятия в предыдущих Главах. Для остальных напомним, что обе этих техники лежат в основе объектно-ориентированного программирования. Наследованием называют технику создания дочернего класса на основе базового, с сохранением полного функционала "родителя". Полиморфизм определяет порядок вызова функции базового класса, если при этом он переопределён (overriden) в дочернем классе (derived class). Рассмотрим простой пример. Допустим, у нас есть класс, в котором определена виртуальная функция. Ты можешь создать на основе этого класса другой класс, который будет являться дочерним (derived) по отношению к первому, базовому (base) классу. В дочернем классе ты можешь переопределить виртуальную функцию базового класса, чтобы при её вызове вызывалась именно переопределённая версия из дочернего класса.
Эти две техники необычайно важны при определении объектов в игре. Они позволяют создать всего один базовый объект, обладающий общими свойствами, присущими всем другим объектам. К примеру, ты захочешь определить, что все объекты в игре должны иметь определённое положение в 3D-пространстве. Вместо того, чтобы добавлять вектор положения в каждый из классов игровых объектов, куда проще создать класс базового объекта (base object class), описав в нём вектор положения. Затем мы просто ответвим (derive) от него несколько дочерних классов объектов, каждый из которых унаследует вектор положения, определённый в базовом классе (классе базового объекта), а следовательно сможет использовать его без ограничений. Тот же принцип мы применим к нашим игровым объектам. Мы определим базовый объект, который будет содержать в себе описание наиболее общих свойств, присущих всем объектам в игре. Всякий раз, когда мы будем создавать новый игровой объект, нам будет достаточно ответвить от него новый дочерний класс. И новый объект автоматически будет иметь весь набор свойств базового объекта. И весь процесс здесь не останавливается. От дочернего класса ты можешь также ветвить другие дочерние классы игровых объектов и все они сохранят в себе полный функционал базового объекта (см. Рис.1).
Класс базового объекта, который мы создадим, позволит нам размещать базовые объекты на нашей 3D-сцене. Они даже будут иметь собственные 3D-меши, назначенный им, а значит будут видимыми на сцене. Как вариант, ты можешь вовсе не назначать меш объекту. Например при нанесении т.н. вейпоинтов (англ. "waypoint" - точка маршрута; здесь - условные точки, по отрезкам которых перемещаются игровые объекты, контролируемые компьютером), которые изначально задуманы как невидимые ориентиры. Путём применения векторов и специального типа матрицы, называемой мировая матрица (world matrix) мы сможем задавать положение, вращать и перемещать объекты в 3D-пространстве. И это будет лишь часть возможностей базового объекта. Создав его однажды, ты можешь ветвить от него другие, более сложные, объекты которые сразу после своего создания будут обладать всеми свойствами базового объекта.
Прежде чем начать разрабатывать наш базовый объект, обсудим ещё пару важных тем, связанных с позиционированием и перемещением игровых объектов в 3D-пространстве. Точнее говоря мы детально рассмотрим ту самую мировую матрицу, т.к. именно через неё происходит размещение любого объекта или меша в 3D-пространстве.

Движение объектов

Раз уж наши объекты существуют в 3D-пространстве, они должны соответствовать Картезианской координатной системе. Другими словами объект должен иметь позицию в 3D-пространстве, определённую координатами (x, y, z). Самый простой способ представить эти координаты - рассмотреть простой вектор положения (position vector). Вообще просто разместить объекты на сцене и оставить их неподвижными - плёвое дело. Но очевидно, что некоторые из них мы захотим привести их в движение. Сам по себе процесс движения объекта не так сложен: он лишь перемещается из одного положения в другое, меняя свои позиционные координаты. Если у тебя есть объект, расположенный в точке, имеющей координату 12.0 по оси X и ты хочешь переместить его ещё на 2 единицы по той же оси, то необходимо просто изменить его позиционную координату по оси X с 12.0 на 14.0 . Как видим, в перемещении объектов в самом деле нет ничего сложного. Намного труднее отследить направление движения.
Вместо того, чтобы пытаться указать как далеко должен двигаться объект и с какой периодичностью он должен преодолевать эту дистанцию, куда проще разработать простую, основанную на реальной физике, систему движения (movement system), которая сама вычислит нужные временные интервалы. Звучит пугающе, но всё не так страшно, как может показаться на первый взгляд.
Рассмотрим ещё один пример, который поможет прояснить ситуацию. Допустим, мы работаем в 3D-пространстве, отмасштабированном так, что 1 единица измерения (в 3DS Max - 1 юнит) равная 1-му метру. Это означает, что 1 метр в реальном мире соответствует 1-му метру в виртуальной сцене. Представим, что в этом 3D-пространстве объект автомобиль движется по оси X со скоростью 10 метров в секунду (м/с). Мы можем сохранить данное движение в отдельном векторе, который назовём вектором скорости и который будет иметь координаты (10.0, 0.0, 0.0). Суть заключается в том, чтобы быть уверенным, что данный автомобиль перемещается на 10.0 единиц по оси X каждую секунду. Данное обстоятельство является ключевым для понимания всего процесса. Мы не хотим, чтобы автомобиль перемещался на 10.0 единиц в каждом кадре. Потому что в этом случае при частоте обновления, например, 50 кадров в секунду мы получим перемещение автомобиля на все 500.0 единиц по оси X каждую секунду, что эквивалентно скорости 500 м/с! Проблема в том, что мы хотим обновлять перемещение автомобиля в каждом кадре, поэтому это не будет выглядеть как простой прыжок на все 10.0 единиц в течение 1-й секунды, т.к. это создаст отрывочное, "дёрганое" движение.
Так как же нам заставить двигаться автомобиль в каждом кадре так, чтобы он перемещался каждую секунду не более чем на 10.0 единиц по оси X? На самом деле это довольно просто. Если нам необходимо распределить движение автомобиля по 50 кадрам (подразумеваем, что игра запущена в режиме 50 кадров в секунду), необходимо просто разделить скорость движения автомобиля на 50, что даст нам величину, которую проходит автомобиль в каждом кадре. В нашем случае нам необходимо перемещать его на о.2 единицы (10.0 делить на 50 получается 0.2) по оси X в каждом кадре. И спустя 50 кадров, автомобиль как раз переместиться на 10.0 единиц. А так как скорость смены кадров составляет 50 кадров в секунду, значит скорость движения автомобиля составит как раз 10 м/с. Проблема решена, не так ли? На самом деле не совсем. Мы двигаемся в верном направлении, но в нашей идее есть один основной промах. Мы не можем гарантировать, что наше приложение всегда будет работать с частотой смены кадров 50 кадров в секунду. Что если наше приложение будет запущено на другом, более мощном или наоборот слабом ПК? Да, оно будет выдавать разные показатели частоты кадров в секунду и таким образом отправит все наши предыдущие вычисления коту под хвост. Допустим, мы запустили наше приложение на слабом ПК, и оно выдаёт всего 25fps. В этом случае наш автомобиль будет двигаться со скоростью всего 5.0 единиц в секунду, т.к. число кадров в секунду составляет лишь половину от расчётных 50 fps, в то время как дистанция, проходимая автомобилем в каждом кадре осталась прежней. Верным решением здесь будет принятие во внимание тот факт, что частота обновления (частота кадров) может изменяться. Причём часто даже на одном и том же компьютере.
Для решения этой проблемы мы должны принять во внимание время смены кадров и использовать этот временной интервал для определения того, как далеко должен переместиться автомобиль в каждом кадре. Такой подход часто называют движением, основанным на времени (time-based movement) и у нас уже готова инфраструктура для его реализации. В Главе 1.2 при разработке класса Engine в тестовом приложении во время выполнения функции Run мы подсчитывали время показа каждого кадра. Как ты уже знаешь, время, затраченное на показ кадра представляет отрезок времени (определённое число миллисекунд), прошедший с момента показа предыдущего кадра. Так как кадры обрабатываются очень быстро, то часто за одну секунду обрабатывается несколько десятков кадров. То есть затраченное время (elapsed time) всегда будет очень малой величиной, равной приблизительно 0,02 секунды (20 миллисекунд) или около того. Это единственная величина, которая нам необходима для организации движения, основанного на времени.
Зная это, вернёмся к нашему примеру с движущимся автомобилем. В случае, когда наше приложение работает с частотой обновления 50 кадров в секунду (50 fps), изменение положения нашего автомобиля будет также обновляться 50 раз за 1 секунду. Таким образом показатель elapsed time (время, затраченное на показ одного кадра) составит 0,02 секунды. Представь себе показатель затраченного времени в виде определённой процентной доли секунды, необходимой для обработки текущего кадра. Это значит, что при частоте обновления 50 кадров в секунду каждый кадр "ответственен" за подготовку 2% от одной секунды игрового процесса. То есть в каждом кадре нам необходимо перемещать автомобиль всего на 2% от скорости его движения. Чтобы проделать это необходимо лишь умножить скорость движения автомобиля (10 метров в секунду) на показатель затраченного времени (0.02 сек.), что даст нам результат 0.2 м/с, что означает величину, на которую мы должны передвигать автомобиль в каждом кадре для достижения скорости 10 м/с за 50 кадров игрового процесса.
Кажется в нашем текущем примере ничего не изменилось, по сравнению с предыдущим. Но это лишь потому, что частота обновления осталась прежней - 50 кадров в секунду. Если частота обновления изменится (которая часто меняется даже в каждой секунде игрового процесса), ты увидишь, что в предыдущем примере автомобиль двигается неаккуратно, часто слишком быстро либо слишком медленно. Во втором примере в случае правильно настроенного движения, основанного на времени, любые изменения частоты обновления, подсчитанные покадровом уровне и с учётом показателя затраченного времени (elapsed time), будут учитываться и корректно отражаться на времени, прошедшем с показа предыдущего и до начала следующего кадра.

Мировая матрица (World matrix)

В конце предыдущей Главы мы впервые познакомились с матрицей вида (view matrix), а в Главе 1.5 мы также обсуждали матрицу проекции (projection matrix). Сейчас пришёл черёд изучить последнюю из трёх матриц, используемых Direct3D - мировую матрицу.
Но для начала быстро вспомним, что из себя представляют первые две.
Матрица вида (view matrix) применяется для позиционирования и ориентации зрителя (= виртуальной камеры) в 3D-пространстве. Как ты видел в предыдущей Главе, библиотека D3DX предоставляет несколько функций, которые применяются для построения матрицы вида. Наш движок не будет использовать ни одну из них, т.к. мы будем вычислять матрицу вида несколько иным путём, как ты увидишь позднее.
Матрица проекции (projection matrix) выступает в качестве своеобразной "линзы" виртуальной камеры. Матрица проекции позволяет задать угол обзора (angle of view) для поля видимости (field of view), ближней плоскости отсечения (near clipping plane) и дальней плоскости отсечения (far clipping plane). С матрицей проекции можно проделывать ряд трюков, как например сужение или расширение угла обзора, создавая иллюзию зума и фокусировки камеры на отдельных объектах сцены (особенно при использовании совместно с матрицей вида). Вообще эта тема выходит за пределы данного курса, но со значениями этих матриц ты можешь "поиграть" самостоятельно, для уяснения что там и к чему.

Рис.2 Сравнение пространства модели (Model space) и пространства мира (World space)
Рис.2 Сравнение пространства модели (Model space) и пространства мира (World space)

Самой интересной из всех трёх матриц является мировая матрица, т.к. именно она применяется для фактического размещения объектов в 3D-пространстве. Когда ты размещаешь камеру для того, чтобы увидеть объект в 3D-пространстве, Direct3D необходимо подсчитать, в каком месте на экране рендерить данный объект, чтобы он был виден в объективе виртуальной камеры. Представь, что экран монитора является своеобразным "окном" в 3D-пространство, которое также определяет позицию "линз" виртуальной камеры. Для корректного отображения объекта Direct3D необходимо спроектировать трёхмерный объект на двумерную плоскость экрана монитора. Это достигается путём применения всех трёх матриц.
Рассмотрим этот процесс более подробно. Единичный меш (который также может быть использован как меш объекта на экране) изначально размещается в так называемом пространстве модели (model, object, local space). Это лишь означает, что меш имеет свою собственную локальную систему координат, схожую с картезианской, которую использует Direct3D. Если ты отметил точку с координатой 2.0 на положительном луче оси X в локальной системе координат меша (mesh's model space), то в этому случае куда бы мы не переместили сам меш в 3D-пространстве (которое также называется "мировое пространство" - world space), отмеченная точка будет иметь ту же самую координату 2.0 в локальной системе координат (в локальном пространстве - model space), т.к. само локальное пространство всегда перемещается вместе с мешем, к которому оно привязано (см. Рис.2).
Так вот. Direct3D использует мировую матрицу (world matrix) для перемещения меша из пространства модели (model space, она же локальная система координат) в пространство мира (world space). При этом меш размещается в одном пространстве вместе с другими мешами сцены (даже если каждый из них имеет своё собственное пространство модели). Сразу вслед за этим Direct3D применяет матрицу вида (view matrix) для трансформирования (как ты помнишь, матрицы применяются именно для трансформаций) меша в пространство камеры (camera space), чтобы таким образом он был расположен относительно текущего положения и ориентации виртуальной камеры. Далее применяется матрица проекции (projection matrix) для трансформирования меша в пространство проекции (projection space). На этом этапе вершины меша масштабируются (scaled) и вытягиваются для получения впечатления 3D-перспективы. Наконец, меш обрезается (при необходимости) для размещения на экране и проектируется на пространство экрана (screen space) для рендеринга и показа на экране монитора. Очевидно, что на всех вышеперечисленных этапах "в фоновом режиме" проделывается уйма других всевозможных операций. К счастью, Direct3D берёт всю "чёрную" работу по обеспечению 3D-рендеринга на себя. Программеру остаётся лишь корректно настроить матрицы, что на самом деле сделать совсем несложно.
В Главе 1.5 мы уже видели как настраивается матрица проекции. И, как мы уже говорили, будучи однажды настроенной, она не требует вмешательства в свою работу в дальнейшем. По крайней мере до тех пор, пока ты не решишь изменить текущую проекцию по каким-либо причинам.
Матрица вида (view matrix) вычисляется и настраивается один раз в каждом кадре. Это логично, ведь в большинстве динамичных игр вид изменяется в каждом кадре вместе с движением игрока по игровой сцене. О настройке матрицы вида мы поговорим чуть позднее в этой Главе.
Что касается мировой матрицы, то нам потребуется создать по одной такой для каждого объекта в игре. Это позволит редактировать и поднастраивать мировую матрицу каждого объекта независимо от других. Другими словами для каждого объекта игровой сцены необходимо вычислять отдельную мировую матрицу. Это вовсе не означает, что мировые матрицы всех объектов будут вычисляться в каждом кадре. Мировую матрицу того или иного объекта необходимо перерассчитывать вновь только при его перемещении. Таким образом, если в игровой сцене присутствует дерево, которое всегда неподвижно, его мировую матрицу необходимо вычислять лишь однажды - при его создании. С другой стороны, мировую матрицу постоянно движущегося объекта необходимо высчитывать заново в каждом кадре.
Когда придёт время рендерить объекты на экране, первым делом настраиваем их мировые матрицы (или просто регистрируем их через интерфейсы Direct3D) ДО рендеринга их 3D-мешей. Эти мы укажем Direct3D, где именно должны находиться объекты в мировом пространстве.

Закрыть
noteОбрати внимание

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

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

Объекты сцены

Базовый объект (base object) это самый фундаментальный объект, который только можно использовать. Он содержит в себе весь базовый функционал, который является общим для объектов любых других типов, которые будут созданы на его основе. Все новые объекты "ветвятся" (derived) от базового объекта и потому полностью наследуют его общий для всех функционал. Раз уж мы назвали игровое 3D-пространство "сценой", то и наш базовый объект мы назовём объект сцены - scene object. Объект сцены представляет собой самый базовый объект, который только может существовать в наших сценах.

Создаём SceneObject.h (Проект Engine)

ОК, приступаем.

  • Стартуй MSVC++ 2010 и открывай Решение GameProject01 (если не сделал это раньше).
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Заголовочные файлы" Проекта Engine.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Заголовочный файл (.h)" и в поле "Имя" введи "SceneObject.h".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле SceneObject.h набираем следующий код:

SceneObject.h (Проект Engine)
//-----------------------------------------------------------------------------
// Необходимый минимум функционала для существования объекта в игровой сцене.
// Объекты, специфичные для приложения, должны ветвиться (derive) от данного класса.
//
// Original SourceCode:
// Programming a Multiplayer First Person Shooter in DirectX
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#ifndef SCENE_OBJECT_H
#define SCENE_OBJECT_H

//-----------------------------------------------------------------------------
// Определение типа объекта сцены
//-----------------------------------------------------------------------------
#define TYPE_SCENE_OBJECT 0

//-----------------------------------------------------------------------------
// Scene Object Class
//-----------------------------------------------------------------------------
class SceneObject : public BoundingVolume
{
public:
	SceneObject( unsigned long type = TYPE_SCENE_OBJECT, char *meshName = NULL, char *meshPath = "./", bool sharedMesh = true );
	virtual ~SceneObject();

	virtual void Update( float elapsed, bool addVelocity = true );
	virtual void Render( D3DXMATRIX *world = NULL );

	virtual void CollisionOccurred( SceneObject *object, unsigned long collisionStamp );

	void Drive( float force, bool lockYAxis = true );
	void Strafe( float force, bool lockYAxis = true );
	void Stop();

	void SetTranslation( float x, float y, float z );
	void SetTranslation( D3DXVECTOR3 translation );
	void AddTranslation( float x, float y, float z );
	void AddTranslation( D3DXVECTOR3 translation );
	D3DXVECTOR3 GetTranslation();

	void SetRotation( float x, float y, float z );
	void SetRotation( D3DXVECTOR3 rotation );
	void AddRotation( float x, float y, float z );
	void AddRotation( D3DXVECTOR3 rotation );
	D3DXVECTOR3 GetRotation();

	void SetVelocity( float x, float y, float z );
	void SetVelocity( D3DXVECTOR3 velocity );
	void AddVelocity( float x, float y, float z );
	void AddVelocity( D3DXVECTOR3 velocity );
	D3DXVECTOR3 GetVelocity();

	void SetSpin( float x, float y, float z );
	void SetSpin( D3DXVECTOR3 spin );
	void AddSpin( float x, float y, float z );
	void AddSpin( D3DXVECTOR3 spin );
	D3DXVECTOR3 GetSpin();

	D3DXVECTOR3 GetForwardVector();
	D3DXVECTOR3 GetRightVector();

	D3DXMATRIX *GetTranslationMatrix();
	D3DXMATRIX *GetRotationMatrix();
	D3DXMATRIX *GetWorldMatrix();
	D3DXMATRIX *GetViewMatrix();

	void SetType( unsigned long type );
	unsigned long GetType();

	void SetFriction( float friction );

	unsigned long GetCollisionStamp();

	void SetVisible( bool visible );
	bool GetVisible();

	void SetEnabled( bool enabled );
	bool GetEnabled();

	void SetGhost( bool ghost );
	bool GetGhost();

	void SetIgnoreCollisions( bool ignoreCollisions );
	bool GetIgnoreCollisions();

	void SetTouchingGroundFlag( bool touchingGround );
	bool IsTouchingGround();

	void SetMesh( char *meshName = NULL, char *meshPath = "./", bool sharedMesh = true );
	Mesh *GetMesh();

protected:
	D3DXVECTOR3 m_forward; // Вектор объекта, направленный вперёд (Object's forward vector).
	D3DXVECTOR3 m_right; // Вектор объекта, направленный вправо (Object's right vector).

	D3DXMATRIX m_worldMatrix; // Мировая матрица (World matrix).
	D3DXMATRIX m_viewMatrix; // Матрица вида (View matrix).

private:
	D3DXVECTOR3 m_translation; // Трансляция объекта в 3D-пространстве.
	D3DXVECTOR3 m_rotation; // Вращение объекта в радианах.

	D3DXVECTOR3 m_velocity; // Скорость объекта в единиц/сек.
	D3DXVECTOR3 m_spin; // Скорость вращения объекта в радиан/сек.

	D3DXMATRIX m_translationMatrix; // Матрица трансляции текущей позиции (Translation matrix).
	D3DXMATRIX m_rotationMatrix; // Матрица вращения (Rotation matrix).

	unsigned long m_type; // Идентифицирует родительский класс объекта сцены.
	float m_friction; // Коэффициент трения (затухания), применяемый к скоростям движения и вращения объекта.
	unsigned long m_collisionStamp; // Отмечает последний кадр, в котором произошло столкновение.
	bool m_visible; // Флаг видимости объекта. Невидимые объекты просто не рендерятся.
	bool m_enabled; // Флаг "включения" объекта в сцену. Отключенные объекты сцены не обновляются.
	bool m_ghost; // Флаг того, является ли объект "призраком" (ghost). Объекты-призраки не могут сталкиваться с другими предметами и проходят через них.
	bool m_ignoreCollisions; // Флаг, отвечающий за то, будет ли объект игнорировать столкновения. "Физические" столкновения будут происходить, но они просто не будут регистрироваться.
	bool m_touchingGround; // Флаг, отмечающий касание объекта с поверхностью земли/пола.
	bool m_sharedMesh; // Флаг указывает, использует ли объект 3D-меш совместно с другими объектами либо имеет эксклюзивный доступ к нему.
	Mesh *m_mesh; // Указатель на 3D-меш объекта.
};

#endif

  • Сохрани Решение (Файл->Сохранить все).

Исследуем код SceneObject.h

Класс SceneObject является самым большим их трёх типов объектов, которые мы рассмотрим в этой Главе. Так что если ты без труда разобрался в приведённом выше исходном коде, то оставшаяся часть Главы покажется тебе не сложнее прогулки в парке.
Для начала отметим, что класс SceneObject ветвится от класса BoundingVolume. Это позволяет нам "обнести" создаваемые на основе базового класса объекты набором из трёх ограничивающих объёмов (прямоугольник, сфера, эллипс) для самых разных целей, самая очевидная из которых - определение столкновений (collision detection). Помимо этого класс SceneObject имеет ставшие уже привычными конструктор, деструктор, функции Update и Render, без которых нам теперь никуда. Их работу мы обсудим чуть позднее в данной Главе.
Класс SceneObject содержит в себе множество различных функций, которые позволяют нам тонко настраивать (adjust) положение объекта в пространстве, а также его вращение и скорость. Помимо этого класс также имеет несколько служебных (utility) функций, которые дают возможность запрашивать различные матрицы, используемые объектами, а также различные свойства и константы, запрашиваемые у движка.

Создаём SceneObject.cpp (Проект Engine)

В файле исходного кода Network.cpp будут размещаться реализации функций, объявленных в SceneObject.h.
ОК, приступаем.

  • Стартуй MSVC++ 2010 и открывай Решение GameProject01 (если не сделал это раньше).
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта Engine.
  • Во всплывающем меню Добавить->Создать элемент...
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи "SceneObject.cpp".
  • Жмём "Добавить".

Добавленный файл сразу откроется в правой части MSVC++2010.

  • В только что созданном и открытом файле SceneObject.cpp набираем следующий код:

SceneObject.cpp (Проект Engine)
//-----------------------------------------------------------------------------
// Реализация классов и функций, объявленных в SceneObject.h.
//
// Original SourceCode:
// Programming a Multiplayer First Person Shooter in DirectX
// Copyright (c) 2004 Vaughan Young
//-----------------------------------------------------------------------------
#include "Engine.h"

//-----------------------------------------------------------------------------
// The scene object class constructor.
//-----------------------------------------------------------------------------
SceneObject::SceneObject( unsigned long type, char *meshName, char *meshPath, bool sharedMesh )
{
	// Устанавливаем тип объекта;
	SetType( type );

	// Обнуляем трансляцию позиции и вращение объекта сцены.
	SetTranslation( 0.0f, 0.0f, 0.0f );
	SetRotation( 0.0f, 0.0f, 0.0f );

	// Изначально объект находится в состоянии покоя.
	SetVelocity( 0.0f, 0.0f, 0.0f );
	SetSpin( 0.0f, 0.0f, 0.0f );

	// Изначально объект устремлён "лицом" по направлению положительного руча оси Z.
	m_forward = D3DXVECTOR3( 0.0f, 0.0f, 1.0f );
	m_right = D3DXVECTOR3( 1.0f, 0.0f, 0.0f );

	// Изначально объект не имеет трения.
	m_friction = 0.0f;

	// Очищаем отметку о столкновении (collision stamp).
	m_collisionStamp = -1;

	// По умолчанию объект видим, включён (enabled), твёрд (не "призрак"), и регистрирует столкновения.
	m_visible = true;
	m_enabled = true;
	m_ghost = false;
	m_ignoreCollisions = false;

	// Изначально объект не касается земли.
	m_touchingGround = false;

	// Устанавливаем 3D-меш объекта.
	m_mesh = NULL;
	SetMesh( meshName, meshPath, sharedMesh );
}

//-----------------------------------------------------------------------------
// The scene object class destructor.
//-----------------------------------------------------------------------------
SceneObject::~SceneObject()
{
	// Уничтожаем меш объекта.
	if( m_sharedMesh == true )
		g_engine->GetMeshManager()->Remove( &m_mesh );
	else
		SAFE_DELETE( m_mesh );
}

//-----------------------------------------------------------------------------
// Обновляем состояние объекта.
//-----------------------------------------------------------------------------
void SceneObject::Update( float elapsed, bool addVelocity )
{
	// Рассчитываем трение для данного обновления (если оно есть).
	float friction = 1.0f - m_friction * elapsed;

	// Перемещаем объект.
	m_velocity *= friction;
	if( addVelocity == true )
	{
		D3DXVECTOR3 velocity = m_velocity * elapsed;
		AddTranslation( velocity.x, velocity.y, velocity.z );
	}

	// Вращаем объект.
	m_spin *= friction;
	D3DXVECTOR3 spin = m_spin * elapsed;
	AddRotation( spin.x, spin.y, spin.z );

	// Обновляем мировую матрицу (world matrix) объекта.
	D3DXMatrixMultiply( &m_worldMatrix, &m_rotationMatrix, &m_translationMatrix );

	// Создаём матрицу вида (view matrix) для объекта.
	D3DXMatrixInverse( &m_viewMatrix, NULL, &m_worldMatrix );

	// Обновляем вектор объекта, направленный вперёд (object's forward vector).
	m_forward.x = (float)sin( m_rotation.y );
	m_forward.y = (float)-tan( m_rotation.x );
	m_forward.z = (float)cos( m_rotation.y );
	D3DXVec3Normalize( &m_forward, &m_forward );

	// Обновляем вектор объекта, направленный вправо (object's right vector).
	m_right.x = (float)cos( m_rotation.y );
	m_right.y = (float)tan( m_rotation.z );
	m_right.z = (float)-sin( m_rotation.y );
	D3DXVec3Normalize( &m_right, &m_right );

	// Обновляем ограничивающий объём объекта с применением одной только матрицы трансляции (translation matrix).
	// Это установит оси, приложенные к ограничивающему прямоугольнику вокруг объекта в мировом пространстве
	// (world space) вместо локального пространства объекта (object's local space).
	RepositionBoundingVolume( &m_translationMatrix );
}

//-----------------------------------------------------------------------------
// Renders the object.
//-----------------------------------------------------------------------------
void SceneObject::Render( D3DXMATRIX *world )
{
	// Игнорируем объект, если он не имеет 3D-меша.
	if( m_mesh == NULL )
		return;

	// Проверяем случай, когда мировая матрица преобразований объекта (the object's world tranformation matrix) была переопределена.
	if( world == NULL )
		g_engine->GetDevice()->SetTransform( D3DTS_WORLD, &m_worldMatrix );
	else
		g_engine->GetDevice()->SetTransform( D3DTS_WORLD, world );

	// Рендерим меш объекта.
	m_mesh->Render();
}

//-----------------------------------------------------------------------------
// Вызывается, когда что-то столкнулось (collide) с объектом.
//-----------------------------------------------------------------------------
void SceneObject::CollisionOccurred( SceneObject *object, unsigned long collisionStamp )
{
	// Ставим отметку о столкновении (collision stamp).
	m_collisionStamp = collisionStamp;
}

//-----------------------------------------------------------------------------
// Применяем данную силу к объекту в направлении вперёд/назад.
//-----------------------------------------------------------------------------
void SceneObject::Drive( float force, bool lockYAxis )
{
	D3DXVECTOR3 realForce = m_forward * force;

	m_velocity.x += realForce.x;
	m_velocity.z += realForce.z;

	if( lockYAxis == false )
		m_velocity.y += realForce.y;
}

//-----------------------------------------------------------------------------
// Применяем данную силу к объекту в напарвлении вправо/влево.
//-----------------------------------------------------------------------------
void SceneObject::Strafe( float force, bool lockYAxis )
{
	D3DXVECTOR3 realForce = m_right * force;

	m_velocity.x += realForce.x;
	m_velocity.z += realForce.z;

	if( lockYAxis == false )
		m_velocity.y += realForce.y;
}

//-----------------------------------------------------------------------------
// Останавливает движение объекта.
//-----------------------------------------------------------------------------
void SceneObject::Stop()
{
	m_velocity = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
	m_spin = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
}

//-----------------------------------------------------------------------------
// Устанавливаем новую трансляцию позиции объекта (object's translation) на основе координат трансляции x, y, z.
//-----------------------------------------------------------------------------
void SceneObject::SetTranslation( float x, float y, float z )
{
	m_translation.x = x;
	m_translation.y = y;
	m_translation.z = z;

	D3DXMatrixTranslation( &m_translationMatrix, m_translation.x, m_translation.y, m_translation.z );
}

//-----------------------------------------------------------------------------
// Устанавливаем новую трансляцию позиции объекта (object's translation) на основе вектора трансляции.
//-----------------------------------------------------------------------------
void SceneObject::SetTranslation( D3DXVECTOR3 translation )
{
	m_translation = translation;

	D3DXMatrixTranslation( &m_translationMatrix, m_translation.x, m_translation.y, m_translation.z );
}

//-----------------------------------------------------------------------------
// Прибавляем (add) данную трансляцию к текущей трансляции позиции объекта на основе координат x, y, z.
//-----------------------------------------------------------------------------
void SceneObject::AddTranslation( float x, float y, float z )
{
	m_translation.x += x;
	m_translation.y += y;
	m_translation.z += z;

	D3DXMatrixTranslation( &m_translationMatrix, m_translation.x, m_translation.y, m_translation.z );
}

//-----------------------------------------------------------------------------
// Прибавляем (add) данную трансляцию к текущей трансляции позиции объекта на основе вектора трансляции.
//-----------------------------------------------------------------------------
void SceneObject::AddTranslation( D3DXVECTOR3 translation )
{
	m_translation += translation;

	D3DXMatrixTranslation( &m_translationMatrix, m_translation.x, m_translation.y, m_translation.z );
}

//-----------------------------------------------------------------------------
// Возвращает текущую трансляцию объекта.
//-----------------------------------------------------------------------------
D3DXVECTOR3 SceneObject::GetTranslation()
{
	return m_translation;
}

//-----------------------------------------------------------------------------
// Устанавливает вращение объекта на основе координат x, y, z.
//-----------------------------------------------------------------------------
void SceneObject::SetRotation( float x, float y, float z )
{
	m_rotation.x = x;
	m_rotation.y = y;
	m_rotation.z = z;

	D3DXMATRIX rotationX, rotationY;
	D3DXMatrixRotationX( &rotationX, m_rotation.x );
	D3DXMatrixRotationY( &rotationY, m_rotation.y );
	D3DXMatrixRotationZ( &m_rotationMatrix, m_rotation.z );
	D3DXMatrixMultiply( &m_rotationMatrix, &m_rotationMatrix, &rotationX );
	D3DXMatrixMultiply( &m_rotationMatrix, &m_rotationMatrix, &rotationY );
}

//-----------------------------------------------------------------------------
// Устанавливает вращение объекта на основе вектора вращения (rotation vector).
//-----------------------------------------------------------------------------
void SceneObject::SetRotation( D3DXVECTOR3 rotation )
{
	m_rotation = rotation;

	D3DXMATRIX rotationX, rotationY;
	D3DXMatrixRotationX( &rotationX, m_rotation.x );
	D3DXMatrixRotationY( &rotationY, m_rotation.y );
	D3DXMatrixRotationZ( &m_rotationMatrix, m_rotation.z );
	D3DXMatrixMultiply( &m_rotationMatrix, &m_rotationMatrix, &rotationX );
	D3DXMatrixMultiply( &m_rotationMatrix, &m_rotationMatrix, &rotationY );
}

//-----------------------------------------------------------------------------
// Добавляем данное вращение к текущему вращению объекта на основе координат x, y, z.
//-----------------------------------------------------------------------------
void SceneObject::AddRotation( float x, float y, float z )
{
	m_rotation.x += x;
	m_rotation.y += y;
	m_rotation.z += z;

	D3DXMATRIX rotationX, rotationY;
	D3DXMatrixRotationX( &rotationX, m_rotation.x );
	D3DXMatrixRotationY( &rotationY, m_rotation.y );
	D3DXMatrixRotationZ( &m_rotationMatrix, m_rotation.z );
	D3DXMatrixMultiply( &m_rotationMatrix, &m_rotationMatrix, &rotationX );
	D3DXMatrixMultiply( &m_rotationMatrix, &m_rotationMatrix, &rotationY );
}

//-----------------------------------------------------------------------------
// Добавляем данное вращение к текущему вращению объекта на основе вектора вращения.
//-----------------------------------------------------------------------------
void SceneObject::AddRotation( D3DXVECTOR3 rotation )
{
	m_rotation += rotation;

	D3DXMATRIX rotationX, rotationY;
	D3DXMatrixRotationX( &rotationX, m_rotation.x );
	D3DXMatrixRotationY( &rotationY, m_rotation.y );
	D3DXMatrixRotationZ( &m_rotationMatrix, m_rotation.z );
	D3DXMatrixMultiply( &m_rotationMatrix, &m_rotationMatrix, &rotationX );
	D3DXMatrixMultiply( &m_rotationMatrix, &m_rotationMatrix, &rotationY );
}

//-----------------------------------------------------------------------------
// Возвращает текущее вращение объекта.
//-----------------------------------------------------------------------------
D3DXVECTOR3 SceneObject::GetRotation()
{
	return m_rotation;
}

//-----------------------------------------------------------------------------
// Устанавливает скорость движения объекта на основе координат x, y, z.
//-----------------------------------------------------------------------------
void SceneObject::SetVelocity( float x, float y, float z )
{
	m_velocity.x = x;
	m_velocity.y = y;
	m_velocity.z = z;
}

//-----------------------------------------------------------------------------
// Устанавливает скорость движения объекта на основе вектора скорости.
//-----------------------------------------------------------------------------
void SceneObject::SetVelocity( D3DXVECTOR3 velocity )
{
	m_velocity = velocity;
}

//-----------------------------------------------------------------------------
// Добавляет данную скорость к текущей скорости объекта на основе координат x, y, z.
//-----------------------------------------------------------------------------
void SceneObject::AddVelocity( float x, float y, float z )
{
	m_velocity.x += x;
	m_velocity.y += y;
	m_velocity.z += z;
}

//-----------------------------------------------------------------------------
// Добавляет данную скорость к текущей скорости объекта на основе вектора скорости.
//-----------------------------------------------------------------------------
void SceneObject::AddVelocity( D3DXVECTOR3 velocity )
{
	m_velocity += velocity;
}

//-----------------------------------------------------------------------------
// Возвращает текущую скорость объекта.
//-----------------------------------------------------------------------------
D3DXVECTOR3 SceneObject::GetVelocity()
{
	return m_velocity;
}

//-----------------------------------------------------------------------------
// Устанавливает кручение (spin) объекта на основе координат x, y, z.
//-----------------------------------------------------------------------------
void SceneObject::SetSpin( float x, float y, float z )
{
	m_spin.x = x;
	m_spin.y = y;
	m_spin.z = z;
}

//-----------------------------------------------------------------------------
// Устанавливает кручение (spin) объекта на основе вектора кручения.
//-----------------------------------------------------------------------------
void SceneObject::SetSpin( D3DXVECTOR3 spin )
{
	m_spin = spin;
}

//-----------------------------------------------------------------------------
// Добавляет данное кручение к текущему кручению объекта на основе координат x, y, z.
//-----------------------------------------------------------------------------
void SceneObject::AddSpin( float x, float y, float z )
{
	m_spin.x += x;
	m_spin.y += y;
	m_spin.z += z;
}

//-----------------------------------------------------------------------------
// Добавляет данное кручение к текущему кручению объекта на основе вектора кручения.
//-----------------------------------------------------------------------------
void SceneObject::AddSpin( D3DXVECTOR3 spin )
{
	m_spin = spin;
}

//-----------------------------------------------------------------------------
// Возвращает текущее кручение объекта.
//-----------------------------------------------------------------------------
D3DXVECTOR3 SceneObject::GetSpin()
{
	return m_spin;
}

//-----------------------------------------------------------------------------
// Возвращает вектор объекта, направленный вперёд (object's forward vector).
//-----------------------------------------------------------------------------
D3DXVECTOR3 SceneObject::GetForwardVector()
{
	return m_forward;
}

//-----------------------------------------------------------------------------
// Возвращает вектор объекта, направленный вправо (object's right vector).
//-----------------------------------------------------------------------------
D3DXVECTOR3 SceneObject::GetRightVector()
{
	return m_right;
}

//-----------------------------------------------------------------------------
// Возвращает указатель на текущую матрицу трансляции позиции объекта (object's current translation matrix).
//-----------------------------------------------------------------------------
D3DXMATRIX *SceneObject::GetTranslationMatrix()
{
	return &m_translationMatrix;
}

//-----------------------------------------------------------------------------
// Возвращает указатель на текущую матрицу вращения объекта (object's current rotation matrix).
//-----------------------------------------------------------------------------
D3DXMATRIX *SceneObject::GetRotationMatrix()
{
	return &m_rotationMatrix;
}

//-----------------------------------------------------------------------------
// Возвращает указатель на текущую мировую матрицу объекта (object's current world matrix).
//-----------------------------------------------------------------------------
D3DXMATRIX *SceneObject::GetWorldMatrix()
{
	return &m_worldMatrix;
}

//-----------------------------------------------------------------------------
// Возвращает указатель на текущую матрицу вида объекта (object's current view matrix).
//-----------------------------------------------------------------------------
D3DXMATRIX *SceneObject::GetViewMatrix()
{
	return &m_viewMatrix;
}

//-----------------------------------------------------------------------------
// Устанавливает тип объекта.
//-----------------------------------------------------------------------------
void SceneObject::SetType( unsigned long type )
{
	m_type = type;
}

//-----------------------------------------------------------------------------
// Возвращает тип объекта.
//-----------------------------------------------------------------------------
unsigned long SceneObject::GetType()
{
	return m_type;
}

//-----------------------------------------------------------------------------
// Устанавливает трение объекта (object's friction).
//-----------------------------------------------------------------------------
void SceneObject::SetFriction( float friction )
{
	m_friction = friction;
}

//-----------------------------------------------------------------------------
// Возвращает отметку о столкновении (collision stamp).
//-----------------------------------------------------------------------------
unsigned long SceneObject::GetCollisionStamp()
{
	return m_collisionStamp;
}

//-----------------------------------------------------------------------------
// Устанавливает флаг видимости объекта (object's visible flag).
//-----------------------------------------------------------------------------
void SceneObject::SetVisible( bool visible )
{
	m_visible = visible;
}

//-----------------------------------------------------------------------------
// Возвращает флаг видимости объекта (object's visible flag).
//-----------------------------------------------------------------------------
bool SceneObject::GetVisible()
{
	return m_visible;
}

//-----------------------------------------------------------------------------
// Устанавливает флаг "включения" объекта (object's enabled flag).
//-----------------------------------------------------------------------------
void SceneObject::SetEnabled( bool enabled )
{
	m_enabled = enabled;
}

//-----------------------------------------------------------------------------
// Возвращает флаг "включения" объекта (object's enabled flag).
//-----------------------------------------------------------------------------
bool SceneObject::GetEnabled()
{
	return m_enabled;
}

//-----------------------------------------------------------------------------
// Устанавливает флаг "призрачности" объекта (object's ghost flag).
//-----------------------------------------------------------------------------
void SceneObject::SetGhost( bool ghost )
{
	m_ghost = ghost;
}

//-----------------------------------------------------------------------------
// Возвращает флаг "призрачности" объекта (object's ghost flag).
//-----------------------------------------------------------------------------
bool SceneObject::GetGhost()
{
	return m_ghost;
}

//-----------------------------------------------------------------------------
// Устанавливает флаг игнорирования столкновений с данным объектом (object's ignore collisions flag).
//-----------------------------------------------------------------------------
void SceneObject::SetIgnoreCollisions( bool ignoreCollisions )
{
	m_ignoreCollisions = ignoreCollisions;
}

//-----------------------------------------------------------------------------
// Возвращает флаг игнорирования столкновений с данным объектом (object's ignore collisions flag).
//-----------------------------------------------------------------------------
bool SceneObject::GetIgnoreCollisions()
{
	return m_ignoreCollisions;
}

//-----------------------------------------------------------------------------
// Устанавливает флага касания объектом земли.
//-----------------------------------------------------------------------------
void SceneObject::SetTouchingGroundFlag( bool touchingGround )
{
	m_touchingGround = touchingGround;
}

//-----------------------------------------------------------------------------
// Возвращает true (истина) если объект касается земли.
//-----------------------------------------------------------------------------
bool SceneObject::IsTouchingGround()
{
	return m_touchingGround;
}

//-----------------------------------------------------------------------------
// Устанавливает 3D-меш для данного объекта сцены.
//-----------------------------------------------------------------------------
void SceneObject::SetMesh( char *meshName, char *meshPath, bool sharedMesh )
{
	// Уничтожаем текущий меш объекта (если есть).
	if( m_sharedMesh == true )
		g_engine->GetMeshManager()->Remove( &m_mesh );
	else
		SAFE_DELETE( m_mesh );

	// Ставим флаг, что объект будет использовать данный меш совместно с другими объектами.
	m_sharedMesh = sharedMesh;

	// Проверяем, что меш указан.
	if( meshName != NULL && meshPath != NULL )
	{
		// Загружаем меш объекта.
		if( m_sharedMesh == true )
			m_mesh = g_engine->GetMeshManager()->Add( meshName, meshPath );
		else
			m_mesh = new Mesh( meshName, meshPath );

		// Клонируем ограничивающий объём (bounding volume) меша. Он будет использоваться
		//  для установления оси, приложенной вдоль ограничивающего объёма в мировом пространстве.
		CloneBoundingVolume( m_mesh->GetBoundingBox(), m_mesh->GetBoundingSphere() );
	}
}

//-----------------------------------------------------------------------------
// Возвращает указатель на меш объекта.
//-----------------------------------------------------------------------------
Mesh *SceneObject::GetMesh()
{
	return m_mesh;
}

  • Сохрани Решение (Файл->Сохранить все).

Исследуем код Material.cpp (Проект Engine)

Обширный список переменных членов мы изучим в порядке их появления в исходном коде.
Начнём с конструктора:

Фрагмент SceneObject.cpp (Проект Engine)
...
//-----------------------------------------------------------------------------
// The scene object class constructor.
//-----------------------------------------------------------------------------
SceneObject::SceneObject( unsigned long type, char *meshName, char *meshPath, bool sharedMesh )
{
	// Устанавливаем тип объекта;
	SetType( type );

	// Обнуляем трансляцию позиции и вращение объекта сцены.
	SetTranslation( 0.0f, 0.0f, 0.0f );
	SetRotation( 0.0f, 0.0f, 0.0f );

	// Изначально объект находится в состоянии покоя.
	SetVelocity( 0.0f, 0.0f, 0.0f );
	SetSpin( 0.0f, 0.0f, 0.0f );

	// Изначально объект устремлён "лицом" по направлению положительного руча оси Z.
	m_forward = D3DXVECTOR3( 0.0f, 0.0f, 1.0f );
	m_right = D3DXVECTOR3( 1.0f, 0.0f, 0.0f );

	// Изначально объект не имеет трения.
	m_friction = 0.0f;

	// Очищаем отметку о столкновении (collision stamp).
	m_collisionStamp = -1;

	// По умолчанию объект видим, включён (enabled), твёрд (не "призрак"), и регистрирует столкновения.
	m_visible = true;
	m_enabled = true;
	m_ghost = false;
	m_ignoreCollisions = false;

	// Изначально объект не касается земли.
	m_touchingGround = false;

	// Устанавливаем 3D-меш объекта.
	m_mesh = NULL;
	SetMesh( meshName, meshPath, sharedMesh );
}
...


Несмотря на свой значительный размер, конструктор выполняет совсем немного действий. Его основное назначение в данном случае - очищать (обнулять) все необходимые переменные члены либо приводить их значения к значениям по умолчанию. Мы обсудим значение каждого переменного члена как только разговор зайдёт об их применении, чуть ниже в этой Главе. Сейчас мы остановимся на двух основных точках конструктора.
Как видим, конструктор принимает type в качестве первого вводного параметра. Он устанавливается путём вызова специальной функции SetType, экспонированной классом SceneObject. В самом начале SceneObject.h стоит определение:

Фрагмент SceneObject.h (Проект Engine)
...
#define TYPE_SCENE_OBJECT 0
...

Оно определяет тип объекта сцены. Каждый новый объект будет обязательно иметь свой тип, хранящийся в переменном члене m_type, и как раз передаваемый в конструкторе класса SceneObject. Позднее, когда ты будешь создавать свои собственные типы объектов сцены, то будешь определять их именно этим способом. Мы используем эти типы в качестве идентификаторов классов, для того, чтобы точно знать, с каким типом объектов сцены мы работаем в данный момент. Например у нас может быть связный список, хранящий указатели на все объекты игры. Для проверки того, что хранимые в нём указатели совместимы со всеми типами объектов, поддерживаемыми игрой, достаточно просто разместить в нём указатели всех дочерних классов класса SceneObject. Для того, чтобы воспользоваться всем функционалом базового объекта, необходимо передать указатель класса SceneObject в соответствующие дочерние классы объектов различных типов. Вот здесь-то идентификатор типа (type's identifier) и выходит на первый план. Если, к примеру, ты проверяешь один из своих объектов и он возвращает значение TYPE_PLAYER_OBJECT, то ты точно знаешь, что данный объект является объектом игрока (PlayerObject). Кроме того, в этом случае будет необходимо разместить указатель класса SceneObject внутри указателя PlayerObject для получения доступа ко всему функционалу класса PlayerObject. Если что-то из данного абзаца осталось непонятным, то мы ещё раз остановимся на этом при разработке различных типов объектов при создании игры.
Второе, на что следует обратить внимание, это то, как конструктор обрабатывает меш объекта. Конструктор принимает имя файла с мешем (2-й параметр meshName), путь до меша (3-й параметр meshPath), а также статус использования меша (эксклюзивно/совместно с другими объектами; 4-й параметр sharedMesh). Имя файла меша и путь до него необходимы для загрузки меш-ресурса (mesh resource), Флаг sharedMesh указывает на то, использовать ли для загрузки меш-ресурса менеджер ресурсов или нет. Меш для совместного использования (shared mesh) загружается в память всего 1 раз а его инстансы запрашиваются различными объектами при необходимости. Меш для эксклюзивного использования (non-shared mesh) загружается специально для использования только с одним объектом. Из этого следует, что если создать 2 объекта и указать им использовать эксклюзивный (non-shared) меш, каждый из объектов загрузит в память по отдельной копии данного меш-ресурса.
Для статичных (static) мешей всегда необходимо выбирать разделяемые (shared) меш-ресурсы. В то же время для анимированных объектов это не всегда возможно. Анимированные объекты могут изменять свою форму и если два или более объекта ссылаются на один и тот же меш, то любые изменения хотя бы одного объекта моментально отразятся на их общем меше и, как следствие, на других объектах, которые его используют. Таким образом для анимированных мешей, как например меш объекта игрок (player object), необходимо использовать неразделяемые (non-shared) меши. К счастью, в типичной FPS-игре едва ли наберётся анимированных объектов, за исключением толпы объектов игроков. Поэтому неразделяемые (эксклюзивные) меши вряд ли доставят тебе массу хлопот.


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  1.15 Объекты в игре

Contributors to this page: slymentat .
Последнее изменение страницы Пятница 20 / Октябрь, 2017 23:18:36 MSK автор slymentat.

Последние комментарии

No records to display