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

Игровой мир, освещение, тени

В листинге 26.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 P19_2
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        //Матрицы
        Matrix viewMatrix;
        Matrix projMatrix;
        //Модели
        modCls[] cls;
        //Положение моделей в пространстве
        Vector3[] offset;
        //Соотношение сторон окна вывода
        float aspectRatio;
        //Плоскость
        modCls plane;
        //Направление света
        Vector3 LightDirection;
        //Стрелки
        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);
            // Дополнительный заряд аккумулятора заблокирован.
            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);
            //Вычислим соотношение сторон
            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);
            LightDirection = new Vector3(-2, -2, 0);
            //Создаем плоскость
            plane = new modCls(this, Content.Load<Model>("plane"), graphics, new Plane());
            //Настраиваем параметры плоскости
            plane.WorldMatrix = Matrix.CreateScale(25) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *
    Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *
    Matrix.CreateTranslation(0, -4, 0);
            plane.ViewMatrix = viewMatrix;
            plane.ProjectMatrix = projMatrix;
            plane.LightDirection = LightDirection;
            Components.Add(plane);
            //Плоскость, на которую проецируется тень
            //Она немного выше плоскости, которую мы выводим на экран 
            //для того, чтобы тень была видна
            Plane pl1 = new Plane(new Vector3(0, -3.9f, 0), new Vector3(2, -3.9f, 1), new Vector3(-1, -3.9f, -2));
            //Три элемента в массиве объектов
            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, pl1);
            cls[1] = new modCls(this, Content.Load<Model>("ball2"), graphics, pl1);
            cls[2] = new modCls(this, Content.Load<Model>("ball2"), graphics, pl1);
            //Задаем параметры матриц и направления света каждой из моделей
            for (int i = 0; i < 3; i++)
            {
                cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
                cls[i].ProjectMatrix = projMatrix;
                cls[i].ViewMatrix = viewMatrix;
                cls[i].LightDirection = LightDirection;
                Components.Add(cls[i]);
            }

            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();

            //Для случайных колебаний моделей
            Random rand = new Random();
            //В каждом проходе цикла
            //Смещаем каждую модель вниз на 0.04 единицы
            //И на небольшое случайное число вдоль оси X и Y - модели
            //слегка колеблются при добавлении этого изменения
            //Мы не ввели ограничения на изменение X и Z - 
            //При практической реалиации данного примера это
            //желательно сделать
            for (int i = 0; i < 3; i++)
            {
                offset[i].Y -= 0.04f;
                offset[i].X -= (0.08f * (float)rand.NextDouble() - 0.08f * (float)rand.NextDouble());
                offset[i].Z -= (0.08f * (float)rand.NextDouble() - 0.08f * (float)rand.NextDouble());
                cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
            }
            //Проверяем попадание мыши по объекту
            CheckTouch();
            //Проверяем пересечение с плоскостью
            CheckPlane();
            //Меняем позицию источника света по нажатию клавиш
            LightSourceControl();
            base.Update(gameTime);

        }
        //Изменение позиции источника света
        void LightSourceControl()
        {

            TouchCollection touchLocations = TouchPanel.GetState();
            foreach (TouchLocation touchLocation in touchLocations)
            {
                if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
                {
                    //Увеличим позицию Y
                    if (MenuSelect(keyForward, touchLocation.Position))
                    {
                        LightDirection.Y += 0.5f;
                    }
                    //Уменьшим позицию по Z
                    if (MenuSelect(keyUp, touchLocation.Position))
                    {
                        LightDirection.Z -= 0.5f;
                    }
                    //Уменьшим позицию Y
                    if (MenuSelect(keyBack, touchLocation.Position))
                    {
                        LightDirection.Y -= 0.5f;
                    }
                    //Уменьшим позицию X
                    if (MenuSelect(keyLeft, touchLocation.Position))
                    {
                        LightDirection.X -= 0.5f;
                    }
                    //Увеличим позицию Z
                    if (MenuSelect(keyDown, touchLocation.Position))
                    {
                        LightDirection.Z += 0.5f;
                    }
                    //Увеличим позицию X
                    if (MenuSelect(keyRight, touchLocation.Position))
                    {
                        LightDirection.X += 0.5f;
                    }

                }
            }

            //Изменим направление источника света для плоскости
            plane.LightDirection = LightDirection;
            //Изменим направление источника света для моделей
            for (int i = 0; i < 3; i++)
            {
                cls[i].LightDirection = LightDirection;
            }

        }
        //Обработка касаний
        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);
            //Вывод стрелок
            spriteBatch.Begin();
            spriteBatch.Draw(txtArrows, new Rectangle(500, 280, 300, 200), Color.White);
            spriteBatch.End();
            //Для нормального отображение 3D-сцены после работы spriteBatch
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
        }
    }
}
Листинг 26.4. Код класса Game1

На рис. 26.5 вы можете видеть игровой экран проекта.

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

Рис. 26.5. Игровой экран проекта P19_2

26.4. Выводы

В данной лабораторной работе мы рассмотрели подходы к созданию игрового мира, рассмотрели некоторые особенности освещения объектов.

26.5. Задание

Разработайте на основе проекта P19_2 собственную игру, которая реализует следующие возможности:

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