Журнал LinuxFormat - перейти на главную

LXF83:Ogre

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
(Новая: === Ogre. Добавь движенья и воды! === '' '''ЧАСТЬ 2''' Что толку от изменяющегося ландшафта, если вы прикованы к ...)
 
м (шаблон)
 
(не показаны 10 промежуточных версий 4 участников)
Строка 1: Строка 1:
=== Ogre. Добавь движенья и воды! ===
+
{{Цикл/Ogre}}
 +
 
 +
== Ogre. Добавь движенья и воды! ==
 
'' '''ЧАСТЬ 2''' Что толку от изменяющегося ландшафта, если вы прикованы к месту? '''Пол Хадсон''' знает ответ.''
 
'' '''ЧАСТЬ 2''' Что толку от изменяющегося ландшафта, если вы прикованы к месту? '''Пол Хадсон''' знает ответ.''
  
Строка 5: Строка 7:
  
 
=== Театр Буфф(еризации) ===
 
=== Театр Буфф(еризации) ===
Для начала добавим поддержку стандартных клавиш WASD. Нам
+
Для начала добавим поддержку стандартных клавиш WASD. Нам самим необходимо перехватывать нажатия клавиш, а затем передавать их в Ogre, чтобы он соответственным образом переместил камеру (помните, что это стрелялка от первого лица, поэтому на самом деле мы Чеда не видим). В прошлый раз мы уже обработали клавишу Escape, поэтому вы уже примерно представляете, как написать новую часть кода. Чтобы было немного понятнее, я объединил классы CChadFrameListner и CChadGame в один класс (CChadGame). Теперь этот класс занимается и обработкой кадра, и вводом от клавиатуры, а в конце концов будет также обрабатывать механизмы игры – надеюсь, это сделает код более читабельным, ценой легкой объектной инкапсуляции!
самим необходимо перехватывать нажатия клавиш, а затем передавать
+
 
их в Ogre, чтобы он соответственным образом переместил камеру (пом-
+
Для начала необходимо буферизовать ввод Ogre. Это значит, что если кто-то лупит по клавишам быстрее, чем Ogre успевает обработать, то Ogre сможет отследить, сколько раз была нажата клавиша, а затем осуществить обработку нажатий как можно скорее – даже в случае, когда клавиша была позже отжата. Например, если некий особо продвинутый игрок путешествует по нашему игровому миру, совершая хитрые трюки и маневры, чтобы побить других игроков, он может превысить число вводов в единицу времени, поддающееся обработке. Если мы будем буферизовать все, то игрок, может, пальнет и на 10 миллисекунд позже, чем планировал, но все-таки пальнет!
ните, что это стрелялка от первого лица, поэтому на самом деле мы
+
 
Чеда не видим). В прошлый раз мы уже обработали клавишу Escape,
+
Достигнуть этого можно всего одной строкой кода Ogre. Добавьте следующее в файл '''chad.h''' в конструкторе, после остальных строк:
поэтому вы уже примерно представляете, как написать новую часть кода.
+
 
Чтобы было немного понятнее, я объединил классы CChadFrameListner
+
<source lang="cpp">
и CChadGame в один класс (CChadGame). Теперь этот класс занимает-
+
ся и обработкой кадра, и вводом от клавиатуры, а в конце концов будет
+
также обрабатывать механизмы игры – надеюсь, это сделает код более
+
читабельным, ценой легкой объектной инкапсуляции!
+
Для начала необходимо буферизовать ввод Ogre. Это значит, что
+
если кто-то лупит по клавишам быстрее, чем Ogre успевает обрабо-
+
тать, то Ogre сможет отследить, сколько раз была нажата клавиша,
+
а затем осуществить обработку нажатий как можно скорее – даже в
+
случае, когда клавиша была позже отжата. Например, если некий
+
особо продвинутый игрок путешествует по нашему игровому миру,
+
совершая хитрые трюки и маневры, чтобы побить других игроков,
+
он может превысить число вводов в единицу времени, поддающе-
+
еся обработке. Если мы будем буферизовать все, то игрок, может,
+
пальнет и на 10 миллисекунд позже, чем планировал, но все-таки
+
пальнет!
+
Достигнуть этого можно всего одной строкой кода Ogre. Добавьте
+
следующее в файл chad.h в конструкторе, после остальных строк:
+
 
m_InputDevice->setBufferedInput(true, true);
 
m_InputDevice->setBufferedInput(true, true);
Теперь займемся нажимаемыми клавишами – для них потребуется
+
</source>
несколько вызовов метода isKeyDown(). Создадим вектор, задающий
+
направление перехода (просто линия, соединяющая конечную и началь-
+
ную точки); мы можем сказать Ogre, что хотим прибавить или вычесть
+
смещение по X, Y и Z. Связав эту операцию с нажатием клавиш, мы и
+
получим движение.
+
Если вы боитесь, что это сложно, расслабьтесь – я представ-
+
лю вам восхитительные 6 строк, которые выполняют всю работу.
+
Вставьте их перед return true в методе frameStarted() заголовка
+
chadframelistner.h.
+
  
Vector3 translateVector = Vector3::ZERO;
+
Теперь займемся нажимаемыми клавишами – для них потребуется несколько вызовов метода isKeyDown(). Создадим вектор, задающий направление перехода (просто линия, соединяющая конечную и начальную точки); мы можем сказать Ogre, что хотим прибавить или вычесть смещение по X, Y и Z. Связав эту операцию с нажатием клавиш, мы и получим движение.
if (m_InputReader->isKeyDown(KC_W)) translateVector.z = -0.1f;
+
if (m_InputReader->isKeyDown(KC_S)) translateVector.z = +0.1f;
+
if (m_InputReader->isKeyDown(KC_A)) translateVector.x = -0.1f;
+
if (m_InputReader->isKeyDown(KC_D)) translateVector.x = +0.1f;
+
m_Camera->moveRelative(translateVector);
+
  
Сохраните, наберите make и запустите игру – наслаждайтесь ново-
+
Если вы боитесь, что это сложно, расслабьтесь – я представлю вам восхитительные 6 строк, которые выполняют всю работу. Вставьте их перед return true в методе frameStarted() заголовка '''chadframelistner.h'''.
обретенной свободой!
+
 
 +
<source lang="cpp">
 +
Vector3 translateVector = Vector3::ZERO;
 +
if (m_InputReader->isKeyDown(KC_W)) translateVector.z = -0.1f;
 +
if (m_InputReader->isKeyDown(KC_S)) translateVector.z = +0.1f;
 +
if (m_InputReader->isKeyDown(KC_A)) translateVector.x = -0.1f;
 +
if (m_InputReader->isKeyDown(KC_D)) translateVector.x = +0.1f;
 +
m_Camera->moveRelative(translateVector);
 +
</source>
 +
 
 +
