| Невозможно пройти тесты, в окне с вопросами пусто |
Взаимодействие объектов
Цель работы: Научиться обрабатывать столкновения трехмерных объектов
25.1. Обработка столкновений объектов
В XNA есть несколько стандартных объектов, которые можно применять для обработки столкновений игровых объектов. Это структуры BoundingBox, BoundingSphere и BoundingFrustum.
Структура BoundingBox представляет собой прямоугольный "ящик", в который можно "упаковать" объект. Надо отметить, что объект класса Model может возвратить объект типа BoundingSphere для каждой своей сети – этот прием можно использовать при обработке столкновений нескольких объектов.
Структура BoundingSphere представляет собой сферу, в которой содержится объект.
Эти структуры подходят лишь для работы с простыми объектами – например – с трехмерными шарами и кубами. Если объект имеет более сложную форму – он либо обрабатывается как набор более простых объектов, каждый из которых можно заключить, например, в собственную BoundingSphere, либо для такого объекта создается отдельный контент-процессор (content pipeline processor), который предназначен для работы с данным объектом.
Структура BoundingFrustum представляет собой область игрового пространства, которая в данный момент видима с учетом параметров видовой и проекционной матриц.
Далее, для обработки столкновений объектов можно применять такие структуры, как Plane – эта структура представляет собой плоскость, Ray – луч.
Рассмотрим использование объекта класса BoundingSphere для обработки столкновений объектов. Выведем в пространство несколько шаров, используем в качестве игрового объекта еще один шар. Будем перемещать шар и проверять, не столкнулся ли он с каким-нибудь из объектов сцены в процессе перемещения. Если столкнулся – перемещение отменяется и расстояние между столкнувшимися объектами увеличивается до тех пор, пока они выйдут из состояния столкновения.
Для проверки столкновения объектов используется метод Intersects – его можно вызывать, например, для объекта BoundingSphere и передать в качестве параметра другой объект BoundingSphere, а так же – объекты других подходящих типов. Метод возвращает True, если объекты пересекаются (то есть – есть столкновение), и false – если не пересекаются.
Создадим новый игровой проект P18_1, на примере которого рассмотрим решение вышеописанной задачи. На рис. 25.1 вы можете видеть окно Обозреватель решений для этого проекта. Обратите внимание на две модели, которые мы будем использовать в примере, а так же – на наличие класса modCls. Объекты этого класса содержат информацию об игровых объектах и используются для их визуализации.
В листинге 25.1 приведен код класса modCls.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace P18_1
{
public class modCls : Microsoft.Xna.Framework.DrawableGameComponent
{
//Модель
public Model myModel;
//Мировая матрица, матрицы вида и проекции
public Matrix WorldMatrix;
public Matrix ViewMatrix;
public Matrix ProjectMatrix;
//Соотношение сторон экрана
public float aspectRatio;
//Для управления графическим устройством
GraphicsDeviceManager graphics;
//Конструктор получает на вход
//игровой класс, модель, объект для управления графическим устройством
public modCls(Game game, Model mod, GraphicsDeviceManager grf)
: base(game)
{
myModel = mod;
graphics = grf;
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
}
public override void Initialize()
{
base.Initialize();
}
public override void Draw(GameTime gameTime)
{
foreach (ModelMesh mesh in myModel.Meshes)
{
//Для каждого эффекта в сети
foreach (BasicEffect effect in mesh.Effects)
{
//Установить освещение по умолчанию
effect.LightingEnabled = true;
effect.EnableDefaultLighting();
//установить матрицы
effect.World = WorldMatrix;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
Листинг
25.1.
Код класса modCls
В листинге 25.2 вы можете видеть код класса Game1.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
namespace P18_1
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модели
Model ball, ball2;
// Позиция объекта, поворот
Vector3 avatarPosition = new Vector3(0, 0, -10);
float avatarlRotation;
// Положение камеры
Vector3 cameraReference = new Vector3(0, 0, 10);
Vector3 thirdPersonReference = new Vector3(0, 200, -200);
// Скорости поворота и движения
float rotationSpeed = 1f / 60f;
float forwardSpeed = 10f / 60f;
//Поле зрения камеры
float viewAngle = MathHelper.ToRadians(45.0f);
//Расстояние от камеры до переднего и заднего плана
float nearClip = 1.0f;
float farClip = 2000.0f;
//Массив моделей сцены
modCls[] cls;
//Игровой объект
modCls ballObj;
//Соотношение сторон экрана
float aspectRatio;
//Стрелки
Texture2D txtArrows;
Rectangle keyForward = new Rectangle(500, 280, 100, 100);
Rectangle keyUp = new Rectangle(600, 280, 100, 100);
Rectangle keyBack = new Rectangle(700, 280, 100, 100);
Rectangle keyLeft = new Rectangle(500, 380, 100, 100);
Rectangle keyDown = new Rectangle(600, 380, 100, 100);
Rectangle keyRight = new Rectangle(700, 380, 100, 100);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
TouchPanel.EnabledGestures = GestureType.Pinch;
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
ball = Content.Load<Model>("ball");
ball2 = Content.Load<Model>("ball2");
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
cls = new modCls[25];
txtArrows = Content.Load<Texture2D>("Arrows");
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
//Проверка попадания касания в область, занимаемую одним из элементов управления
private bool MenuSelect(Rectangle m, Vector2 p)
{
bool res = false;
if (p.X > m.X && p.X < m.X + m.Width && p.Y > m.Y && p.Y < m.Y + m.Height)
{
res = true;
}
return res;
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//обновить положение объекта
UpdateAvatarPosition();
base.Update(gameTime);
UpdateCameraThirdPerson();
//Cформировать объекты сцены
DrawScene();
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
//Вывод стрелок
spriteBatch.Begin();
spriteBatch.Draw(txtArrows, new Rectangle(500, 280, 300, 200), Color.White);
spriteBatch.End();
//Для нормального отображение 3D-сцены после работы spriteBatch
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
}
//Обновляем состояние объекта
void UpdateAvatarPosition()
{
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Движение вверх
if (MenuSelect(keyForward, touchLocation.Position))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
while (IsCollide())
{
avatarPosition.Y -= v.Y;
}
}
//Вперед
if (MenuSelect(keyUp, touchLocation.Position))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, 0, forwardSpeed);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Z += v.Z;
avatarPosition.X += v.X;
//До тех пор, пока объект сталкивается
//с другим объектом - изменять его положение
//в направлении, противоположном перемещению
//вызвавшему столкновение
while (IsCollide())
{
avatarPosition.Z -= v.Z / 10;
avatarPosition.X -= v.X / 10;
}
}
//Движение вниз
if (MenuSelect(keyBack, touchLocation.Position))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, -forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
while (IsCollide())
{
avatarPosition.Y -= v.Y;
}
}
//Поворот влево
if (MenuSelect(keyLeft, touchLocation.Position))
{
avatarlRotation += rotationSpeed;
}
//Назад
if (MenuSelect(keyDown, touchLocation.Position))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, 0, -forwardSpeed);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Z += v.Z;
avatarPosition.X += v.X;
while (IsCollide())
{
avatarPosition.Z -= v.Z;
avatarPosition.X -= v.X;
}
}
//Поворот вправо
if (MenuSelect(keyRight, touchLocation.Position))
{
avatarlRotation -= rotationSpeed;
}
}
}
//До тех пор, пока разрешена обработка жестов
while (TouchPanel.IsGestureAvailable)
{
//прочитаем жест
GestureSample gs = TouchPanel.ReadGesture();
//Если жест - FreeDrag и разрешено перемещение
//то есть, пользователь касается объекта на экране
if (gs.GestureType == GestureType.Pinch)
{
//Найдем предыдущие координаты, вычтя из текущих координат приращение
Vector2 oldPos = gs.Position - gs.Delta;
Vector2 oldPos2 = gs.Position2 - gs.Delta2;
//Получим расстояние между текущими точками касания
float currentDist = Vector2.Distance(gs.Position, gs.Position2);
//Получим расстояние между предыдущими точками касания
float oldDist = Vector2.Distance(oldPos, oldPos2);
//Умножим переменную, отвечающую за масштабирование спрайта
//на результат деления текущего расстояния на предыдущее
if (currentDist / oldDist > 1)
{
viewAngle -= MathHelper.ToRadians(0.5f);
}
else
{
viewAngle += MathHelper.ToRadians(0.5f);
}
}
}
//Если новый угол обзора вышел за дозволенные пределы
//изменяем его
if (viewAngle > MathHelper.ToRadians(180.0f)) viewAngle = MathHelper.ToRadians(179.9f);
if (viewAngle < MathHelper.ToRadians(0.0f)) viewAngle = MathHelper.ToRadians(0.1f);
}
//Обновляем положение камеры при выбранном виде от третьего лица
void UpdateCameraThirdPerson()
{
//Поворот камеры
Matrix rotationMatrix = Matrix.CreateRotationY(avatarlRotation);
// Направление камеры
Vector3 transformedReference = Vector3.Transform(thirdPersonReference, rotationMatrix);
// Позиция камеры
Vector3 cameraPosition = transformedReference + avatarPosition;
//Матрица вида
viewMatrix = Matrix.CreateLookAt(cameraPosition, avatarPosition, new Vector3(0.0f, 1.0f, 0.0f));
//Проецкионная матрица
projMatrix = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearClip, farClip);
}
//Логическая функция, проверяющая столкновение игрового объекта и
//объектов сцены
bool IsCollide()
{
//Для объекта BoundingSphere, соответствующего
//текущему объекту сцены
BoundingSphere b1;
//Получить BoundingSpherer для игрового объекта
BoundingSphere b = ball.Meshes[0].BoundingSphere;
//Установить центр сферы в соответствии с положением
//игрового объекта
b.Center = avatarPosition;
//Переменная для хранения вектора размера модели
Vector3 scale;
//Переменная для хранения информации о повороте модели
Quaternion rotation;
//Переменая для хранения информации о позиции модели
Vector3 translation;
//Цикл обхода объектов сцены
for (int i = 0; i < 25; i++)
{
//Получить BoundingSphere для текущего объекта
b1 = cls[i].myModel.Meshes[0].BoundingSphere;
//Получить параметры - размер, поворот, позицию для объекта
cls[i].WorldMatrix.Decompose(out scale, out rotation, out translation);
//Установить центр сферы в соответствии с позицией объекта
b1.Center = translation;
//Если сферы игрового объекта и текущего объекта
if (b1.Intersects(b))
{
return true;
}
}
//Если выполняется этот код -
//столкновения не было
return false;
}
//Вывод объектов сцены
void DrawScene()
{
//очистить коллекцию компонентов
Components.Clear();
//Счетчик для элементов массива
int i = 0;
//Вывести шары, расположенные в пять рядов
//по пять штук
for (int x = 0; x < 5; x++)
{
for (int z = 0; z < 5; z++)
{
//Добавляем в массив новый объект
cls[i] = new modCls(this, ball2, graphics);
//Устанавливаем его свойства
cls[i].WorldMatrix = Matrix.CreateTranslation(x * 40f, 0, z * 40f);
cls[i].ViewMatrix = viewMatrix;
cls[i].ProjectMatrix = projMatrix;
//Добавляем в коллекцию компонентов
Components.Add(cls[i]);
i++;
}
}
//выведем игровой объект
ballObj = new modCls(this, ball, graphics);
ballObj.WorldMatrix = Matrix.CreateRotationY(avatarlRotation) * Matrix.CreateTranslation(avatarPosition);
ballObj.ViewMatrix = viewMatrix;
ballObj.ProjectMatrix = projMatrix;
Components.Add(ballObj);
}
}
}
Листинг
25.2.
Код класса Game1
На рис. 25.2 вы можете видеть игровой экран проекта P18_1.

