Опубликован: 14.08.2012 | Уровень: специалист | Доступ: платный
Самостоятельная работа 18:

Взаимодействие объектов

Аннотация: Эта лабораторная работа посвящена обработке взаимодействия трехмерных объектов.

Цель работы: Научиться обрабатывать столкновения трехмерных объектов

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. Объекты этого класса содержат информацию об игровых объектах и используются для их визуализации.

Обозреватель решений проекта P18_1

Рис. 25.1. Обозреватель решений проекта P18_1

В листинге 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.

Игровой экран проекта P18_1

Рис. 25.2. Игровой экран проекта P18_1
Гулич Анна
Гулич Анна
Невозможно пройти тесты, в окне с вопросами пусто
Сашечка Огнев
Сашечка Огнев
Россия, Красноярский край
Андрей Корягин
Андрей Корягин
Россия, Пенза, Вазерская средняя школа, 2001