Сохраните, наберите make и запустите игру – наслаждайтесь новообретенной свободой!
  
 
=== Птица высокого полета ===
 
=== Птица высокого полета ===
 +
 +
{{Врезка
 +
|Заголовок=Почему ввод Ogre так несносен
 +
|Содержание=Поэкспериментировав со вводом Ogre, вы заметите одну вещь: он капризный, непредсказуемый, простецкий и в целом невыносим. Как случилось, что лучший в мире инструментарий разработки игр имеет столь хромую систему ввода? Ответ прост: Ogre не претендует на роль инструментария для игр, занимаясь графикой, графикой и еще раз графикой – система ввода была дана разработчикам игр исключительно для создания кросс-платформенных демо. Будьте спокойны: как только понадобится более продвинутый вариант ввода, мы тут же оставим систему Ogre, но пока ее вполне достаточно для путешествий по пространству этаким одиноким облаком.
 +
|Ширина=250px
 +
}}
 +
 
Передвигаясь, вы столкнетесь с тремя большими проблемами:
 
Передвигаясь, вы столкнетесь с тремя большими проблемами:
Так как ландшафт бугристый, кое-где вы можете вдруг провалиться
+
*Так как ландшафт бугристый, кое-где вы можете вдруг провалиться сквозь землю.
сквозь землю.
+
*Направив мышь вверх и шагнув вперед (или направив ее вниз и шагнув назад), вы ни с того ни с сего взлетаете.
Направив мышь вверх и шагнув вперед (или направив ее вниз и шаг-
+
*Когда вы упираетесь в границу мира (или взлетаете в небо), ландшафт кончается, и вы видите черноту там, куда небо не достает.
нув назад), вы ни с того ни с сего взлетаете.
+
 
Когда вы упираетесь в границу мира (или взлетаете в небо), ландшафт
+
Первые две проблемы можно решить, отслеживая позицию игрока относительно ландшафта и автоматически сопоставляя высоту камеры и высоту ландшафта. Не самым худшим будет чтение файла ландшафта для извлечения точной высоты нашей текущей позиции. Реализация этой идеи сопряжена с трудностями, потому что ландшафт описан как набор точек и их высот, которые интерполируются для создания гладкой поверхности: пусть точка (0,0) имеет высоту 0, а точка (0,1) высоту 1 – все работает гладко, если игрок решит перейти из точки (0,0) в (0,1). Но в стрелялке от первого лица передвижение измеряется не в целочисленных единицах – игрок может перейти в точку (0,0.1239), а для этого значения в файле данных нет.
кончается, и вы видите черноту там, куда небо не достает.
+
 
Первые две проблемы можно решить, отслеживая позицию игрока
+
Поэтому вместо этого мы прочертим линию или луч от места, где находится наша камера, вниз, затем найдем координату, где он пересекается с ландшафтом. Таким образом мы получим высоту ландшафта относительно нашей координатной системы, а затем надо изменить ее на высоту камеры относительно ландшафта (то есть рост игрока).
относительно ландшафта и автоматически сопоставляя высоту камеры
+
 
и высоту ландшафта. Не самым худшим будет чтение файла ландшаф-
+
Это немного сложнее, но Ogre предоставляет некоторые необходимые ресурсы. Когда начинается кадр, вызывается метод frameStarted (который теперь находится в файле '''chad.cpp''', т.к. довольно быстро разрастается!), и мы обрабатываем нажатия клавиш. Передвинув камеру, надо проверить расстояние до земли и сделать соответствующие поправки.
та для извлечения точной высоты нашей текущей позиции. Реализация
+
 
этой идеи сопряжена с трудностями, потому что ландшафт описан как
+
Ogre предоставляет класс Ray, экземпляр которого можно создать, задав начало луча и направление. Начало задается просто координатами X,Z нашей камеры (координата Y должна быть довольно большой, чтобы гарантировать, что мы находимся над землей), а направление – вниз. Затем мы создаем объект RaySceneObject, который и проведет проверку на пересечение с землей – мы просто передадим объекту луч, созданный ранее, и велим ему выполнить запрос.
набор точек и их высот, которые интерполируются для создания глад-
+
 
кой поверхности: пусть точка (0,0) имеет высоту 0, а точка (0,1) высоту
+
По выполнении запроса мы беремся за пересечение (если оно есть) и устанавливаем высоту камеры соответственно высоте ландшафта. Наконец, надо освободить память, занимаемую запросом, с помощью метода destroyQuery(). Если вы думаете, что это не так легко, вы правы. Но по крайней мере этим не придется заниматься часто. Добавьте следующий код до выражения return true в методе frameStarted():
1 – все работает гладко, если игрок решит перейти из точки (0,0) в (0,1).
+
 
Но в стрелялке от первого лица передвижение измеряется не в целочис-
+
<source lang="cpp">
ленных единицах – игрок может перейти в точку (0,0.1239), а для этого
+
Vector3 campos = m_Camera->getPosition();
значения в файле данных нет.
+
Ray camray(Vector3(campos.x, 1000.0f, campos.z), Vector3::NEGATIVE_UNIT_Y);
Поэтому вместо этого мы прочертим линию или луч от места, где
+
RaySceneQuery* query = m_SceneMgr->createRayQuery(Ray() );
находится наша камера, вниз, затем найдем координату, где он пересе-
+
query->setRay(camray);
кается с ландшафтом. Таким образом мы получим высоту ландшафта
+
RaySceneQueryResult &result = query->execute();
относительно нашей координатной системы, а затем надо изменить ее
+
RaySceneQueryResult::iterator iter = result.begin();
на высоту камеры относительно ландшафта (то есть рост игрока).
+
if (iter != result.end() && iter->worldFragment) {
Это немного сложнее, но Ogre предоставляет некоторые необходи-
+
  float terrainheight = iter->worldFragment->singleIntersection.y;
мые ресурсы. Когда начинается кадр, вызывается метод frameStarted
+
  if ((terrainheight + 3.0f) != campos.y) m_Camera->setPosition(campos.x, terrainheight + 3.0f, campos.z);
(который теперь находится в файле chad.cpp, т.к. довольно быстро
+
  }
разрастается!), и мы обрабатываем нажатия клавиш. Передвинув каме-
+
m_SceneMgr->destroyQuery(query);
ру, надо проверить расстояние до земли и сделать соответствующие
+
</source>
поправки.
+
 
