LXF86:Учебники:Ogre
Aldebober (обсуждение | вклад) (→Прицел) |
Aldebober (обсуждение | вклад) |
||
Строка 81: | Строка 81: | ||
Параметр zorder определяет, где панель отобразится на экране, в терминах глубины, то есть мы можем (если пожелаем) задать способ расположения элементов по слоям. В Ogre его максимальное значение 650 (прицел на вершине стека). Последний шаг – создание слоя, он займет всего две строки. Добавьте такой код в конец метода createOutdoorScene(): | Параметр zorder определяет, где панель отобразится на экране, в терминах глубины, то есть мы можем (если пожелаем) задать способ расположения элементов по слоям. В Ogre его максимальное значение 650 (прицел на вершине стека). Последний шаг – создание слоя, он займет всего две строки. Добавьте такой код в конец метода createOutdoorScene(): | ||
− | Overlay *TargetSight = (Overlay*)OverlayManager::getSingleton(). getByName(“chadtarget”); | + | Overlay *TargetSight = (Overlay*)OverlayManager::getSingleton(). |
+ | getByName(“chadtarget”); | ||
TargetSight->show(); | TargetSight->show(); | ||
Код загружает слой с именем, определенным в target.overlay, а затем отображает его. Не надо беспокоиться о потере указателя на слой – Ogre автоматически удерживает его посреди экрана, как определено в файле. Наши три шага проделаны; запустите игру и загляните в прицел. Пусть программировать было скучновато, но зато как удобно теперь целиться! | Код загружает слой с именем, определенным в target.overlay, а затем отображает его. Не надо беспокоиться о потере указателя на слой – Ogre автоматически удерживает его посреди экрана, как определено в файле. Наши три шага проделаны; запустите игру и загляните в прицел. Пусть программировать было скучновато, но зато как удобно теперь целиться! | ||
+ | |||
+ | === Стреляем на поражение === | ||
+ | |||
+ | Настает главное событие этого урока: отстрел роботов, которые резвились в прошлом номере. Правду сказать, я не особо хотел отягчать насилием Висельника Чеда, но Ребекка – спец по насилию в нашей команде – отказалась плодить опечатки, пока мы не разнесем когонибудь на куски. Пусть будет так. У нас уже есть метод mousePressed() для проигрывания звука лазера, поэтому код для стрельбы подойдет именно сюда. Стрелять будем так: с позиции камеры проводим луч, аналогично тому, как мы делали для определения высоты игрока над уровнем земли. Это непростая геометрическая задача, но, к счастью, Ogre все делает сам, одним методом: getCameraToViewportRay(), который преобразует позицию на экране в позицию в нашем мире и позволяет пустить луч из положения камеры. При необходимости выбирать объекты мышью, можно было бы использовать указатель на объект-событие, передаваемый методу mousePressed(), но мы хотим просто пустить луч через центр экрана, и поэтому используем для координат X и Y значения 0.5. Вот код улучшенного метода mousePressed(): | ||
+ | void CChadGame::mousePressed(MouseEvent* e) { | ||
+ | Mix_PlayChannel(-1, m_MixFire, 0); | ||
+ | Ray mouseray = m_Camera->getCameraToViewportRay(0.5, | ||
+ | 0.5); | ||
+ | RaySceneQuery* scenequery = m_SceneMgr | ||
+ | ->createRayQuery(Ray()); | ||
+ | scenequery->setRay(mouseray); | ||
+ | RaySceneQueryResult &result = scenequery->execute(); | ||
+ | if (!result.empty()) { | ||
+ | for (unsigned int i = 0; i < result.size(); ++i) { | ||
+ | RaySceneQueryResultEntry &re = result[i]; | ||
+ | if (re.movable && re.movable->getMovableType() == | ||
+ | “Entity”) { | ||
+ | Entity *ent = (Entity*)(re.movable); | ||
+ | String name = ent->getName(); | ||
+ | if (name == “water”) continue; // игнорируем воду | ||
+ | for (unsigned int j = 0; j < Enemies.size(); ++j) { | ||
+ | if (Enemies[j]->m_EnemyName == | ||
+ | name) { | ||
+ | Enemies[j]->Hit(); | ||
+ | return; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | Послав луч, я прошелся в цикле по откликам в поисках сущностей (в отличие от | ||
+ | элементов ландшафта), а затем отсек водную поверхность. Остаются только роботы; получим имя жертвы и найдем в | ||
+ | списке роботов соответствующий объект, а затем вызовем его метод Hit(). Те, кто следил за нашими уроками с самого начала, возможно, воскликнет: у наших врагов нет ни имени, ни метода Hit()! Исправим это: откройте файл chadenemy.h и добавьте эти строки до объявления m_Speed: | ||
+ | char m_EnemyName[32]; | ||
+ | bool m_IsDead | ||
+ | |||
+ | Теперь добавьте следующие две строчки после метода Update(): | ||
+ | void SetAnimation(String animation, bool loop); | ||
+ | void Hit(); | ||
+ | |||
+ | Метод SetAnimation() я вставил, потому что мне было тошно писать три строчки кода для выполнения одной простой вещи. Можете игнорировать его, если хотите. | ||
+ | |||
+ | Мы уже устанавливали имена врагов в конструкторе (файл chadenemy.cpp), но использовали временную локальную пере | ||
+ | менную. Теперь, когда у нас есть m_EnemyName, мы можем хранить ее там, чтобы найти правильный объект сцены, поп | ||
+ | ав в робота. Просто замените следующие три строки кода… | ||
+ | char enemyname[32]; | ||
+ | sprintf(enemyname, “Robot %d”, ++EnemyNum); | ||
+ | m_Entity = m_SceneMgr->createEntity(enemyname, “robot. | ||
+ | mesh”); | ||
+ | |||
+ | ... на следующие две... | ||
+ | sprintf(m_EnemyName, “Robot %d”, ++EnemyNum); | ||
+ | m_Entity = m_SceneMgr->createEntity(m_EnemyName, “robot. | ||
+ | mesh”); | ||
+ | |||
+ | Теперь нужно написать метод Hit(), изменить анимацию робота и | ||
+ | установить переменную m_IsDead в значение true, например, так: | ||
+ | void CChadEnemy::Hit() { | ||
+ | if (!m_IsDead) { | ||
+ | m_AnimationState->setEnabled(false); | ||
+ | SetAnimation(“Die”, false); | ||
+ | m_IsDead = true; | ||
+ | } | ||
+ | } | ||
Версия 19:31, 10 марта 2008
|
|
|
Содержание |
Ogre: Лазеры и звук
ЧАСТЬ 5: Последний урок в данной серии – музыка для ушей Пола Хадсона: под такую сподручно убивать роботов.
На данном этапе наша игра содержит все базовые элементы стрелялки от первого лица, но имеет легкий недостаток: п одстрелить-то вы никого и не можете. Пожалуй, это скорее тяжелый недостаток, если учесть, что Висельник Чед для стрельбы и задуман. Не хватает также звука и музыки, да и прицела оружия, чтоб видеть, куда мы стреляем. А фанаты С++, наверно, заметили, что отсутствует какое-либо высвобождение памяти.
Изящно завершим Висельника Чеда: реализуем все эти элементы на следующих четырех страницах, и притом запросто – обещаю!
Включаем громкость
Имя Ogre не дает забыть о сильных сторонах программы: это акроним, означающий Объектно-ориентированный Графический Движок Рендеринга [Object-Oriented Graphics Rendering Engine]. Звук – как для эффектов, так и фоновый – не принимается в расчет и, согласно разработчикам Ogre, приниматься не будет. Но это не проблема, благодаря библиотеке SDL и ее расширению SDL_Mixer: вместе они позаботились о поддержке аудио. Если вы следили за нашими уроками с LXF82, то уже установили библиотеки libsdl-devel и libsdl-mixer-devel; а те, кто этот номер пропустил, пусть начнут с их установки, иначе код данного урока работать не будет. Прежде всего, надо изменить файл chad.h, объявив в нем звуковые файлы стрельбы (я использую laser1.wav) и музыкального фона (tipperary.mp3). В SDL-терминах это Mix_Chunk и Mix_Music соответственно, поэтому добавьте две строки в конец класса CChadGame:
Mix_Chunk* m_MixFire; Mix_Music* m_Music;
Загрузка нашего аудиоматериала осуществляется в файле chad. cpp, в методе initialise(). В конец этого метода (т .е. после установки m_SceneMgr в NULL), добавьте следующие четыре строки:
SDL_Init(SDL_INIT_AUDIO); Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 2048); m_MixFire = Mix_LoadWAV(“laser1.wav”); m_Music = Mix_LoadMUS(“tipperary.mp3”);
Первая строка инициирует поддержку звука, потому она и идет первой. Функция SDL_Init() сообщает SDL, какие части вы хотите использовать – графику, звук, ввод, таймеры и т.д., обычно через передачу списка констант, объединенных оператором ИЛИ – например, SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_CDROM. Для инициализации всех доступных в библиотеке подсистем (это изрядная расточительность, если вы не намерены все их использовать!), просто укажите SDL_INIT_EVERYTHING. Инициализировав звуковую подсистему SDL, можно открывать звуковое устройство. Это делает функция Mix_OpenAudio(): у нее четыре параметра, определяющих свойства звука. Первый параметр – частота дискретизации: 44100 соответствует CD-качеству; чтобы игра лучше работала на старых компьютерах, попробуйте уменьшить ее до 22050. Второй параметр – формат сэмпла (AUDIO_S16SYS означает 16 бит, какой байт старший – определяется системой), третий – количество каналов (1 для моно, 2 для стерео), а четвертый определяет размер буфера для проигрывания звука. Вам эти параметры ни о чем не говорят? Можете их проигнорировать. Просто скопируйте и вставьте приведенную мной строку кода и больше о ней не вспоминайте. Магия!
Запускаем звуковые файлы
Настроив звуковую систему, мы, наконец, можем заказывать наши звук и музыку. SDL_Mixer берет на себя их загрузку, а вам остается сделать два вызова функций Mix_LoadWAV() и Mix_LoadMSU(). Они принимают имя загружаемого файла и автоматически обрабатывают множество популярных форматов – WAV, MP3, OGG, MID и MOD, но если ваш дистрибутив не поддерживает формат MP3, то SDL, скорее всего, не сможет его проиграть. Кому интересно, общедоступную запись tipperary.mp3 я нашел в Сети – она совершенно не подходит для игры [«Путь далекий до Типперери» – популярная песенка английских солдат времен I Мировой войны, – прим. ред.], поэтому вы уж сами подберите нужный файл! Чтобы покончить с поддержкой звука, остается еще два шага. Добавьте в методе frameStarted() следующие три строки кода:
if (!Mix_PlayingMusic()) { Mix_PlayMusic(m_Music, 0); }
Я не собираюсь вас унижать, объясняя этот код, кроме 0 в конце: это число повторов нашей мелодии [0 значит, что она будет проиграна всего один раз, без повтора, – прим. ред.]. Добавление звука лазера потребует немного мозгов, поскольку потребуется определить метод mousePressed(). В настоящий момент он пуст и сидит в chad.h. Заменим «заглушку» в chad.h на прототип и напишем реализацию этого метода в chad.cpp (чтобы лазер зазвучал). В chad.h, превратим строку....
void mousePressed(MouseEvent* e) { }
... в...
void mousePressed(MouseEvent* e);
Тело этого метода надо поместить где-то в файле chad.cpp:
void CChadGame::mousePressed(MouseEvent* e) { Mix_PlayChannel(-1, m_MixFire, 0); }
Звук лазера теперь будет раздаваться при каждом нажатии кнопки мыши – не сногсшибательно, но начало хорошее! Можете скомпилировать свой код и насладиться звуками лазера.
Прицел
Если вы не снайпер сразу после дембеля, то вряд ли поражение цели на дальней дистанции покажется вам несложным. Для упрощения этой задачи многие игры содержат на экране небольшой прицел. Добавим и мы прицел в виде точки в игру Висельник Чед. Для этого необходимо проделать три шага: 1 Создать материал, который даст имя файлу. 2 Создать слой, который использует материал, и позиционировать его на экране. 3 Отобразить слой. Первые два пункта реализуются через систему скриптов Ogre; но для последнего шага придется написать код на С++. Начнем с материала. Сохраните следующий ‘код’ как target.material:
material Chad/TargetSights { technique { pass { lighting off scene_blend alpha_blend texture_unit { texture terrain_detail.jpg } } } }
Заметили? Я использовал для прицела текстуру terrain_detail.jpg, но только потому, что прицел очень мал: игроки увидят лишь небольшую серую точку. Вы можете взять свою картинку, но пока сойдет и эта. Следующий шаг – определить слой, который принимает материал и помещает его на экранной панели. Затем можно пристроить эту панель на экране, используя координату относительно левого верхнего угла, а также ширину и высоту. В любом случае, вот код – сохраните его в файл target.overlay:
chadtarget { zorder 650 container Panel(chadsight) { metrics_mode relative left 0.495 top 0.495 width 0.004 height 0.007 transparent false material Chad/ TargetSights } }
Параметр zorder определяет, где панель отобразится на экране, в терминах глубины, то есть мы можем (если пожелаем) задать способ расположения элементов по слоям. В Ogre его максимальное значение 650 (прицел на вершине стека). Последний шаг – создание слоя, он займет всего две строки. Добавьте такой код в конец метода createOutdoorScene():
Overlay *TargetSight = (Overlay*)OverlayManager::getSingleton(). getByName(“chadtarget”); TargetSight->show();
Код загружает слой с именем, определенным в target.overlay, а затем отображает его. Не надо беспокоиться о потере указателя на слой – Ogre автоматически удерживает его посреди экрана, как определено в файле. Наши три шага проделаны; запустите игру и загляните в прицел. Пусть программировать было скучновато, но зато как удобно теперь целиться!
Стреляем на поражение
Настает главное событие этого урока: отстрел роботов, которые резвились в прошлом номере. Правду сказать, я не особо хотел отягчать насилием Висельника Чеда, но Ребекка – спец по насилию в нашей команде – отказалась плодить опечатки, пока мы не разнесем когонибудь на куски. Пусть будет так. У нас уже есть метод mousePressed() для проигрывания звука лазера, поэтому код для стрельбы подойдет именно сюда. Стрелять будем так: с позиции камеры проводим луч, аналогично тому, как мы делали для определения высоты игрока над уровнем земли. Это непростая геометрическая задача, но, к счастью, Ogre все делает сам, одним методом: getCameraToViewportRay(), который преобразует позицию на экране в позицию в нашем мире и позволяет пустить луч из положения камеры. При необходимости выбирать объекты мышью, можно было бы использовать указатель на объект-событие, передаваемый методу mousePressed(), но мы хотим просто пустить луч через центр экрана, и поэтому используем для координат X и Y значения 0.5. Вот код улучшенного метода mousePressed():
void CChadGame::mousePressed(MouseEvent* e) { Mix_PlayChannel(-1, m_MixFire, 0); Ray mouseray = m_Camera->getCameraToViewportRay(0.5, 0.5); RaySceneQuery* scenequery = m_SceneMgr ->createRayQuery(Ray()); scenequery->setRay(mouseray); RaySceneQueryResult &result = scenequery->execute(); if (!result.empty()) { for (unsigned int i = 0; i < result.size(); ++i) { RaySceneQueryResultEntry &re = result[i]; if (re.movable && re.movable->getMovableType() == “Entity”) { Entity *ent = (Entity*)(re.movable); String name = ent->getName(); if (name == “water”) continue; // игнорируем воду for (unsigned int j = 0; j < Enemies.size(); ++j) { if (Enemies[j]->m_EnemyName == name) { Enemies[j]->Hit(); return; } } } } } }
Послав луч, я прошелся в цикле по откликам в поисках сущностей (в отличие от
элементов ландшафта), а затем отсек водную поверхность. Остаются только роботы; получим имя жертвы и найдем в
списке роботов соответствующий объект, а затем вызовем его метод Hit(). Те, кто следил за нашими уроками с самого начала, возможно, воскликнет: у наших врагов нет ни имени, ни метода Hit()! Исправим это: откройте файл chadenemy.h и добавьте эти строки до объявления m_Speed:
char m_EnemyName[32]; bool m_IsDead
Теперь добавьте следующие две строчки после метода Update():
void SetAnimation(String animation, bool loop); void Hit();
Метод SetAnimation() я вставил, потому что мне было тошно писать три строчки кода для выполнения одной простой вещи. Можете игнорировать его, если хотите.
Мы уже устанавливали имена врагов в конструкторе (файл chadenemy.cpp), но использовали временную локальную пере менную. Теперь, когда у нас есть m_EnemyName, мы можем хранить ее там, чтобы найти правильный объект сцены, поп ав в робота. Просто замените следующие три строки кода…
char enemyname[32]; sprintf(enemyname, “Robot %d”, ++EnemyNum); m_Entity = m_SceneMgr->createEntity(enemyname, “robot. mesh”);
... на следующие две...
sprintf(m_EnemyName, “Robot %d”, ++EnemyNum); m_Entity = m_SceneMgr->createEntity(m_EnemyName, “robot. mesh”);
Теперь нужно написать метод Hit(), изменить анимацию робота и установить переменную m_IsDead в значение true, например, так:
void CChadEnemy::Hit() { if (!m_IsDead) { m_AnimationState->setEnabled(false); SetAnimation(“Die”, false); m_IsDead = true; } }
Наш эксперт
Пол Хадсон написал три книги по Linux и одну по PHP, он также поддерживает на SourceForge два проекта на Mono по лицензии GPL. Пол любит Emacs.