Основы разработки компьютерных игр в XNA Game Studio

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

Разбить на страницы
Показывать лекцию целиком

Примеры к лабораторной работе

Задачи работы

  • Рассмотреть подходы к созданию игрового мира
  • Научиться использовать в качестве игрового мира готовые модели
  • Ознакомиться с управлением освещением и с выводом теней в трехмерных играх
  • Подходы к созданию игрового мира

    Для создания игрового мира трехмерной игры используют несколько подходов.

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

    Загрузка игрового мира

    В качестве объекта игрового мира, который мы использовали для демонстрации, применен готовый игровой объект – .

    Создадим новый игровой проект P17_1. На рис. 22.1. вы можете видеть его окно Project Explorer.

    (рис 22.1) Окно Project Explorer проекта P17_1

    В качестве игрового объекта мы используем модель ball.fbx, для построения игрового мира применим модель дома house1.x и плоскость plane.fbx. В этом проекте мы используем уже знакомый вам по предыдущим проектам класс modCls – он применяется для вывода моделей.

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

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
    
    namespace P17_1
    {
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            //Матрицы
            Matrix viewMatrix;
            Matrix projMatrix;
            //Модели
            Model ball, house, plane;
            // Позиция объекта, поворот
            Vector3 avatarPosition = new Vector3(0, 0, -10);
            float avatarlRotation;
    
            // Положение камеры
            Vector3 cameraReference = new Vector3(0, 0, 10);
            Vector3 thirdPersonReference = new Vector3(0, 20, 100);
    
            // Скорости поворота и движения
            float rotationSpeed = 1f / 60f;
            float forwardSpeed = 10f / 60f;
    
            //Поле зрения камеры
            float viewAngle = MathHelper.ToRadians(45.0f);
    
            //Расстояние от камеры до переднего и заднего плана
            float nearClip = 1.0f;
            float farClip = 2000.0f;
            //Объекты
            modCls ballObj;
            modCls mHouse;
            modCls mPlane;
            
            //Соотношение сторон экрана
            float aspectRatio;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
    
            }
    
            protected override void Initialize()
            {
                // TODO: Add your initialization logic here
    
                base.Initialize();
            }
    
            
            protected override void LoadContent()
            {
               //Загрузка моделей
               ball = Content.Load<Model>("ball");
               house  = Content.Load<Model>("house1");
               plane = Content.Load<Model>("plane");
                aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
      (float)graphics.GraphicsDevice.Viewport.Height;
    
    
            }
    
            
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
    
            protected override void Update(GameTime gameTime)
            {
                //обновить положение объекта
                UpdateAvatarPosition();
                base.Update(gameTime);
            }
    
            protected override void Draw(GameTime gameTime)
            {
              graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
    
                UpdateCameraThirdPerson();
    
    
                //вывести объекты сцены
                DrawScene();
    
                base.Draw(gameTime);
    
            }
    
            //Обновляем состояние объекта
            void UpdateAvatarPosition()
            {
                KeyboardState keyboardState = Keyboard.GetState();
    
                //Поворот влево
                if (keyboardState.IsKeyDown(Keys.Left))
                {
                    avatarlRotation += rotationSpeed;
                }
                //Поворот вправо
                if (keyboardState.IsKeyDown(Keys.Right))
                {
                    avatarlRotation -= rotationSpeed;
                }
                //Движение вперед
                if (keyboardState.IsKeyDown(Keys.Up))
                {
                    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;
                    
                }
                //Движение назад
                if (keyboardState.IsKeyDown(Keys.Down))
                {
                    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;
                  
                }
                //Движение вверх
                if (keyboardState.IsKeyDown(Keys.W))
                {
                    Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
                    Vector3 v = new Vector3(0, forwardSpeed, 0);
                    v = Vector3.Transform(v, forwardMovement);
                    avatarPosition.Y += v.Y;
                   
                }
                //Движение вниз
                if (keyboardState.IsKeyDown(Keys.S))
                {
                    Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
                    Vector3 v = new Vector3(0, -forwardSpeed, 0);
                    v = Vector3.Transform(v, forwardMovement);
                    avatarPosition.Y += v.Y;
                   
                }
    
                //Уменьшение угла обзора камеры
                if (keyboardState.IsKeyDown(Keys.R))
                {
                    viewAngle -= MathHelper.ToRadians(1.0f);
                }
                //Увеличение угла обзора камеры
                if (keyboardState.IsKeyDown(Keys.F))
                {
                    viewAngle += MathHelper.ToRadians(1.0f);
                }
                //Если новый угол обзора вышел за дозволенные пределы
                //изменяем его
                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);
            }
    
            //Вывод объектов сцены
            void DrawScene()
            {
                //очистить коллекцию компонентов
                Components.Clear();
                //Вывести дом
                mHouse = new modCls(this, house, graphics);
                mHouse .WorldMatrix = Matrix.CreateTranslation(0, 0, -5)*Matrix .CreateScale (5f);
                mHouse .ViewMatrix = viewMatrix;
                mHouse .ProjectMatrix = projMatrix;
                Components .Add (mHouse );
                //выведем игровой объект
                ballObj = new modCls(this, ball, graphics);
                ballObj.WorldMatrix = Matrix.CreateRotationY(avatarlRotation) * Matrix.CreateTranslation(avatarPosition);
                ballObj.ViewMatrix = viewMatrix;
                ballObj.ProjectMatrix = projMatrix;
                Components.Add(ballObj);
                //Вывести плоскость
                mPlane = new modCls(this, plane, graphics);
                mPlane.WorldMatrix = Matrix.CreateScale(700) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *
                    Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *
                    Matrix.CreateTranslation(0, -2, 0);
                mPlane.ViewMatrix = viewMatrix;
                mPlane.ProjectMatrix = projMatrix;
                Components.Add(mPlane);
            }
        }
    }

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

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

    Управление освещением, тени

    Рассмотрим управление освещением и отображением теней в XNA. Существуют различные техники работы с тенями, мы рассмотрим отрисовку теней с использованием так называемого буфера трафаретов, или, по-английски - Stencil Buffer. В частности, мы модифицируем проект P16_2 таким образом, чтобы шарики, падающие на плоскость, отбрасывали тени.

    Stencil Buffer является стандартным устройством, входящим в состав современных видеокарт. Однако, различные видеокарты могут иметь различный размер этого буфера, поэтому, перед его использованием, необходимо определить, какой именно буфер доступен на видеокарте, используемой в данный момент.

    Для создания тени мы воспользуемся методом CreateShadow объекта Matrix. Он позволяет создавать тень от объекта на основе информации об источнике освещения и плоскости, на которую должна проецироваться тень.

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

    В этом же примере мы рассмотрим настройку источника света. В частности, мы применим для рисования объектов один направленный источник света, направление которого можно менять с помощью клавиш клавиатуры – координата Z изменяется с помощью клавиш-стрелок вверх-вниз, координата X – клавишами вправо-влево, координата Y – клавишами W и S. Изменение направления освещения влияет не только на освещение объектов, но и на тень.

    Создадим новый проект – P17_2.

    На рис. 22.3. вы можете видеть его окно Project Explorer.

    (рис 22.3) Окно Project Explorer проекта P17_2

    Для представления моделей, выводимых на экран, мы используем класс modCls, в качестве ресурсов игры используем шар ball2 и плоскость plane.

    Рассмотрим код класса modCls (листинг. 22.2.) В проекте P16_2 этот класс отвечал за вывод изображений на экран. Сейчас он выполняет ту же функцию, дополнив вывод изображений визуализацией теней.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P17_2
    {
        /// <summary>
        /// This is a game component that implements IUpdateable.
        /// </summary>
        public class modCls : Microsoft.Xna.Framework.DrawableGameComponent
        {
            //Модель
            public Model myModel;
            //Мировая матрица, матрицы вида и проекции
            public Matrix WorldMatrix;
            public Matrix ViewMatrix;
            public Matrix ProjectMatrix;
            //Направление света
            public Vector3 LightDirection;
            //Матрица для отображения тени
            Matrix shadow;
            //Плоскость, на которой отображается тень
            Plane sPlane;
            //Соотношение сторон экрана
            public float aspectRatio;
            //Для управления графическим устройством
            GraphicsDeviceManager graphics;
            //Конструктор получает на вход 
            //игровой класс, модель, объект для управления графическим устройством и плоскость для вывода тени
            public modCls(Game game, Model mod, GraphicsDeviceManager grf, Plane pl)
                : base(game)
            {
                myModel = mod;
                graphics = grf;
                sPlane = pl;
                aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
    (float)graphics.GraphicsDevice.Viewport.Height;
                LightDirection = new Vector3();
            }
    
            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)
                    {
                        //Включить источник направленного света №0
                        effect.DirectionalLight0.Enabled = true;
                        //Настроить параметры
                        effect.DirectionalLight0.DiffuseColor = Vector3.One;
                        effect.DirectionalLight0.SpecularColor = Vector3.One;
                        //Направление света - в класса Game1 мы меняем направление
                        //по клавиатурным командам
                        effect.DirectionalLight0.Direction = Vector3.Normalize(LightDirection);
                        //Включить освещение
                        effect.LightingEnabled = true;
                        //Установка матриц
                        effect.World = WorldMatrix;
                        effect.View = ViewMatrix;
                        effect.Projection = ProjectMatrix;
                    }
                    mesh.Draw();
                }
    
                //Создать матрицу тени
                shadow = Matrix.CreateShadow(-LightDirection, sPlane);
                //Очистить буфер трафаретов
                graphics.GraphicsDevice.Clear(ClearOptions.Stencil, Color.Black, 0, 0);
                //Включть буфер трафаретов
                graphics.GraphicsDevice.RenderState.StencilEnable = true;
                // Вывод на экран элемента буфера в том случае, если он установлен в 0         
                graphics.GraphicsDevice.RenderState.ReferenceStencil = 0;
                graphics.GraphicsDevice.RenderState.StencilFunction = CompareFunction.Equal;
                //При выводе применяем операцию увеличения
                graphics.GraphicsDevice.RenderState.StencilPass = StencilOperation.Increment;
                //Включаем альфа-смешивание для того, чтобы сделать тень полупрозрачной
                graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
                graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
                graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                //Выводим тень
                foreach (ModelMesh mesh in myModel.Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.AmbientLightColor = Vector3.Zero;
                        effect.Alpha = 0.5f;
                        effect.DirectionalLight0.Enabled = false;
                        effect.DirectionalLight1.Enabled = false;
                        effect.DirectionalLight2.Enabled = false;
                        effect.View = ViewMatrix;
                        effect.Projection = ProjectMatrix;
                        //При выводе тени умножаем мировую матрицу
                        //на матрицу вывода тени
                        effect.World = WorldMatrix*shadow;
                    }
                    mesh.Draw();
                }
                //Отключить буфер
                graphics.GraphicsDevice.RenderState.StencilEnable = false;
                //Отключить альфа-смешивание
                graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
                base.Draw(gameTime);
            }
        }
    }

    В листинге 22.3. вы можете найти код класса Game1.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
    
    namespace P17_2
    {
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            //Матрицы
            Matrix viewMatrix;
            Matrix projMatrix;
            //Модели
            modCls[] cls;
            //Положение моделей в пространстве
            Vector3[] offset;
            //Соотношение сторон окна вывода
            float aspectRatio;
            //Плоскость
            modCls plane;
            //Направление света
            Vector3 LightDirection;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
                //Установить формат буфера трафаретов
                graphics.PreferredDepthStencilFormat = SelectStencilMode();
            }
    
           //Проверка доступного буфера трафаретов и возврат соответствующей переменной типа
           //DepthFormat. Если буфер трафаретов не найден - выдадим исключение
            private static DepthFormat SelectStencilMode()
            {
                //Графическая карта
                GraphicsAdapter adapter = GraphicsAdapter.DefaultAdapter;
                //Формат поверхности
                SurfaceFormat format = adapter.CurrentDisplayMode.Format;
                //Проверка имеющихся технических средств карты
                if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8))
                    return DepthFormat.Depth24Stencil8;
                else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8Single))
                    return DepthFormat.Depth24Stencil8Single;
                else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil4))
                    return DepthFormat.Depth24Stencil4;
                else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth15Stencil1))
                    return DepthFormat.Depth15Stencil1;
                else
                    throw new InvalidOperationException("Could Not Find Stencil Buffer for Default Adapter");
            }
    
            protected override void Initialize()
            {
                //Отобразим указатель мыши
                IsMouseVisible = true;
                base.Initialize();
            }
    
            
            protected override void LoadContent()
            {           
                //Вычислим соотношение сторон
                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]);
                }
            }
    
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
    
            
            protected override void Update(GameTime gameTime)
            {
                //Для случайных колебаний моделей
                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]);
                }
                //Проверяем попадание мыши по объекту
                CheckMouseClick();
                //Проверяем пересечение с плоскостью
                CheckPlane();
                //Меняем позицию источника света по нажатию клавиш
                LightSourceControl();
                base.Update(gameTime);
            }
            //Изменение позиции источника света
            void LightSourceControl()
            {
                //Получим состояние клавиатуры
                KeyboardState key = Keyboard.GetState();
                //Клавиша "вверх" - уменьшим позицию по Z
                if (key.IsKeyDown(Keys .Up ))
                {
                    LightDirection.Z -= 0.5f;
                }
                //Клавиша "вниз" - увеличим Z
                if (key.IsKeyDown(Keys.Down))
                {
                    LightDirection.Z += 0.5f;
                }
                //"Влево" - уменьшим X
                if (key.IsKeyDown(Keys.Left))
                {
                    LightDirection.X -= 0.5f;
                }
                //"Вправо" - увеличим X
                if (key.IsKeyDown(Keys.Right))
                {
                    LightDirection.X += 0.5f;
                }
                //"W" - увеличим Y
                if (key.IsKeyDown(Keys.W))
                {
                    LightDirection.Y += 0.5f;
                }
                //"S" - уменьшим Y
                if (key.IsKeyDown(Keys.S))
                {
                    LightDirection.Y -= 0.5f;
                }
                //Изменим направление источника света для плоскости
                plane.LightDirection = LightDirection;
                //Изменим направление источника света для моделей
                for (int i = 0; i < 3; i++)
                {
                    cls[i].LightDirection = LightDirection;
                }
                //Выведем в заголовок окна информацию о направлении
                this.Window.Title = "Light source: " + LightDirection.ToString();
    
            }
            //Проверка на нажатие клавиши мыши
            void CheckMouseClick()
            {
                //получим состояние мыши
                MouseState mouseState = Mouse.GetState();
                //если нажата левая кнопка
                if (mouseState.LeftButton == ButtonState.Pressed)
                {
                    //Получим луч, идущий от позиции мыши на экране в пространство
                    Ray pickRay = GetPickRay();
                    //Переменная для хранения сферы, соответствующей объекту
                    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()
            {
                //получить состояние мыши
                MouseState mouseState = Mouse.GetState();
                //Сохранить координаты
                int mouseX = mouseState.X;
                int mouseY = mouseState.Y;
                //Точки для вычисления направления луча - одна, соответствующая координате мыши
                //вторая - направленная по оси Z
                Vector3 nearsource = new Vector3((float)mouseX, (float)mouseY, 0f);
                Vector3 farsource = new Vector3((float)mouseX, (float)mouseY, 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>
            /// This is called when the game should draw itself.
            /// </summary>
            /// <param name="gameTime">Provides a snapshot of timing values.</param>
            protected override void Draw(GameTime gameTime)
            {
                graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
    
                // TODO: Add your drawing code here
    
                base.Draw(gameTime);
            }
        }
    }

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

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

    Задание

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

  • Систему меню
  • Систему подсчета очков пользователя
  • Увеличение сложности игры при прохождении в следующий уровень
  • Страницы:

    Примеры к лабораторной работе

    Задачи работы

  • Рассмотреть подходы к созданию игрового мира
  • Научиться использовать в качестве игрового мира готовые модели
  • Ознакомиться с управлением освещением и с выводом теней в трехмерных играх
  • Подходы к созданию игрового мира

    Для создания игрового мира трехмерной игры используют несколько подходов.

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

    Загрузка игрового мира

    В качестве объекта игрового мира, который мы использовали для демонстрации, применен готовый игровой объект – .

    Создадим новый игровой проект P17_1. На рис. 22.1. вы можете видеть его окно Project Explorer.

    (рис 22.1) Окно Project Explorer проекта P17_1

    В качестве игрового объекта мы используем модель ball.fbx, для построения игрового мира применим модель дома house1.x и плоскость plane.fbx. В этом проекте мы используем уже знакомый вам по предыдущим проектам класс modCls – он применяется для вывода моделей.

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

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
    
    namespace P17_1
    {
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            //Матрицы
            Matrix viewMatrix;
            Matrix projMatrix;
            //Модели
            Model ball, house, plane;
            // Позиция объекта, поворот
            Vector3 avatarPosition = new Vector3(0, 0, -10);
            float avatarlRotation;
    
            // Положение камеры
            Vector3 cameraReference = new Vector3(0, 0, 10);
            Vector3 thirdPersonReference = new Vector3(0, 20, 100);
    
            // Скорости поворота и движения
            float rotationSpeed = 1f / 60f;
            float forwardSpeed = 10f / 60f;
    
            //Поле зрения камеры
            float viewAngle = MathHelper.ToRadians(45.0f);
    
            //Расстояние от камеры до переднего и заднего плана
            float nearClip = 1.0f;
            float farClip = 2000.0f;
            //Объекты
            modCls ballObj;
            modCls mHouse;
            modCls mPlane;
            
            //Соотношение сторон экрана
            float aspectRatio;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
    
            }
    
            protected override void Initialize()
            {
                // TODO: Add your initialization logic here
    
                base.Initialize();
            }
    
            
            protected override void LoadContent()
            {
               //Загрузка моделей
               ball = Content.Load<Model>("ball");
               house  = Content.Load<Model>("house1");
               plane = Content.Load<Model>("plane");
                aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
      (float)graphics.GraphicsDevice.Viewport.Height;
    
    
            }
    
            
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
    
            protected override void Update(GameTime gameTime)
            {
                //обновить положение объекта
                UpdateAvatarPosition();
                base.Update(gameTime);
            }
    
            protected override void Draw(GameTime gameTime)
            {
              graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
    
                UpdateCameraThirdPerson();
    
    
                //вывести объекты сцены
                DrawScene();
    
                base.Draw(gameTime);
    
            }
    
            //Обновляем состояние объекта
            void UpdateAvatarPosition()
            {
                KeyboardState keyboardState = Keyboard.GetState();
    
                //Поворот влево
                if (keyboardState.IsKeyDown(Keys.Left))
                {
                    avatarlRotation += rotationSpeed;
                }
                //Поворот вправо
                if (keyboardState.IsKeyDown(Keys.Right))
                {
                    avatarlRotation -= rotationSpeed;
                }
                //Движение вперед
                if (keyboardState.IsKeyDown(Keys.Up))
                {
                    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;
                    
                }
                //Движение назад
                if (keyboardState.IsKeyDown(Keys.Down))
                {
                    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;
                  
                }
                //Движение вверх
                if (keyboardState.IsKeyDown(Keys.W))
                {
                    Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
                    Vector3 v = new Vector3(0, forwardSpeed, 0);
                    v = Vector3.Transform(v, forwardMovement);
                    avatarPosition.Y += v.Y;
                   
                }
                //Движение вниз
                if (keyboardState.IsKeyDown(Keys.S))
                {
                    Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
                    Vector3 v = new Vector3(0, -forwardSpeed, 0);
                    v = Vector3.Transform(v, forwardMovement);
                    avatarPosition.Y += v.Y;
                   
                }
    
                //Уменьшение угла обзора камеры
                if (keyboardState.IsKeyDown(Keys.R))
                {
                    viewAngle -= MathHelper.ToRadians(1.0f);
                }
                //Увеличение угла обзора камеры
                if (keyboardState.IsKeyDown(Keys.F))
                {
                    viewAngle += MathHelper.ToRadians(1.0f);
                }
                //Если новый угол обзора вышел за дозволенные пределы
                //изменяем его
                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);
            }
    
            //Вывод объектов сцены
            void DrawScene()
            {
                //очистить коллекцию компонентов
                Components.Clear();
                //Вывести дом
                mHouse = new modCls(this, house, graphics);
                mHouse .WorldMatrix = Matrix.CreateTranslation(0, 0, -5)*Matrix .CreateScale (5f);
                mHouse .ViewMatrix = viewMatrix;
                mHouse .ProjectMatrix = projMatrix;
                Components .Add (mHouse );
                //выведем игровой объект
                ballObj = new modCls(this, ball, graphics);
                ballObj.WorldMatrix = Matrix.CreateRotationY(avatarlRotation) * Matrix.CreateTranslation(avatarPosition);
                ballObj.ViewMatrix = viewMatrix;
                ballObj.ProjectMatrix = projMatrix;
                Components.Add(ballObj);
                //Вывести плоскость
                mPlane = new modCls(this, plane, graphics);
                mPlane.WorldMatrix = Matrix.CreateScale(700) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *
                    Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *
                    Matrix.CreateTranslation(0, -2, 0);
                mPlane.ViewMatrix = viewMatrix;
                mPlane.ProjectMatrix = projMatrix;
                Components.Add(mPlane);
            }
        }
    }

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

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

    Управление освещением, тени

    Рассмотрим управление освещением и отображением теней в XNA. Существуют различные техники работы с тенями, мы рассмотрим отрисовку теней с использованием так называемого буфера трафаретов, или, по-английски - Stencil Buffer. В частности, мы модифицируем проект P16_2 таким образом, чтобы шарики, падающие на плоскость, отбрасывали тени.

    Stencil Buffer является стандартным устройством, входящим в состав современных видеокарт. Однако, различные видеокарты могут иметь различный размер этого буфера, поэтому, перед его использованием, необходимо определить, какой именно буфер доступен на видеокарте, используемой в данный момент.

    Для создания тени мы воспользуемся методом CreateShadow объекта Matrix. Он позволяет создавать тень от объекта на основе информации об источнике освещения и плоскости, на которую должна проецироваться тень.

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

    В этом же примере мы рассмотрим настройку источника света. В частности, мы применим для рисования объектов один направленный источник света, направление которого можно менять с помощью клавиш клавиатуры – координата Z изменяется с помощью клавиш-стрелок вверх-вниз, координата X – клавишами вправо-влево, координата Y – клавишами W и S. Изменение направления освещения влияет не только на освещение объектов, но и на тень.

    Создадим новый проект – P17_2.

    На рис. 22.3. вы можете видеть его окно Project Explorer.

    (рис 22.3) Окно Project Explorer проекта P17_2

    Для представления моделей, выводимых на экран, мы используем класс modCls, в качестве ресурсов игры используем шар ball2 и плоскость plane.

    Рассмотрим код класса modCls (листинг. 22.2.) В проекте P16_2 этот класс отвечал за вывод изображений на экран. Сейчас он выполняет ту же функцию, дополнив вывод изображений визуализацией теней.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Storage;
    using Microsoft.Xna.Framework.Content;
    
    namespace P17_2
    {
        /// <summary>
        /// This is a game component that implements IUpdateable.
        /// </summary>
        public class modCls : Microsoft.Xna.Framework.DrawableGameComponent
        {
            //Модель
            public Model myModel;
            //Мировая матрица, матрицы вида и проекции
            public Matrix WorldMatrix;
            public Matrix ViewMatrix;
            public Matrix ProjectMatrix;
            //Направление света
            public Vector3 LightDirection;
            //Матрица для отображения тени
            Matrix shadow;
            //Плоскость, на которой отображается тень
            Plane sPlane;
            //Соотношение сторон экрана
            public float aspectRatio;
            //Для управления графическим устройством
            GraphicsDeviceManager graphics;
            //Конструктор получает на вход 
            //игровой класс, модель, объект для управления графическим устройством и плоскость для вывода тени
            public modCls(Game game, Model mod, GraphicsDeviceManager grf, Plane pl)
                : base(game)
            {
                myModel = mod;
                graphics = grf;
                sPlane = pl;
                aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
    (float)graphics.GraphicsDevice.Viewport.Height;
                LightDirection = new Vector3();
            }
    
            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)
                    {
                        //Включить источник направленного света №0
                        effect.DirectionalLight0.Enabled = true;
                        //Настроить параметры
                        effect.DirectionalLight0.DiffuseColor = Vector3.One;
                        effect.DirectionalLight0.SpecularColor = Vector3.One;
                        //Направление света - в класса Game1 мы меняем направление
                        //по клавиатурным командам
                        effect.DirectionalLight0.Direction = Vector3.Normalize(LightDirection);
                        //Включить освещение
                        effect.LightingEnabled = true;
                        //Установка матриц
                        effect.World = WorldMatrix;
                        effect.View = ViewMatrix;
                        effect.Projection = ProjectMatrix;
                    }
                    mesh.Draw();
                }
    
                //Создать матрицу тени
                shadow = Matrix.CreateShadow(-LightDirection, sPlane);
                //Очистить буфер трафаретов
                graphics.GraphicsDevice.Clear(ClearOptions.Stencil, Color.Black, 0, 0);
                //Включть буфер трафаретов
                graphics.GraphicsDevice.RenderState.StencilEnable = true;
                // Вывод на экран элемента буфера в том случае, если он установлен в 0         
                graphics.GraphicsDevice.RenderState.ReferenceStencil = 0;
                graphics.GraphicsDevice.RenderState.StencilFunction = CompareFunction.Equal;
                //При выводе применяем операцию увеличения
                graphics.GraphicsDevice.RenderState.StencilPass = StencilOperation.Increment;
                //Включаем альфа-смешивание для того, чтобы сделать тень полупрозрачной
                graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
                graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
                graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                //Выводим тень
                foreach (ModelMesh mesh in myModel.Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.AmbientLightColor = Vector3.Zero;
                        effect.Alpha = 0.5f;
                        effect.DirectionalLight0.Enabled = false;
                        effect.DirectionalLight1.Enabled = false;
                        effect.DirectionalLight2.Enabled = false;
                        effect.View = ViewMatrix;
                        effect.Projection = ProjectMatrix;
                        //При выводе тени умножаем мировую матрицу
                        //на матрицу вывода тени
                        effect.World = WorldMatrix*shadow;
                    }
                    mesh.Draw();
                }
                //Отключить буфер
                graphics.GraphicsDevice.RenderState.StencilEnable = false;
                //Отключить альфа-смешивание
                graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
                base.Draw(gameTime);
            }
        }
    }

    В листинге 22.3. вы можете найти код класса Game1.

    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
    
    namespace P17_2
    {
        public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            //Матрицы
            Matrix viewMatrix;
            Matrix projMatrix;
            //Модели
            modCls[] cls;
            //Положение моделей в пространстве
            Vector3[] offset;
            //Соотношение сторон окна вывода
            float aspectRatio;
            //Плоскость
            modCls plane;
            //Направление света
            Vector3 LightDirection;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
                //Установить формат буфера трафаретов
                graphics.PreferredDepthStencilFormat = SelectStencilMode();
            }
    
           //Проверка доступного буфера трафаретов и возврат соответствующей переменной типа
           //DepthFormat. Если буфер трафаретов не найден - выдадим исключение
            private static DepthFormat SelectStencilMode()
            {
                //Графическая карта
                GraphicsAdapter adapter = GraphicsAdapter.DefaultAdapter;
                //Формат поверхности
                SurfaceFormat format = adapter.CurrentDisplayMode.Format;
                //Проверка имеющихся технических средств карты
                if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8))
                    return DepthFormat.Depth24Stencil8;
                else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8Single))
                    return DepthFormat.Depth24Stencil8Single;
                else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil4))
                    return DepthFormat.Depth24Stencil4;
                else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth15Stencil1))
                    return DepthFormat.Depth15Stencil1;
                else
                    throw new InvalidOperationException("Could Not Find Stencil Buffer for Default Adapter");
            }
    
            protected override void Initialize()
            {
                //Отобразим указатель мыши
                IsMouseVisible = true;
                base.Initialize();
            }
    
            
            protected override void LoadContent()
            {           
                //Вычислим соотношение сторон
                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]);
                }
            }
    
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
    
            
            protected override void Update(GameTime gameTime)
            {
                //Для случайных колебаний моделей
                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]);
                }
                //Проверяем попадание мыши по объекту
                CheckMouseClick();
                //Проверяем пересечение с плоскостью
                CheckPlane();
                //Меняем позицию источника света по нажатию клавиш
                LightSourceControl();
                base.Update(gameTime);
            }
            //Изменение позиции источника света
            void LightSourceControl()
            {
                //Получим состояние клавиатуры
                KeyboardState key = Keyboard.GetState();
                //Клавиша "вверх" - уменьшим позицию по Z
                if (key.IsKeyDown(Keys .Up ))
                {
                    LightDirection.Z -= 0.5f;
                }
                //Клавиша "вниз" - увеличим Z
                if (key.IsKeyDown(Keys.Down))
                {
                    LightDirection.Z += 0.5f;
                }
                //"Влево" - уменьшим X
                if (key.IsKeyDown(Keys.Left))
                {
                    LightDirection.X -= 0.5f;
                }
                //"Вправо" - увеличим X
                if (key.IsKeyDown(Keys.Right))
                {
                    LightDirection.X += 0.5f;
                }
                //"W" - увеличим Y
                if (key.IsKeyDown(Keys.W))
                {
                    LightDirection.Y += 0.5f;
                }
                //"S" - уменьшим Y
                if (key.IsKeyDown(Keys.S))
                {
                    LightDirection.Y -= 0.5f;
                }
                //Изменим направление источника света для плоскости
                plane.LightDirection = LightDirection;
                //Изменим направление источника света для моделей
                for (int i = 0; i < 3; i++)
                {
                    cls[i].LightDirection = LightDirection;
                }
                //Выведем в заголовок окна информацию о направлении
                this.Window.Title = "Light source: " + LightDirection.ToString();
    
            }
            //Проверка на нажатие клавиши мыши
            void CheckMouseClick()
            {
                //получим состояние мыши
                MouseState mouseState = Mouse.GetState();
                //если нажата левая кнопка
                if (mouseState.LeftButton == ButtonState.Pressed)
                {
                    //Получим луч, идущий от позиции мыши на экране в пространство
                    Ray pickRay = GetPickRay();
                    //Переменная для хранения сферы, соответствующей объекту
                    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()
            {
                //получить состояние мыши
                MouseState mouseState = Mouse.GetState();
                //Сохранить координаты
                int mouseX = mouseState.X;
                int mouseY = mouseState.Y;
                //Точки для вычисления направления луча - одна, соответствующая координате мыши
                //вторая - направленная по оси Z
                Vector3 nearsource = new Vector3((float)mouseX, (float)mouseY, 0f);
                Vector3 farsource = new Vector3((float)mouseX, (float)mouseY, 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>
            /// This is called when the game should draw itself.
            /// </summary>
            /// <param name="gameTime">Provides a snapshot of timing values.</param>
            protected override void Draw(GameTime gameTime)
            {
                graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
    
                // TODO: Add your drawing code here
    
                base.Draw(gameTime);
            }
        }
    }

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

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

    Задание

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

  • Систему меню
  • Систему подсчета очков пользователя
  • Увеличение сложности игры при прохождении в следующий уровень
  • Вернуться к учебному плану