Ogre предоставляет класс Ray, экземпляр которого можно создать,
+
Перекомпилируйте. Вы увидите, что теперь две первые проблемы решены! Ура!
задав начало луча и направление. Начало задается просто координата-
+
ми X,Z нашей камеры (координата Y должна быть довольно большой,
+
чтобы гарантировать, что мы находимся над землей), а направление –
+
вниз. Затем мы создаем объект RaySceneObject, который и проведет
+
проверку на пересечение с землей – мы просто передадим объекту луч,
+
созданный ранее, и велим ему выполнить запрос.
+
По выполнении запроса мы беремся за пересечение (если оно
+
есть) и устанавливаем высоту камеры соответственно высоте ланд-
+
шафта. Наконец, надо освободить память, занимаемую запросом, с
+
помощью метода destroyQuery(). Если вы думаете, что это не так
+
легко, вы правы. Но по крайней мере этим не придется заниматься
+
часто. Добавьте следующий код до выражения return true в методе
+
frameStarted():
+
Vector3 campos = m_Camera->getPosition();
+
Ray camray(Vector3(campos.x, 1000.0f, campos.z), Vector3::NEGATIVE_UNIT_Y);
+
RaySceneQuery* query = m_SceneMgr->createRayQuery(Ray() );
+
query->setRay(camray);
+
RaySceneQueryResult &result = query->execute();
+
RaySceneQueryResult::iterator iter = result.begin();
+
if (iter != result.end() && iter->worldFragment) {
+
  float terrainheight = iter->worldFragment->singleIntersection.y;
+
  if ((terrainheight + 3.0f) != campos.y) m_Camera->setPosition(campos.x, terrainheight + 3.0f, campos.z);
+
  }
+
m_SceneMgr->destroyQuery(query);
+
Перекомпилируйте. Вы увидите, что теперь две первые проблемы
+
решены! Ура!
+
  
 
=== Водный мир ===
 
=== Водный мир ===
Наша третья проблема состоит в том, что, добравшись до границы ланд-
+
Наша третья проблема состоит в том, что, добравшись до границы ландшафта, вы видите черную пустоту там, где кончается небосвод, а могли бы созерцать что-нибудь поприятнее. Ситуацию можно поправить, окружив наш остров океаном – пусть вы все еще выходите «за грань», но по крайней мере видите реалистичную воду.
шафта, вы видите черную пустоту там, где кончается небосвод, а могли
+
 
бы созерцать что-нибудь поприятнее. Ситуацию можно поправить, окру-
+
Используем для нашего океана плоскость, на которую мы можем наложить любой материал (так же, как было с небом). Мы проделаем это за пять шагов, с каждым из которых наша вода будет становиться все привлекательнее.
жив наш остров океаном – пусть вы все еще выходите «за грань», но по
+
 
крайней мере видите реалистичную воду.
+
Сначала необходимо создать плоскость, а затем наложить на нее одну из предустановленных в Ogre водных текстур. Для создания плоскости используем класс Plane и метод createPlane(), затем создадим объект Ogre и добавим плоскость к сцене. Вот этот код:
Используем для нашего океана плоскость, на которую мы можем
+
 
наложить любой материал (так же, как было с небом). Мы проделаем
+
<source lang="cpp">
это за пять шагов, с каждым из которых наша вода будет становиться
+
все привлекательнее.
+
Сначала необходимо создать плоскость, а затем наложить на нее
+
одну из предустановленных в Ogre водных текстур. Для создания плос-
+
кости используем класс Plane и метод createPlane(), затем создадим
+
объект Ogre и добавим плоскость к сцене. Вот этот код:
+
 
Entity* OceanEntity;
 
Entity* OceanEntity;
 
Plane OceanPlane;
 
Plane OceanPlane;
 
OceanPlane.normal = Vector3::UNIT_Y;
 
OceanPlane.normal = Vector3::UNIT_Y;
 
MeshManager::getSingleton().createPlane(
 
MeshManager::getSingleton().createPlane(
“OceanPlane”,
+
  "OceanPlane",
ResourceGroupManager::DEFAULT_RESOURCE_
+
      ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
GROUP_NAME,
+
      OceanPlane,
OceanPlane,
+
      16000, 16000,
16000, 16000,
+
      10, 10,
10, 10,
+
      true, 1,
true, 1,
+
      50, 50,
50, 50,
+
      Vector3::UNIT_Z);
Vector3::UNIT_Z);
+
OceanEntity = m_SceneMgr->createEntity("water", "OceanPlane");
OceanEntity = m_SceneMgr->createEntity(“water”,
+
OceanEntity->setMaterialName("Examples/TextureEffect2");
“OceanPlane”);
+
OceanEntity->setMaterialName(“Examples/TextureEffect2”);
+
 
OceanNode = m_SceneMgr->getRootSceneNode()->createChildScene
 
OceanNode = m_SceneMgr->getRootSceneNode()->createChildScene
Node(“OceanNode”);
+
Node("OceanNode");
 
OceanNode->attachObject(OceanEntity)
 
OceanNode->attachObject(OceanEntity)
Также вам понадобится добавить одну строчку в конец CChadGame
+
</source>
в chad.h:
+
 
 +
Также вам понадобится добавить одну строчку в конец CChadGame в '''chad.h''':
 +
 
 +
<source lang="cpp">
 
SceneNode* OceanNode;
 
SceneNode* OceanNode;
Подойдя к границе острова, вы увидите простирающийся океан,
+
</source>
представленной простой текстурой (Рис. 1). Для первой попытки
+
 
неплохо, но пора создать нашу собственную настраиваемую текстуру
+
Подойдя к границе острова, вы увидите простирающийся океан, представленной простой текстурой <!--(Рис. 1)!-->. Для первой попытки неплохо, но пора создать нашу собственную настраиваемую текстуру воды. Создайте новый файл '''chad.material''' и введите
воды. Создайте новый файл chad.material и введите
+
 
 +
<source lang="cpp">
 
material Chad/Water
 
material Chad/Water
 
