Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений Windows (до Windows 10) Разработка трехмерных игр для Windows 8 с помощью C++ и Microsoft DirectX RSS

Разработка трехмерных игр для Windows 8 с помощью C++ и Microsoft DirectX

Текущий рейтинг: 5 (проголосовало 1)
 Посетителей: 830 | Просмотров: 1167 (сегодня 0)  Шрифт: - +

Добавление вратаря

Движение мяча уже готово, ворота на месте, теперь нужно добавить вратаря, который будет ловить мяч. В роли вратаря у нас будет искаженный куб. В папке Assets добавьте новый элемент (новую трехмерную сцену) и назовите его goalkeeper.fbx.

Добавьте куб из набора инструментов и выберите его. Задайте масштаб: 0,3 по оси X, 1,9 по оси Y и 1 по оси Z. Для свойства MaterialAmbient установите значение 1 для красного цвета и значение 0 для синего и зеленого цвета, чтобы сделать объект красным. 

Измените значение свойства Red в разделе MaterialSpecular на 1 и значение свойства MaterialSpecularPower на 0,2.

Загрузите новый ресурс в методе CreateDeviceDependentResources:

auto loadMeshTask = Mesh::LoadFromFileAsync(
m_graphics,
L"gamelevel.cmo",
L"",
L"",
m_meshModels)
.then([this]()
{
return Mesh::LoadFromFileAsync(
m_graphics,
L"field.cmo",
L"",
L"",
m_meshModels,
false // Do not clear the vector of meshes
);
}).then([this]()
{
return Mesh::LoadFromFileAsync(
m_graphics,
L"soccer_goal.cmo",
L"",
L"",
m_meshModels, false // Do not clear the vector of meshes
);
}).then([this]()
{
return Mesh::LoadFromFileAsync(
m_graphics,
L"goalkeeper.cmo",
L"",
L"",
m_meshModels, false // Do not clear the vector of meshes
);
});

Теперь нужно расположить вратаря в середине ворот и отрисовать его. Это нужно сделать в методе Render в Game.cpp:

void Game::Render()
{
// snip
auto goalTransform = XMMatrixScaling(2.0f, 2.0f, 2.0f) * XMMatrixRotationY(-XM_PIDIV2)* XMMatrixTranslation(85.5f, -0.5f, 0);
auto goalkeeperTransform = XMMatrixTranslation(85.65f, 1.4f, 0) ;
for (UINT i = 0; i < m_meshModels.size(); i++)
{
XMMATRIX modelTransform = rotation;

String^ meshName = ref new String (m_meshModels [i]->Name ()) ;

m_graphics.UpdateMiscConstants(m_miscConstants);
if (String::CompareOrdinal(meshName, L"Sphere_Node") == 0)
m_meshModels[i]->Render(m_graphics, modelTransform);
else if (String::CompareOrdinal(meshName, L"Plane_Node") == 0)
m_meshModels[i]->Render(m_graphics, XMMatrixIdentity());
else if (String::CompareOrdinal(meshName, L"Cube_Node") == 0)
m_meshModels[i]->Render(m_graphics, goalkeeperTransform);
else
m_meshModels[i]->Render(m_graphics, goalTransform);
}
}

Этот код размещает вратаря в середине ворот. Теперь нужно сделать так, чтобы вратарь мог перемещаться влево и вправо, чтобы ловить мяч. Для управления движением вратаря пользователь будет нажимать на клавиши со стрелками влево и вправо.

Движение вратаря ограничено штангами ворот, расположенными на расстоянии +7 и -7 единиц по оси Z. Ширина вратаря составляет 1 единицу в каждую сторону, поэтому он может перемещаться на 6 единиц влево или вправо.

Нажатие клавиши перехватывается на странице XAML (Directxpage.xaml) и перенаправляется в класс Game. Добавляем обработчик событий KeyDown в Directxpage.xaml:

<Page
x:Class="StarterKit.DirectXPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:StarterKit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" KeyDown="OnKeyDown">

