Разработка компьютерных игр для Windows Phone 7 с использованием технологий Silverlight и XNA

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

Показывать лекцию целиком

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

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) Обозреватель решений проекта 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.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 вы можете видеть игровой экран проекта P18_1.

(рис 25.2) Игровой экран проекта P18_1

25.2. Касания экрана и трехмерные объекты, объекты Ray и Plane

Разработаем следующую простую компьютерную игру. На экране видны несколько объектов. Игрок должен не допустить падения объектов "на пол", касаясь их. После каждого удачного касания объект "взлетает", но потом снова начинает "падать".

Для реализации этого проекта нам понадобится обрабатывать взаимодействие позиции касания и трехмерных объектов, то есть – объекта, который находится в двумерной системе координат и объекта, который находится в трехмерной системе. Первая сложность, которая возникает при обработке подобного взаимодействия, заключается в том, что каждый из объектов находится в собственной системе координат – прежде чем переходить к каким-либо действиям, нужно найти способ сопоставить эти системы. Далее, после того, как найдено соответствие систем, нужно создать объект Ray, который используется для проверки попадания по объекту.

В случае с созданием "пола", на который будут падать объекты – можно отметить, что для его моделирования можно воспользоваться объектом Plane, представляющим плоскость.

В основу нашего примера мы положим пример, размещенный в справочной системе XNA. Он строит луч, исходящий из позиции, заданной двумерной координатой на экране. После получения луча мы проверяем, пересекается ли он со сферой, ограничивающей объект и по результатам проверки проводим определенные действия.

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

На рис. 25.3 вы можете видеть окно ProjectExplorer проекта P18_2.

(рис 25.3) Обозреватель решений для P18_2

В листинге 25.3 вы можете видеть код класса modCls.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


namespace P18_2
{
    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;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here
            base.Initialize();
        }

        public override void Draw(GameTime gameTime)
        {

            foreach (ModelMesh mesh in myModel.Meshes)
            {
                //Для каждого эффекта в сети
                foreach (BasicEffect effect in mesh.Effects)
                {
                    //Установим освещение 

                    effect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f);
                    effect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
                    effect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
                    effect.SpecularPower = 7.0f;
                    effect.LightingEnabled = true;


                    effect.DirectionalLight0.Enabled = true; // активируем каждый источник света отдельно
                    if (effect.DirectionalLight0.Enabled)
                    {
                        // Направление по Х
                        effect.DirectionalLight0.DiffuseColor = new Vector3(1, 0, 0); // диапазон от 0 до 1
                        effect.DirectionalLight0.Direction = Vector3.Normalize(new Vector3(-1, 0, 0));
                        // Направление от источника света к началу координат сцены
                        effect.DirectionalLight0.SpecularColor = Vector3.One;
                    }

                    effect.DirectionalLight1.Enabled = true;
                    if (effect.DirectionalLight1.Enabled)
                    {
                        // Направление по У
                        effect.DirectionalLight1.DiffuseColor = new Vector3(0, 0.75f, 0);
                        effect.DirectionalLight1.Direction = Vector3.Normalize(new Vector3(0, -1, 0));
                        effect.DirectionalLight1.SpecularColor = Vector3.One;
                    }

                    effect.DirectionalLight2.Enabled = true;
                    if (effect.DirectionalLight2.Enabled)
                    {
                        // Направление по Z
                        effect.DirectionalLight2.DiffuseColor = new Vector3(0, 0, 0.5f);
                        effect.DirectionalLight2.Direction = Vector3.Normalize(new Vector3(0, 0, -1));
                        effect.DirectionalLight2.SpecularColor = Vector3.One;
                    }
                    //установить матрицы
                    effect.World = WorldMatrix;
                    effect.View = ViewMatrix;
                    effect.Projection = ProjectMatrix;
                }
                mesh.Draw();
            }

            base.Draw(gameTime);
        }
    }

}