{
 
{
technique
+
technique
{
+
{
pass
+
  pass
{
+
  {
ambient 0.2 0.5 0.8
+
  ambient 0.2 0.5 0.8
texture_unit
+
  texture_unit
{
+
  {
texture Water02.jpg
+
    texture Water02.jpg
scroll_anim 0.02 0
+
    scroll_anim 0.02 0
 +
  }
 +
  }
 +
}
 
}
 
}
}
+
</source>
}
+
 
Водная текстура та же, что и до этого, но взято меньшее значение
+
Водная текстура та же, что и до этого, но взято меньшее значение scroll_anim, чтобы вода двигалась не так быстро. Также устанавливается синий цвет среды, чтобы вода стала более темной (Рис. 2). В '''resources.cfg''' проверьте, что за строкой [General] идет:
scroll_anim, чтобы вода двигалась не так быстро. Также устанавли-
+
 
вается синий цвет среды, чтобы вода стала более темной (Рис. 2). В
+
FileSystem=.
resources.cfg проверьте, что за строкой [General] идет:
+
 
FileSystem=.
+
Это позволит нам загружать материалы и другие ресурсы из рабочего каталога нашей игры, поэтому теперь можно использовать '''chad.material'''. Вернемся к файлу '''chad.cpp''': найдите строку
Это позволит нам загружать материалы и другие ресурсы из рабо-
+
 
чего каталога нашей игры, поэтому теперь можно использовать chad.
+
<source lang="cpp">
material. Вернемся к файлу chad.cpp: найдите строку
+
OceanEntity->setMaterialName("Examples/TextureEffect2");
OceanEntity->setMaterialName(“Examples/TextureEffect2”);
+
</source>
 +
 
 
и замените ее на
 
и замените ее на
OceanEntity->setMaterialName(“Chad/Water”);
+
 
Перекомпилируйте и увидите, что океан стал малость (ну самую
+
<source lang="cpp">
малость) реалистичнее.
+
OceanEntity->setMaterialName("Chad/Water");
Следующий шаг – использование материала из двух текстур, одна
+
</source>
из которых будет двигаться по прямой, а вторая будет менять свое
+
 
направление во время движения. Вместе они придают более естест-
+
Перекомпилируйте и увидите, что океан стал малость (ну самую малость) реалистичнее.
венный вид воде и больше реализма игре. В chad.material добавьте
+
 
второй блок texture_unit сразу же после первого:
+
Следующий шаг – использование материала из двух текстур, одна из которых будет двигаться по прямой, а вторая будет менять свое направление во время движения. Вместе они придают более естественный вид воде и больше реализма игре. В '''chad.material''' добавьте второй блок texture_unit сразу же после первого:
 +
 
 +
<source lang="cpp">
 
texture_unit
 
texture_unit
 
{
 
{
texture Water02.jpg
+
  texture Water02.jpg
wave_xform scroll_y sine 0 0.1 0 0.1
+
  wave_xform scroll_y sine 0 0.1 0 0.1
 
}
 
}
Так как мы меняли только файл с материалом, запускать make необ-
+
</source>
ходимости нет: просто наберите chad – и увидите новую воду.
+
 
На следующем этапе, специально для любителей горных омутов,
+
Так как мы меняли только файл с материалом, запускать make необходимости нет: просто наберите chad – и увидите новую воду.
поднимем нашу воду над землей так, чтобы она затопила низины лан-
+
 
дшафта и образовала бассейны (Рис. 3). Это очень просто – добавьте
+
На следующем этапе, специально для любителей горных омутов, поднимем нашу воду над землей так, чтобы она затопила низины ландшафта и образовала бассейны (Рис. 3). Это очень просто – добавьте одну строчку в конец метода createScene():
одну строчку в конец метода createScene():
+
 
 +
<source lang="cpp">
 
OceanNode->translate(2000, 20, 0);
 
OceanNode->translate(2000, 20, 0);
И последнее изменение: добавим воде немного движения, напо-
+
</source>
минающего легкий прилив/отлив. Реализацию можно осуществить
+
 
множеством хороших, продвинутых и сложных способов – но мы этого
+
И последнее изменение: добавим воде немного движения, напоминающего легкий прилив/отлив. Реализацию можно осуществить множеством хороших, продвинутых и сложных способов – но мы этого делать не будем! На самом деле наша плоскость с водой будет подниматься и опускаться каждый кадр. Некоторые норовят написать для этого не меньше десяти строк кода, хотя на самом деле нужно всего три. Вот первая – добавьте ее под CChadGame в файле '''chad.'''h, но до определения OceanNode:
делать не будем! На самом деле наша плоскость с водой будет подни-
+
 
маться и опускаться каждый кадр. Некоторые норовят написать для
+
<source lang="cpp">
этого не меньше десяти строк кода, хотя на самом деле нужно всего
+
три. Вот первая – добавьте ее под CChadGame в файле chad.h, но до
+
определения OceanNode:
+
 
double m_OceanFlowNum;
 
double m_OceanFlowNum;
Теперь добавьте следующие две строки в конец frameStarted() в
+
</source>
файле chad.cpp перед выражением return true:
+
 
 +
Теперь добавьте следующие две строки в конец frameStarted() в файле '''chad.cpp''' перед выражением return true:
 +
 
 +
<source lang="cpp">
 
OceanNode->translate(0, sin(m_OceanFlowNum) / 2000, 0);
 
OceanNode->translate(0, sin(m_OceanFlowNum) / 2000, 0);
 
m_OceanFlowNum += 0.001;
 
m_OceanFlowNum += 0.001;
Теперь у нас есть вода, которая использует две независимо двигаю-
+
</source>
щиеся текстуры, и она имеет определенную высоту, что позволяет обра-
+
 
зовать небольшие водоемы на острове. В качестве бонуса, мы добавили
+
Теперь у нас есть вода, которая использует две независимо двигающиеся текстуры, и она имеет определенную высоту, что позволяет образовать небольшие водоемы на острове. В качестве бонуса, мы добавили небольшой эффект текучести, придав сцене динамики. Пока этого более чем достаточно; перейдем к следующей теме...
небольшой эффект текучести, придав сцене динамики. Пока этого более
+
чем достаточно; перейдем к следующей теме...
+
  
 
=== Берем еще одну планку ===
 
=== Берем еще одну планку ===
Все выглядит неплохо, но на этом уроке я хочу реализовать еще одну
+
Все выглядит неплохо, но на этом уроке я хочу реализовать еще одну вещь: простую инфраструктуру для нашего игрока. Развивать ее мы будем после, а сейчас я хочу представить вам базовый класс игрока, отслеживающий, бежим мы или идем. В данный момент наша камера по нажатию клавиш передвигается на фиксированное расстояние, поэтому надо немного поработать, чтобы класс игрока сам решал, как мы должны передвигаться, в зависимости от состояния (бежит, ползет, прыгает и так далее).
вещь: простую инфраструктуру для нашего игрока. Развивать ее мы
+
 
будем после, а сейчас я хочу представить вам базовый класс игрока,
+
Заинтригованы? Отлично. Создайте новый файл '''chadplayer.h''' и запишите в него следующее:
отслеживающий, бежим мы или идем. В данный момент наша каме-
+
 
ра по нажатию клавиш передвигается на фиксированное расстояние,
+
<source lang="cpp">
поэтому надо немного поработать, чтобы класс игрока сам решал, как
+
мы должны передвигаться, в зависимости от состояния (бежит, ползет,
+
прыгает и так далее).
+
Заинтригованы? Отлично. Создайте новый файл chadplayer.h и
+
запишите в него следующее:
+
 
class CChadPlayer {
 
class CChadPlayer {
 
public:
 
public:
CChadPlayer();
+
  CChadPlayer();
float getSpeed();
+
  float getSpeed();
bool m_IsRunning;
+
  bool m_IsRunning;
bool m_IsCrouching;
+
  bool m_IsCrouching;
 
};
 
};
Это определение простого класса игрока; позже мы добавим и дру-
+
</source>
гие элементы. Сейчас он содержит только метод getSpeed(), который
+
 
возвращает скорость, с которой надо передвинуть игрока, а также две
+
Это определение простого класса игрока; позже мы добавим и другие элементы. Сейчас он содержит только метод getSpeed(), который возвращает скорость, с которой надо передвинуть игрока, а также две булевые переменные, отслеживающие наше состояние – бежим или ползем. Реализация этого класса, '''chadplayer.cpp''', выглядит так:
булевые переменные, отслеживающие наше состояние – бежим или
+
 
ползем. Реализация этого класса, chadplayer.cpp, выглядит так:
+
<source lang="cpp">
#include “chadplayer.h”
+
#include "chadplayer.h"
 
CChadPlayer::CChadPlayer() {
 
CChadPlayer::CChadPlayer() {
m_IsRunning = false;
+
  m_IsRunning = false;
m_IsCrouching = false;
+
  m_IsCrouching = false;
 
}
 
}
 
float CChadPlayer::getSpeed() {
 
float CChadPlayer::getSpeed() {
if (m_IsCrouching) return 0.005f;
+
  if (m_IsCrouching) return 0.005f;
if (m_IsRunning) return 0.2f;
+
  if (m_IsRunning) return 0.2f;
return 0.1f;
+
  return 0.1f;
 
}
 
}
Пока ничего сложного, но, как я уже сказал, на следующих уроках
+
</source>
мы расширим его возможности.
+
 
Следующим шагом создадим и удалим объект игрока внутри chad.
+
Пока ничего сложного, но, как я уже сказал, на следующих уроках мы расширим его возможности.
cpp, поэтому добавьте эту строку после объявления m_Viewport в
+
 
chad.h...
+
Следующим шагом создадим и удалим объект игрока внутри '''chad.cpp''', поэтому добавьте эту строку после объявления m_Viewport в '''chad.h'''...
 +
 
 +
<source lang="cpp">
 
CChadPlayer* m_Player;
 
CChadPlayer* m_Player;
...затем в chad.cpp добавьте строку в начало метода run()...
+
</source>
 +
 
 +
...затем в '''chad.cpp''' добавьте строку в начало метода run()...
  
 +
<source lang="cpp">
 
m_Player = new CChadPlayer();
 
m_Player = new CChadPlayer();
...и, наконец, строку в деструктор ~CChadGame() в chad.cpp
+
</source>
 +
 
 +
...и, наконец, строку в деструктор ~CChadGame() в '''chad.cpp'''
 +
 
 +
<source lang="cpp">
 
delete m_Player;
 
delete m_Player;
Мы добавили код для игрока, но чтобы игра заново скомпилиро-
+
</source>
валась, необходимо подредактировать Makefile так, чтобы chadplayer.
+
 
cpp оказался в списке сразу после chad.cpp в цели all.
+
Мы добавили код для игрока, но чтобы игра заново скомпилировалась, необходимо подредактировать Makefile так, чтобы '''chadplayer.cpp''' оказался в списке сразу после '''chad.cpp''' в цели all.
Теперь надо изменить код, отвечающий за передвижение игрока
+
 
в методе frameStarted(), чтобы он использовал метод getSpeed().
+
Теперь надо изменить код, отвечающий за передвижение игрока в методе frameStarted(), чтобы он использовал метод getSpeed(). Самым простым будет вызвать getSpeed() четыре раза, но гораздо лучше использовать промежуточную переменную, например:
Самым простым будет вызвать getSpeed() четыре раза, но гораздо
+
 
лучше использовать промежуточную переменную, например:
+
<source lang="cpp">
 
float playerspeed = m_Player->getSpeed();
 
float playerspeed = m_Player->getSpeed();
if (m_InputReader->isKeyDown(KC_W)) translateVector.z =
+
if (m_InputReader->isKeyDown(KC_W)) translateVector.z = -playerspeed;
-playerspeed;
+
if (m_InputReader->isKeyDown(KC_S)) translateVector.z = +playerspeed;
if (m_InputReader->isKeyDown(KC_S)) translateVector.z =
+
if (m_InputReader->isKeyDown(KC_A)) translateVector.x = -playerspeed;
+playerspeed;
+
if (m_InputReader->isKeyDown(KC_D)) translateVector.x = +playerspeed;
if (m_InputReader->isKeyDown(KC_A)) translateVector.x =
+
</source>
-playerspeed;
+
 
if (m_InputReader->isKeyDown(KC_D)) translateVector.x =
+
Теперь игра должна компилироваться и запускаться, хотя сейчас вы никакой разницы не заметите, потому что мы не устанавливали значения m_IsRunnig и m_IsCrouching. Приступим к последнему заданию на сегодня
+playerspeed;
+
Теперь игра должна компилироваться и запускаться, хотя сейчас вы
+
никакой разницы не заметите, потому что мы не устанавливали значе-
+
ния m_IsRunnig и m_IsCrouching. Приступим к последнему заданию
+
на сегодня
+
  
 
=== Идем верным путем ===
 
=== Идем верным путем ===
Это сравнительно небольшое изменение, но оно действительно
+
 
придаст игре достойный вид. Мы собираемся отслеживать нажатие
+
{{Врезка
левого Ctrl (ползти) и левого Shift (бежать). Придется немного пора-
+
|Заголовок=Тестируем воду
ботать, потому что надо расширить класс CChadGame, чтобы реализо-
+
|Содержание=На каждом новом уроке мы будем увеличивать реализм игры, чтобы игрок все больше втягивался в игровой мир. Но, пытаясь быть реалистичным, вы
вать класс KeyListener (а также классы FrameListener, MouseListner,
+
столкнетесь с проблемой, похожей на ту, что мы получили с водой – что считать реализмом? Какого цвета должна быть вода – синего, зеленого, смешанного или просто прозрачной? Должны ли отдаленные предметы заволакиваться дымкой? Если да, то насколько?
MouseMotionListener).
+
Если вы живете в правильном месте, можете выйти наружу и посмотреть, какие цвета и настройки использовать для игры; увы, для большинства из нас это роскошь. Но не горюйте: иногда реализм не означает приближения к реальной жизни.
Шаг первый: добавьте в проект файл OgreKeyEvent.h. Он позво-
+
На самом деле необходимо выдерживать стереотип, который сложился у сотен людей, листавших каталоги путешествий. Сделайте вашу воду синей (или зеленой), на собственный вкус, лишь бы она казалась реальной лично вам и не мешала продолжать разработку – только это нам и важно.
лит определять, какая клавиша была нажата. Добавьте следующий код
+
|Ширина=250px
рядом с другими выражениями #include в chad.h:
+
}}
#include “OgreKeyEvent.h”
+
 
 +
Это сравнительно небольшое изменение, но оно действительно придаст игре достойный вид. Мы собираемся отслеживать нажатие левого Ctrl (ползти) и левого Shift (бежать). Придется немного поработать, потому что надо расширить класс CChadGame, чтобы реализовать класс KeyListener (а также классы FrameListener, MouseListner, MouseMotionListener).
 +
 
 +
Шаг первый: добавьте в проект файл '''OgreKeyEvent.h'''. Он позволит определять, какая клавиша была нажата. Добавьте следующий код рядом с другими выражениями #include в '''chad.h''':
 +
 
 +
<source lang="cpp">
 +
#include "OgreKeyEvent.h"
 +
</source>
 +
 
 
Ниже вы увидите строку:
 
Ниже вы увидите строку:
class CChadGame : public FrameListener, public
+
 
MouseListener, public MouseMotionListener {
+
<source lang="cpp">
 +
class CChadGame : public FrameListener, public MouseListener, public MouseMotionListener {
 +
</source>
 +
 
 
Добавьте после MouseMotionListner запятую, а затем KeyListner.
 
Добавьте после MouseMotionListner запятую, а затем KeyListner.
Идем дальше вниз – перед строкой Root* m_Ogre добавьте эти
+
 
три строки:
+
Идем дальше вниз – перед строкой Root* m_Ogre добавьте эти три строки:
 +
 
 +
<source lang="cpp">
 
void keyPressed(KeyEvent *e);
 
void keyPressed(KeyEvent *e);
 
void keyReleased(KeyEvent *e);
 
void keyReleased(KeyEvent *e);
 
void keyClicked(KeyEvent *e) { }
 
void keyClicked(KeyEvent *e) { }
Эти три метода необходимы для реализации класса KeyListener.
+
</source>
Мы собираемся реализовать первые два метода, когда клавиша нажата
+
 
и когда отпущена соответственно. Последний метод мы оставим пустым,
+
Эти три метода необходимы для реализации класса KeyListener. Мы собираемся реализовать первые два метода, когда клавиша нажата и когда отпущена соответственно. Последний метод мы оставим пустым, так как он нам не нужен – этот метод вызывается, когда клавиша сначала нажимается, затем отпускается.
так как он нам не нужен – этот метод вызывается, когда клавиша снача-
+
 
ла нажимается, затем отпускается.
+
Покончив с файлом '''chad.h''', переходим к '''chad.cpp''', в котором нам надо реализовать методы keyPressed() и keyReleased():
Покончив с файлом chad.h, переходим к chad.cpp, в котором нам
+
 
надо реализовать методы keyPressed() и keyReleased():
+
<source lang="cpp">
 
void CChadGame::keyPressed(KeyEvent *e) {
 
void CChadGame::keyPressed(KeyEvent *e) {
printf(“Key pressed!\n”);
+
  printf("Key pressed!\n");
switch (e->getKey()) {
+
  switch (e->getKey()) {
case KC_LCONTROL:
+
    case KC_LCONTROL:
m_Player->m_IsCrouching = true;
+
    m_Player->m_IsCrouching = true;
break;
+
    break;
case KC_LSHIFT:
+
    case KC_LSHIFT:
m_Player->m_IsRunning = true;
+
    m_Player->m_IsRunning = true;
break;
+
    break;
}
+
  }
 
}
 
}
 
void CChadGame::keyReleased(KeyEvent *e) {
 
void CChadGame::keyReleased(KeyEvent *e) {
switch (e->getKey()) {
+
  switch (e->getKey()) {
case KC_LCONTROL:
+
    case KC_LCONTROL:
m_Player->m_IsCrouching = false;
+
    m_Player->m_IsCrouching = false;
break;
+
    break;
case KC_LSHIFT:
+
    case KC_LSHIFT:
m_Player->m_IsRunning = false;
+
    m_Player->m_IsRunning = false;
break;
+
    break;
 +
  }
 
}
 
}
}
+
</source>
Не думаю, что стоит комментировать этот код, но на всякий слу-
+
 
чай скажу, что KC_LCONTROL означает ‘код клавиши левого Ctrl’, а
+
Не думаю, что стоит комментировать этот код, но на всякий случай скажу, что KC_LCONTROL означает 'код клавиши левого Ctrl', а KC_LSHIFT означает 'код левого Shift'.
KC_LSHIFT означает ‘код левого Shift’.
+
 
Далее найдите метод addFrameListner(). Сразу же за ним добавь-
+
Далее найдите метод addFrameListner(). Сразу же за ним добавьте эту строчку:
те эту строчку:
+
 
 +
<source lang="cpp">
 
m_Ogre->addKeyListener(this);
 
m_Ogre->addKeyListener(this);
Сохраните все файлы и запустите make – теперь вы играете в
+
</source>
полноценную игру: можете и бегать, и на четвереньках ползти, наблю-
+
 
дать волны и даже наслаждаться простым обнаружением столкно-
+
Сохраните все файлы и запустите make – теперь вы играете в полноценную игру: можете и бегать, и на четвереньках ползти, наблюдать волны и даже наслаждаться простым обнаружением столкновений. Система ввода Ogre хотя и слабовата, но работает, и поэтому мы можем в следующем номере заняться чем-нибудь поинтереснее. Присоединяйтесь!
вений. Система ввода Ogre хотя и слабовата, но работает, и поэтому
+
 
мы можем в следующем номере заняться чем-нибудь поинтереснее.
+
[[Категория:Учебники]]
Присоединяйтесь!
+

Текущая версия на 15:13, 27 июня 2008

Содержание

[править] Ogre. Добавь движенья и воды!

ЧАСТЬ 2 Что толку от изменяющегося ландшафта, если вы прикованы к месту? Пол Хадсон знает ответ.

В прошлый раз мы начали работать над игрой – кандидатом в самые продаваемые в 2007 году: Висельник Чед. Мы поместили нашего героя Чеда Холла на автоматически сгенерированный ландшафт под пустым небосводом. Он готов убегать от полицейских. Но из-за пространственных ограничений мы не дали ему возможности перемещаться по игровому полю – он может только осмотреться вокруг при помощи игрока, держащего мышь. Какой преступник не может убежать от полиции? Только совершенно бестолковый. Значит, надо сделать его подвижным: пусть носится по территории. Итак, ввод с клавиатуры будет преобразовываться в движение по горизонтали и по вертикали в соответствии с рельефом.

[править] Театр Буфф(еризации)

Для начала добавим поддержку стандартных клавиш WASD. Нам самим необходимо перехватывать нажатия клавиш, а затем передавать их в Ogre, чтобы он соответственным образом переместил камеру (помните, что это стрелялка от первого лица, поэтому на самом деле мы Чеда не видим). В прошлый раз мы уже обработали клавишу Escape, поэтому вы уже примерно представляете, как написать новую часть кода. Чтобы было немного понятнее, я объединил классы CChadFrameListner и CChadGame в один класс (CChadGame). Теперь этот класс занимается и обработкой кадра, и вводом от клавиатуры, а в конце концов будет также обрабатывать механизмы игры – надеюсь, это сделает код более читабельным, ценой легкой объектной инкапсуляции!

Для начала необходимо буферизовать ввод Ogre. Это значит, что если кто-то лупит по клавишам быстрее, чем Ogre успевает обработать, то Ogre сможет отследить, сколько раз была нажата клавиша, а затем осуществить обработку нажатий как можно скорее – даже в случае, когда клавиша была позже отжата. Например, если некий особо продвинутый игрок путешествует по нашему игровому миру, совершая хитрые трюки и маневры, чтобы побить других игроков, он может превысить число вводов в единицу времени, поддающееся обработке. Если мы будем буферизовать все, то игрок, может, пальнет и на 10 миллисекунд позже, чем планировал, но все-таки пальнет!

Достигнуть этого можно всего одной строкой кода Ogre. Добавьте следующее в файл chad.h в конструкторе, после остальных строк:

m_InputDevice->setBufferedInput(true, true);

Теперь займемся нажимаемыми клавишами – для них потребуется несколько вызовов метода isKeyDown(). Создадим вектор, задающий направление перехода (просто линия, соединяющая конечную и начальную точки); мы можем сказать Ogre, что хотим прибавить или вычесть смещение по X, Y и Z. Связав эту операцию с нажатием клавиш, мы и получим движение.

Если вы боитесь, что это сложно, расслабьтесь – я представлю вам восхитительные 6 строк, которые выполняют всю работу. Вставьте их перед return true в методе frameStarted() заголовка chadframelistner.h.

Vector3 translateVector = Vector3::ZERO;
if (m_InputReader->isKeyDown(KC_W)) translateVector.z = -0.1f;
if (m_InputReader->isKeyDown(KC_S)) translateVector.z = +0.1f;
if (m_InputReader->isKeyDown(KC_A)) translateVector.x = -0.1f;
if (m_InputReader->isKeyDown(KC_D)) translateVector.x = +0.1f;
m_Camera->moveRelative(translateVector);

Сохраните, наберите make и запустите игру – наслаждайтесь новообретенной свободой!

[править] Птица высокого полета

Передвигаясь, вы столкнетесь с тремя большими проблемами:

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

Первые две проблемы можно решить, отслеживая позицию игрока относительно ландшафта и автоматически сопоставляя высоту камеры и высоту ландшафта. Не самым худшим будет чтение файла ландшафта для извлечения точной высоты нашей текущей позиции. Реализация этой идеи сопряжена с трудностями, потому что ландшафт описан как набор точек и их высот, которые интерполируются для создания гладкой поверхности: пусть точка (0,0) имеет высоту 0, а точка (0,1) высоту 1 – все работает гладко, если игрок решит перейти из точки (0,0) в (0,1). Но в стрелялке от первого лица передвижение измеряется не в целочисленных единицах – игрок может перейти в точку (0,0.1239), а для этого значения в файле данных нет.

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

Это немного сложнее, но Ogre предоставляет некоторые необходимые ресурсы. Когда начинается кадр, вызывается метод frameStarted (который теперь находится в файле chad.cpp, т.к. довольно быстро разрастается!), и мы обрабатываем нажатия клавиш. Передвинув камеру, надо проверить расстояние до земли и сделать соответствующие поправки.

Ogre предоставляет класс Ray, экземпляр которого можно создать, задав начало луча и направление. Начало задается просто координатами X,Z нашей камеры (координата Y должна быть довольно большой, чтобы гарантировать, что мы находимся над землей), а направление – вниз. Затем мы создаем объект RaySceneObject, который и проведет проверку на пересечение с землей – мы просто передадим объекту луч, созданный ранее, и велим ему выполнить запрос.

По выполнении запроса мы беремся за пересечение (если оно есть) и устанавливаем высоту камеры соответственно высоте ландшафта. Наконец, надо освободить память, занимаемую запросом, с помощью метода destroyQuery(). Если вы думаете, что это не так легко, вы правы. Но по крайней мере этим не придется заниматься часто. Добавьте следующий код до выражения return true в методе frameStarted():

Vector3 campos = m_Camera->getPosition();
Ray camray(Vector3(campos.x, 1000.0f, campos.z), Vector3::NEGATIVE_UNIT_Y);
RaySceneQuery* query = m_SceneMgr->createRayQuery(Ray() );
query->setRay(camray);
RaySceneQueryResult &result = query->execute();
RaySceneQueryResult::iterator iter = result.begin();
if (iter != result.end() && iter->worldFragment) {
  float terrainheight = iter->worldFragment->singleIntersection.y;
  if ((terrainheight + 3.0f) != campos.y) m_Camera->setPosition(campos.x, terrainheight + 3.0f, campos.z);
  }
m_SceneMgr->destroyQuery(query);

Перекомпилируйте. Вы увидите, что теперь две первые проблемы решены! Ура!

[править] Водный мир

Наша третья проблема состоит в том, что, добравшись до границы ландшафта, вы видите черную пустоту там, где кончается небосвод, а могли бы созерцать что-нибудь поприятнее. Ситуацию можно поправить, окружив наш остров океаном – пусть вы все еще выходите «за грань», но по крайней мере видите реалистичную воду.

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

Сначала необходимо создать плоскость, а затем наложить на нее одну из предустановленных в Ogre водных текстур. Для создания плоскости используем класс Plane и метод createPlane(), затем создадим объект Ogre и добавим плоскость к сцене. Вот этот код:

Entity* OceanEntity;
Plane OceanPlane;
OceanPlane.normal = Vector3::UNIT_Y;
MeshManager::getSingleton().createPlane(
  "OceanPlane",
      ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
      OceanPlane,
      16000, 16000,
      10, 10,
      true, 1,
      50, 50,
      Vector3::UNIT_Z);
OceanEntity = m_SceneMgr->createEntity("water", "OceanPlane");
OceanEntity->setMaterialName("Examples/TextureEffect2");
OceanNode = m_SceneMgr->getRootSceneNode()->createChildScene
Node("OceanNode");
OceanNode->attachObject(OceanEntity)

Также вам понадобится добавить одну строчку в конец CChadGame в chad.h:

SceneNode* OceanNode;

Подойдя к границе острова, вы увидите простирающийся океан, представленной простой текстурой . Для первой попытки неплохо, но пора создать нашу собственную настраиваемую текстуру воды. Создайте новый файл chad.material и введите

material Chad/Water
{
 technique
 {
  pass
  {
   ambient 0.2 0.5 0.8
   texture_unit
   {
    texture Water02.jpg
    scroll_anim 0.02 0
   }
  }
 }
}

Водная текстура та же, что и до этого, но взято меньшее значение scroll_anim, чтобы вода двигалась не так быстро. Также устанавливается синий цвет среды, чтобы вода стала более темной (Рис. 2). В resources.cfg проверьте, что за строкой [General] идет:

FileSystem=.

Это позволит нам загружать материалы и другие ресурсы из рабочего каталога нашей игры, поэтому теперь можно использовать chad.material. Вернемся к файлу chad.cpp: найдите строку

OceanEntity->setMaterialName("Examples/TextureEffect2");

и замените ее на

OceanEntity->setMaterialName("Chad/Water");

Перекомпилируйте и увидите, что океан стал малость (ну самую малость) реалистичнее.

Следующий шаг – использование материала из двух текстур, одна из которых будет двигаться по прямой, а вторая будет менять свое направление во время движения. Вместе они придают более естественный вид воде и больше реализма игре. В chad.material добавьте второй блок texture_unit сразу же после первого:

texture_unit
{
   texture Water02.jpg
   wave_xform scroll_y sine 0 0.1 0 0.1
}

Так как мы меняли только файл с материалом, запускать make необходимости нет: просто наберите chad – и увидите новую воду.

На следующем этапе, специально для любителей горных омутов, поднимем нашу воду над землей так, чтобы она затопила низины ландшафта и образовала бассейны (Рис. 3). Это очень просто – добавьте одну строчку в конец метода createScene():

OceanNode->translate(2000, 20, 0);

И последнее изменение: добавим воде немного движения, напоминающего легкий прилив/отлив. Реализацию можно осуществить множеством хороших, продвинутых и сложных способов – но мы этого делать не будем! На самом деле наша плоскость с водой будет подниматься и опускаться каждый кадр. Некоторые норовят написать для этого не меньше десяти строк кода, хотя на самом деле нужно всего три. Вот первая – добавьте ее под CChadGame в файле chad.h, но до определения OceanNode:

double m_OceanFlowNum;

Теперь добавьте следующие две строки в конец frameStarted() в файле chad.cpp перед выражением return true:

OceanNode->translate(0, sin(m_OceanFlowNum) / 2000, 0);
m_OceanFlowNum += 0.001;

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

[править] Берем еще одну планку

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

Заинтригованы? Отлично. Создайте новый файл chadplayer.h и запишите в него следующее:

class CChadPlayer {
public:
  CChadPlayer();
  float getSpeed();
  bool m_IsRunning;
  bool m_IsCrouching;
};

Это определение простого класса игрока; позже мы добавим и другие элементы. Сейчас он содержит только метод getSpeed(), который возвращает скорость, с которой надо передвинуть игрока, а также две булевые переменные, отслеживающие наше состояние – бежим или ползем. Реализация этого класса, chadplayer.cpp, выглядит так:

#include "chadplayer.h"
CChadPlayer::CChadPlayer() {
  m_IsRunning = false;
  m_IsCrouching = false;
}
float CChadPlayer::getSpeed() {
  if (m_IsCrouching) return 0.005f;
  if (m_IsRunning) return 0.2f;
  return 0.1f;
}

Пока ничего сложного, но, как я уже сказал, на следующих уроках мы расширим его возможности.

Следующим шагом создадим и удалим объект игрока внутри chad.cpp, поэтому добавьте эту строку после объявления m_Viewport в chad.h...

CChadPlayer* m_Player;

...затем в chad.cpp добавьте строку в начало метода run()...

m_Player = new CChadPlayer();

...и, наконец, строку в деструктор ~CChadGame() в chad.cpp

delete m_Player;

Мы добавили код для игрока, но чтобы игра заново скомпилировалась, необходимо подредактировать Makefile так, чтобы chadplayer.cpp оказался в списке сразу после chad.cpp в цели all.

Теперь надо изменить код, отвечающий за передвижение игрока в методе frameStarted(), чтобы он использовал метод getSpeed(). Самым простым будет вызвать getSpeed() четыре раза, но гораздо лучше использовать промежуточную переменную, например:

float playerspeed = m_Player->getSpeed();
if (m_InputReader->isKeyDown(KC_W)) translateVector.z = -playerspeed;
if (m_InputReader->isKeyDown(KC_S)) translateVector.z = +playerspeed;
if (m_InputReader->isKeyDown(KC_A)) translateVector.x = -playerspeed;
if (m_InputReader->isKeyDown(KC_D)) translateVector.x = +playerspeed;

Теперь игра должна компилироваться и запускаться, хотя сейчас вы никакой разницы не заметите, потому что мы не устанавливали значения m_IsRunnig и m_IsCrouching. Приступим к последнему заданию на сегодня

[править] Идем верным путем

Это сравнительно небольшое изменение, но оно действительно придаст игре достойный вид. Мы собираемся отслеживать нажатие левого Ctrl (ползти) и левого Shift (бежать). Придется немного поработать, потому что надо расширить класс CChadGame, чтобы реализовать класс KeyListener (а также классы FrameListener, MouseListner, MouseMotionListener).

Шаг первый: добавьте в проект файл OgreKeyEvent.h. Он позволит определять, какая клавиша была нажата. Добавьте следующий код рядом с другими выражениями #include в chad.h:

#include "OgreKeyEvent.h"

Ниже вы увидите строку:

class CChadGame : public FrameListener, public MouseListener, public MouseMotionListener {

Добавьте после MouseMotionListner запятую, а затем KeyListner.

Идем дальше вниз – перед строкой Root* m_Ogre добавьте эти три строки:

void keyPressed(KeyEvent *e);
void keyReleased(KeyEvent *e);
void keyClicked(KeyEvent *e) { }

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

Покончив с файлом chad.h, переходим к chad.cpp, в котором нам надо реализовать методы keyPressed() и keyReleased():

void CChadGame::keyPressed(KeyEvent *e) {
  printf("Key pressed!\n");
  switch (e->getKey()) {
    case KC_LCONTROL:
    m_Player->m_IsCrouching = true;
    break;
    case KC_LSHIFT:
    m_Player->m_IsRunning = true;
    break;
  }
}
void CChadGame::keyReleased(KeyEvent *e) {
  switch (e->getKey()) {
    case KC_LCONTROL:
    m_Player->m_IsCrouching = false;
    break;
    case KC_LSHIFT:
    m_Player->m_IsRunning = false;
    break;
  }
}

Не думаю, что стоит комментировать этот код, но на всякий случай скажу, что KC_LCONTROL означает 'код клавиши левого Ctrl', а KC_LSHIFT означает 'код левого Shift'.

Далее найдите метод addFrameListner(). Сразу же за ним добавьте эту строчку:

m_Ogre->addKeyListener(this);

Сохраните все файлы и запустите make – теперь вы играете в полноценную игру: можете и бегать, и на четвереньках ползти, наблюдать волны и даже наслаждаться простым обнаружением столкновений. Система ввода Ogre хотя и слабовата, но работает, и поэтому мы можем в следующем номере заняться чем-нибудь поинтереснее. Присоединяйтесь!

Персональные инструменты
купить
подписаться
Яндекс.Метрика