Невозможно пройти тесты, в окне с вопросами пусто |
Взаимодействие объектов
Цель работы: Научиться обрабатывать столкновения трехмерных объектов
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.