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

LXF83:Ogre

Материал из Linuxformat
Перейти к: навигация, поиск

Содержание

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; Подойдя к границе острова, вы увидите простирающийся океан, представленной простой текстурой (Рис. 1). Для первой попытки неплохо, но пора создать нашу собственную настраиваемую текстуру воды. Создайте новый файл 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, выглядит так:

  1. 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:

  1. 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 хотя и слабовата, но работает, и поэтому мы можем в следующем номере заняться чем-нибудь поинтереснее. Присоединяйтесь!

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