Обработчик событий в DirectXPage.xaml.cpp:

void DirectXPage::OnKeyDown(Platform::ObjectΛ sender,
Windows::UI::Xaml::Input::KeyRoutedEventArgsΛ e)
{
m_main->OnKeyDown( ->Key);
}

m_main является экземпляром класса StarterKitMain, который отрисовывает сцены игры и счетчик кадровой скорости. Нужно объявить публичный метод в StarterKitMain.h:

class StarterKitMain : public DX::IDeviceNotify
{
public:
StarterKitMain(const std::shared_ptr<DX::DeviceResources>& deviceResources);
~StarterKitMain();

// Public methods passed straight to the Game renderer.
Platform: : String'^ OnHitObject (int x, int y) {
return m_sceneRenderer->OnHitObject(x, y); }
void OnKeyDown(Windows::System::VirtualKey key) {
m_sceneRenderer->OnKeyDown(key); }
… .

Этот метод перенаправляет клавишу методу OnKeyDown в классе Game. Теперь нужно объявить метод OnKeyDown в файле Game.h:

class Game
{ public:
Gamefconst std::shared_ptr<DX::DeviceResources>& deviceResources);
void	CreateDeviceDependentResources();
void	CreateWindowSizeDependentResources();
void	ReleaseDeviceDependentResources();
void	Update(DX::StepTimer consts timer);
void	Render();
void	OnKeyDown(Windows::System::VirtualKey key);
…

Этот метод обрабатывает нажатие клавиш и перемещает вратаря в соответствующую сторону. Перед созданием этого метода нужно объявить частное поле в файле Game.h для сохранения положения вратаря:

