netlib.narod.ru | < Назад | Оглавление | Далее > |
Перемещение объектов и полеты — это, конечно, хорошо, но что делать, если вам понадобится что-то другое? Давайте посмотрим, как создать контроллер движения для более интересного объекта — космического танка Mark VII с доплеровским радаром X-диапазона. Танк может передвигаться по поверхности планеты с различной скоростью и поворачивать на ходу. Его башня быстро вращается, а пушка поднимается. Кажется, я забыл упомянуть о радаре, который радостно вертится на башне? На рис. 6.11 изображен танк Mark VII при выполнении боевого задания.
Рис. 6.11. Космический танк Mark VII с доплеровским радаром X-диапазона
Хмм... вы обратили внимание на то, что у танка нет колес? Могу предложить два объяснения:
Это летающий танк.
Мне было лень возиться с колесами.
Решайте сами.
На рис. 6.12 изображена диаграмма подвижных частей танка (вместе с колесами). Иллюстрация приведена на цветной вкладке.
Рис. 6.12. Составные части танка
В приложении Tank класс C3dTank является производным от C3dFrame. Последовательность, в которой строится танк, такова: сначала мы присоединяем корпус к внешнему фрейму, затем присоединяем башню к корпусу и в последнюю очередь присоединяем пушку и радар к башне. Радар приводится в постоянное вращение. Пушка может подниматься и опускаться, вращаясь вокруг своей горизонтальной оси. Башня может вращаться вокруг вертикальной оси корпуса.
Перед тем как заниматься контроллером, давайте рассмотрим фрагмент кода, в котором создается танк, чтобы нам было легче управлять им:
C3dTank::C3dTank() { // Создать фрейм C3dFrame::Create(NULL); // Загрузить составные части танка и построить танк m_hull.Load(IDX_HULL); AddChild(&m_hull); m_turret.Load(IDX_TURRET); m_hull.AddChild(&m_turret); m_gun.Load(IDX_GUN); m_turret.AddChild(&m_gun); // Радар имеет собственный фрейм, // чтобы было удобнее управлять осью вращения C3dFrame rframe; rframe.Create(&m_turret); C3dShape radar; radar.Load(IDX_RADAR); rframe.AddChild(&radar); radar.SetPosition(0, 0, -0.3); rframe.SetPosition(0, 0, 0.3); rframe.SetRotation(0, 1, 0, 0.1); SetGun(25); }
Единственное, что может здесь показаться странным, — это то, что я использовал для радара отдельный фрейм. Мне пришлось поступить так из-за того, что в первоначальном варианте танка ось y радара была смещена относительно того места, где я хотел расположить радар. Поэтому я задал начало координат фрейма в той точке башни, где помещается ось, и сместил объект-радар внутри фрейма, чтобы он находился над осью вращения (рис. 6.13).
Рис. 6.13. Размещение радара внутри фрейма
Все объекты, из которых состоит наш танк, были созданы в 3D Studio и преобразованы в формат .X с помощью утилиты conv3ds, входящей в DirectX 2 SDK. Они были включены в файл приложения RC2 в качестве ресурсов:
// // STAGE.RC2 — resources Microsoft Visual C++ does not edit directly // #ifdef APSTUDIO_INVOKED #error this file is not editable by Microsoft Visual C++ #endif //APSTUDIO_INVOKED //////////////////////////////////////////////////////////////////// // Add manually edited resources here... #include "3dPlus.rc" // Части танка IDX_HULL XOF res\T_hull.x IDX_TURRET XOF res\turret.x IDX_GUN XOF res\gun.x IDX_RADAR XOF res\radar.x camo.bmp BITMAP res\camo.bmp camousa.bmp BITMAP res\camousa.bmp // Звуковые эффекты IDS_BANG WAVE res\bang.wav ///////////////////////////////////////////////////////////////////////
ПРИМЕЧАНИЕ |
Тэг XOF, встречающийся в файле ресурсов, на самом деле можно заменить любой другой строкой. Я выбрал XOF лишь потому, что такое расширение используется в файлах описания фигур. Единственное место программы, где встречается строка XOF — функция C3dShape::Load, где эта строка используется для того, чтобы отличать XOF-файлы от других типов ресурсов. |
В класс танка вошли три функции, находящиеся в файле 3dTank.cpp и предназначенные для регулирования углов башни и пушки, а также для стрельбы:
#define D2R 0.01745329251994 void C3dTank::SetTurret(double angle) { if ((angle < 0) || (angle >= 360)) { angle = 0; } double x = sin(angle * D2R); double z = cos(angle * D2R); m_turret.SetDirection(x, 0, z, &m_hull); } void C3dTank::SetGun(double angle) { if (angle < 0) { angle = 0; } else if (angle >= 60) { angle = 60; } double y = -sin(angle * D2R); double z = cos(angle * D2R); m_gun.SetDirection(0, y, z, &m_turret); } void C3dTank::FireGun() { PlaySound(MAKEINTRESOURCE(IDS_BANG), AfxGetResourceHandle(), SND_RESOURCE); }
Как видите, чтобы определить положение башни и пушки, мы вычисляем вектор направления. Затем мы задаем направление объекта по отношению к его родителю; кстати говоря, именно так функция SetDirection действует по умолчанию. Но я хотел сделать свой код максимально простым и очевидным, поэтому при каждом вызове передаю дополнительный аргумент — эталонный фрейм.
Танк готов. Осталось научиться управлять им.
Большая часть кода контроллера находится в классах C3dWnd и C3dController. Чтобы создать собственный контроллер, необходимо лишь ввести новый класс, производный от C3dController, переопределить в нем функцию OnUpdate и установить новый контроллер в своем приложении. Однако перед тем, как писать функцию OnUpdate, следует распределить параметры джойстика по выполняемым функциям. Конфигурация, на которой я остановился, приведена в таблице 6.3.
Таблица 6.3. Управление танком | |
Входной параметр | Параметр танка |
y | Скорость |
x | Поворот |
r | Поворот |
POV (кнопка выбора вида) | Направление башни |
Кнопки 3 и 4 | Подъем и опускание пушки |
Кнопка 1 | Выстрел из пушки |
Я решил использовать параметры x и r для поворотов, чтобы даже при наличии самого простого джойстика с двумя осями можно было управлять танком. Я выбрал для этого приложения джойстик SideWinder Pro — он дает более реалистичные ощущения, чем SpaceBall. К тому же кнопка выбора вида, находящаяся на рукояти джойстика, замечательно подходит для поворотов башни.
Определившись с управлением, можно писать программу. Весь код контроллера состоит из двух функций:
CTankCtrl::CTankCtrl() { m_dGunAngle = 25; m_bWasFire = FALSE; } void CTankCtrl::OnUpdate(_3DINPUTSTATE& st, C3dFrame* pFrame) { // Задать скорость (руководствуясь значением y) double v = st.dY / 2; // Определить текущее положение C3dVector pos; pFrame->GetPosition(pos); // Получить текущий вектор направления C3dVector dir, up; pFrame->GetDirection(dir, up); // Определить новое направление (с учетом // параметров x и r) double dr = -st.dX + -st.dR; C3dMatrix r; r.Rotate(0, dr * 3, 0); dir = r * dir; // Умножить вектор направления на скорость, // чтобы определить смещение танка C3dVector ds = dir * v; // Задать новое положение и направление pos += ds; pFrame->SetPosition(pos); pFrame->SetDirection(dir); // Воспользоваться информацией POV для задания // ориентации башни. // Для этого необходимо работать с объектом C3dTank, // а не C3dFrame. C3dTank* pTank = (C3dTank*) pFrame; ASSERT(pTank->IsKindOf(RUNTIME_CLASS(C3dTank))); if (st.dPov >= 0) { pTank->SetTurret(st.dPov); } // Кнопки 3 и 4 поднимают и опускают пушку if (st.dwButtons & 0x04) { m_dGunAngle += 0.1; } if (st.dwButtons & 0x08) { m_dGunAngle -= 0.1; } if (m_dGunAngle < 0) { m_dGunAngle = 0; } else if (m_dGunAngle > 45) { m_dGunAngle = 45; } pTank->SetGun(m_dGunAngle); // Проверить, не пора ли стрелять if (st.dwButtons & 0x01) { if (!m_bWasFire) { pTank->FireGun(); m_bWasFire = TRUE; } } else { m_bWasFire = FALSE; } }
Конструктор лишь инициализирует некоторые локальные данные; вся настоящая работа выполняется в функции OnUpdate. Параметр y задает текущую скорость. Текущая позиция и направление танка хранятся в объектах C3dVector. Параметры x и r определяют матрицу поворота, которая задает новую ориентацию вектора направления. Вектор направления умножается на скорость — полученный вектор смещения складывается с вектором прежнего положения танка. Затем мы перемещаем танк в новое положение и задаем для танка новое направление.
Кнопка выбора вида определяет направление башни. Мы проверяем состояние кнопок 3 и 4, и если они нажаты, то угол подъема пушки изменяется на небольшую величину. Если держать одну из этих кнопок нажатой, ствол пушки будет медленно подниматься или опускаться.
Остается лишь учесть кнопку стрельбы. Проверка локальной переменной m_bWasFire предотвращает повторные выстрелы при нажатой кнопке — автоматическое оружие в США запрещено.
За основу приложения Tank был взят код приложения Moving. Я удалил ненужные команды меню и заменил текущую фигуру объектом C3dTank. Кроме того, я включил в макет фоновое изображение. Ниже приведен фрагмент кода, в котором происходит настройка главного окна приложения:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { ... //Загрузить фоновое изображение m_imgBkgnd.Load(IDB_BKGND); NewScene(); ASSERT(m_pScene); // Создать объект-контроллер m_pController = new CTankCtrl; m_pController ->Create(&m_wnd3d, OnGetCtrlFrame, this); // Восстановить конфигурацию контроллера m_pController->SelectDevice(m_iObjCtrlDev); return 0; }
Функция NewScene создает макет и задает начальные условия:
BOOL CMainFrame::NewScene() { // Удалить макет, если он уже существует if (m_pScene) { m_wnd3d.SetScene(NULL); delete m_pScene; m_pScene = NULL; } // Создать исходный макет m_pScene = new C3dScene; if (!m_pScene->Create()) return FALSE; // Задать источники света C3dDirLight dl; dl.Create(0.8, 0.8, 0.8); m_pScene->AddChild(&dl); dl.SetPosition(-2, 2, -5); dl.SetDirection(1, -1, 1); m_pScene->SetAmbientLight(0.4, 0.4, 0.4); // Установить положение и направление камеры // в исходное состояние m_pScene->SetCameraPosition(C3dVector(0, 5, -25)); m_pScene->SetCameraDirection(C3dVector(0, 0, 1)); m_wnd3d.SetScene(m_pScene); // Задать фоновое изображение m_pScene->SetBackground(&m_imgBkgnd); // Разместить танк в макете if (!m_pTank) m_pTank = new C3dTank; m_pScene->AddChild(m_pTank); m_pTank->SetPosition(0, 0, 0); m_pTank->SetDirection(0, 0, 1); return TRUE; }
Если танк уедет за край окна и потеряется, можно выполнить команду File&mnsp;| New, чтобы вызвать функцию NewScene и начать все заново. Осталось сказать о последнем изменении, внесенном мной, — когда контроллер запрашивает указатель на фрейм, с которым он должен работать, функция OnGetCtrlFrame возвращает ему указатель на танк:
C3dFrame* CMainFrame::OnGetCtrlFrame(void* pArg) { CMainFrame* pThis = (CMainFrame*) pArg; ASSERT(pThis); ASSERT(pThis->IsKindOf(RUNTIME_CLASS(CMainFrame))); return pThis->m_pTank; }
Обратите внимание — хотя функция должна возвращать указатель на C3dFrame, на самом деле она передает указатель на объект C3dTank. Мы пользуемся этим обстоятельством в функции OnUpdate, код которой был приведен выше. Если раньше вам могло показаться, что преобразование указателя на C3dFrame в указатель на C3dTank выглядит сомнительно, то теперь нетрудно убедиться, что мы имели полное право поступать таким образом.
netlib.narod.ru | < Назад | Оглавление | Далее > |