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

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

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

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

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

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

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

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

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

Обозреватель решений для 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.3. Код класса modCls

В листинге 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. Код класса Game1

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

Экран проекта P18_2

Рис. 25.4. Экран проекта P18_2

25.3. Выводы

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

25.4. Задание

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

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