class Game {
// snip
private:
// snip
float m_goalkeeperPosition;

Изначально вратарь занимает положение 0. Это значение будет увеличиваться или уменьшаться при нажатии пользователем клавиши со стрелкой. Если положение больше 6 или меньше -6, положение вратаря не изменяется. Это нужно сделать в методе OnKeyDown в Game.cpp:

void Game::OnKeyDown(Windows::System::VirtualKey key)
{
const float MaxGoalkeeperPosition = 6.0;
const float MinGoalkeeperPosition = -6.0;
if (key == Windows::System::VirtualKey::Right)
m_goalkeeperPosition = m_goalkeeperPosition >= MaxGoalkeeperPosition ?
m_goalkeeperPosition : m_goalkeeperPosition + 0.1f;
else if (key == Windows::System::VirtualKey::Left)
m_goalkeeperPosition = m_goalkeeperPosition <= MinGoalkeeperPosition ?
m_goalkeeperPosition : m_goalkeeperPosition - 0.1f;
}

Новое положение вратаря используется в методе Render файла Game.cpp, где вычисляется перемещение вратаря:
auto goalkeeperTransform = XMMatrixTranslation(85.65f, 1.40f, m_goalkeeperPosition);

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

*
Увеличить

Рисунок 8. Игра с вратарем в нужном положении

До сих пор мяч двигался постоянно, но это нам не нужно. Мяч должен начинать движение непосредственно после удара и останавливаться при достижении ворот. Вратарь также не должен двигаться до удара по мячу.

Необходимо объявить частное поле m_isAnimating в файле Game.h, чтобы игра «знала», когда мяч движется:

class Game
{
public:
// snip
private:
// snip
bool m_isAnimating;

Эта переменная используется в методах Update и Render в Game.cpp, поэтому мяч перемещается, только когда m_isAnimating имеет значение true:

void Game::Update(DX::StepTimer consts timer)
{
if (m_isAnimating)
{
m_rotation = static_cast<float>(timer.GetTotalSeconds()) * 0.5f;
auto totalTime = (float) fmod(timer.GetTotalSeconds(), 2.3f);
m_translationX = 63.0f + 11.5f * totalTime;
m_translationY = 11.5f * totalTime - 5.Of * totalTime*totalTime;
m_translationZ = 3.0f * totalTime;
}
}
void Game::Render()
{
// snip
XMMATRIX modelTransform;
if (m_isAnimating)
{
modelTransform = XMMatrixRotationY(m_rotation);
modelTransform *= XMMatrixTranslation(m_translationX, m_translationY,
m_translationZ);
}
else
modelTransform = XMMatrixTranslation(63.0f, 0.0f, 0.0f);
… .

Переменная modelTransform перемещается из цикла к началу. Нажатие клавиш со стрелками следует обрабатывать в методе OnKeyDown, только когда m_isAnimating имеет значение true:

void Game::OnKeyDown(Windows::System::VirtualKey key)
{
const float MaxGoalkeeperPosition = 6.0f;
if (m_isAnimating)
{
auto goalKeeperVelocity = key == Windows::System::VirtualKey::Right ?
0.1f : -0.1f;
m_goalkeeperPosition = fabs(m_goalkeeperPosition) >=
MaxGoalkeeperPosition ?
m_goalkeeperPosition :
m_goalkeeperPosition +
goalKeeperVelocity;
}
}

Теперь нужно ударить по мячу. Это происходит, когда пользователь нажимает пробел. Объявите новое частное поле m_isKick в файле Game.h:

class Game
{
public:
// snip
private:
// snip
bool m_isKick;

Установите для этого поля значение true в методе OnKeyDown в Game.cpp:

void Game::OnKeyDown(Windows::System::VirtualKey key)
{
const float MaxGoalkeeperPosition = 6. Of;
if (m_isAnimating)
{
auto goalKeeperVelocity = key == Windows::System::VirtualKey::Right ?
0.1f : -0.1f;
m_goalkeeperPosition = fabs(m_goalkeeperPosition) >=
MaxGoalkeeperPosition ?
m_goalkeeperPosition :
m_goalkeeperPosition + goalKeeperVelocity;
}
else if ( y == Windows::System::VirtualKey::Space)
m_isKick = true;
}

Когда m_isKick имеет значение true, в методе Update запускается анимация:

void Game::Update(DX::StepTimer consts timer)
{
if (m_isKick)
{
m_startTime = static_cast<float>(timer.GetTotalSeconds());
m_isAnimating = true;
m_isKick = false;
}
if (m_isAnimating)
{
auto totalTime = static_cast<float>(timer.GetTotalSeconds()) –
m_startTime;
m_rotation = totalTime * 0.5f; m_translationX = 63.0f + 11.5f * totalTime;
m_translationY = 11.5f * totalTime - 5.Of * totalTime*totalTime;
m_translationZ = 3.0f * totalTime;
if (totalTime > 2.3f)
ResetGame();
}
}

Начальное время удара хранится в переменной m_startTime (объявленной как приватное поле в файле Game.h), которая используется для вычисления времени удара. Если оно превышает 2,3 секунды, игра сбрасывается (за это время мяч уже должен был достигнуть ворот). Метод ResetGame объявляется как частный в Game.h:

void Game::ResetGame()
{
m_isAnimating = false;
m_goalkeeperPosition = 0;
}

Этот метод устанавливает для m_isAnimating значение false и сбрасывает положение вратаря. Положение мяча изменять не нужно: мяч будет отрисован на 11-метровой отметке, если m_isAnimating имеет значение false. Также нужно изменить угол удара. Этот код направляет удар вблизи правой штанги:
m_translationZ = 3.0f * totalTime;

Нужно изменить этот подход, чтобы удары были случайными и пользователь не знал, куда будет направлен следующий удар. Необходимо объявить приватное поле m_ballAngle в файле Game.h и инициализировать его при ударе по мячу в методе Update:

void Game::Update(DX::StepTimer const& timer)
{
    if (m_isKick)
    {
        m_startTime = static_cast<float>(timer.GetTotalSeconds());
        m_isAnimating = true;
        m_isKick = false;
        m_ballAngle = (static_cast <float> (rand()) /
            static_cast <float> (RAND_MAX) -0.5f) * 6.0f;
    }
… .

Rand()/RAND_MAX дает результат от 0 до 1. Нужно вычесть из результата 0,5, чтобы получить число от -0,5 до 0,5, а затем умножить на 6, чтобы получить итоговый угол до -3 до 3. Чтобы в каждой игре использовать разные последовательности, нужно инициализировать генератор, вызвав srand в методе CreateDeviceDependentResources:

void Game::CreateDeviceDependentResources()
{
    srand(static_cast <unsigned int> (time(0)));
… .

Чтобы вызвать функцию времени, нужно включить ctime. Чтобы использовать новый угол для мяча, нужно применить m_ballAngle в методе Update:
m_translationZ = m_ballAngle * totalTime;

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

void Game::Update(DX::StepTimer consts timer) {
if (m_isKick) {
m_startTime = static_cast<float>(timer.GetTotalSeconds());
m_isAnimating = true;
m_isKick = false;
m_isGoal = m_isCaught = false;
m_ballAngle = (static_cast <float> (rand()) /
static_cast <float> (RAND_MAX) -0.5f) * 6.0f;
}
if (m_isAnimating)
{
auto totalTime = static_cast<float>(timer.GetTotalSeconds()) –
m_startTime;
m_rotation = totalTime * 0.5f; if ( !m_isCaught)
{
// ball traveling
m_translationX = 63.0f + 11.5f * totalTime;
m_translationY = 11.5f * totalTime - 5.0f * totalTime*totalTime;
m_translationZ = m_ballAngle * totalTime;
}
else
{
// if ball is caught, position it in the center of the goalkeeper
m_translationX = 83.35f;
m_translationY = 1.8f;
m_translationZ = m_goalkeeperPosition;
}
if (!m_isGoal && !m_isCaught && m_translationX >= 85.5f)
{
// ball passed the goal line - goal or caught
auto ballMin = m_translationZ - 0.5f + 7.0f;
auto ballMax = m_translationZ + 0.5f + 7.0f;
auto goalkeeperMin = m_goalkeeperPosition - 1.0f + 7.0f;
auto goalkeeperMax = m_goalkeeperPosition + 1.0f + 7.0f;
m_isGoal = (goalkeeperMax < ballMin || goalkeeperMin > ballMax);
m_isCaught = !m_isGoal;
}

if (totalTime > 2.3f)
ResetGame();
}
}

Объявляем два частных поля в файле Game.h: m_isGoal и m_IsCaught. Эти поля говорят нам о том, что произошло: пользователь забил гол или вратарь поймал мяч. Если оба поля имеют значение false, мяч еще летит. Когда мяч достигает вратаря, программа вычисляет границы мяча и вратаря и определяет, налагаются ли границы мяча на границы вратаря. Если посмотрите в код, то увидите, что я добавил 7.0 f к каждой границе. Я сделал это, поскольку границы могут быть положительными или отрицательными, а это усложнит вычисление наложения. Добавив 7.0 f, я добился того, что все значения стали положительными, чтобы упростить вычисление. Если мяч пойман, его положение устанавливается по центру вратаря. m_isGoal и m_IsCaught сбрасываются при ударе.

Итак, мы получили вполне рабочий вариант игры. Желающих продолжить ее совершенствовать, в частности, добавить ведение счета или сенсорное управление, отсылаем к исходному материалу на IDZ.

Заключение

Итак, дело сделано. От танцующего чайника мы пришли к игре на DirectX. Языки программирования становятся все более похожими, поэтому использование C++/DX не вызвало особых затруднений у разработчика, привыкшего пользоваться C#.

Основное затруднение состоит в освоении трехмерных моделей, в их движении и расположении привычным образом. Для этого потребовалось применить знания физики, геометрии, тригонометрии и математики.

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

Автор: Виктор Гурылев  •  Иcточник: msdn.microsoft.com  •  Опубликована: 02.03.2015
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   DirectX.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.