В листинге 25.4 вы можете видеть код класса 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_2
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        //Матрицы
        Matrix viewMatrix;
        Matrix projMatrix;
        //Модели
        modCls[] cls;
        //Положение моделей в пространстве
        Vector3[] offset;
        //Соотношение сторон окна вывода
        float aspectRatio;
        //Плоскость
        modCls plane;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.IsFullScreen = true;
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {

            base.Initialize();
        }

        /// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {

            //Три элемента в массиве объектов
            cls = new modCls[3];
            //Три элемента в массиве положений объектов
            offset = new Vector3[3];
            //Зададим начальные положения моделей
            offset[0] = new Vector3(0, 2, 0);
            offset[1] = new Vector3(-6, 3, -4);
            offset[2] = new Vector3(6, 4, -6);
            //Создадим модели
            cls[0] = new modCls(this, Content.Load<Model>("ball2"), graphics);
            cls[1] = new modCls(this, Content.Load<Model>("ball2"), graphics);
            cls[2] = new modCls(this, Content.Load<Model>("ball2"), graphics);
            //Вычислим соотношение сторон
            aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
                (float)graphics.GraphicsDevice.Viewport.Height;
            //Матрица вида
            viewMatrix = Matrix.CreateLookAt(new Vector3(0, 10, 15),
                new Vector3(0, 0, 0),
                new Vector3(0.0f, 1.0f, 0.0f));
            //Матрица проекции
            projMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),
                aspectRatio, 1.0f, 1000.0f);
            //Задаем параметры матриц каждой из моделей
            for (int i = 0; i < 3; i++)
            {
                cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
                cls[i].ProjectMatrix = projMatrix;
                cls[i].ViewMatrix = viewMatrix;
                Components.Add(cls[i]);
            }
            //Создаем плоскость
            plane = new modCls(this, Content.Load<Model>("plane"), graphics);
            //Настраиваем параметры плоскости
            plane.WorldMatrix = Matrix.CreateScale(8) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *
    Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *
    Matrix.CreateTranslation(0, -4, 0);
            plane.ViewMatrix = viewMatrix;
            plane.ProjectMatrix = projMatrix;
            Components.Add(plane);
        }

        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            //В каждом проходе цикла
            //Смещаем каждую модель вниз на 0.04 единицы
            for (int i = 0; i < 3; i++)
            {
                offset[i].Y -= 0.04f;
                cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
            }
            //Проверяем попадание  по объекту
            CheckTouch();
            //Проверяем пересечение с плоскостью
            CheckPlane();

            base.Update(gameTime);
        }
        void CheckTouch()
        {
            TouchCollection touchLocations = TouchPanel.GetState();
            foreach (TouchLocation touchLocation in touchLocations)
            {
                if (touchLocation.State == TouchLocationState.Pressed)
                {

                    //Получим луч, идущий от позиции мыши на экране в пространство
                    Ray pickRay = GetPickRay(touchLocation.Position);
                    //Переменная для хранения сферы, соответствующей объекту
                    BoundingSphere b1;
                    //Переменная для хранения вектора размера модели
                    Vector3 scale;
                    //Переменная для хранения информации о повороте модели
                    Quaternion rotation;
                    //Переменая для хранения информации о позиции модели
                    Vector3 translation;
                    for (int i = 0; i < 3; i++)
                    {
                        //Получить BoundingSphere для текущего объекта
                        b1 = cls[i].myModel.Meshes[0].BoundingSphere;
                        //Получить параметры - размер, поворот, позицию для объекта
                        cls[i].WorldMatrix.Decompose(out scale, out rotation, out translation);
                        //Установить центр сферы в соответствии с позицией объекта
                        b1.Center = translation;
                        //Получить результат пересечения луча и сферы
                        Nullable<float> result = pickRay.Intersects(b1);
                        //Если луч и сфера пересеклись - "поднять" объект до позиции Y=4
                        if (result.HasValue)
                        {
                            offset[i].Y = 4;
                            cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
                        }
                    }
                }
            }
        }
        //Проверка на столкновение с плоскостью
        void CheckPlane()
        {
            //Создадим плоскость по трем точкам - она пересекает ось Y в точке -4 и не пересекает
            //другие оси
            Plane pl = new Plane(new Vector3(0, -4, 0), new Vector3(2, -4, 1), new Vector3(-1, -4, -2));
            //Переменная для хранения типа пересечения с плоскостью
            //Она может сигнализировать о нахождении объекта перед плоскостью (выше в нашем случае)
            //за плоскостью и о пересечении с плоскостью
            PlaneIntersectionType plType;
            //Сфера для объекта
            BoundingSphere b1;
            //Переменная для хранения вектора размера модели
            Vector3 scale;
            //Переменная для хранения информации о повороте модели
            Quaternion rotation;
            //Переменая для хранения информации о позиции модели
            Vector3 translation;
            for (int i = 0; i < 3; i++)
            {
                //Получить BoundingSphere для текущего объекта
                b1 = cls[i].myModel.Meshes[0].BoundingSphere;
                //Получить параметры - размер, поворот, позицию для объекта
                cls[i].WorldMatrix.Decompose(out scale, out rotation, out translation);
                //Установить центр сферы в соответствии с позицией объекта
                b1.Center = translation;
                //Получить тип пересечения сферы объекта и плоскости
                plType = pl.Intersects(b1);
                //Если сфера пересекла плоскость
                if (plType == PlaneIntersectionType.Intersecting)
                {
                    //Удалить соответствующий игровой объект из коллекции
                    Components.Remove(cls[i]);
                }
            }

        }
        //Функция для вычисления луча, исходящего из точки касания
        Ray GetPickRay(Vector2 touchPos)
        {

            //Точки для вычисления направления луча - одна, соответствующая координате мыши
            //вторая - направленная по оси Z
            Vector3 nearsource = new Vector3(touchPos.X, touchPos.Y, 0f);
            Vector3 farsource = new Vector3(touchPos.X, touchPos.Y, 1f);
            //Мировая матрица для вычислений
            Matrix world = Matrix.CreateTranslation(0, 0, 0);
            //Переведем координаты мыши в координаты трехмерного пространства
            Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearsource, projMatrix, viewMatrix, world);
            Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farsource, projMatrix, viewMatrix, world);
            // Получим направление луча
            Vector3 direction = farPoint - nearPoint;
            //Нормализуем его (преобразуем к единичному вектору)
            direction.Normalize();
            //Создадим новый луч, начинающийся в точке, соответствующей координате
            //указателя мыши в объектном пространстве, с направлением, 
            //соответствующим вычисленному направлению
            Ray pickRay = new Ray(nearPoint, direction);
            //возвратим луч в процедуру, вызывавшую данную функцию
            return pickRay;
        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            base.Draw(gameTime);
        }
    }
}

На рис. 25.4 вы можете видеть игровое окно проекта P18_2.

(рис 25.4) Экран проекта P18_2

25.3. Выводы

В данной лабораторной работе мы рассмотрели организацию взаимодействия трехмерных объектов, объекты, которые можно использовать для данных целей. В частности, поработали с объектами BoundingSphere, Plane, Ray.

25.4. Задание

Изучите самостоятельно использование объекта BoundingFrustum. Разработайте приложение, игровое пространство которого представляет собой "комнату", одна из стен которой совпадает с плоскостью экрана. Наполните "комнату" движущимися, управляемыми автоматически, объектами, которые "отскакивают" от стен комнаты, соприкасаясь с ними.

25.5. Дополнительные материалы

К данной лабораторной работе прилагается видеоурок

и демонстрационный проект.

Вернуться к учебному плану