Практикум прикладного программирования на C# в среде VS.NET 2008

Компьютерная графика 3D в XNA

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

Введение

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.

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

В трехмерной графике используется мировая система координат, ориентированная так, что плоскость xOy направлена параллельно экрану монитора, а ось Oz направлена либо к пользователю перпендикулярно экрану (правая система координат), либо от пользователя вглубь экрана (левая система координат). Если вытянуть в разные стороны три пальца правой руки (большой, указательный и средний) под прямыми углами, то большой будет указывать направление оси x, указательный - оси y, а средний - оси z. В DirectX используется левая система координат, а в OpenGL и XNA Framework - правая.

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

  • Поворот или ротация (на любой угол относительно любой произвольной оси, чаще складывается из последовательности поворотов относительно осей системы координат)
  • Сдвиг или трансляция (параллельное перемещение в пространстве)
  • Масштабирование (увеличение или уменьшение)
  • Чтобы выполнить одно из указанных действий для объекта в целом, нужно применить его к вершине каждого примитива, описывающего модель. Для выполнения таких действий служит мировая матрица преобразования. Это матрица размером 4x4, которая может принимать одно из следующих значений, в зависимости от выполняемых действий:

    Поворот относительно оси Ox
    1 0 0 0
    0 cos(a) sin(a) 0
    0 -sin(a) cos(a) 0
    0 0 0 1

    Поворот относительно оси Oy
    cos(a) 0 -sin(a) 0
    0 1 0 0
    sin(a) 0 cos(a) 0
    0 0 0 1

    Поворот относительно оси Oz
    cos(a) sin(a) 0 0
    -sin(a) cos(a) 0 0
    0 0 1 0
    0 0 0 1

    Смещение на Tx, Ty, Tz
    1 0 0 0
    0 1 0 0
    0 0 1 0
    Tx Ty Tz 1

    Масштабирование на Sx, Sy, Sz
    Sx 0 0 0
    0 Sy 0 0
    0 0 Sz 0
    0 0 0 1

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

    Кроме перечисленных модельных матриц имееются еще две матрицы:

  • Матрица вида
  • Проекционная матрица
  • Матрица вида содержит в себе координаты размещения камеры в трехмерном пространстве. У камеры есть вертикальная ось, совпадающая с координатой Oy мировой системы координат. Обычно ограничивают операции преобразования для координат камеры поворотами относительно этой оси и параллельными смещениями вправо/влево вверх/вниз, а также отдалением/приближением камеры к сцене.

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

    Создание заготовки приложения

    Новый проект мы разместим в уже существующей папке XNAProjects, созданной нами ранее при выполнении работы Lab58.

  • Откройте решение XNAProjects работы Lab58 и в панели Solution Explorer вызовите контекстное меню для узла самого решения Solution
  • Командой Add/New Project вызовите окно мастера и настройте его, с учетом версии библиотеки .NET Framework 3.0, так
  • Переименуйте файл Game1.cs нового проекта в StartGame3D.cs
  • Как видно из панели Solution Explorer, теперь в решении присутствуют два проекта: Game2D и новый проект Game3D. Обратите внимание, что Game2D выделен жирным шрифтом. Это значит, что он по умолчанию в оболочке установлен как стартовый. Если сейчас запустить выполнение решения, то будет выполнен выделенный проект Game2D. В дальнейшем есть два пути: либо назначить новый проект стартовым, либо исключить из решения старый проект, тогда новый проект автоматически станет стартовым. Для тренировки, мы вначале назначим новый проект стартовым, а потом исключим старый проект.

  • В панели Solution Explorer вызовите контекстное меню для узла Game3D и выполните команду Set as StartUp Project, чтобы сделать новый проект стартовым
  • Добавьте в конструктор класса заготовки нового проекта игры код установки размеров окна
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        graphics.PreferredBackBufferWidth = 300;
        graphics.PreferredBackBufferHeight = 200;
    }
  • Щелкните на кнопке Start Debugging (F5) и убедитесь, что запустилась именно заготовка приложения нового проекта Game3D
  • В панели Solution Explorer вызовите контекстное меню для узла Game2D и выполните команду Remove, чтобы исключить из решения старый проект, который после этого останется существовать в пассивном режиме
  • В прошлой игре мы устанавливали жестко размер экрана, и если это разрешение не совпадало с текущим перед запуском игры, то после запуска приложения система некоторое время настраивала требуемое разрешение, то есть была некоторая задержка в появлении экрана. В этом приложении мы оставим неизменными текущие размеры экрана, но определим их из видеоадаптера и запомним в полях класса игры для дальнейшего использования.

  • Удалите из конструктора класса игры код жесткой установки размеров экрана
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        //graphics.PreferredBackBufferWidth = 300;
        //graphics.PreferredBackBufferHeight = 200;
    }

    Поля класса для удобства разместим в секции #region.

  • Дополните основной класс игры и его методы кодом автоматической установки размеров экрана и выхода из игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        #region Поля класса 
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int screenWidth, screenHeight;  // Размеры экрана
        KeyboardState keyboardState;    // Буфер клавиатуры
        #endregion
    
        .....................................................
    
        protected override void Initialize()
        {
            GraphicsAdapter adapter = graphics.GraphicsDevice.
                CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
            screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
            screenHeight = adapter.CurrentDisplayMode.Height;   
            graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
            graphics.PreferredBackBufferHeight = screenHeight;  
            graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
            graphics.ApplyChanges();                            // Применить настройки
    
            base.Initialize();
        }
    
        .....................................................
    
        protected override void Update(GameTime gameTime)
        {
            // Читать буфер клавиатуры 
            keyboardState = Keyboard.GetState();
    
            // Выход из игрового процесса
            if (keyboardState.IsKeyDown(Keys.Escape))
                this.Exit();
    
            base.Update(gameTime);
        }
    
        .....................................................
    
    }
  • В текущее пространство имен перед классом StartGame3D добавьте объявление типа перечисления GameState с элементами обозначений игровых состояний, а в классе создайте поле этого типа и инициализируйте ее заставкой начала игры
  • namespace Game3D
    {
        // Объявление состояний экранов
        enum GameState
        {
            AboutScreen,    // Экран с информацией об игре
            GameOverScreen, // Экран проигрыша
            GameScreen,     // Экран игрового процесса
            HelpScreen,     // Инструкция о правилах
            MenuScreen,     // Меню игры
            GameScreen,   // Первая игровая заставка 
            VictotyScreen   // Экран выигрыша
        }
        
        public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.GameScreen;   // Переменная состояния игры
            #endregion
            ..................................................
        
        }
    }
  • Для работы нужного блока кода в соответствии с установленным состоянием введите переключатель switch в функциях Update() и Draw()
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            ...................................................
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        }
    }

    Загрузка модели мяча

    В 3D-задачах используются уже не спрайты, а модели. Трехмерная модель в памяти компьютера называется мэш-объектом и хранится до загрузки в файле с расширением .x, возможно, вместе со своей текстурой. В трехмерной модели содержится набор координат примитивов (простых плоских фигур, чаще всего треугольников), с помощью которых конструируется сложная поверхность модели. Чем сложнее объемная фигура, тем больше треугольников требуется для ее точного описания.

    Создадим класс, который будет загружать модель мяча и обеспечивать вывод его изображения на экран.

  • Командой Project/Add Class добавьте к проекту класс с именем ModelClass
  • Скопируйте из файла StartGame3D.cs в новый файл ModelClass.cs секцию using с подключениями пространств имен
  • Заполните файл ModelClass.cs следующим кодом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class ModelClass
        {
            // Поля
            Model model;
            Vector3 position;
        
            // Свойства
            public Vector3 Position
            { 
                get { return position; }
                set { position = value; }
            }
        
            // Конструктор
            public ModelClass()
            {
                position = new Vector3(0, 0, 0);
            }
        
            // Загрузка модели
            public void Load(ContentManager content, String strModel)
            {
                model = content.Load<Model>(strModel);
            }
        
            // Рисуем модель на экране
            public void DrawModel(Matrix world, Matrix view, Matrix proj)
            {
                Matrix[] transforms = new Matrix[model.Bones.Count];
                model.CopyAbsoluteBoneTransformsTo(transforms);
                foreach (ModelMesh mesh in model.Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.EnableDefaultLighting();
                        effect.World = transforms[mesh.ParentBone.Index] * world;
                        effect.View = view;
                        effect.Projection = proj;
                    }
        
                    mesh.Draw();
                }
            }
        }
    }
  • Добавьте в узел Content проекта папку Models командой Add/New Folder и скопируйте в нее командой Add/Existing Item файл Soccerball.x из прилагаемого каталога Source
  • Добавьте в секцию #region Поля класса файла StartGame3D.cs следующие поля
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        #region Поля класса 
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int screenWidth, screenHeight;  // Размеры экрана
        KeyboardState keyboardState;    // Буфер клавиатуры 
        GameState gameState = GameState.GameScreen;   // Переменная состояния игры
    
        Matrix world;
        Matrix view;
        Matrix proj;
        float aspectRatio;
        float FOV = MathHelper.PiOver4;
        float nearClip = 1.0f;
        float farClip = 1000.0f;
        Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);           
        ModelClass ball;
        #endregion
    
        ..........................................................
    }
  • Добавьте в конструктор класса StartGame3D код создания экземпляра класса ModelClass
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    
        // Создание экземпляра класса ModelClass 
        ball = new ModelClass();
    }
  • Добавьте в функцию Initialize() класса StartGame3D код вычисления коэффициента aspectRatio для компенсации искажения в проекционной матрице
  • protected override void Initialize()
    {
        GraphicsAdapter adapter = graphics.GraphicsDevice.
            CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
        screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
        screenHeight = adapter.CurrentDisplayMode.Height;   
        graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
        graphics.PreferredBackBufferHeight = screenHeight;  
        graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
        graphics.ApplyChanges();                            // Применить настройки
        aspectRatio = (float)screenWidth / screenHeight;    // Коэффициент искажения проекции
    
        base.Initialize();
    }

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

  • Добавьте в функцию LoadContent() класса StartGame3D код загрузки в программу модели мяча
  • protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
    
        ball.Load(this.Content, "Models\\Soccerball");
    }
  • Добавьте в вариант GameState.SplashScreen переключателя switch функции Draw() код отображения мяча на экране
  • protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                ball.DrawModel(world, view, proj);
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }
  • Откомпилируйте проект командой Rebuild и запустите его на выполнение. Должен получиться рисунок с начальным положением мяча
  • Расширение кода до трех разных моделей мячей и обеспечение их движения

    Здесь мы выведем на экран три разных мяча и будем перемещать их в пространстве.

  • Добавьте в папку Models проекта командой Add/New Item из прилагаемого каталога Source файлы SoccerballGreen.x и SoccerballRed.x
  • Добавьте в класс ModelClass поле и свойство доступа, определяющие скорость падения мячей сверху вниз
  • class ModelClass
    {
        // Поля
        Model model;
        Vector3 position;
        float speed = 20.0f / 60.0f;
    
        // Свойства
        public Vector3 Position
        { 
            get { return position; }
            set { position = value; }
        }
    
        public float Speed
        { get { return speed; } }
    
        // Конструктор
        public ModelClass()
        {
            position = new Vector3(0, 0, 0);
        }
    
        // Загрузка модели
        public void Load(ContentManager content, String strModel)
        {
            model = content.Load<Model>(strModel);
        }
    
        // Рисуем модель на экране
        public void DrawModel(Matrix world, Matrix view, Matrix proj)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * world;
                    effect.View = view;
                    effect.Projection = proj;
                }
    
                mesh.Draw();
            }
        }
    }

    Переменная speed инициализируется значением 20.0f / 60.0f, где 60 означает количество секунд в одной минуте.

  • Добавьте в основной класс игры код загрузки трех моделей мяча и установки их начальной позиции
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        #region Поля класса 
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int screenWidth, screenHeight;  // Размеры экрана
        KeyboardState keyboardState;    // Буфер клавиатуры 
        GameState gameState = GameState.GameScreen;   // Переменная состояния игры
    
        Matrix world;
        Matrix view;
        Matrix proj;
        float aspectRatio;
        float FOV = MathHelper.PiOver4;
        float nearClip = 1.0f;
        float farClip = 1000.0f;
        Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);           
        //ModelClass ball;                      
        ModelClass[] ball = new ModelClass[3];  
        MouseState mouseState;                  
        Random rand = new Random();             
        #endregion
    
        public StartGame3D()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
    
            // Создание экземпляров класса ModelClass   
            //ball = new ModelClass();                  
            for (int i = 0; i < ball.Length; i++)       
            {                                           
                ball[i] = new ModelClass();             
            }                                           
        }
    
        protected override void Initialize()
        {
            .......................................................
        }
    
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
    
            //ball.Load(this.Content, "Models\\Soccerball");            
            // Загружаем модели мячей
            ball[0].Load(this.Content, "Models\\Soccerball");           
            ball[1].Load(this.Content, "Models\\SoccerballGreen");      
            ball[2].Load(this.Content, "Models\\SoccerballRed");        
    
            // Устанавливаем начальную позицию объектов                 
            BeginPosition();    
        }
    
        // Устанавливаем начальную позицию объекта
        void BeginPosition()
        {
            for (int i = 0; i < ball.Length; i++)
            {
                ball[i].Position = new Vector3(
                    rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                    rand.Next(0, 80),
                    -rand.Next(20, 150));
            }                                                           
        }
    
        .......................................................
    }
  • Модифицируйте метод Draw() основного класса игры так, чтобы отображались все загруженные мячи
  • protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                //world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                //ball.DrawModel(world, view, proj);
                for (int i = 0; i < ball.Length; i++)
                {
                    // Определить матрицу преобразования
                    world = Matrix.CreateTranslation(ball[i].Position);
                    // Нарисовать модель в начальном состоянии
                    ball[i].DrawModel(world, view, proj);
                }
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }
  • Откомпилируйте проект командой Rebuild и выполните его. Должно получиться примерно следующее
  • Для имитации движения мячей сверху вниз нам нужно уменьшать координату y в методе обновленных вычислений Update(), поскольку ось Oy направлена снизу вверх. Кроме того, на данном этапе предусмотрим временный механизм возврата мячей в исходное состояние с помощью щелчка мыши на экране во вспомогательном методе MouseClick(). Ну, и временно для этого включим стандартный курсор в полноэкранном режиме.

  • Добавьте в класс StartGame3D код включения курсора, а также методы MoveBalls() и MouseClick() со следующим содержимым
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    
        // Создание экземпляров класса ModelClass   
        //ball = new ModelClass();                  
        for (int i = 0; i < ball.Length; i++)       
        {                                           
            ball[i] = new ModelClass();             
        }           
    
        // Включаем стандартный курсор мыши
        this.IsMouseVisible = true;        
    }
    .........................................................
    
    void MoveBalls()
    {
        for (int i = 0; i < ball.Length; i++)
        {
            ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
        }
    }
    
    void MouseClick()
    {
        mouseState = Mouse.GetState();
        if (mouseState.LeftButton == ButtonState.Pressed)
            BeginPosition();
    }
  • Добавьте в вычислительный метод Update() основного класса игры вызов функций MoveBalls() и MouseClick()
  • protected override void Update(GameTime gameTime)
    {
        // Читать буфер клавиатуры 
        keyboardState = Keyboard.GetState();
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Выход из игрового процесса
                if (keyboardState.IsKeyDown(Keys.Escape))
                    this.Exit();
    
                MoveBalls();
                MouseClick();
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Update(gameTime);
    }
  • Запустите проект и убедитесь в работоспособности добавленных в программу механизмов
  • Механизм стрельбы по целям

    На данном этапе мы добавим к программе вместо стандартного курсора прицел для стрельбы по мячам и создадим механизм попаданий выстрелов в цель.

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

  • Командой Project/Add Existing Item скопируйте из предыдущей лабораторной работы или прилагаемого каталога Source файл Sprite.cs в корневой каталог текущего проекта
  • В этом разделе мы будем использовать библиотечный класс BoundingSphere для определения столкновений моделей и курсора и нам понадобится радиус ограничивающей мячи сферы. Чтобы не измерять этот радиус, введем переменную radius и определим ее значение, которое немного больше радиуса мячей.

  • Добавьте в класс ModelClass поле и свойство доступа для работы с радиусом попадания в цель
  • class ModelClass
    {
        // Поля
        Model model;
        Vector3 position;
        float speed = 20.0f / 60.0f;
        float radius = 8.0F;
    
        // Свойства
        public Vector3 Position
        { 
            get { return position; }
            set { position = value; }
        }
    
        public float Speed
        { get { return speed; } }
    
        public float Radius
        { get { return radius; } }
    
        ...........................................
    }
  • В конструкторе класса StartGame3D отключите стандартный курсор мыши, закомментировав соответствующий код
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    
        // Создание экземпляров класса ModelClass   
        //ball = new ModelClass();                  
        for (int i = 0; i < ball.Length; i++)       
        {                                           
            ball[i] = new ModelClass();             
        }           
    
        // Включаем стандартный курсор мыши
        //this.IsMouseVisible = true;        
    }
  • Через Проводник решений создайте для узла Content командой Add/New Folder контекстного меню папку Textures и скопируйте в нее из прилагаемого каталога Source файл рисунка прицела cursor.png
  • В классе StartGame3D создайте поле для хранения ссылки на объект курсора и инициализируйте ее экземпляром класса Sprite
  • #region Поля класса 
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    int screenWidth, screenHeight;  // Размеры экрана
    KeyboardState keyboardState;    // Буфер клавиатуры 
    GameState gameState = GameState.GameScreen;   // Переменная состояния игры
    
    Matrix world;
    Matrix view;
    Matrix proj;
    float aspectRatio;
    float FOV = MathHelper.PiOver4;
    float nearClip = 1.0f;
    float farClip = 1000.0f;
    Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);           
    //ModelClass ball;                      
    ModelClass[] ball = new ModelClass[3];  
    MouseState mouseState;                  
    Random rand = new Random();   
    
    // Создание объекта курсора-прицела
    Game2D.Sprite cursor = new Game2D.Sprite();
    #endregion
  • В методе LoadContent() класса StartGame3D загрузите спрайт курсора в объект, а в методе Draw() поместите код рисования курсора последним - после рисования мячей, чтобы он оказался самым ближним к пользователю на экране монитора
  • protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
    
        //ball.Load(this.Content, "Models\\Soccerball");            
        // Загружаем модели мячей
        ball[0].Load(this.Content, "Models\\Soccerball");           
        ball[1].Load(this.Content, "Models\\SoccerballGreen");      
        ball[2].Load(this.Content, "Models\\SoccerballRed");        
    
        // Устанавливаем начальную позицию объектов 
        BeginPosition();    
    
        // Загружаем рисунок курсора-прицела 
        cursor.Load(this.Content, "Textures\\cursor");
    }
    
    .......................................................
    
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                //world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                //ball.DrawModel(world, view, proj);
                for (int i = 0; i < ball.Length; i++)
                {
                    // Определить матрицу преобразования
                    world = Matrix.CreateTranslation(ball[i].Position);
                    // Нарисовать модель в начальном состоянии
                    ball[i].DrawModel(world, view, proj);
                }
    
                // Нарисовать курсор-прицел
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                cursor.DrawSprite(spriteBatch);
                spriteBatch.End();
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }

    Система координат двухмерного и трехмерного пространств в XNA Framework существенно отличаются. Поэтому нужен особый механизм перевода двухмерных координат курсора, генерируемых операционной системой, в трехмерное пространство, построенное XNA. Мы воспользуемся готовым решением, приведенным в справочной информации по XNA Game Studio. Главным условием достоверности пересчета координат курсора на трехмерную сцену является совпадение значений матрицы вида и проекционной матрицы, применяемых для построения сцены, с теми же матрицами, определения координат курсора на этой сцене.

  • Добавьте к классу StartGame3D метод GetPickRay() пересчета координат курсора в трехмерное пространство сцены
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        ............................................................
    
        // Метод пересчета координат курсора в трехмерную сцену игры
        Ray GetPickRay()
        {
            // Читаем стандартные координаты курсора
            mouseState = Mouse.GetState();
    
            // Вспомогательные переменные
            int mouseX = mouseState.X;
            int mouseY = mouseState.Y;
            Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
            Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
    
            // Обнуляем мировую матрицу
            world = Matrix.CreateTranslation(0, 0, 0);
            // Матрица вида, как в трехмерной сцене
            view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
            // Матрица проекции, как в трехмерной сцене
            proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
            // Проекция курсора на ближнюю плоскость отсечения объема 
            Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
            // Проекция курсора на дальнюю плоскость отсечения объема
            Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();
    
            return new Ray(nearPoint, direction);
        }
    }

    Теперь нужно применить этот метод для создания механизма перемещения спрайта курсора по экрану и определения попаданий в движущиеся объекты мячей. Спусковым крючком выстрела по мячу будет служить левая кнопка мыши, поэтому весь механизм поместим в уже созданную нами ранее заготовку метода MouseClick().

  • Добавьте в класс StartGame3D поле массива объектов типа BoundingSphere и модифицируйте метод MouseClick() следующим образом
  • // Механизм стрельбы по целям 
    BoundingSphere[] bb = new BoundingSphere[3];
    void MouseClick()
    {
        // Получаем текущие координаты курсора и передаем их центру объекта прицела
        mouseState = Mouse.GetState();
        cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
        cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
    
        // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
        for (int i = 0; i < bb.Length; i++)
        {
            bb[i].Center = ball[i].Position;
            bb[i].Radius = ball[i].Radius;
        }
    
        // Определяем попадание и при успехе 
        // устанавливаем мяч в начальную позицию
        if (mouseState.LeftButton == ButtonState.Pressed)
        {
            // Пересчитываем координаты курсора на трехмерный объем
            Ray pickRay = GetPickRay();
    
            // Проверяем попадание
            for (int i = 0; i < ball.Length; i++)
            {
                Nullable<float> result = pickRay.Intersects(bb[i]);
                if (result.HasValue == true)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        -rand.Next(20, 150));
                }
            }
        }
    }
  • Запустите приложение и проверьте механизм стрельбы по целям
  • Формирование трехмерной сцены

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

  • В классе StartGame3D установите новое значение поля camera для выбора наилучшего ракурса (угла зрения) всей сцены игры и добавьте объект стадиона, в который мы позднее загрузим трехмерный мэш
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        #region Поля класса 
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int screenWidth, screenHeight;  // Размеры экрана
        KeyboardState keyboardState;    // Буфер клавиатуры 
        GameState gameState = GameState.GameScreen;   // Переменная состояния игры
    
        Matrix world;
        Matrix view;
        Matrix proj;
        float aspectRatio;
        float FOV = MathHelper.PiOver4;
        float nearClip = 1.0f;
        float farClip = 1000.0f;
        //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
        Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
        //ModelClass ball;   
        ModelClass[] ball = new ModelClass[3];  
        MouseState mouseState;                  
        Random rand = new Random();   
    
        // Создание объекта курсора-прицела
        Game2D.Sprite cursor = new Game2D.Sprite();
    
        // Создание объекта модели стадиона
        ModelClass stadium = new ModelClass();
        #endregion
        ..................................................
        
    }
  • Добавьте из прилагаемого каталога Source в папку Models проекта файл с именем stadium 1.x
  • В методе LoadContent() основного класса игры загрузите модель стадиона в созданный ранее объект stadium программы
  • protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
    
        //ball.Load(this.Content, "Models\\Soccerball");            
        // Загружаем модели мячей
        ball[0].Load(this.Content, "Models\\Soccerball");           
        ball[1].Load(this.Content, "Models\\SoccerballGreen");      
        ball[2].Load(this.Content, "Models\\SoccerballRed");   
    
        // Загружаем трехмерную модель стадиона
        stadium.Load(this.Content, "Models\\stadium 1");
    
        // Устанавливаем начальную позицию объектов 
        BeginPosition();    
    
        // Загружаем рисунок курсора-прицела 
        cursor.Load(this.Content, "Textures\\cursor");
    }
  • В методе Draw() основного класса игры вставьте код вывода изображения стадиона на экран монитора (раньше кода вывода других объектов)
  • protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                //world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                //ball.DrawModel(world, view, proj);
    
                // Нарисовать модель стадиона самой первой 
                world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                stadium.DrawModel(world, view, proj);
    
                // Нарисовать модели мячей
                for (int i = 0; i < ball.Length; i++)
                {
                    // Определить матрицу преобразования
                    world = Matrix.CreateTranslation(ball[i].Position);
                    // Нарисовать модель в начальном состоянии
                    ball[i].DrawModel(world, view, proj);
                }
    
                // Нарисовать курсор-прицел
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                cursor.DrawSprite(spriteBatch);
                spriteBatch.End();
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }
  • Запустите игру и убедитесь, что начальные позиции мячей нуждаются в корректировке
  • Обратите внимание на позиции установки всех моделей игры, включая камеру. Если бы камера была совмещена с экраном монитора, то начало мировой системы координат находилось бы в плоскости экрана. Позиции мячей мы формируем в отрицательном направлении оси Oz в диапазоне [-20,-150] пикселов. Сам стадион рисуем относительно начала мировой системы координат. И для того, чтобы увидеть его целиком, мы камеру отодвинули в положительном направлении на 250 пикселов. В результате мячи удалились от наблюдателя и к тому же падают на дальней границе стадиона. Поэтому нужно приблизить мячи к наблюдателю, скорректировав их начальные координаты по оси Oz в новом диапазоне [-50,150].

  • Введите изменения в код функций BeginPosition() и MouseClick(), где задается координата z полета мячей
  • // Устанавливаем начальную позицию объекта
    void BeginPosition()
    {
        for (int i = 0; i < ball.Length; i++)
        {
            ball[i].Position = new Vector3(
                rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                rand.Next(0, 80),
                //-rand.Next(20, 150));
                rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
        }                                                           
    }
    
    .....................................................
    
    // Механизм стрельбы по целям 
    BoundingSphere[] bb = new BoundingSphere[3];
    void MouseClick()
    {
        // Получаем текущие координаты курсора и передаем их центру объекта прицела
        mouseState = Mouse.GetState();
        cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
        cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
    
        // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
        for (int i = 0; i < bb.Length; i++)
        {
            bb[i].Center = ball[i].Position;
            bb[i].Radius = ball[i].Radius;
        }
    
        // Определяем попадание и при успехе 
        // устанавливаем мяч в начальную позицию
        if (mouseState.LeftButton == ButtonState.Pressed)
        {
            // Пересчитываем координаты курсора на трехмерный объем
            Ray pickRay = GetPickRay();
    
            // Проверяем попадание
            for (int i = 0; i < ball.Length; i++)
            {
                Nullable<float> result = pickRay.Intersects(bb[i]);
                if (result.HasValue == true)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        //-rand.Next(20, 150));
                        rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                }
            }
        }
    }
  • Запустите приложение и убедитесь, что мячи теперь падают над поверхностью поля стадиона
  • Нам нужно сделать, чтобы мячи останавливались на поверхности поля, а не проваливались сквозь него. Поверхность поля стадиона проходит через ось Oy мировой системы координат на расстоянии ( -32 ) пиксела, поэтому двигать мячи в вертикальном направлении следует только до этой величины.

  • Внесите следующие изменения в функцию MoveBalls() основного класса игры
  • void MoveBalls()
    {
        for (int i = 0; i < ball.Length; i++)
        {
            if (ball[i].Position.Y > -32)
                ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
        }
    }
  • Запустите приложение и убедитесь, что мячи теперь останавливаются на поверхности поля стадиона
  • Дополним сцену подходящим задним фоном, чтобы стадион и вся сцена смотрелись более красочно.

  • Добавьте в папку Textures проекта файл hallake001.png из прилагаемого каталога Source
  • В классе StartGame3D добавьте поле-ссылку background для хранения объекта текстуры, а в методах LoadContent() и Draw() - код загрузки и вывода рисунка на экран
  • #region Поля класса 
    ..................................................
    
    // Создание объекта модели стадиона
    ModelClass stadium = new ModelClass();
    
    // Создание объекта для рисунка пейзажа
    Game2D.Sprite background = new Game2D.Sprite();
    #endregion
    
    ..................................................
    
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
    
        //ball.Load(this.Content, "Models\\Soccerball");            
        // Загружаем модели мячей
        ball[0].Load(this.Content, "Models\\Soccerball");           
        ball[1].Load(this.Content, "Models\\SoccerballGreen");      
        ball[2].Load(this.Content, "Models\\SoccerballRed");   
    
        // Загружаем трехмерную модель стадиона
        stadium.Load(this.Content, "Models\\stadium 1");
    
        // Устанавливаем начальную позицию объектов 
        BeginPosition();    
    
        // Загружаем рисунок курсора-прицела 
        cursor.Load(this.Content, "Textures\\cursor");
    
        // Загружаем рисунок заднего фона
        background.Load(this.Content, "Textures\\hallake001");
        // Чуть приподнимем вверх экрана 
        background.spritePosition = new Vector2(0, -50);
    }
    
    ..................................................
    
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Нарисовать фон самым первым
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                background.DrawSprite(spriteBatch);
                spriteBatch.End();
    
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                //world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                //ball.DrawModel(world, view, proj);
    
                // Нарисовать стадион
                world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                stadium.DrawModel(world, view, proj);
    
                // Нарисовать модели мячей
                for (int i = 0; i < ball.Length; i++)
                {
                    // Определить матрицу преобразования
                    world = Matrix.CreateTranslation(ball[i].Position);
                    // Нарисовать модель в начальном состоянии
                    ball[i].DrawModel(world, view, proj);
                }
    
                // Нарисовать курсор-прицел
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                cursor.DrawSprite(spriteBatch);
                spriteBatch.End();
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }
  • Запустите приложение и полюбуйтесь на солидную сцену, сделанную своими руками
  • Добавление экранных заставок

    В самом начале построения приложения мы в методах LoadContent() и Draw() с помощью оператора switch заложили несколько состояний игры, определенных в перечислении GameState

  • AboutScreen,         // Экран с информацией об игре
  • GameOverScreen, // Экран проигрыша
  • GameScreen,       // Экран игрового процесса
  • HelpScreen,          // Инструкция о правилах
  • MenuScreen,         // Меню игры
  • SplashScreen,       // Первая игровая заставка
  • VictotyScreen,        // Экран выигрыша
  • До сих пор мы реализовали только одно из них - саму игру. В этом разделе закодируем еще три состояния: SplashScreen, AboutScreen, HelpScreen. Смену режимов закрепим за клавишами Enter и Esc.

    Экран игровой заставки SplashScreen

    Первой будет появляться игровая заставка, из которой по клавише Enter попадем в меню, а по клавише Esc выйдем из игры. Игровая заставка будет состоять из нескольких отдельных компонентов:

  • Фоновый рисунок splash.png
  • Вехний заголовок игры title.png
  • Нижняя подсказка enter.png
  • Изображение пулевого отверстия hole.png, которое мы разбросаем по экрану заставки случайным образом в 30 экземплярах
  • Модели вращающегося мяча Soccerball.x посередине экрана на переднем плане заставки
  • Прежде всего нужно скопировать в проект эти компоненты заставки.

  • В панели Solution Explorer вызовите контекстное меню для каталога проекта Textures и дабавьте командой Add/Existing Item из прилагаемого каталога Source файлы с рисунками splash.png, title.png, enter.png, hole.png
  • Код игровой заставки упакуем в класс SplashScreen.

  • Командой Project/Add Class меню оболочки добавьте к проекту файл SplashScreen.cs и скопируйте в него все инструкции using из файла StartGame3D
  • Заполните файл SplashScreen.cs следующим кодом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class SplashScreen
        {
            #region Поля класса
            Texture2D splash, title, enter, hole;   // Объекты рисунков 
            Vector2[] posHole = new Vector2[30];    // Массив позиций рисунка пули
            Random rand = new Random();             // Генератор случайных чисел
        
            ModelClass ball = new ModelClass();     // Объект мяча
            float aspectRatio;                      // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4;         // Ракурс камеры 45 градусов
            float nearClip = 1.0f;                  // Ближняя отсекающая плоскость
            float farClip = 1000.0f;                // Дальняя отсекающай плоскость
            float angle = 0;                        // Угол поворота
            Matrix world, view, proj;               // Матрицы преобразований
            #endregion
        
            // Инициализация объектов
            // content - контент экземпляра основного класса игры
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            public void InitializeSplashScreen(ContentManager content, int width, int height)
            {
                // Загружаем в объекты изображения и модель мяча
                splash = content.Load<Texture2D>("Textures\\splash");
                title = content.Load<Texture2D>("Textures\\title");
                enter = content.Load<Texture2D>("Textures\\enter");
                hole = content.Load<Texture2D>("Textures\\hole");
                ball.Load(content, "Models\\Soccerball");
                ball.Position = new Vector3(0, 0, 0);// В центре экрана, где начало мировой системы координат
        
                // Разбрасываем по экрану пулевые отверстия
                for (int i = 0; i < posHole.Length; i++)
                {
                    posHole[i].X = rand.Next(20, width - 60);
                    posHole[i].Y = rand.Next(100, height - 60);
                }
        
                // Коэффициент искажения проекции
                aspectRatio = (float)width / height;
            }
        
            // Вывод заставки на экран
            // spriteBatch - объект рисования спрайтов
            // graphics - объект графического устройства GDI
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            // gameTime - длительность одного такта игры
            public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
                int width, int height, GameTime gameTime)
            {
                // Очищаем экран своим цветом
                graphics.GraphicsDevice.Clear(Color.DarkGreen); 
        
                // Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(splash, new Vector2(width / 2 - splash.Width / 2,
                    height / 2 - splash.Height / 2), Color.White);
                spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2, 
                    30), // Чуть опустили
                    Color.White);
                spriteBatch.Draw(enter, new Vector2(width / 2 - enter.Width / 2,
                    height - enter.Height - 30), // Чуть приподняли
                    Color.White);
                for (int i = 0; i < posHole.Length; i++)
                    spriteBatch.Draw(hole, posHole[i], Color.White);
                spriteBatch.End();
        
                // Рисуем объемный мяч на переднем плане
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
                view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 150.0f), Vector3.Zero, Vector3.Up);
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                world = Matrix.CreateTranslation(ball.Position);
                angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 2.0f);// Увеличиваем угол вращения мяча
                world *= Matrix.CreateRotationY(angle);
                ball.DrawModel(world, view, proj);
            }
        }
    }
  • Для тестирования разработанного класса SplashScreen введите временно следующие изменения в основной класс игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            //GameState gameState = GameState.GameScreen;   // Переменная состояния игры
            GameState gameState = GameState.SplashScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            #endregion
        
            protected override void LoadContent()
            {
                ....................................................
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
          
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        .......................................................
        
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        }
  • Запустите приложение и убедитесь, что код класса SplashScreen работает нормально в основном цикле игры. Получится картинка, приведенная ниже, с вращающимся в центре мячом
  • Экран с информацией об игре AboutScreen

    Код этой заставки мы спроектируем на основе только что созданного класса SplashScreen, внеся в него незначительные коррективы. Эти изменения будет связаны в основном с тем, что вместо одного мяча, вращающегося в центре экрана, мы введем три мяча, которые будут вращаться как и прежде - относительно оси y, и одновременно - относительно оси z. В итоге получим сложное вращение мячей в плоскости экрана относительно его центра.

  • Загрузите в папку Textures проекта файлы изображений about.png и esc.png из прилагаемого к работе каталога Source
  • В панели Solution Explorer сделайте копию файла SplashScreen.cs и переименуйте ее в файл AboutScreen.cs
  • Модифицируйте файл AboutScreen.cs так, чтобы он выглядел следующим образом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class AboutScreen
        {
            #region Поля класса
            Texture2D about, title, esc, hole;      // Объекты рисунков 
            Vector2[] posHole = new Vector2[30];    // Массив позиций рисунка пули
            Random rand = new Random();             // Генератор случайных чисел
        
            ModelClass [] ball = new ModelClass[3]; // Объекты мячей 
            float aspectRatio;                      // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4;         // Ракурс камеры 45 градусов
            float nearClip = 1.0f;                  // Ближняя отсекающая плоскость
            float farClip = 1000.0f;                // Дальняя отсекающай плоскость
            float angle = 0;                        // Угол поворота
            Matrix world, view, proj;               // Матрицы преобразований
            #endregion
        
            // Инициализация объектов
            // content - контент экземпляра основного класса игры
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            public void InitializeSplashScreen(ContentManager content, int width, int height)
            {
                // Создаем объекты для мячей 
                for (int i = 0; i < ball.Length; i++)
                    ball[i] = new ModelClass();
        
                // Загружаем в объекты изображения и модель мяча
                about = content.Load<Texture2D>("Textures\\about");
                title = content.Load<Texture2D>("Textures\\title");
                esc = content.Load<Texture2D>("Textures\\esc");
                hole = content.Load<Texture2D>("Textures\\hole");
                ball[0].Load(content, "Models\\Soccerball");
                ball[0].Position = new Vector3(-30, 40, -30);
                ball[1].Load(content, "Models\\SoccerballGreen");
                ball[1].Position = new Vector3(50, 30, -50);
                ball[2].Load(content, "Models\\SoccerballRed");
                ball[2].Position = new Vector3(-50, -30, 40);
        
                // Разбрасываем по экрану пулевые отверстия
                for (int i = 0; i < posHole.Length; i++)
                {
                    posHole[i].X = rand.Next(20, width - 60);
                    posHole[i].Y = rand.Next(100, height - 60);
                }
        
                // Коэффициент искажения проекции
                aspectRatio = (float)width / height;
            }
        
            // Вывод заставки на экран
            // spriteBatch - объект рисования спрайтов
            // graphics - объект графического устройства GDI
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            // gameTime - длительность одного такта игры
            public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
                int width, int height, GameTime gameTime)
            {
                // Очищаем экран своим цветом
                graphics.GraphicsDevice.Clear(Color.DarkGreen); 
        
                // Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(about, new Vector2(width / 2 - about.Width / 2,
                    height / 2 - about.Height / 2), Color.White);
                spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2, 
                    30), // Чуть опустили
                    Color.White);
                spriteBatch.Draw(esc, new Vector2(width / 2 - esc.Width / 2,
                    height - esc.Height - 30), // Чуть приподняли
                    Color.White);
                for (int i = 0; i < posHole.Length; i++)
                    spriteBatch.Draw(hole, posHole[i], Color.White);
                spriteBatch.End();
        
                // Рисуем мячи на переднем плане
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
                view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 260.0f), Vector3.Zero, Vector3.Up);
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
                Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
                Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
                for (int i = 0; i < ball.Length; i++)
                {
                    world = Matrix.CreateTranslation(ball[i].Position);
                    ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
                }
            }
        }
    }
  • Для тестирования разработанного класса AboutScreen введите временно следующие изменения в основной класс игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.AboutScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            #endregion
        
            ..............................................................
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
        
            ..............................................................
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        ..............................................................
        
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            ..............................................................
        
        }
  • Запустите приложение и убедитесь, что код класса AboutScreen работает нормально в основном цикле игры. Получится картинка, приведенная ниже, с вращающимися в плоскости экрана относительно его центра мячами
  • Экран с инструкцией о правилах HelpScreen

    Код этой заставки мы спроектируем на основе только что созданного класса AboutScreen, внеся в него вместо загрузки файла about.png загрузку файла help.png. Здесь получится немного нерационально, потому что остальной код класса будет неизменным. Было бы проще добавить в конструктор класса AboutScreen параметр с именем загружаемого файла. Но учитывая, что у нас подобных заставок будет всего две, просто повторим этот код еще раз.

  • Загрузите в папку Textures проекта файл изображений help.png из прилагаемого к работе каталога Source
  • В панели Solution Explorer сделайте копию файла AboutScreen.cs и переименуйте ее в файл HelpScreen.cs
  • Перейдите в режим редактирования файла HelpScreen.cs, вызовите комбинацией клавиш Ctrl-H окно замены редактора кода и замените все вхождения about на help документа. После этого файл должен стать таким
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class HelpScreen
        {
            #region Поля класса
            Texture2D help, title, esc, hole;      // Объекты рисунков 
            Vector2[] posHole = new Vector2[30];    // Массив позиций рисунка пули
            Random rand = new Random();             // Генератор случайных чисел
        
            ModelClass [] ball = new ModelClass[3]; // Объекты мячей 
            float aspectRatio;                      // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4;         // Ракурс камеры 45 градусов
            float nearClip = 1.0f;                  // Ближняя отсекающая плоскость
            float farClip = 1000.0f;                // Дальняя отсекающай плоскость
            float angle = 0;                        // Угол поворота
            Matrix world, view, proj;               // Матрицы преобразований
            #endregion
        
            // Инициализация объектов
            // content - контент экземпляра основного класса игры
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            public void InitializeSplashScreen(ContentManager content, int width, int height)
            {
                // Создаем объекты для мячей 
                for (int i = 0; i < ball.Length; i++)
                    ball[i] = new ModelClass();
        
                // Загружаем в объекты изображения и модель мяча
                help = content.Load<Texture2D>("Textures\\help");
                title = content.Load<Texture2D>("Textures\\title");
                esc = content.Load<Texture2D>("Textures\\esc");
                hole = content.Load<Texture2D>("Textures\\hole");
                ball[0].Load(content, "Models\\Soccerball");
                ball[0].Position = new Vector3(-30, 40, -30);
                ball[1].Load(content, "Models\\SoccerballGreen");
                ball[1].Position = new Vector3(50, 30, -50);
                ball[2].Load(content, "Models\\SoccerballRed");
                ball[2].Position = new Vector3(-50, -30, 40);
        
                // Разбрасываем по экрану пулевые отверстия
                for (int i = 0; i < posHole.Length; i++)
                {
                    posHole[i].X = rand.Next(20, width - 60);
                    posHole[i].Y = rand.Next(100, height - 60);
                }
        
                // Коэффициент искажения проекции
                aspectRatio = (float)width / height;
            }
        
            // Вывод заставки на экран
            // spriteBatch - объект рисования спрайтов
            // graphics - объект графического устройства GDI
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            // gameTime - длительность одного такта игры
            public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
                int width, int height, GameTime gameTime)
            {
                // Очищаем экран своим цветом
                graphics.GraphicsDevice.Clear(Color.DarkGreen); 
        
                // Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(help, new Vector2(width / 2 - help.Width / 2,
                    height / 2 - help.Height / 2), Color.White);
                spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2, 
                    30), // Чуть опустили
                    Color.White);
                spriteBatch.Draw(esc, new Vector2(width / 2 - esc.Width / 2,
                    height - esc.Height - 30), // Чуть приподняли
                    Color.White);
                for (int i = 0; i < posHole.Length; i++)
                    spriteBatch.Draw(hole, posHole[i], Color.White);
                spriteBatch.End();
        
                // Рисуем мячи на переднем плане
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
                view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 260.0f), Vector3.Zero, Vector3.Up);
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
                Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
                Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
                for (int i = 0; i < ball.Length; i++)
                {
                    world = Matrix.CreateTranslation(ball[i].Position);
                    ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
                }
            }
        }
    }
  • Для тестирования разработанного класса HelpScreen введите временно следующие изменения в основной класс игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.HelpScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            #endregion
        
            ........................................................
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
        
            ............................................................
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        .........................................................
        
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            .............................................................
        
        }
  • Запустите приложение и убедитесь, что код класса HelpScreen работает нормально в основном цикле игры. Получится картинка, приведенная ниже, с вращающимися в плоскости экрана относительно его центра мячами
  • Добавление меню

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

    В центре экрана разместим команды:

  • Игра - режим GameScreen
  • Помощь - режим HelpScreen
  • О программе - режим AboutScreen
  • Выход - завершение работы
  • Каждую команду будем представлять текстовой надписью, под которую будем подкладывать желтый прямоугольник, если она имеет фокус ввода. Для этого придется подключить кириллический шрифт требуемого размера и гарнитуры. По краям экрана для украшательства разместим фигуры ' girl ' свободного поведения. И еще уберем отображение пуль.

  • Добавьте из прилагаемого к работе каталога Source в папку Textures проекта файлы menu.jpg, cursorMenu.png, girlLeft.png, girlRight.png
  • Добавьте к папке Content подпапку Fonts
  • Добавьте в папку Fonts файл font.spritefont
  • Откорректируйте файл font.spritefont следующим образом
  • <?xml version="1.0" encoding="utf-8"?>
    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
      <Asset Type="Graphics:FontDescription">
        <FontName>Arial</FontName>
        <Size>24</Size>
        <Spacing>0</Spacing>
        <UseKerning>true</UseKerning>
        <Style>Bold</Style>
        <CharacterRegions>
          <CharacterRegion>
            <Start>#32;</Start>
            <End>#126;</End>
          </CharacterRegion>
          <CharacterRegion>
            <Start>#1040;</Start>
            <End>#1103;</End>
          </CharacterRegion>
        </CharacterRegions>
      </Asset>
    </XnaContent>
  • Скопируйте файл HelpScreen.cs и назовите копию MenuScreen.cs
  • Откорректируйте файл следующим образом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        // Объявление состояний меню
        enum MenuState
        {
            Game,   // Меню установлено на первой позиции
            Help, 
            About,  
            Esc  
        }
        
        class MenuScreen
        {
            #region Поля класса   
            // Объекты рисунков 
            Texture2D menu, title, enter, cursorMenu, girlLeft, girlRight;
            // Объекты мячей
            ModelClass [] ball = new ModelClass[3];    
      
            float aspectRatio;                          // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4;             // Ракурс камеры 45 градусов
            float nearClip = 1.0f;                      // Ближняя отсекающая плоскость
            float farClip = 1000.0f;                    // Дальняя отсекающай плоскость
            float angle = 0;                            // Угол поворота
            Matrix world, view, proj;                   // Матрицы преобразований
            SpriteFont font;                            // Для загрузки шрифта Arial 24 Bold 
            String[] menuCommant = {                    // Команды меню
                                     "Игра",
                                     "Помощь",
                                     "О программе",
                                     "Выход"
                                   };
            #endregion
        
            // Инициализация объектов
            // content - контент экземпляра основного класса игры
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            public void InitializeSplashScreen(ContentManager content, int width, int height)
            {
                // Создаем объекты для мячей 
                for (int i = 0; i < ball.Length; i++)
                    ball[i] = new ModelClass();
        
                // Загружаем в объекты изображения и модели мячей
                menu = content.Load<Texture2D>("Textures\\menu");
                cursorMenu = content.Load<Texture2D>("Textures\\cursorMenu");
                title = content.Load<Texture2D>("Textures\\title");
                enter = content.Load<Texture2D>("Textures\\enter");
                girlLeft = content.Load<Texture2D>("Textures\\girlLeft");
                girlRight = content.Load<Texture2D>("Textures\\girlRight");
                ball[0].Load(content, "Models\\Soccerball");
                ball[0].Position = new Vector3(-30, 40, -30);
                ball[1].Load(content, "Models\\SoccerballGreen");
                ball[1].Position = new Vector3(50, 30, -50);
                ball[2].Load(content, "Models\\SoccerballRed");
                ball[2].Position = new Vector3(-50, -30, 40);
        
                // Коэффициент искажения проекции
                aspectRatio = (float)width / height;
        
                // Загружаем шрифт
                font = content.Load<SpriteFont>("Fonts\\font");
            }
        
            // Вывод заставки на экран
            // spriteBatch - объект рисования спрайтов
            // graphics - объект графического устройства GDI
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            // gameTime - длительность одного такта игры
            public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
                int width, int height, GameTime gameTime, int menuState)
            {
                // Очищаем экран своим цветом
                graphics.GraphicsDevice.Clear(Color.DarkGreen);
        
                // Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(menu, new Vector2(width / 2 - menu.Width / 2,
                    height / 2 - menu.Height / 2), Color.White);
                spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
                    30), // Чуть опустили
                    Color.White);
                spriteBatch.Draw(enter, new Vector2(width / 2 - enter.Width / 2,
                    height - enter.Height - 30), // Чуть приподняли
                    Color.White);
                // Рисуем girls
                spriteBatch.Draw(girlLeft, new Vector2(
                    0, height / 2 - girlLeft.Height / 2), Color.White);
                spriteBatch.Draw(girlRight, new Vector2(
                    width - girlRight.Width, height / 2 - girlRight.Height / 2), Color.White);
                spriteBatch.End();
        
                // Рисуем мячи
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
                view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 250.0f), Vector3.Zero, Vector3.Up);
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
                Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
                Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
                for (int i = 0; i < ball.Length; i++)
                {
                    world = Matrix.CreateTranslation(ball[i].Position);
                    ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
                }
        
                // Рисуем надписи меню и фон
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                int posX=width / 2 + width / 7;
                int posY= height / 2;
                int shift = 50;
                spriteBatch.Draw(cursorMenu, 
                    new Vector2(posX - 7, posY + shift * menuState), 
                    Color.White);
                for (int i = 0; i < menuCommant.Length; i++)
                {
                    spriteBatch.DrawString(font,
                        menuCommant[i],
                        new Vector2(posX, posY),
                        Color.Red);
                    posY += shift;
                }
                spriteBatch.End();
            }
        }
    }

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

  • Для тестирования разработанного класса MenuScreen введите временно следующие изменения в основной класс игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.MenuScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            MenuScreen menu = new MenuScreen();
            int menuState = (int)MenuState.Game;
            #endregion
        
            ......................................................................
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
        
            ........................................................
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.MenuScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        ..................................................
        
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        menu.DrawScreen(
                            this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        }
  • Запустите приложение и убедитесь, что код класса MenuScreen работает нормально в основном цикле игры. Должна получиться примерно такая картинка
  • Управление состояниями программы

    В предыдущих разделах мы подготовили графические заставки, включая саму игру, которые нам нужно показать пользователю в зависимости от его действий. При запуске программы мы автоматически покажем титульную заставку, определенную классом SplashScreen. Из этой заставки есть два пути: нажать клавишу Esc, чтобы завершить работу приложения, или нажать клавишу Enter. После нажатия клавиши Enter пользователь должен попасть на заставку меню, поддерживаемую классом MenuScreen. А далее после его выбора программа должна перейти в одно из состояний, определяемых основным классом игры StartGame3D или классами HelpScreen и AboutScreen.

    По клавише Esc программа должна вернуться в состояние меню, откуда при повторном нажатии Esc или выбора пункта ' Выход ' - перейти в первую экранную заставку. В режиме меню для смены пунктов задействуем клавиши Up и Down (Стрелка вверх/Стрелка вниз), а для исполнения выбранной команды - клавишу Enter в соответствии с подсказкой в нижней части заставки меню.

    Такую логику управления состояниями программы мы должны реализовать в этом разделе. Но здесь есть одна проблема, которую сразу нужно решить. Дело в том, что любая клавиша клавиатуры, кроме расширяющих клавиш Ctrl, Shift и Alt, при ее нажатии начинает непрерывно генерировать соответствующий ей код, пока не будет опущена. По этой причине в большинстве офисных программ, когда смена состояний закреплена за какими-то клавишами, ее связывают не с нажатием, а с отпусканием соответствующей клавиши.

    В нашем игровом случае проблема смены состояния связана с тем, что пользователь ожидает ответных действий программы именно от нажатия командной клавиши, которое должно обеспечить одиночную реакцию программы. Для надежного блокирования эффекта генерации применим прием, который напоминает решение, используемое в гениальном создании под названием автомат Калашникова. В нем для гарантированного ведения огня одиночными выстрелами используется специальный запорный крючок - ' шептало одиночного огня '. Это 'шептало' после произведенного выстрела блокирует ударный механизм до тех пор, пока стрелок не отпустит спусковой крючок в исходное состояние. Только после этого можно производить следующий выстрел.

    Примечание. Эх! Вспомнил Армию, в которой честно и с удовольствием служил 41 год тому назад. Прекрасная была пора: молодой, здоровый, холостой, на полном гособеспечении, оденут, обуют, напоят, накормят, обогреют/подогреют, в баньку сводят, спать уложат, сказку расскажут - самому ни о чем думать не надо (кроме как о girls ).

    В нашей программе режим меню использует клавиши Enter, Up, Down и Esc. Поэтому в качестве запорного механизма будем использовать флаги enterFree, upFree, downFree и escFree. В исходном состоянии эти флаги подняты, но стоит пользователю нажать на управляющую клавишу, как срабатывает соответствующий блок кода и флаг опускается, блокируя повторное выполнение этого же кода при многократной генерации клавишей символов. Флаг восстанавливается только после отпускания пользователем активной клавиши.

    Аналогичная проблема существует и с длительностью удержания кнопки мыши в нажатом состоянии. Если сейчас запустить приложение в режиме GameScreen, нажать левую кнопку мыши и, не отпуская ее, направлять курсор на мячи, то мы увидим этот эффект. В качестве запорного механизма для мыши будем использовать флаг mouseFree.

    Пока мы разработали состояния программы, определяемые следующими значениями перечисления GameState:

  • AboutScreen,       // Экран с информацией об игре
  • GameScreen,     // Экран игрового процесса
  • HelpScreen,       // Инструкция о правилах
  • MenuScreen,    // Меню игры
  • SplashScreen  // Первая игровая заставка
  • Для реализации устойчивого механизма управления этими состояниями внесите необходимые изменения в код класса StartGame3D, как показано в следующем его полном, на данный момент, листинге
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        // Объявление состояний экранов
        enum GameState
        {
            AboutScreen,    // Экран с информацией об игре
            GameOverScreen, // Экран проигрыша
            GameScreen,     // Экран игрового процесса
            HelpScreen,     // Инструкция о правилах
            MenuScreen,     // Меню игры
            SplashScreen,   // Первая игровая заставка 
            VictotyScreen   // Экран выигрыша
        }
        
        public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.SplashScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            MenuScreen menu = new MenuScreen();
            int menuState = (int)MenuState.Game;
        
            // Флаги освобождения задействованных клавиш 
            bool enterFree = true;  // Enter
            bool upFree=true;       // Стрелка вверх
            bool downFree = true;   // Стрелка вниз
            bool escFree = true;    // Esc
            // Флаг освобождения мыши
            bool mouseFree = true;
            #endregion
        
            public StartGame3D()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
        
                // Создание экземпляров класса ModelClass   
                //ball = new ModelClass();                  
                for (int i = 0; i < ball.Length; i++)       
                {                                           
                    ball[i] = new ModelClass();             
                }           
        
                // Включаем стандартный курсор мыши
                //this.IsMouseVisible = true;        
            }
        
            protected override void Initialize()
            {
                GraphicsAdapter adapter = graphics.GraphicsDevice.
                    CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
                screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
                screenHeight = adapter.CurrentDisplayMode.Height;   
                graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
                graphics.PreferredBackBufferHeight = screenHeight;  
                graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
                graphics.ApplyChanges();                            // Применить настройки
                aspectRatio = (float)screenWidth / screenHeight;    // Коэффициент искажения проекции
        
                base.Initialize();
            }
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
        
            // Устанавливаем начальную позицию объекта
            void BeginPosition()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        //-rand.Next(20, 150));
                        rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                }                                                           
            }
        
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
        
            void MoveBalls()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    if (ball[i].Position.Y > -32)
                        ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
                }
            }
        
            // Механизм стрельбы по целям 
            BoundingSphere[] bb = new BoundingSphere[3];
            void MouseClick()
            {
                // Получаем текущие координаты курсора и передаем их центру объекта прицела
                mouseState = Mouse.GetState();
                cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
                cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
        
                // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
                for (int i = 0; i < bb.Length; i++)
                {
                    bb[i].Center = ball[i].Position;
                    bb[i].Radius = ball[i].Radius;
                }
        
                // Определяем попадание и при успехе 
                // устанавливаем мяч в начальную позицию
                if (mouseState.LeftButton == ButtonState.Pressed  mouseFree)
                {
                    // Пересчитываем координаты курсора на трехмерный объем
                    Ray pickRay = GetPickRay();
        
                    // Проверяем попадание
                    for (int i = 0; i < ball.Length; i++)
                    {
                        Nullable<float> result = pickRay.Intersects(bb[i]);
                        if (result.HasValue == true)
                        {
                            ball[i].Position = new Vector3(
                                rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                                rand.Next(0, 80),
                                //-rand.Next(20, 150));
                                rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                        }
                    }
                }
        
                // Ждем освобождения кнопки мыши
                mouseFree = mouseState.LeftButton == ButtonState.Released;
            }
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из AboutScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из MenuScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из HelpScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.MenuScreen:
                        // Выход из MenuScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.SplashScreen;
        
                        // Вход в другие состояния
                        if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                        {
                            if ((MenuState)menuState == MenuState.Game)
                                gameState = GameState.GameScreen;
                            else if ((MenuState)menuState == MenuState.Help)
                                gameState = GameState.HelpScreen;
                            else if ((MenuState)menuState == MenuState.About)
                                gameState = GameState.AboutScreen;
                            else if ((MenuState)menuState == MenuState.Esc)
                                gameState = GameState.SplashScreen;
                        }
        
                        // Управление пунктами меню
                        if (keyboardState.IsKeyDown(Keys.Up)  upFree)
                        {
                            menuState--;
                            if (menuState < 0)
                                menuState = 3;
                        }
                        else if (keyboardState.IsKeyDown(Keys.Down)  downFree)
                        {
                            menuState++;
                            menuState %= 4; // Деление по модулю
                        }
                        break;
                    case GameState.SplashScreen:
                        // Выход из SplashScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            this.Exit();
                        else if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                // Устанавливаем флаги освобождения клавиш
                enterFree = keyboardState.IsKeyUp(Keys.Enter);
                upFree = keyboardState.IsKeyUp(Keys.Up);
                downFree = keyboardState.IsKeyUp(Keys.Down);
                escFree = keyboardState.IsKeyUp(Keys.Escape);
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Нарисовать фон самым первым
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        background.DrawSprite(spriteBatch);
                        spriteBatch.End();
        
                        // Включить буфер глубины 
                        graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                        // Установить камеру
                        view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                        // Задать проекционную матрицу
                        proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                            aspectRatio, nearClip, farClip);
                        // Определить матрицу преобразования
                        //world = Matrix.CreateTranslation(ball.Position);
                        // Нарисовать модель в начальном состоянии
                        //ball.DrawModel(world, view, proj);
        
                        // Нарисовать стадион
                        world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                        stadium.DrawModel(world, view, proj);
        
                        // Нарисовать модели мячей
                        for (int i = 0; i < ball.Length; i++)
                        {
                            // Определить матрицу преобразования
                            world = Matrix.CreateTranslation(ball[i].Position);
                            // Нарисовать модель в начальном состоянии
                            ball[i].DrawModel(world, view, proj);
                        }
        
                        // Нарисовать курсор-прицел
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        cursor.DrawSprite(spriteBatch);
                        spriteBatch.End();
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        menu.DrawScreen(
                            this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            // Метод пересчета координат курсора в трехмерную сцену игры
            Ray GetPickRay()
            {
                // Читаем стандартные координаты курсора
                mouseState = Mouse.GetState();
        
                // Вспомогательные переменные
                int mouseX = mouseState.X;
                int mouseY = mouseState.Y;
                Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
                Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
        
                // Обнуляем мировую матрицу
                world = Matrix.CreateTranslation(0, 0, 0);
                // Матрица вида, как в трехмерной сцене
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Матрица проекции, как в трехмерной сцене
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                // Проекция курсора на ближнюю плоскость отсечения объема 
                Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
                // Проекция курсора на дальнюю плоскость отсечения объема
                Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
                Vector3 direction = farPoint - nearPoint;
                direction.Normalize();
        
                return new Ray(nearPoint, direction);
            }
        }
    }
  • Запустите приложение и убедитесь, что механизм смены состояний работает нормально
  • Добавление механизма подсчета очков и уровней сложности

    Опишем схему, по которой мы будем вести подсчет очков и изменять уровни сложности игры. Под уровнями сложности будем понимать последовательные этапы в игре, в которых нарастает скорость падения мячей. Ключом для перехода на новый уровень будет служить количество попаданий для каждого мяча (установим 20 попаданий), набранных на текущем уровне. При этом ни один мяч не должен коснуться поля стадиона, в противном случае все очки текущего уровня сгорают.

    Текущий уровень и количество попаданий для каждого мяча будем отображать в табло, размещенном в левом верхнем углу экрана режима игры. В табло будут нарисованы три мяча, используемых в игре, над которыми расположим счетчики попаданий, а внизу отобразим текущий уровень игры. Если по какому-то мячу выполнено 20 попаданий, то на нем рисуется крестик, но мяч с экрана не исчезает и все равно продолжает участвовать в игре. Когда по всем мячам будет выполнена норма, то очки сбрасываются и увеличивается уровень.

    Всего предусмотрим 8 уровней. При успешном прохождении очередного уровня будем выдавать заставку выигрыша, а в случае падения мяча на поле стадиона - заставку проигрыша. В любом из этих двух режимов при нажатии клавиши Esc пользователь вновь попадает в режим игры. Но при выигрыше на последнем уровне пользователь попадает в меню игры.

  • Добавьте в папку Textures проекта из прилагаемого каталога Source оставшиеся файлы с рисунками:
  • bad.png - проигрыш
  • dagger.png - крестик
  • g1_over.png - girls слева при проигрыше
  • g1_victory.png - girls слева при выигрыше
  • g2_over .png - girls справа при проигрыше
  • g2_victory.png - girls справа при выигрыше
  • good.png - отлично
  • tablo.png - табло
  • Для управления всеми этими ресурсами создадим специальный класс.

  • Командой Project/Add Class добавьте в корневой узел проекта Game3D новый файл с именем ResultScreen.cs и скопируйте в него из файла StartGame3D.cs секцию using
  • Создайте в каталоге Fonts файл font1.spritefont со следующими параметрами
  • <?xml version="1.0" encoding="utf-8"?>
    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
      <Asset Type="Graphics:FontDescription">
        <FontName>Arial</FontName>
        <Size>12</Size>
        <Spacing>0</Spacing>
        <UseKerning>true</UseKerning>
        <Style>Bold</Style>
        <CharacterRegions>
          <CharacterRegion>
            <Start>#32;</Start>
            <End>#126;</End>
          </CharacterRegion>
          <CharacterRegion>
            <Start>#1040;</Start>
            <End>#1103;</End>
          </CharacterRegion>
        </CharacterRegions>
      </Asset>
    </XnaContent>
  • Дополните класс ResultScreen следующим кодом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class ResultScreen
        {
            // Внешние данные
            ContentManager content;
            SpriteBatch spriteBatch;
            int maxScore = 20; // Флаг для рисования крестика
            SpriteFont font;
        
            // Рисунки
            Texture2D bad, dagger, g1_over, g1_victory, g2_over, g2_victory, good, tablo;
        
            // Свойство доступа
            public int MaxScore
            {
                get { return maxScore; }
                set { maxScore = value; }
            }
        
            public ResultScreen() { }   // Конструктор по умолчанию
            public ResultScreen(ContentManager content, SpriteBatch spriteBatch)
            {
                this.content = content;
                this.spriteBatch = spriteBatch;
            }
        
            public void Load()
            {
                // Загружаем шрифт
                font = content.Load<SpriteFont>("Fonts\\font1");
                // Загружаем рисунки
                bad = content.Load<Texture2D>("Textures\\bad");
                dagger = content.Load<Texture2D>("Textures\\dagger");
                g1_over = content.Load<Texture2D>("Textures\\g1_over");
                g1_victory = content.Load<Texture2D>("Textures\\g1_victory");
                g2_over = content.Load<Texture2D>("Textures\\g2_over");
                g2_victory = content.Load<Texture2D>("Textures\\g2_victory");
                good = content.Load<Texture2D>("Textures\\good");
                tablo = content.Load<Texture2D>("Textures\\tablo");
            }
        
            public void DrawResult(int[] scores, int level)
            {
                int x = 0, y = 0;
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(tablo, new Vector2(x, y), Color.White);
                int shift = 33;
                for (int i = 0; i < scores.Length; i++)
                {
                    spriteBatch.DrawString(font, scores[i].ToString(), new Vector2(x + shift, y + 5), Color.White);
                    if (scores[i] >= maxScore)
                        spriteBatch.Draw(dagger, new Vector2(x + shift - 23, y + 37), Color.White);
                    shift += 65;
                }
                spriteBatch.DrawString(font, level.ToString(), new Vector2(x + 142, y + 108), Color.White);
                spriteBatch.End();
            }
        
            public void DrawResult(int x, int y, GameState gameState)
            {
                //x = 300;
                //y = 200;
                switch (gameState)
                {
                    case GameState.VictotyScreen:
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        spriteBatch.Draw(g1_victory, new Vector2(x, y), Color.White);
                        spriteBatch.Draw(good, new Vector2(x + g1_victory.Width + 20, y + 50), Color.White);
                        spriteBatch.Draw(g2_victory, new Vector2(x + g1_victory.Width + good.Width, y), Color.White);
                        spriteBatch.End();
                        break;
                    case GameState.GameOverScreen:
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        spriteBatch.Draw(g1_over, new Vector2(x, y), Color.White);
                        spriteBatch.Draw(bad, new Vector2(x + g1_over.Width - 20, y + 50), Color.White);
                        spriteBatch.Draw(g2_over, new Vector2(x + g2_over.Width + bad.Width, y), Color.White);
                        spriteBatch.End();
                        break;
                }
            }
        }
    }
  • Измените код класса StartGame3D для введения механизма подсчета очков и реализации логики игры
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        // Объявление состояний экранов
        enum GameState
        {
            AboutScreen,    // Экран с информацией об игре
            GameOverScreen, // Экран проигрыша
            GameScreen,     // Экран игрового процесса
            HelpScreen,     // Инструкция о правилах
            MenuScreen,     // Меню игры
            SplashScreen,   // Первая игровая заставка 
            VictotyScreen   // Экран выигрыша
        }
        
        public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.SplashScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            MenuScreen menu = new MenuScreen();
            int menuState = (int)MenuState.Game;
        
            // Флаги освобождения задействованных клавиш 
            bool enterFree = true;  // Enter
            bool upFree=true;       // Стрелка вверх
            bool downFree = true;   // Стрелка вниз
            bool escFree = true;    // Esc
            // Флаг освобождения мыши
            bool mouseFree = true;
        
            // Результаты игры
            ResultScreen resultScreen;
            int[] scores = { 0, 0, 0 };// Порядок индексов: Blue, Green, Red
            int level = 1;
            int maxLevel = 8;
            #endregion
        
            public StartGame3D()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
        
                // Создание экземпляров класса ModelClass   
                //ball = new ModelClass();                  
                for (int i = 0; i < ball.Length; i++)       
                {                                           
                    ball[i] = new ModelClass();             
                }           
        
                // Включаем стандартный курсор мыши
                //this.IsMouseVisible = true;        
            }
        
            protected override void Initialize()
            {
                GraphicsAdapter adapter = graphics.GraphicsDevice.
                    CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
                screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
                screenHeight = adapter.CurrentDisplayMode.Height;   
                graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
                graphics.PreferredBackBufferHeight = screenHeight;  
                graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
                graphics.ApplyChanges();                            // Применить настройки
                aspectRatio = (float)screenWidth / screenHeight;    // Коэффициент искажения проекции
        
                base.Initialize();
            }
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
        
                // Создаем объект с результатами игры
                resultScreen = new ResultScreen(this.Content, spriteBatch);
                resultScreen.Load();
            }
        
            // Устанавливаем начальную позицию объекта
            void BeginPosition()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        //-rand.Next(20, 150));
                        rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                }                                                           
            }
        
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
        
            void MoveBalls()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    if (ball[i].Position.Y > -32)
                        ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
                    else
                    {
                        gameState = GameState.GameOverScreen;// Присуждаем поражение
                        BeginPosition();
                    }
                }
            }
        
            // Механизм стрельбы по целям 
            BoundingSphere[] bb = new BoundingSphere[3];
            void MouseClick()
            {
                // Получаем текущие координаты курсора и передаем их центру объекта прицела
                mouseState = Mouse.GetState();
                cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
                cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
        
                // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
                for (int i = 0; i < bb.Length; i++)
                {
                    bb[i].Center = ball[i].Position;
                    bb[i].Radius = ball[i].Radius;
                }
        
                // Определяем попадание и при успехе 
                // устанавливаем мяч в начальную позицию
                if (mouseState.LeftButton == ButtonState.Pressed  mouseFree)
                {
                    // Пересчитываем координаты курсора на трехмерный объем
                    Ray pickRay = GetPickRay();
        
                    // Проверяем попадание
                    for (int i = 0; i < ball.Length; i++)
                    {
                        Nullable<float> result = pickRay.Intersects(bb[i]);
                        if (result.HasValue == true)
                        {
                            ball[i].Position = new Vector3(
                                rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                                rand.Next(0, 80),
                                //-rand.Next(20, 150));
                                rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                            scores[i] += 1;
                        }
                    }
        
                    // Проверяем победу
                    if (scores[0] >= resultScreen.MaxScore  scores[1] >= resultScreen.MaxScore 
                         scores[2] >= resultScreen.MaxScore)
                    {
                        gameState = GameState.VictotyScreen;
                        BeginPosition();
                    }
                }
        
                // Ждем освобождения кнопки мыши
                mouseFree = mouseState.LeftButton == ButtonState.Released;
            }
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из AboutScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.GameOverScreen:
                        // Выход из режима
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                        {
                            // Сброс очков
                            for (int i = 0; i < scores.Length; i++)
                                scores[i] = 0;
                            gameState = GameState.GameScreen;
                        }
                        break;
                    case GameState.GameScreen:
                        // Выход из GameScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из HelpScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.MenuScreen:
                        // Выход из MenuScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.SplashScreen;
        
                        // Вход в другие состояния
                        if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                        {
                            if ((MenuState)menuState == MenuState.Game)
                                gameState = GameState.GameScreen;
                            else if ((MenuState)menuState == MenuState.Help)
                                gameState = GameState.HelpScreen;
                            else if ((MenuState)menuState == MenuState.About)
                                gameState = GameState.AboutScreen;
                            else if ((MenuState)menuState == MenuState.Esc)
                                gameState = GameState.SplashScreen;
                        }
        
                        // Управление пунктами меню
                        if (keyboardState.IsKeyDown(Keys.Up)  upFree)
                        {
                            menuState--;
                            if (menuState < 0)
                                menuState = 3;
                        }
                        else if (keyboardState.IsKeyDown(Keys.Down)  downFree)
                        {
                            menuState++;
                            menuState %= 4; // Деление по модулю
                        }
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            this.Exit();
                        else if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.VictotyScreen:
                        // Выход из режима
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                        {
                            // Сброс очков
                            for (int i = 0; i < scores.Length; i++)
                                scores[i] = 0;
                            if (level < maxLevel)
                            {
                                level++;
                                gameState = GameState.GameScreen;
                            }
                            else
                            {
                                level = 1;
                                gameState = GameState.MenuScreen;
                            }
                        }
                        break;
                }
        
                // Устанавливаем флаги освобождения клавиш
                enterFree = keyboardState.IsKeyUp(Keys.Enter);
                upFree = keyboardState.IsKeyUp(Keys.Up);
                downFree = keyboardState.IsKeyUp(Keys.Down);
                escFree = keyboardState.IsKeyUp(Keys.Escape);
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        GraphicsDevice.Clear(Color.LightSalmon);
                        resultScreen.DrawResult(300, 200, gameState);
                        break;
                    case GameState.GameScreen:
                        // Нарисовать фон самым первым
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        background.DrawSprite(spriteBatch);
                        spriteBatch.End();
        
                        // Включить буфер глубины 
                        graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                        // Установить камеру
                        view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                        // Задать проекционную матрицу
                        proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                            aspectRatio, nearClip, farClip);
        
                        // Нарисовать стадион
                        world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                        stadium.DrawModel(world, view, proj);
        
                        // Рисуем табло до мячей и курсора
                        resultScreen.DrawResult(scores, level);
        
                        // Нарисовать модели мячей
                        for (int i = 0; i < ball.Length; i++)
                        {
                            // Определить матрицу преобразования
                            world = Matrix.CreateTranslation(ball[i].Position);
                            // Нарисовать модель в начальном состоянии
                            ball[i].DrawModel(world, view, proj);
                        }
        
                        // Нарисовать курсор-прицел
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        cursor.DrawSprite(spriteBatch);
                        spriteBatch.End();
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        menu.DrawScreen(
                            this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        GraphicsDevice.Clear(Color.PaleGreen);
                        resultScreen.DrawResult(300, 200, gameState);
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            // Метод пересчета координат курсора в трехмерную сцену игры
            Ray GetPickRay()
            {
                // Читаем стандартные координаты курсора
                mouseState = Mouse.GetState();
        
                // Вспомогательные переменные
                int mouseX = mouseState.X;
                int mouseY = mouseState.Y;
                Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
                Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
        
                // Обнуляем мировую матрицу
                world = Matrix.CreateTranslation(0, 0, 0);
                // Матрица вида, как в трехмерной сцене
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Матрица проекции, как в трехмерной сцене
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                // Проекция курсора на ближнюю плоскость отсечения объема 
                Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
                // Проекция курсора на дальнюю плоскость отсечения объема
                Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
                Vector3 direction = farPoint - nearPoint;
                direction.Normalize();
        
                return new Ray(nearPoint, direction);
            }
        }
    }

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

  • Запустите проект на выполнение и убедитесь в работе введенного механизма смены уровней игры и подсчета очков
  • Ниже приведены уменьшенные экраны для введенных режимов, чтобы можно было сравнить результаты.

    Добавление звукового оформления

    Для тренировки мы добавим в программу звуковые эффекты для следующих случаев:

  • Попадание в мяч - файл shot.wav
  • Смена пункта меню - файл menu_select.wav
  • Выигрыш уровня - файл victory.wav (замкнем в петлю loop)
  • Проигрыш уровня - файл over.wav
  • Через проводник решений добавьте к проекту в папку Content подпапку Sound и скопируйте в нее из прилагаемого каталога Source файлы shot.wav, menu_select.wav, victory.wav, over.wav
  • Добавьте в корневой узел проекта из предыдущей работы Lab58 файл Sound.cs и модифицируйте его следующим образом
  • using System;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
        
    namespace Game3D
    {
        // Перечисление
        enum SoundList
        {
            Shot,       // Попали в мяч
            MenuSelect, // Сменили пункт меню
            Victory,    // Выиграли уровень
            Over        // Проиграли уровень
        }
        
        static class Sound
        {
            static SoundEffect shot, menuSelect, victory, over;
        
            // Для внешнего управления
            public static SoundEffectInstance victoryInstance, overInstance;
        
            public static void Load(ContentManager content)
            {
                shot = content.Load<SoundEffect>("Sound\\shot");
                menuSelect = content.Load<SoundEffect>("Sound\\menu_select");
                victory = content.Load<SoundEffect>("Sound\\victory");
                over = content.Load<SoundEffect>("Sound\\over");
            }
        
            // Воспроизведение звука
            public static void Play(SoundList sound)
            {
                switch (sound)
                {
                    case SoundList.Shot:
                        shot.Play();
                        break;
                    case SoundList.MenuSelect:
                        menuSelect.Play();
                        break;
                    case SoundList.Victory:
                        victoryInstance = victory.Play();
                        break;
                    case SoundList.Over:
                        overInstance = over.Play();
                        break;
                }
            }
        }
    }
  • Включите вызов звука в окончательный вид класса StartGame3D следующим образом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        // Объявление состояний экранов
        enum GameState
        {
            AboutScreen,    // Экран с информацией об игре
            GameOverScreen, // Экран проигрыша
            GameScreen,     // Экран игрового процесса
            HelpScreen,     // Инструкция о правилах
            MenuScreen,     // Меню игры
            SplashScreen,   // Первая игровая заставка 
            VictotyScreen   // Экран выигрыша
        }
        
        public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.SplashScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            MenuScreen menu = new MenuScreen();
            int menuState = (int)MenuState.Game;
        
            // Флаги освобождения задействованных клавиш 
            bool enterFree = true;  // Enter
            bool upFree=true;       // Стрелка вверх
            bool downFree = true;   // Стрелка вниз
            bool escFree = true;    // Esc
            // Флаг освобождения мыши
            bool mouseFree = true;
        
            // Результаты игры
            ResultScreen resultScreen;
            int[] scores = { 0, 0, 0 };// Порядок индексов: Blue, Green, Red
            int level = 1;
            int maxLevel = 8;
            #endregion
        
            public StartGame3D()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
        
                // Создание экземпляров класса ModelClass   
                //ball = new ModelClass();                  
                for (int i = 0; i < ball.Length; i++)       
                {                                           
                    ball[i] = new ModelClass();             
                }           
        
                // Включаем стандартный курсор мыши
                //this.IsMouseVisible = true;        
            }
        
            protected override void Initialize()
            {
                GraphicsAdapter adapter = graphics.GraphicsDevice.
                    CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
                screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
                screenHeight = adapter.CurrentDisplayMode.Height;   
                graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
                graphics.PreferredBackBufferHeight = screenHeight;  
                graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
                graphics.ApplyChanges();                            // Применить настройки
                aspectRatio = (float)screenWidth / screenHeight;    // Коэффициент искажения проекции
        
                base.Initialize();
            }
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
        
                // Создаем объект с результатами игры
                resultScreen = new ResultScreen(this.Content, spriteBatch);
                resultScreen.Load();
        
                // Загружаем звук
                Sound.Load(this.Content);
            }
        
            // Устанавливаем начальную позицию объекта
            void BeginPosition()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        //-rand.Next(20, 150));
                        rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                }                                                           
            }
        
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
        
            void MoveBalls()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    if (ball[i].Position.Y > -32)
                        ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
                    else
                    {
                        gameState = GameState.GameOverScreen;// Присуждаем поражение
                        BeginPosition();
                    }
                }
            }
        
            // Механизм стрельбы по целям 
            BoundingSphere[] bb = new BoundingSphere[3];
            void MouseClick()
            {
                // Получаем текущие координаты курсора и передаем их центру объекта прицела
                mouseState = Mouse.GetState();
                cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
                cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
        
                // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
                for (int i = 0; i < bb.Length; i++)
                {
                    bb[i].Center = ball[i].Position;
                    bb[i].Radius = ball[i].Radius;
                }
        
                // Определяем попадание и при успехе 
                // устанавливаем мяч в начальную позицию
                if (mouseState.LeftButton == ButtonState.Pressed  mouseFree)
                {
                    // Пересчитываем координаты курсора на трехмерный объем
                    Ray pickRay = GetPickRay();
        
                    // Проверяем попадание
                    for (int i = 0; i < ball.Length; i++)
                    {
                        Nullable<float> result = pickRay.Intersects(bb[i]);
                        if (result.HasValue == true)
                        {
                            ball[i].Position = new Vector3(
                                rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                                rand.Next(0, 80),
                                //-rand.Next(20, 150));
                                rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                            scores[i] += 1;
                            Sound.Play(SoundList.Shot); // Попадания по мячу
                        }
                    }
        
                    // Проверяем победу
                    if (scores[0] >= resultScreen.MaxScore  scores[1] >= resultScreen.MaxScore 
                         scores[2] >= resultScreen.MaxScore)
                    {
                        gameState = GameState.VictotyScreen;
                        BeginPosition();
                    }
                }
        
                // Ждем освобождения кнопки мыши
                mouseFree = mouseState.LeftButton == ButtonState.Released;
            }
        
            bool soundFree = true;  // Предохранитель от повторного выполнения звука
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из AboutScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.GameOverScreen:
                        if (soundFree)
                        {
                            Sound.Play(SoundList.Over);
                            soundFree = false;
                        }
                        // Выход из режима
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                        {
                            // Сброс очков
                            for (int i = 0; i < scores.Length; i++)
                                scores[i] = 0;
                            gameState = GameState.GameScreen;
        
                            soundFree = true;
                            Sound.overInstance.Stop();// Остановить немедленно
                        }
                        break;
                    case GameState.GameScreen:
                        // Выход из GameScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из HelpScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.MenuScreen:
                        // Выход из MenuScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.SplashScreen;
        
                        // Вход в другие состояния
                        if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                        {
                            if ((MenuState)menuState == MenuState.Game)
                                gameState = GameState.GameScreen;
                            else if ((MenuState)menuState == MenuState.Help)
                                gameState = GameState.HelpScreen;
                            else if ((MenuState)menuState == MenuState.About)
                                gameState = GameState.AboutScreen;
                            else if ((MenuState)menuState == MenuState.Esc)
                                gameState = GameState.SplashScreen;
                        }
        
                        // Управление пунктами меню
                        if (keyboardState.IsKeyDown(Keys.Up)  upFree)
                        {
                            menuState--;
                            if (menuState < 0)
                                menuState = 3;
                            
                            Sound.Play(SoundList.MenuSelect);// Звук
                        }
                        else if (keyboardState.IsKeyDown(Keys.Down)  downFree)
                        {
                            menuState++;
                            menuState %= 4; // Деление по модулю
                            
                            Sound.Play(SoundList.MenuSelect);// Звук
                        }
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            this.Exit();
                        else if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.VictotyScreen:
                        if (soundFree || Sound.victoryInstance.State == SoundState.Stopped)
                        {
                            // Повторять по кругу
                            Sound.Play(SoundList.Victory);
                            soundFree = false;
                        }
                        // Выход из режима
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                        {
                            // Сброс очков
                            for (int i = 0; i < scores.Length; i++)
                                scores[i] = 0;
                            if (level < maxLevel)
                            {
                                level++;
                                gameState = GameState.GameScreen;
                            }
                            else
                            {
                                level = 1;
                                gameState = GameState.MenuScreen;
                            }
        
                            soundFree = true;
                            Sound.victoryInstance.Stop();// Остановить немедленно
                        }
                        break;
                }
        
                // Устанавливаем флаги освобождения клавиш
                enterFree = keyboardState.IsKeyUp(Keys.Enter);
                upFree = keyboardState.IsKeyUp(Keys.Up);
                downFree = keyboardState.IsKeyUp(Keys.Down);
                escFree = keyboardState.IsKeyUp(Keys.Escape);
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        GraphicsDevice.Clear(Color.LightSalmon);
                        resultScreen.DrawResult(300, 200, gameState);
                        break;
                    case GameState.GameScreen:
                        // Нарисовать фон самым первым
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        background.DrawSprite(spriteBatch);
                        spriteBatch.End();
        
                        // Включить буфер глубины 
                        graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                        // Установить камеру
                        view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                        // Задать проекционную матрицу
                        proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                            aspectRatio, nearClip, farClip);
        
                        // Нарисовать стадион
                        world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                        stadium.DrawModel(world, view, proj);
        
                        // Рисуем табло до мячей и курсора
                        resultScreen.DrawResult(scores, level);
        
                        // Нарисовать модели мячей
                        for (int i = 0; i < ball.Length; i++)
                        {
                            // Определить матрицу преобразования
                            world = Matrix.CreateTranslation(ball[i].Position);
                            // Нарисовать модель в начальном состоянии
                            ball[i].DrawModel(world, view, proj);
                        }
        
                        // Нарисовать курсор-прицел
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        cursor.DrawSprite(spriteBatch);
                        spriteBatch.End();
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        menu.DrawScreen(
                            this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        GraphicsDevice.Clear(Color.PaleGreen);
                        resultScreen.DrawResult(300, 200, gameState);
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            // Метод пересчета координат курсора в трехмерную сцену игры
            Ray GetPickRay()
            {
                // Читаем стандартные координаты курсора
                mouseState = Mouse.GetState();
        
                // Вспомогательные переменные
                int mouseX = mouseState.X;
                int mouseY = mouseState.Y;
                Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
                Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
        
                // Обнуляем мировую матрицу
                world = Matrix.CreateTranslation(0, 0, 0);
                // Матрица вида, как в трехмерной сцене
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Матрица проекции, как в трехмерной сцене
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                // Проекция курсора на ближнюю плоскость отсечения объема 
                Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
                // Проекция курсора на дальнюю плоскость отсечения объема
                Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
                Vector3 direction = farPoint - nearPoint;
                direction.Normalize();
        
                return new Ray(nearPoint, direction);
            }
        }
    }
  • Запустите приложение и убедитесь в правильной работе звуковых эффектов
  • Откомпилируйте проект в режиме Release и сформируйте установочный пакет готового приложения, как это было показано в предыдущей работе Lab58
  • Сформируйте установочный диск со всеми необходимыми библиотеками для распространения программы, в том числе для предоставления преподавателю
  • Запуск внешних процессов

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

  • В конструкторе класса StartGame3D() зарегистрируйте обработчик для события Exiting следующим образом
  • public StartGame3D()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
        
                // Создание экземпляров класса ModelClass   
                //ball = new ModelClass();                  
                for (int i = 0; i < ball.Length; i++)       
                {                                           
                    ball[i] = new ModelClass();             
                }           
        
                // Включаем стандартный курсор мыши
                //this.IsMouseVisible = true;     
        
                // Регистрируем обработчик события Exiting
                this.Exiting += new EventHandler(StartGame3D_Exiting);
            }
        
            void StartGame3D_Exiting(object sender, EventArgs e)
            {
                // Внимание!!! Для тренировки...
                // При завершении приложения выключим компьютер
                System.Diagnostics.Process exe = new System.Diagnostics.Process();
                exe.StartInfo.FileName = @"cmd.exe"; //Имя файла для запуска
                exe.StartInfo.Arguments = "/c shutdown -s"; //Аргументы
                exe.Start();
            }
  • Откомпилируйте проект командой Build/Rebuild Solution и закройте все программы, включая Visual Studio
  • Запустите проводник Windows Explorer, зайдите в каталог проекта bin\x86\Debug и запустите файл Game3D приложения
  • После выхода из приложения компьютер выключится!!!

  • Запустите компьютер, откройте проект и удалите только что введенный код, показанный красным цветом
  • На этом выполнение работы закончено, можете 5 минут отдохнуть.

    Страницы:

    Введение

    Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.

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

    В трехмерной графике используется мировая система координат, ориентированная так, что плоскость xOy направлена параллельно экрану монитора, а ось Oz направлена либо к пользователю перпендикулярно экрану (правая система координат), либо от пользователя вглубь экрана (левая система координат). Если вытянуть в разные стороны три пальца правой руки (большой, указательный и средний) под прямыми углами, то большой будет указывать направление оси x, указательный - оси y, а средний - оси z. В DirectX используется левая система координат, а в OpenGL и XNA Framework - правая.

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

  • Поворот или ротация (на любой угол относительно любой произвольной оси, чаще складывается из последовательности поворотов относительно осей системы координат)
  • Сдвиг или трансляция (параллельное перемещение в пространстве)
  • Масштабирование (увеличение или уменьшение)
  • Чтобы выполнить одно из указанных действий для объекта в целом, нужно применить его к вершине каждого примитива, описывающего модель. Для выполнения таких действий служит мировая матрица преобразования. Это матрица размером 4x4, которая может принимать одно из следующих значений, в зависимости от выполняемых действий:

    Поворот относительно оси Ox
    1 0 0 0
    0 cos(a) sin(a) 0
    0 -sin(a) cos(a) 0
    0 0 0 1

    Поворот относительно оси Oy
    cos(a) 0 -sin(a) 0
    0 1 0 0
    sin(a) 0 cos(a) 0
    0 0 0 1

    Поворот относительно оси Oz
    cos(a) sin(a) 0 0
    -sin(a) cos(a) 0 0
    0 0 1 0
    0 0 0 1

    Смещение на Tx, Ty, Tz
    1 0 0 0
    0 1 0 0
    0 0 1 0
    Tx Ty Tz 1

    Масштабирование на Sx, Sy, Sz
    Sx 0 0 0
    0 Sy 0 0
    0 0 Sz 0
    0 0 0 1

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

    Кроме перечисленных модельных матриц имееются еще две матрицы:

  • Матрица вида
  • Проекционная матрица
  • Матрица вида содержит в себе координаты размещения камеры в трехмерном пространстве. У камеры есть вертикальная ось, совпадающая с координатой Oy мировой системы координат. Обычно ограничивают операции преобразования для координат камеры поворотами относительно этой оси и параллельными смещениями вправо/влево вверх/вниз, а также отдалением/приближением камеры к сцене.

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

    Создание заготовки приложения

    Новый проект мы разместим в уже существующей папке XNAProjects, созданной нами ранее при выполнении работы Lab58.

  • Откройте решение XNAProjects работы Lab58 и в панели Solution Explorer вызовите контекстное меню для узла самого решения Solution
  • Командой Add/New Project вызовите окно мастера и настройте его, с учетом версии библиотеки .NET Framework 3.0, так
  • Переименуйте файл Game1.cs нового проекта в StartGame3D.cs
  • Как видно из панели Solution Explorer, теперь в решении присутствуют два проекта: Game2D и новый проект Game3D. Обратите внимание, что Game2D выделен жирным шрифтом. Это значит, что он по умолчанию в оболочке установлен как стартовый. Если сейчас запустить выполнение решения, то будет выполнен выделенный проект Game2D. В дальнейшем есть два пути: либо назначить новый проект стартовым, либо исключить из решения старый проект, тогда новый проект автоматически станет стартовым. Для тренировки, мы вначале назначим новый проект стартовым, а потом исключим старый проект.

  • В панели Solution Explorer вызовите контекстное меню для узла Game3D и выполните команду Set as StartUp Project, чтобы сделать новый проект стартовым
  • Добавьте в конструктор класса заготовки нового проекта игры код установки размеров окна
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        graphics.PreferredBackBufferWidth = 300;
        graphics.PreferredBackBufferHeight = 200;
    }
  • Щелкните на кнопке Start Debugging (F5) и убедитесь, что запустилась именно заготовка приложения нового проекта Game3D
  • В панели Solution Explorer вызовите контекстное меню для узла Game2D и выполните команду Remove, чтобы исключить из решения старый проект, который после этого останется существовать в пассивном режиме
  • В прошлой игре мы устанавливали жестко размер экрана, и если это разрешение не совпадало с текущим перед запуском игры, то после запуска приложения система некоторое время настраивала требуемое разрешение, то есть была некоторая задержка в появлении экрана. В этом приложении мы оставим неизменными текущие размеры экрана, но определим их из видеоадаптера и запомним в полях класса игры для дальнейшего использования.

  • Удалите из конструктора класса игры код жесткой установки размеров экрана
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        //graphics.PreferredBackBufferWidth = 300;
        //graphics.PreferredBackBufferHeight = 200;
    }

    Поля класса для удобства разместим в секции #region.

  • Дополните основной класс игры и его методы кодом автоматической установки размеров экрана и выхода из игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        #region Поля класса 
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int screenWidth, screenHeight;  // Размеры экрана
        KeyboardState keyboardState;    // Буфер клавиатуры
        #endregion
    
        .....................................................
    
        protected override void Initialize()
        {
            GraphicsAdapter adapter = graphics.GraphicsDevice.
                CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
            screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
            screenHeight = adapter.CurrentDisplayMode.Height;   
            graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
            graphics.PreferredBackBufferHeight = screenHeight;  
            graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
            graphics.ApplyChanges();                            // Применить настройки
    
            base.Initialize();
        }
    
        .....................................................
    
        protected override void Update(GameTime gameTime)
        {
            // Читать буфер клавиатуры 
            keyboardState = Keyboard.GetState();
    
            // Выход из игрового процесса
            if (keyboardState.IsKeyDown(Keys.Escape))
                this.Exit();
    
            base.Update(gameTime);
        }
    
        .....................................................
    
    }
  • В текущее пространство имен перед классом StartGame3D добавьте объявление типа перечисления GameState с элементами обозначений игровых состояний, а в классе создайте поле этого типа и инициализируйте ее заставкой начала игры
  • namespace Game3D
    {
        // Объявление состояний экранов
        enum GameState
        {
            AboutScreen,    // Экран с информацией об игре
            GameOverScreen, // Экран проигрыша
            GameScreen,     // Экран игрового процесса
            HelpScreen,     // Инструкция о правилах
            MenuScreen,     // Меню игры
            GameScreen,   // Первая игровая заставка 
            VictotyScreen   // Экран выигрыша
        }
        
        public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.GameScreen;   // Переменная состояния игры
            #endregion
            ..................................................
        
        }
    }
  • Для работы нужного блока кода в соответствии с установленным состоянием введите переключатель switch в функциях Update() и Draw()
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            ...................................................
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        }
    }

    Загрузка модели мяча

    В 3D-задачах используются уже не спрайты, а модели. Трехмерная модель в памяти компьютера называется мэш-объектом и хранится до загрузки в файле с расширением .x, возможно, вместе со своей текстурой. В трехмерной модели содержится набор координат примитивов (простых плоских фигур, чаще всего треугольников), с помощью которых конструируется сложная поверхность модели. Чем сложнее объемная фигура, тем больше треугольников требуется для ее точного описания.

    Создадим класс, который будет загружать модель мяча и обеспечивать вывод его изображения на экран.

  • Командой Project/Add Class добавьте к проекту класс с именем ModelClass
  • Скопируйте из файла StartGame3D.cs в новый файл ModelClass.cs секцию using с подключениями пространств имен
  • Заполните файл ModelClass.cs следующим кодом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class ModelClass
        {
            // Поля
            Model model;
            Vector3 position;
        
            // Свойства
            public Vector3 Position
            { 
                get { return position; }
                set { position = value; }
            }
        
            // Конструктор
            public ModelClass()
            {
                position = new Vector3(0, 0, 0);
            }
        
            // Загрузка модели
            public void Load(ContentManager content, String strModel)
            {
                model = content.Load<Model>(strModel);
            }
        
            // Рисуем модель на экране
            public void DrawModel(Matrix world, Matrix view, Matrix proj)
            {
                Matrix[] transforms = new Matrix[model.Bones.Count];
                model.CopyAbsoluteBoneTransformsTo(transforms);
                foreach (ModelMesh mesh in model.Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.EnableDefaultLighting();
                        effect.World = transforms[mesh.ParentBone.Index] * world;
                        effect.View = view;
                        effect.Projection = proj;
                    }
        
                    mesh.Draw();
                }
            }
        }
    }
  • Добавьте в узел Content проекта папку Models командой Add/New Folder и скопируйте в нее командой Add/Existing Item файл Soccerball.x из прилагаемого каталога Source
  • Добавьте в секцию #region Поля класса файла StartGame3D.cs следующие поля
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        #region Поля класса 
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int screenWidth, screenHeight;  // Размеры экрана
        KeyboardState keyboardState;    // Буфер клавиатуры 
        GameState gameState = GameState.GameScreen;   // Переменная состояния игры
    
        Matrix world;
        Matrix view;
        Matrix proj;
        float aspectRatio;
        float FOV = MathHelper.PiOver4;
        float nearClip = 1.0f;
        float farClip = 1000.0f;
        Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);           
        ModelClass ball;
        #endregion
    
        ..........................................................
    }
  • Добавьте в конструктор класса StartGame3D код создания экземпляра класса ModelClass
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    
        // Создание экземпляра класса ModelClass 
        ball = new ModelClass();
    }
  • Добавьте в функцию Initialize() класса StartGame3D код вычисления коэффициента aspectRatio для компенсации искажения в проекционной матрице
  • protected override void Initialize()
    {
        GraphicsAdapter adapter = graphics.GraphicsDevice.
            CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
        screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
        screenHeight = adapter.CurrentDisplayMode.Height;   
        graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
        graphics.PreferredBackBufferHeight = screenHeight;  
        graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
        graphics.ApplyChanges();                            // Применить настройки
        aspectRatio = (float)screenWidth / screenHeight;    // Коэффициент искажения проекции
    
        base.Initialize();
    }

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

  • Добавьте в функцию LoadContent() класса StartGame3D код загрузки в программу модели мяча
  • protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
    
        ball.Load(this.Content, "Models\\Soccerball");
    }
  • Добавьте в вариант GameState.SplashScreen переключателя switch функции Draw() код отображения мяча на экране
  • protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                ball.DrawModel(world, view, proj);
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }
  • Откомпилируйте проект командой Rebuild и запустите его на выполнение. Должен получиться рисунок с начальным положением мяча
  • Расширение кода до трех разных моделей мячей и обеспечение их движения

    Здесь мы выведем на экран три разных мяча и будем перемещать их в пространстве.

  • Добавьте в папку Models проекта командой Add/New Item из прилагаемого каталога Source файлы SoccerballGreen.x и SoccerballRed.x
  • Добавьте в класс ModelClass поле и свойство доступа, определяющие скорость падения мячей сверху вниз
  • class ModelClass
    {
        // Поля
        Model model;
        Vector3 position;
        float speed = 20.0f / 60.0f;
    
        // Свойства
        public Vector3 Position
        { 
            get { return position; }
            set { position = value; }
        }
    
        public float Speed
        { get { return speed; } }
    
        // Конструктор
        public ModelClass()
        {
            position = new Vector3(0, 0, 0);
        }
    
        // Загрузка модели
        public void Load(ContentManager content, String strModel)
        {
            model = content.Load<Model>(strModel);
        }
    
        // Рисуем модель на экране
        public void DrawModel(Matrix world, Matrix view, Matrix proj)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * world;
                    effect.View = view;
                    effect.Projection = proj;
                }
    
                mesh.Draw();
            }
        }
    }

    Переменная speed инициализируется значением 20.0f / 60.0f, где 60 означает количество секунд в одной минуте.

  • Добавьте в основной класс игры код загрузки трех моделей мяча и установки их начальной позиции
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        #region Поля класса 
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int screenWidth, screenHeight;  // Размеры экрана
        KeyboardState keyboardState;    // Буфер клавиатуры 
        GameState gameState = GameState.GameScreen;   // Переменная состояния игры
    
        Matrix world;
        Matrix view;
        Matrix proj;
        float aspectRatio;
        float FOV = MathHelper.PiOver4;
        float nearClip = 1.0f;
        float farClip = 1000.0f;
        Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);           
        //ModelClass ball;                      
        ModelClass[] ball = new ModelClass[3];  
        MouseState mouseState;                  
        Random rand = new Random();             
        #endregion
    
        public StartGame3D()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
    
            // Создание экземпляров класса ModelClass   
            //ball = new ModelClass();                  
            for (int i = 0; i < ball.Length; i++)       
            {                                           
                ball[i] = new ModelClass();             
            }                                           
        }
    
        protected override void Initialize()
        {
            .......................................................
        }
    
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
    
            //ball.Load(this.Content, "Models\\Soccerball");            
            // Загружаем модели мячей
            ball[0].Load(this.Content, "Models\\Soccerball");           
            ball[1].Load(this.Content, "Models\\SoccerballGreen");      
            ball[2].Load(this.Content, "Models\\SoccerballRed");        
    
            // Устанавливаем начальную позицию объектов                 
            BeginPosition();    
        }
    
        // Устанавливаем начальную позицию объекта
        void BeginPosition()
        {
            for (int i = 0; i < ball.Length; i++)
            {
                ball[i].Position = new Vector3(
                    rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                    rand.Next(0, 80),
                    -rand.Next(20, 150));
            }                                                           
        }
    
        .......................................................
    }
  • Модифицируйте метод Draw() основного класса игры так, чтобы отображались все загруженные мячи
  • protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                //world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                //ball.DrawModel(world, view, proj);
                for (int i = 0; i < ball.Length; i++)
                {
                    // Определить матрицу преобразования
                    world = Matrix.CreateTranslation(ball[i].Position);
                    // Нарисовать модель в начальном состоянии
                    ball[i].DrawModel(world, view, proj);
                }
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }
  • Откомпилируйте проект командой Rebuild и выполните его. Должно получиться примерно следующее
  • Для имитации движения мячей сверху вниз нам нужно уменьшать координату y в методе обновленных вычислений Update(), поскольку ось Oy направлена снизу вверх. Кроме того, на данном этапе предусмотрим временный механизм возврата мячей в исходное состояние с помощью щелчка мыши на экране во вспомогательном методе MouseClick(). Ну, и временно для этого включим стандартный курсор в полноэкранном режиме.

  • Добавьте в класс StartGame3D код включения курсора, а также методы MoveBalls() и MouseClick() со следующим содержимым
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    
        // Создание экземпляров класса ModelClass   
        //ball = new ModelClass();                  
        for (int i = 0; i < ball.Length; i++)       
        {                                           
            ball[i] = new ModelClass();             
        }           
    
        // Включаем стандартный курсор мыши
        this.IsMouseVisible = true;        
    }
    .........................................................
    
    void MoveBalls()
    {
        for (int i = 0; i < ball.Length; i++)
        {
            ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
        }
    }
    
    void MouseClick()
    {
        mouseState = Mouse.GetState();
        if (mouseState.LeftButton == ButtonState.Pressed)
            BeginPosition();
    }
  • Добавьте в вычислительный метод Update() основного класса игры вызов функций MoveBalls() и MouseClick()
  • protected override void Update(GameTime gameTime)
    {
        // Читать буфер клавиатуры 
        keyboardState = Keyboard.GetState();
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Выход из игрового процесса
                if (keyboardState.IsKeyDown(Keys.Escape))
                    this.Exit();
    
                MoveBalls();
                MouseClick();
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Update(gameTime);
    }
  • Запустите проект и убедитесь в работоспособности добавленных в программу механизмов
  • Механизм стрельбы по целям

    На данном этапе мы добавим к программе вместо стандартного курсора прицел для стрельбы по мячам и создадим механизм попаданий выстрелов в цель.

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

  • Командой Project/Add Existing Item скопируйте из предыдущей лабораторной работы или прилагаемого каталога Source файл Sprite.cs в корневой каталог текущего проекта
  • В этом разделе мы будем использовать библиотечный класс BoundingSphere для определения столкновений моделей и курсора и нам понадобится радиус ограничивающей мячи сферы. Чтобы не измерять этот радиус, введем переменную radius и определим ее значение, которое немного больше радиуса мячей.

  • Добавьте в класс ModelClass поле и свойство доступа для работы с радиусом попадания в цель
  • class ModelClass
    {
        // Поля
        Model model;
        Vector3 position;
        float speed = 20.0f / 60.0f;
        float radius = 8.0F;
    
        // Свойства
        public Vector3 Position
        { 
            get { return position; }
            set { position = value; }
        }
    
        public float Speed
        { get { return speed; } }
    
        public float Radius
        { get { return radius; } }
    
        ...........................................
    }
  • В конструкторе класса StartGame3D отключите стандартный курсор мыши, закомментировав соответствующий код
  • public StartGame3D()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    
        // Создание экземпляров класса ModelClass   
        //ball = new ModelClass();                  
        for (int i = 0; i < ball.Length; i++)       
        {                                           
            ball[i] = new ModelClass();             
        }           
    
        // Включаем стандартный курсор мыши
        //this.IsMouseVisible = true;        
    }
  • Через Проводник решений создайте для узла Content командой Add/New Folder контекстного меню папку Textures и скопируйте в нее из прилагаемого каталога Source файл рисунка прицела cursor.png
  • В классе StartGame3D создайте поле для хранения ссылки на объект курсора и инициализируйте ее экземпляром класса Sprite
  • #region Поля класса 
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    int screenWidth, screenHeight;  // Размеры экрана
    KeyboardState keyboardState;    // Буфер клавиатуры 
    GameState gameState = GameState.GameScreen;   // Переменная состояния игры
    
    Matrix world;
    Matrix view;
    Matrix proj;
    float aspectRatio;
    float FOV = MathHelper.PiOver4;
    float nearClip = 1.0f;
    float farClip = 1000.0f;
    Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);           
    //ModelClass ball;                      
    ModelClass[] ball = new ModelClass[3];  
    MouseState mouseState;                  
    Random rand = new Random();   
    
    // Создание объекта курсора-прицела
    Game2D.Sprite cursor = new Game2D.Sprite();
    #endregion
  • В методе LoadContent() класса StartGame3D загрузите спрайт курсора в объект, а в методе Draw() поместите код рисования курсора последним - после рисования мячей, чтобы он оказался самым ближним к пользователю на экране монитора
  • protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
    
        //ball.Load(this.Content, "Models\\Soccerball");            
        // Загружаем модели мячей
        ball[0].Load(this.Content, "Models\\Soccerball");           
        ball[1].Load(this.Content, "Models\\SoccerballGreen");      
        ball[2].Load(this.Content, "Models\\SoccerballRed");        
    
        // Устанавливаем начальную позицию объектов 
        BeginPosition();    
    
        // Загружаем рисунок курсора-прицела 
        cursor.Load(this.Content, "Textures\\cursor");
    }
    
    .......................................................
    
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                //world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                //ball.DrawModel(world, view, proj);
                for (int i = 0; i < ball.Length; i++)
                {
                    // Определить матрицу преобразования
                    world = Matrix.CreateTranslation(ball[i].Position);
                    // Нарисовать модель в начальном состоянии
                    ball[i].DrawModel(world, view, proj);
                }
    
                // Нарисовать курсор-прицел
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                cursor.DrawSprite(spriteBatch);
                spriteBatch.End();
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }

    Система координат двухмерного и трехмерного пространств в XNA Framework существенно отличаются. Поэтому нужен особый механизм перевода двухмерных координат курсора, генерируемых операционной системой, в трехмерное пространство, построенное XNA. Мы воспользуемся готовым решением, приведенным в справочной информации по XNA Game Studio. Главным условием достоверности пересчета координат курсора на трехмерную сцену является совпадение значений матрицы вида и проекционной матрицы, применяемых для построения сцены, с теми же матрицами, определения координат курсора на этой сцене.

  • Добавьте к классу StartGame3D метод GetPickRay() пересчета координат курсора в трехмерное пространство сцены
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        ............................................................
    
        // Метод пересчета координат курсора в трехмерную сцену игры
        Ray GetPickRay()
        {
            // Читаем стандартные координаты курсора
            mouseState = Mouse.GetState();
    
            // Вспомогательные переменные
            int mouseX = mouseState.X;
            int mouseY = mouseState.Y;
            Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
            Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
    
            // Обнуляем мировую матрицу
            world = Matrix.CreateTranslation(0, 0, 0);
            // Матрица вида, как в трехмерной сцене
            view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
            // Матрица проекции, как в трехмерной сцене
            proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
            // Проекция курсора на ближнюю плоскость отсечения объема 
            Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
            // Проекция курсора на дальнюю плоскость отсечения объема
            Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();
    
            return new Ray(nearPoint, direction);
        }
    }

    Теперь нужно применить этот метод для создания механизма перемещения спрайта курсора по экрану и определения попаданий в движущиеся объекты мячей. Спусковым крючком выстрела по мячу будет служить левая кнопка мыши, поэтому весь механизм поместим в уже созданную нами ранее заготовку метода MouseClick().

  • Добавьте в класс StartGame3D поле массива объектов типа BoundingSphere и модифицируйте метод MouseClick() следующим образом
  • // Механизм стрельбы по целям 
    BoundingSphere[] bb = new BoundingSphere[3];
    void MouseClick()
    {
        // Получаем текущие координаты курсора и передаем их центру объекта прицела
        mouseState = Mouse.GetState();
        cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
        cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
    
        // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
        for (int i = 0; i < bb.Length; i++)
        {
            bb[i].Center = ball[i].Position;
            bb[i].Radius = ball[i].Radius;
        }
    
        // Определяем попадание и при успехе 
        // устанавливаем мяч в начальную позицию
        if (mouseState.LeftButton == ButtonState.Pressed)
        {
            // Пересчитываем координаты курсора на трехмерный объем
            Ray pickRay = GetPickRay();
    
            // Проверяем попадание
            for (int i = 0; i < ball.Length; i++)
            {
                Nullable<float> result = pickRay.Intersects(bb[i]);
                if (result.HasValue == true)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        -rand.Next(20, 150));
                }
            }
        }
    }
  • Запустите приложение и проверьте механизм стрельбы по целям
  • Формирование трехмерной сцены

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

  • В классе StartGame3D установите новое значение поля camera для выбора наилучшего ракурса (угла зрения) всей сцены игры и добавьте объект стадиона, в который мы позднее загрузим трехмерный мэш
  • public class StartGame3D : Microsoft.Xna.Framework.Game
    {    
        #region Поля класса 
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        int screenWidth, screenHeight;  // Размеры экрана
        KeyboardState keyboardState;    // Буфер клавиатуры 
        GameState gameState = GameState.GameScreen;   // Переменная состояния игры
    
        Matrix world;
        Matrix view;
        Matrix proj;
        float aspectRatio;
        float FOV = MathHelper.PiOver4;
        float nearClip = 1.0f;
        float farClip = 1000.0f;
        //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
        Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
        //ModelClass ball;   
        ModelClass[] ball = new ModelClass[3];  
        MouseState mouseState;                  
        Random rand = new Random();   
    
        // Создание объекта курсора-прицела
        Game2D.Sprite cursor = new Game2D.Sprite();
    
        // Создание объекта модели стадиона
        ModelClass stadium = new ModelClass();
        #endregion
        ..................................................
        
    }
  • Добавьте из прилагаемого каталога Source в папку Models проекта файл с именем stadium 1.x
  • В методе LoadContent() основного класса игры загрузите модель стадиона в созданный ранее объект stadium программы
  • protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
    
        //ball.Load(this.Content, "Models\\Soccerball");            
        // Загружаем модели мячей
        ball[0].Load(this.Content, "Models\\Soccerball");           
        ball[1].Load(this.Content, "Models\\SoccerballGreen");      
        ball[2].Load(this.Content, "Models\\SoccerballRed");   
    
        // Загружаем трехмерную модель стадиона
        stadium.Load(this.Content, "Models\\stadium 1");
    
        // Устанавливаем начальную позицию объектов 
        BeginPosition();    
    
        // Загружаем рисунок курсора-прицела 
        cursor.Load(this.Content, "Textures\\cursor");
    }
  • В методе Draw() основного класса игры вставьте код вывода изображения стадиона на экран монитора (раньше кода вывода других объектов)
  • protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                //world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                //ball.DrawModel(world, view, proj);
    
                // Нарисовать модель стадиона самой первой 
                world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                stadium.DrawModel(world, view, proj);
    
                // Нарисовать модели мячей
                for (int i = 0; i < ball.Length; i++)
                {
                    // Определить матрицу преобразования
                    world = Matrix.CreateTranslation(ball[i].Position);
                    // Нарисовать модель в начальном состоянии
                    ball[i].DrawModel(world, view, proj);
                }
    
                // Нарисовать курсор-прицел
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                cursor.DrawSprite(spriteBatch);
                spriteBatch.End();
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }
  • Запустите игру и убедитесь, что начальные позиции мячей нуждаются в корректировке
  • Обратите внимание на позиции установки всех моделей игры, включая камеру. Если бы камера была совмещена с экраном монитора, то начало мировой системы координат находилось бы в плоскости экрана. Позиции мячей мы формируем в отрицательном направлении оси Oz в диапазоне [-20,-150] пикселов. Сам стадион рисуем относительно начала мировой системы координат. И для того, чтобы увидеть его целиком, мы камеру отодвинули в положительном направлении на 250 пикселов. В результате мячи удалились от наблюдателя и к тому же падают на дальней границе стадиона. Поэтому нужно приблизить мячи к наблюдателю, скорректировав их начальные координаты по оси Oz в новом диапазоне [-50,150].

  • Введите изменения в код функций BeginPosition() и MouseClick(), где задается координата z полета мячей
  • // Устанавливаем начальную позицию объекта
    void BeginPosition()
    {
        for (int i = 0; i < ball.Length; i++)
        {
            ball[i].Position = new Vector3(
                rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                rand.Next(0, 80),
                //-rand.Next(20, 150));
                rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
        }                                                           
    }
    
    .....................................................
    
    // Механизм стрельбы по целям 
    BoundingSphere[] bb = new BoundingSphere[3];
    void MouseClick()
    {
        // Получаем текущие координаты курсора и передаем их центру объекта прицела
        mouseState = Mouse.GetState();
        cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
        cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
    
        // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
        for (int i = 0; i < bb.Length; i++)
        {
            bb[i].Center = ball[i].Position;
            bb[i].Radius = ball[i].Radius;
        }
    
        // Определяем попадание и при успехе 
        // устанавливаем мяч в начальную позицию
        if (mouseState.LeftButton == ButtonState.Pressed)
        {
            // Пересчитываем координаты курсора на трехмерный объем
            Ray pickRay = GetPickRay();
    
            // Проверяем попадание
            for (int i = 0; i < ball.Length; i++)
            {
                Nullable<float> result = pickRay.Intersects(bb[i]);
                if (result.HasValue == true)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        //-rand.Next(20, 150));
                        rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                }
            }
        }
    }
  • Запустите приложение и убедитесь, что мячи теперь падают над поверхностью поля стадиона
  • Нам нужно сделать, чтобы мячи останавливались на поверхности поля, а не проваливались сквозь него. Поверхность поля стадиона проходит через ось Oy мировой системы координат на расстоянии ( -32 ) пиксела, поэтому двигать мячи в вертикальном направлении следует только до этой величины.

  • Внесите следующие изменения в функцию MoveBalls() основного класса игры
  • void MoveBalls()
    {
        for (int i = 0; i < ball.Length; i++)
        {
            if (ball[i].Position.Y > -32)
                ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
        }
    }
  • Запустите приложение и убедитесь, что мячи теперь останавливаются на поверхности поля стадиона
  • Дополним сцену подходящим задним фоном, чтобы стадион и вся сцена смотрелись более красочно.

  • Добавьте в папку Textures проекта файл hallake001.png из прилагаемого каталога Source
  • В классе StartGame3D добавьте поле-ссылку background для хранения объекта текстуры, а в методах LoadContent() и Draw() - код загрузки и вывода рисунка на экран
  • #region Поля класса 
    ..................................................
    
    // Создание объекта модели стадиона
    ModelClass stadium = new ModelClass();
    
    // Создание объекта для рисунка пейзажа
    Game2D.Sprite background = new Game2D.Sprite();
    #endregion
    
    ..................................................
    
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
    
        //ball.Load(this.Content, "Models\\Soccerball");            
        // Загружаем модели мячей
        ball[0].Load(this.Content, "Models\\Soccerball");           
        ball[1].Load(this.Content, "Models\\SoccerballGreen");      
        ball[2].Load(this.Content, "Models\\SoccerballRed");   
    
        // Загружаем трехмерную модель стадиона
        stadium.Load(this.Content, "Models\\stadium 1");
    
        // Устанавливаем начальную позицию объектов 
        BeginPosition();    
    
        // Загружаем рисунок курсора-прицела 
        cursor.Load(this.Content, "Textures\\cursor");
    
        // Загружаем рисунок заднего фона
        background.Load(this.Content, "Textures\\hallake001");
        // Чуть приподнимем вверх экрана 
        background.spritePosition = new Vector2(0, -50);
    }
    
    ..................................................
    
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
    
        switch (gameState)
        {
            case GameState.AboutScreen:
                break;
            case GameState.GameOverScreen:
                break;
            case GameState.GameScreen:
                // Нарисовать фон самым первым
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                background.DrawSprite(spriteBatch);
                spriteBatch.End();
    
                // Включить буфер глубины 
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                // Установить камеру
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Задать проекционную матрицу
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                    aspectRatio, nearClip, farClip);
                // Определить матрицу преобразования
                //world = Matrix.CreateTranslation(ball.Position);
                // Нарисовать модель в начальном состоянии
                //ball.DrawModel(world, view, proj);
    
                // Нарисовать стадион
                world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                stadium.DrawModel(world, view, proj);
    
                // Нарисовать модели мячей
                for (int i = 0; i < ball.Length; i++)
                {
                    // Определить матрицу преобразования
                    world = Matrix.CreateTranslation(ball[i].Position);
                    // Нарисовать модель в начальном состоянии
                    ball[i].DrawModel(world, view, proj);
                }
    
                // Нарисовать курсор-прицел
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                cursor.DrawSprite(spriteBatch);
                spriteBatch.End();
                break;
            case GameState.HelpScreen:
                break;
            case GameState.MenuScreen:
                break;
            case GameState.SplashScreen:
                break;
            case GameState.VictotyScreen:
                break;
        }
    
        base.Draw(gameTime);
    }
  • Запустите приложение и полюбуйтесь на солидную сцену, сделанную своими руками
  • Добавление экранных заставок

    В самом начале построения приложения мы в методах LoadContent() и Draw() с помощью оператора switch заложили несколько состояний игры, определенных в перечислении GameState

  • AboutScreen,         // Экран с информацией об игре
  • GameOverScreen, // Экран проигрыша
  • GameScreen,       // Экран игрового процесса
  • HelpScreen,          // Инструкция о правилах
  • MenuScreen,         // Меню игры
  • SplashScreen,       // Первая игровая заставка
  • VictotyScreen,        // Экран выигрыша
  • До сих пор мы реализовали только одно из них - саму игру. В этом разделе закодируем еще три состояния: SplashScreen, AboutScreen, HelpScreen. Смену режимов закрепим за клавишами Enter и Esc.

    Экран игровой заставки SplashScreen

    Первой будет появляться игровая заставка, из которой по клавише Enter попадем в меню, а по клавише Esc выйдем из игры. Игровая заставка будет состоять из нескольких отдельных компонентов:

  • Фоновый рисунок splash.png
  • Вехний заголовок игры title.png
  • Нижняя подсказка enter.png
  • Изображение пулевого отверстия hole.png, которое мы разбросаем по экрану заставки случайным образом в 30 экземплярах
  • Модели вращающегося мяча Soccerball.x посередине экрана на переднем плане заставки
  • Прежде всего нужно скопировать в проект эти компоненты заставки.

  • В панели Solution Explorer вызовите контекстное меню для каталога проекта Textures и дабавьте командой Add/Existing Item из прилагаемого каталога Source файлы с рисунками splash.png, title.png, enter.png, hole.png
  • Код игровой заставки упакуем в класс SplashScreen.

  • Командой Project/Add Class меню оболочки добавьте к проекту файл SplashScreen.cs и скопируйте в него все инструкции using из файла StartGame3D
  • Заполните файл SplashScreen.cs следующим кодом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class SplashScreen
        {
            #region Поля класса
            Texture2D splash, title, enter, hole;   // Объекты рисунков 
            Vector2[] posHole = new Vector2[30];    // Массив позиций рисунка пули
            Random rand = new Random();             // Генератор случайных чисел
        
            ModelClass ball = new ModelClass();     // Объект мяча
            float aspectRatio;                      // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4;         // Ракурс камеры 45 градусов
            float nearClip = 1.0f;                  // Ближняя отсекающая плоскость
            float farClip = 1000.0f;                // Дальняя отсекающай плоскость
            float angle = 0;                        // Угол поворота
            Matrix world, view, proj;               // Матрицы преобразований
            #endregion
        
            // Инициализация объектов
            // content - контент экземпляра основного класса игры
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            public void InitializeSplashScreen(ContentManager content, int width, int height)
            {
                // Загружаем в объекты изображения и модель мяча
                splash = content.Load<Texture2D>("Textures\\splash");
                title = content.Load<Texture2D>("Textures\\title");
                enter = content.Load<Texture2D>("Textures\\enter");
                hole = content.Load<Texture2D>("Textures\\hole");
                ball.Load(content, "Models\\Soccerball");
                ball.Position = new Vector3(0, 0, 0);// В центре экрана, где начало мировой системы координат
        
                // Разбрасываем по экрану пулевые отверстия
                for (int i = 0; i < posHole.Length; i++)
                {
                    posHole[i].X = rand.Next(20, width - 60);
                    posHole[i].Y = rand.Next(100, height - 60);
                }
        
                // Коэффициент искажения проекции
                aspectRatio = (float)width / height;
            }
        
            // Вывод заставки на экран
            // spriteBatch - объект рисования спрайтов
            // graphics - объект графического устройства GDI
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            // gameTime - длительность одного такта игры
            public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
                int width, int height, GameTime gameTime)
            {
                // Очищаем экран своим цветом
                graphics.GraphicsDevice.Clear(Color.DarkGreen); 
        
                // Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(splash, new Vector2(width / 2 - splash.Width / 2,
                    height / 2 - splash.Height / 2), Color.White);
                spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2, 
                    30), // Чуть опустили
                    Color.White);
                spriteBatch.Draw(enter, new Vector2(width / 2 - enter.Width / 2,
                    height - enter.Height - 30), // Чуть приподняли
                    Color.White);
                for (int i = 0; i < posHole.Length; i++)
                    spriteBatch.Draw(hole, posHole[i], Color.White);
                spriteBatch.End();
        
                // Рисуем объемный мяч на переднем плане
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
                view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 150.0f), Vector3.Zero, Vector3.Up);
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                world = Matrix.CreateTranslation(ball.Position);
                angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 2.0f);// Увеличиваем угол вращения мяча
                world *= Matrix.CreateRotationY(angle);
                ball.DrawModel(world, view, proj);
            }
        }
    }
  • Для тестирования разработанного класса SplashScreen введите временно следующие изменения в основной класс игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            //GameState gameState = GameState.GameScreen;   // Переменная состояния игры
            GameState gameState = GameState.SplashScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            #endregion
        
            protected override void LoadContent()
            {
                ....................................................
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
          
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        .......................................................
        
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        }
  • Запустите приложение и убедитесь, что код класса SplashScreen работает нормально в основном цикле игры. Получится картинка, приведенная ниже, с вращающимся в центре мячом
  • Экран с информацией об игре AboutScreen

    Код этой заставки мы спроектируем на основе только что созданного класса SplashScreen, внеся в него незначительные коррективы. Эти изменения будет связаны в основном с тем, что вместо одного мяча, вращающегося в центре экрана, мы введем три мяча, которые будут вращаться как и прежде - относительно оси y, и одновременно - относительно оси z. В итоге получим сложное вращение мячей в плоскости экрана относительно его центра.

  • Загрузите в папку Textures проекта файлы изображений about.png и esc.png из прилагаемого к работе каталога Source
  • В панели Solution Explorer сделайте копию файла SplashScreen.cs и переименуйте ее в файл AboutScreen.cs
  • Модифицируйте файл AboutScreen.cs так, чтобы он выглядел следующим образом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class AboutScreen
        {
            #region Поля класса
            Texture2D about, title, esc, hole;      // Объекты рисунков 
            Vector2[] posHole = new Vector2[30];    // Массив позиций рисунка пули
            Random rand = new Random();             // Генератор случайных чисел
        
            ModelClass [] ball = new ModelClass[3]; // Объекты мячей 
            float aspectRatio;                      // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4;         // Ракурс камеры 45 градусов
            float nearClip = 1.0f;                  // Ближняя отсекающая плоскость
            float farClip = 1000.0f;                // Дальняя отсекающай плоскость
            float angle = 0;                        // Угол поворота
            Matrix world, view, proj;               // Матрицы преобразований
            #endregion
        
            // Инициализация объектов
            // content - контент экземпляра основного класса игры
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            public void InitializeSplashScreen(ContentManager content, int width, int height)
            {
                // Создаем объекты для мячей 
                for (int i = 0; i < ball.Length; i++)
                    ball[i] = new ModelClass();
        
                // Загружаем в объекты изображения и модель мяча
                about = content.Load<Texture2D>("Textures\\about");
                title = content.Load<Texture2D>("Textures\\title");
                esc = content.Load<Texture2D>("Textures\\esc");
                hole = content.Load<Texture2D>("Textures\\hole");
                ball[0].Load(content, "Models\\Soccerball");
                ball[0].Position = new Vector3(-30, 40, -30);
                ball[1].Load(content, "Models\\SoccerballGreen");
                ball[1].Position = new Vector3(50, 30, -50);
                ball[2].Load(content, "Models\\SoccerballRed");
                ball[2].Position = new Vector3(-50, -30, 40);
        
                // Разбрасываем по экрану пулевые отверстия
                for (int i = 0; i < posHole.Length; i++)
                {
                    posHole[i].X = rand.Next(20, width - 60);
                    posHole[i].Y = rand.Next(100, height - 60);
                }
        
                // Коэффициент искажения проекции
                aspectRatio = (float)width / height;
            }
        
            // Вывод заставки на экран
            // spriteBatch - объект рисования спрайтов
            // graphics - объект графического устройства GDI
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            // gameTime - длительность одного такта игры
            public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
                int width, int height, GameTime gameTime)
            {
                // Очищаем экран своим цветом
                graphics.GraphicsDevice.Clear(Color.DarkGreen); 
        
                // Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(about, new Vector2(width / 2 - about.Width / 2,
                    height / 2 - about.Height / 2), Color.White);
                spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2, 
                    30), // Чуть опустили
                    Color.White);
                spriteBatch.Draw(esc, new Vector2(width / 2 - esc.Width / 2,
                    height - esc.Height - 30), // Чуть приподняли
                    Color.White);
                for (int i = 0; i < posHole.Length; i++)
                    spriteBatch.Draw(hole, posHole[i], Color.White);
                spriteBatch.End();
        
                // Рисуем мячи на переднем плане
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
                view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 260.0f), Vector3.Zero, Vector3.Up);
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
                Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
                Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
                for (int i = 0; i < ball.Length; i++)
                {
                    world = Matrix.CreateTranslation(ball[i].Position);
                    ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
                }
            }
        }
    }
  • Для тестирования разработанного класса AboutScreen введите временно следующие изменения в основной класс игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.AboutScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            #endregion
        
            ..............................................................
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
        
            ..............................................................
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        ..............................................................
        
                        break;
                    case GameState.HelpScreen:
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            ..............................................................
        
        }
  • Запустите приложение и убедитесь, что код класса AboutScreen работает нормально в основном цикле игры. Получится картинка, приведенная ниже, с вращающимися в плоскости экрана относительно его центра мячами
  • Экран с инструкцией о правилах HelpScreen

    Код этой заставки мы спроектируем на основе только что созданного класса AboutScreen, внеся в него вместо загрузки файла about.png загрузку файла help.png. Здесь получится немного нерационально, потому что остальной код класса будет неизменным. Было бы проще добавить в конструктор класса AboutScreen параметр с именем загружаемого файла. Но учитывая, что у нас подобных заставок будет всего две, просто повторим этот код еще раз.

  • Загрузите в папку Textures проекта файл изображений help.png из прилагаемого к работе каталога Source
  • В панели Solution Explorer сделайте копию файла AboutScreen.cs и переименуйте ее в файл HelpScreen.cs
  • Перейдите в режим редактирования файла HelpScreen.cs, вызовите комбинацией клавиш Ctrl-H окно замены редактора кода и замените все вхождения about на help документа. После этого файл должен стать таким
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class HelpScreen
        {
            #region Поля класса
            Texture2D help, title, esc, hole;      // Объекты рисунков 
            Vector2[] posHole = new Vector2[30];    // Массив позиций рисунка пули
            Random rand = new Random();             // Генератор случайных чисел
        
            ModelClass [] ball = new ModelClass[3]; // Объекты мячей 
            float aspectRatio;                      // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4;         // Ракурс камеры 45 градусов
            float nearClip = 1.0f;                  // Ближняя отсекающая плоскость
            float farClip = 1000.0f;                // Дальняя отсекающай плоскость
            float angle = 0;                        // Угол поворота
            Matrix world, view, proj;               // Матрицы преобразований
            #endregion
        
            // Инициализация объектов
            // content - контент экземпляра основного класса игры
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            public void InitializeSplashScreen(ContentManager content, int width, int height)
            {
                // Создаем объекты для мячей 
                for (int i = 0; i < ball.Length; i++)
                    ball[i] = new ModelClass();
        
                // Загружаем в объекты изображения и модель мяча
                help = content.Load<Texture2D>("Textures\\help");
                title = content.Load<Texture2D>("Textures\\title");
                esc = content.Load<Texture2D>("Textures\\esc");
                hole = content.Load<Texture2D>("Textures\\hole");
                ball[0].Load(content, "Models\\Soccerball");
                ball[0].Position = new Vector3(-30, 40, -30);
                ball[1].Load(content, "Models\\SoccerballGreen");
                ball[1].Position = new Vector3(50, 30, -50);
                ball[2].Load(content, "Models\\SoccerballRed");
                ball[2].Position = new Vector3(-50, -30, 40);
        
                // Разбрасываем по экрану пулевые отверстия
                for (int i = 0; i < posHole.Length; i++)
                {
                    posHole[i].X = rand.Next(20, width - 60);
                    posHole[i].Y = rand.Next(100, height - 60);
                }
        
                // Коэффициент искажения проекции
                aspectRatio = (float)width / height;
            }
        
            // Вывод заставки на экран
            // spriteBatch - объект рисования спрайтов
            // graphics - объект графического устройства GDI
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            // gameTime - длительность одного такта игры
            public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
                int width, int height, GameTime gameTime)
            {
                // Очищаем экран своим цветом
                graphics.GraphicsDevice.Clear(Color.DarkGreen); 
        
                // Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(help, new Vector2(width / 2 - help.Width / 2,
                    height / 2 - help.Height / 2), Color.White);
                spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2, 
                    30), // Чуть опустили
                    Color.White);
                spriteBatch.Draw(esc, new Vector2(width / 2 - esc.Width / 2,
                    height - esc.Height - 30), // Чуть приподняли
                    Color.White);
                for (int i = 0; i < posHole.Length; i++)
                    spriteBatch.Draw(hole, posHole[i], Color.White);
                spriteBatch.End();
        
                // Рисуем мячи на переднем плане
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
                view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 260.0f), Vector3.Zero, Vector3.Up);
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
                Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
                Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
                for (int i = 0; i < ball.Length; i++)
                {
                    world = Matrix.CreateTranslation(ball[i].Position);
                    ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
                }
            }
        }
    }
  • Для тестирования разработанного класса HelpScreen введите временно следующие изменения в основной класс игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.HelpScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            #endregion
        
            ........................................................
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
        
            ............................................................
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        .........................................................
        
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            .............................................................
        
        }
  • Запустите приложение и убедитесь, что код класса HelpScreen работает нормально в основном цикле игры. Получится картинка, приведенная ниже, с вращающимися в плоскости экрана относительно его центра мячами
  • Добавление меню

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

    В центре экрана разместим команды:

  • Игра - режим GameScreen
  • Помощь - режим HelpScreen
  • О программе - режим AboutScreen
  • Выход - завершение работы
  • Каждую команду будем представлять текстовой надписью, под которую будем подкладывать желтый прямоугольник, если она имеет фокус ввода. Для этого придется подключить кириллический шрифт требуемого размера и гарнитуры. По краям экрана для украшательства разместим фигуры ' girl ' свободного поведения. И еще уберем отображение пуль.

  • Добавьте из прилагаемого к работе каталога Source в папку Textures проекта файлы menu.jpg, cursorMenu.png, girlLeft.png, girlRight.png
  • Добавьте к папке Content подпапку Fonts
  • Добавьте в папку Fonts файл font.spritefont
  • Откорректируйте файл font.spritefont следующим образом
  • <?xml version="1.0" encoding="utf-8"?>
    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
      <Asset Type="Graphics:FontDescription">
        <FontName>Arial</FontName>
        <Size>24</Size>
        <Spacing>0</Spacing>
        <UseKerning>true</UseKerning>
        <Style>Bold</Style>
        <CharacterRegions>
          <CharacterRegion>
            <Start>#32;</Start>
            <End>#126;</End>
          </CharacterRegion>
          <CharacterRegion>
            <Start>#1040;</Start>
            <End>#1103;</End>
          </CharacterRegion>
        </CharacterRegions>
      </Asset>
    </XnaContent>
  • Скопируйте файл HelpScreen.cs и назовите копию MenuScreen.cs
  • Откорректируйте файл следующим образом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        // Объявление состояний меню
        enum MenuState
        {
            Game,   // Меню установлено на первой позиции
            Help, 
            About,  
            Esc  
        }
        
        class MenuScreen
        {
            #region Поля класса   
            // Объекты рисунков 
            Texture2D menu, title, enter, cursorMenu, girlLeft, girlRight;
            // Объекты мячей
            ModelClass [] ball = new ModelClass[3];    
      
            float aspectRatio;                          // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4;             // Ракурс камеры 45 градусов
            float nearClip = 1.0f;                      // Ближняя отсекающая плоскость
            float farClip = 1000.0f;                    // Дальняя отсекающай плоскость
            float angle = 0;                            // Угол поворота
            Matrix world, view, proj;                   // Матрицы преобразований
            SpriteFont font;                            // Для загрузки шрифта Arial 24 Bold 
            String[] menuCommant = {                    // Команды меню
                                     "Игра",
                                     "Помощь",
                                     "О программе",
                                     "Выход"
                                   };
            #endregion
        
            // Инициализация объектов
            // content - контент экземпляра основного класса игры
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            public void InitializeSplashScreen(ContentManager content, int width, int height)
            {
                // Создаем объекты для мячей 
                for (int i = 0; i < ball.Length; i++)
                    ball[i] = new ModelClass();
        
                // Загружаем в объекты изображения и модели мячей
                menu = content.Load<Texture2D>("Textures\\menu");
                cursorMenu = content.Load<Texture2D>("Textures\\cursorMenu");
                title = content.Load<Texture2D>("Textures\\title");
                enter = content.Load<Texture2D>("Textures\\enter");
                girlLeft = content.Load<Texture2D>("Textures\\girlLeft");
                girlRight = content.Load<Texture2D>("Textures\\girlRight");
                ball[0].Load(content, "Models\\Soccerball");
                ball[0].Position = new Vector3(-30, 40, -30);
                ball[1].Load(content, "Models\\SoccerballGreen");
                ball[1].Position = new Vector3(50, 30, -50);
                ball[2].Load(content, "Models\\SoccerballRed");
                ball[2].Position = new Vector3(-50, -30, 40);
        
                // Коэффициент искажения проекции
                aspectRatio = (float)width / height;
        
                // Загружаем шрифт
                font = content.Load<SpriteFont>("Fonts\\font");
            }
        
            // Вывод заставки на экран
            // spriteBatch - объект рисования спрайтов
            // graphics - объект графического устройства GDI
            // width - текущаяя ширина экрана
            // height - текущая высота экрана
            // gameTime - длительность одного такта игры
            public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
                int width, int height, GameTime gameTime, int menuState)
            {
                // Очищаем экран своим цветом
                graphics.GraphicsDevice.Clear(Color.DarkGreen);
        
                // Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(menu, new Vector2(width / 2 - menu.Width / 2,
                    height / 2 - menu.Height / 2), Color.White);
                spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
                    30), // Чуть опустили
                    Color.White);
                spriteBatch.Draw(enter, new Vector2(width / 2 - enter.Width / 2,
                    height - enter.Height - 30), // Чуть приподняли
                    Color.White);
                // Рисуем girls
                spriteBatch.Draw(girlLeft, new Vector2(
                    0, height / 2 - girlLeft.Height / 2), Color.White);
                spriteBatch.Draw(girlRight, new Vector2(
                    width - girlRight.Width, height / 2 - girlRight.Height / 2), Color.White);
                spriteBatch.End();
        
                // Рисуем мячи
                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
                view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 250.0f), Vector3.Zero, Vector3.Up);
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
                Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
                Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
                for (int i = 0; i < ball.Length; i++)
                {
                    world = Matrix.CreateTranslation(ball[i].Position);
                    ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
                }
        
                // Рисуем надписи меню и фон
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                int posX=width / 2 + width / 7;
                int posY= height / 2;
                int shift = 50;
                spriteBatch.Draw(cursorMenu, 
                    new Vector2(posX - 7, posY + shift * menuState), 
                    Color.White);
                for (int i = 0; i < menuCommant.Length; i++)
                {
                    spriteBatch.DrawString(font,
                        menuCommant[i],
                        new Vector2(posX, posY),
                        Color.Red);
                    posY += shift;
                }
                spriteBatch.End();
            }
        }
    }

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

  • Для тестирования разработанного класса MenuScreen введите временно следующие изменения в основной класс игры
  • public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.MenuScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            MenuScreen menu = new MenuScreen();
            int menuState = (int)MenuState.Game;
            #endregion
        
            ......................................................................
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
        
            ........................................................
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.MenuScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape))
                            this.Exit();
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        ..................................................
        
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        menu.DrawScreen(
                            this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        }
  • Запустите приложение и убедитесь, что код класса MenuScreen работает нормально в основном цикле игры. Должна получиться примерно такая картинка
  • Управление состояниями программы

    В предыдущих разделах мы подготовили графические заставки, включая саму игру, которые нам нужно показать пользователю в зависимости от его действий. При запуске программы мы автоматически покажем титульную заставку, определенную классом SplashScreen. Из этой заставки есть два пути: нажать клавишу Esc, чтобы завершить работу приложения, или нажать клавишу Enter. После нажатия клавиши Enter пользователь должен попасть на заставку меню, поддерживаемую классом MenuScreen. А далее после его выбора программа должна перейти в одно из состояний, определяемых основным классом игры StartGame3D или классами HelpScreen и AboutScreen.

    По клавише Esc программа должна вернуться в состояние меню, откуда при повторном нажатии Esc или выбора пункта ' Выход ' - перейти в первую экранную заставку. В режиме меню для смены пунктов задействуем клавиши Up и Down (Стрелка вверх/Стрелка вниз), а для исполнения выбранной команды - клавишу Enter в соответствии с подсказкой в нижней части заставки меню.

    Такую логику управления состояниями программы мы должны реализовать в этом разделе. Но здесь есть одна проблема, которую сразу нужно решить. Дело в том, что любая клавиша клавиатуры, кроме расширяющих клавиш Ctrl, Shift и Alt, при ее нажатии начинает непрерывно генерировать соответствующий ей код, пока не будет опущена. По этой причине в большинстве офисных программ, когда смена состояний закреплена за какими-то клавишами, ее связывают не с нажатием, а с отпусканием соответствующей клавиши.

    В нашем игровом случае проблема смены состояния связана с тем, что пользователь ожидает ответных действий программы именно от нажатия командной клавиши, которое должно обеспечить одиночную реакцию программы. Для надежного блокирования эффекта генерации применим прием, который напоминает решение, используемое в гениальном создании под названием автомат Калашникова. В нем для гарантированного ведения огня одиночными выстрелами используется специальный запорный крючок - ' шептало одиночного огня '. Это 'шептало' после произведенного выстрела блокирует ударный механизм до тех пор, пока стрелок не отпустит спусковой крючок в исходное состояние. Только после этого можно производить следующий выстрел.

    Примечание. Эх! Вспомнил Армию, в которой честно и с удовольствием служил 41 год тому назад. Прекрасная была пора: молодой, здоровый, холостой, на полном гособеспечении, оденут, обуют, напоят, накормят, обогреют/подогреют, в баньку сводят, спать уложат, сказку расскажут - самому ни о чем думать не надо (кроме как о girls ).

    В нашей программе режим меню использует клавиши Enter, Up, Down и Esc. Поэтому в качестве запорного механизма будем использовать флаги enterFree, upFree, downFree и escFree. В исходном состоянии эти флаги подняты, но стоит пользователю нажать на управляющую клавишу, как срабатывает соответствующий блок кода и флаг опускается, блокируя повторное выполнение этого же кода при многократной генерации клавишей символов. Флаг восстанавливается только после отпускания пользователем активной клавиши.

    Аналогичная проблема существует и с длительностью удержания кнопки мыши в нажатом состоянии. Если сейчас запустить приложение в режиме GameScreen, нажать левую кнопку мыши и, не отпуская ее, направлять курсор на мячи, то мы увидим этот эффект. В качестве запорного механизма для мыши будем использовать флаг mouseFree.

    Пока мы разработали состояния программы, определяемые следующими значениями перечисления GameState:

  • AboutScreen,       // Экран с информацией об игре
  • GameScreen,     // Экран игрового процесса
  • HelpScreen,       // Инструкция о правилах
  • MenuScreen,    // Меню игры
  • SplashScreen  // Первая игровая заставка
  • Для реализации устойчивого механизма управления этими состояниями внесите необходимые изменения в код класса StartGame3D, как показано в следующем его полном, на данный момент, листинге
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        // Объявление состояний экранов
        enum GameState
        {
            AboutScreen,    // Экран с информацией об игре
            GameOverScreen, // Экран проигрыша
            GameScreen,     // Экран игрового процесса
            HelpScreen,     // Инструкция о правилах
            MenuScreen,     // Меню игры
            SplashScreen,   // Первая игровая заставка 
            VictotyScreen   // Экран выигрыша
        }
        
        public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.SplashScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            MenuScreen menu = new MenuScreen();
            int menuState = (int)MenuState.Game;
        
            // Флаги освобождения задействованных клавиш 
            bool enterFree = true;  // Enter
            bool upFree=true;       // Стрелка вверх
            bool downFree = true;   // Стрелка вниз
            bool escFree = true;    // Esc
            // Флаг освобождения мыши
            bool mouseFree = true;
            #endregion
        
            public StartGame3D()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
        
                // Создание экземпляров класса ModelClass   
                //ball = new ModelClass();                  
                for (int i = 0; i < ball.Length; i++)       
                {                                           
                    ball[i] = new ModelClass();             
                }           
        
                // Включаем стандартный курсор мыши
                //this.IsMouseVisible = true;        
            }
        
            protected override void Initialize()
            {
                GraphicsAdapter adapter = graphics.GraphicsDevice.
                    CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
                screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
                screenHeight = adapter.CurrentDisplayMode.Height;   
                graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
                graphics.PreferredBackBufferHeight = screenHeight;  
                graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
                graphics.ApplyChanges();                            // Применить настройки
                aspectRatio = (float)screenWidth / screenHeight;    // Коэффициент искажения проекции
        
                base.Initialize();
            }
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
            }
        
            // Устанавливаем начальную позицию объекта
            void BeginPosition()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        //-rand.Next(20, 150));
                        rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                }                                                           
            }
        
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
        
            void MoveBalls()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    if (ball[i].Position.Y > -32)
                        ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
                }
            }
        
            // Механизм стрельбы по целям 
            BoundingSphere[] bb = new BoundingSphere[3];
            void MouseClick()
            {
                // Получаем текущие координаты курсора и передаем их центру объекта прицела
                mouseState = Mouse.GetState();
                cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
                cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
        
                // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
                for (int i = 0; i < bb.Length; i++)
                {
                    bb[i].Center = ball[i].Position;
                    bb[i].Radius = ball[i].Radius;
                }
        
                // Определяем попадание и при успехе 
                // устанавливаем мяч в начальную позицию
                if (mouseState.LeftButton == ButtonState.Pressed  mouseFree)
                {
                    // Пересчитываем координаты курсора на трехмерный объем
                    Ray pickRay = GetPickRay();
        
                    // Проверяем попадание
                    for (int i = 0; i < ball.Length; i++)
                    {
                        Nullable<float> result = pickRay.Intersects(bb[i]);
                        if (result.HasValue == true)
                        {
                            ball[i].Position = new Vector3(
                                rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                                rand.Next(0, 80),
                                //-rand.Next(20, 150));
                                rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                        }
                    }
                }
        
                // Ждем освобождения кнопки мыши
                mouseFree = mouseState.LeftButton == ButtonState.Released;
            }
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из AboutScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Выход из MenuScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из HelpScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.MenuScreen:
                        // Выход из MenuScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.SplashScreen;
        
                        // Вход в другие состояния
                        if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                        {
                            if ((MenuState)menuState == MenuState.Game)
                                gameState = GameState.GameScreen;
                            else if ((MenuState)menuState == MenuState.Help)
                                gameState = GameState.HelpScreen;
                            else if ((MenuState)menuState == MenuState.About)
                                gameState = GameState.AboutScreen;
                            else if ((MenuState)menuState == MenuState.Esc)
                                gameState = GameState.SplashScreen;
                        }
        
                        // Управление пунктами меню
                        if (keyboardState.IsKeyDown(Keys.Up)  upFree)
                        {
                            menuState--;
                            if (menuState < 0)
                                menuState = 3;
                        }
                        else if (keyboardState.IsKeyDown(Keys.Down)  downFree)
                        {
                            menuState++;
                            menuState %= 4; // Деление по модулю
                        }
                        break;
                    case GameState.SplashScreen:
                        // Выход из SplashScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            this.Exit();
                        else if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                // Устанавливаем флаги освобождения клавиш
                enterFree = keyboardState.IsKeyUp(Keys.Enter);
                upFree = keyboardState.IsKeyUp(Keys.Up);
                downFree = keyboardState.IsKeyUp(Keys.Down);
                escFree = keyboardState.IsKeyUp(Keys.Escape);
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        break;
                    case GameState.GameScreen:
                        // Нарисовать фон самым первым
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        background.DrawSprite(spriteBatch);
                        spriteBatch.End();
        
                        // Включить буфер глубины 
                        graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                        // Установить камеру
                        view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                        // Задать проекционную матрицу
                        proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                            aspectRatio, nearClip, farClip);
                        // Определить матрицу преобразования
                        //world = Matrix.CreateTranslation(ball.Position);
                        // Нарисовать модель в начальном состоянии
                        //ball.DrawModel(world, view, proj);
        
                        // Нарисовать стадион
                        world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                        stadium.DrawModel(world, view, proj);
        
                        // Нарисовать модели мячей
                        for (int i = 0; i < ball.Length; i++)
                        {
                            // Определить матрицу преобразования
                            world = Matrix.CreateTranslation(ball[i].Position);
                            // Нарисовать модель в начальном состоянии
                            ball[i].DrawModel(world, view, proj);
                        }
        
                        // Нарисовать курсор-прицел
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        cursor.DrawSprite(spriteBatch);
                        spriteBatch.End();
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        menu.DrawScreen(
                            this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            // Метод пересчета координат курсора в трехмерную сцену игры
            Ray GetPickRay()
            {
                // Читаем стандартные координаты курсора
                mouseState = Mouse.GetState();
        
                // Вспомогательные переменные
                int mouseX = mouseState.X;
                int mouseY = mouseState.Y;
                Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
                Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
        
                // Обнуляем мировую матрицу
                world = Matrix.CreateTranslation(0, 0, 0);
                // Матрица вида, как в трехмерной сцене
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Матрица проекции, как в трехмерной сцене
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                // Проекция курсора на ближнюю плоскость отсечения объема 
                Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
                // Проекция курсора на дальнюю плоскость отсечения объема
                Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
                Vector3 direction = farPoint - nearPoint;
                direction.Normalize();
        
                return new Ray(nearPoint, direction);
            }
        }
    }
  • Запустите приложение и убедитесь, что механизм смены состояний работает нормально
  • Добавление механизма подсчета очков и уровней сложности

    Опишем схему, по которой мы будем вести подсчет очков и изменять уровни сложности игры. Под уровнями сложности будем понимать последовательные этапы в игре, в которых нарастает скорость падения мячей. Ключом для перехода на новый уровень будет служить количество попаданий для каждого мяча (установим 20 попаданий), набранных на текущем уровне. При этом ни один мяч не должен коснуться поля стадиона, в противном случае все очки текущего уровня сгорают.

    Текущий уровень и количество попаданий для каждого мяча будем отображать в табло, размещенном в левом верхнем углу экрана режима игры. В табло будут нарисованы три мяча, используемых в игре, над которыми расположим счетчики попаданий, а внизу отобразим текущий уровень игры. Если по какому-то мячу выполнено 20 попаданий, то на нем рисуется крестик, но мяч с экрана не исчезает и все равно продолжает участвовать в игре. Когда по всем мячам будет выполнена норма, то очки сбрасываются и увеличивается уровень.

    Всего предусмотрим 8 уровней. При успешном прохождении очередного уровня будем выдавать заставку выигрыша, а в случае падения мяча на поле стадиона - заставку проигрыша. В любом из этих двух режимов при нажатии клавиши Esc пользователь вновь попадает в режим игры. Но при выигрыше на последнем уровне пользователь попадает в меню игры.

  • Добавьте в папку Textures проекта из прилагаемого каталога Source оставшиеся файлы с рисунками:
  • bad.png - проигрыш
  • dagger.png - крестик
  • g1_over.png - girls слева при проигрыше
  • g1_victory.png - girls слева при выигрыше
  • g2_over .png - girls справа при проигрыше
  • g2_victory.png - girls справа при выигрыше
  • good.png - отлично
  • tablo.png - табло
  • Для управления всеми этими ресурсами создадим специальный класс.

  • Командой Project/Add Class добавьте в корневой узел проекта Game3D новый файл с именем ResultScreen.cs и скопируйте в него из файла StartGame3D.cs секцию using
  • Создайте в каталоге Fonts файл font1.spritefont со следующими параметрами
  • <?xml version="1.0" encoding="utf-8"?>
    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
      <Asset Type="Graphics:FontDescription">
        <FontName>Arial</FontName>
        <Size>12</Size>
        <Spacing>0</Spacing>
        <UseKerning>true</UseKerning>
        <Style>Bold</Style>
        <CharacterRegions>
          <CharacterRegion>
            <Start>#32;</Start>
            <End>#126;</End>
          </CharacterRegion>
          <CharacterRegion>
            <Start>#1040;</Start>
            <End>#1103;</End>
          </CharacterRegion>
        </CharacterRegions>
      </Asset>
    </XnaContent>
  • Дополните класс ResultScreen следующим кодом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        class ResultScreen
        {
            // Внешние данные
            ContentManager content;
            SpriteBatch spriteBatch;
            int maxScore = 20; // Флаг для рисования крестика
            SpriteFont font;
        
            // Рисунки
            Texture2D bad, dagger, g1_over, g1_victory, g2_over, g2_victory, good, tablo;
        
            // Свойство доступа
            public int MaxScore
            {
                get { return maxScore; }
                set { maxScore = value; }
            }
        
            public ResultScreen() { }   // Конструктор по умолчанию
            public ResultScreen(ContentManager content, SpriteBatch spriteBatch)
            {
                this.content = content;
                this.spriteBatch = spriteBatch;
            }
        
            public void Load()
            {
                // Загружаем шрифт
                font = content.Load<SpriteFont>("Fonts\\font1");
                // Загружаем рисунки
                bad = content.Load<Texture2D>("Textures\\bad");
                dagger = content.Load<Texture2D>("Textures\\dagger");
                g1_over = content.Load<Texture2D>("Textures\\g1_over");
                g1_victory = content.Load<Texture2D>("Textures\\g1_victory");
                g2_over = content.Load<Texture2D>("Textures\\g2_over");
                g2_victory = content.Load<Texture2D>("Textures\\g2_victory");
                good = content.Load<Texture2D>("Textures\\good");
                tablo = content.Load<Texture2D>("Textures\\tablo");
            }
        
            public void DrawResult(int[] scores, int level)
            {
                int x = 0, y = 0;
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                spriteBatch.Draw(tablo, new Vector2(x, y), Color.White);
                int shift = 33;
                for (int i = 0; i < scores.Length; i++)
                {
                    spriteBatch.DrawString(font, scores[i].ToString(), new Vector2(x + shift, y + 5), Color.White);
                    if (scores[i] >= maxScore)
                        spriteBatch.Draw(dagger, new Vector2(x + shift - 23, y + 37), Color.White);
                    shift += 65;
                }
                spriteBatch.DrawString(font, level.ToString(), new Vector2(x + 142, y + 108), Color.White);
                spriteBatch.End();
            }
        
            public void DrawResult(int x, int y, GameState gameState)
            {
                //x = 300;
                //y = 200;
                switch (gameState)
                {
                    case GameState.VictotyScreen:
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        spriteBatch.Draw(g1_victory, new Vector2(x, y), Color.White);
                        spriteBatch.Draw(good, new Vector2(x + g1_victory.Width + 20, y + 50), Color.White);
                        spriteBatch.Draw(g2_victory, new Vector2(x + g1_victory.Width + good.Width, y), Color.White);
                        spriteBatch.End();
                        break;
                    case GameState.GameOverScreen:
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        spriteBatch.Draw(g1_over, new Vector2(x, y), Color.White);
                        spriteBatch.Draw(bad, new Vector2(x + g1_over.Width - 20, y + 50), Color.White);
                        spriteBatch.Draw(g2_over, new Vector2(x + g2_over.Width + bad.Width, y), Color.White);
                        spriteBatch.End();
                        break;
                }
            }
        }
    }
  • Измените код класса StartGame3D для введения механизма подсчета очков и реализации логики игры
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        // Объявление состояний экранов
        enum GameState
        {
            AboutScreen,    // Экран с информацией об игре
            GameOverScreen, // Экран проигрыша
            GameScreen,     // Экран игрового процесса
            HelpScreen,     // Инструкция о правилах
            MenuScreen,     // Меню игры
            SplashScreen,   // Первая игровая заставка 
            VictotyScreen   // Экран выигрыша
        }
        
        public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.SplashScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            MenuScreen menu = new MenuScreen();
            int menuState = (int)MenuState.Game;
        
            // Флаги освобождения задействованных клавиш 
            bool enterFree = true;  // Enter
            bool upFree=true;       // Стрелка вверх
            bool downFree = true;   // Стрелка вниз
            bool escFree = true;    // Esc
            // Флаг освобождения мыши
            bool mouseFree = true;
        
            // Результаты игры
            ResultScreen resultScreen;
            int[] scores = { 0, 0, 0 };// Порядок индексов: Blue, Green, Red
            int level = 1;
            int maxLevel = 8;
            #endregion
        
            public StartGame3D()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
        
                // Создание экземпляров класса ModelClass   
                //ball = new ModelClass();                  
                for (int i = 0; i < ball.Length; i++)       
                {                                           
                    ball[i] = new ModelClass();             
                }           
        
                // Включаем стандартный курсор мыши
                //this.IsMouseVisible = true;        
            }
        
            protected override void Initialize()
            {
                GraphicsAdapter adapter = graphics.GraphicsDevice.
                    CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
                screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
                screenHeight = adapter.CurrentDisplayMode.Height;   
                graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
                graphics.PreferredBackBufferHeight = screenHeight;  
                graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
                graphics.ApplyChanges();                            // Применить настройки
                aspectRatio = (float)screenWidth / screenHeight;    // Коэффициент искажения проекции
        
                base.Initialize();
            }
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
        
                // Создаем объект с результатами игры
                resultScreen = new ResultScreen(this.Content, spriteBatch);
                resultScreen.Load();
            }
        
            // Устанавливаем начальную позицию объекта
            void BeginPosition()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        //-rand.Next(20, 150));
                        rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                }                                                           
            }
        
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
        
            void MoveBalls()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    if (ball[i].Position.Y > -32)
                        ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
                    else
                    {
                        gameState = GameState.GameOverScreen;// Присуждаем поражение
                        BeginPosition();
                    }
                }
            }
        
            // Механизм стрельбы по целям 
            BoundingSphere[] bb = new BoundingSphere[3];
            void MouseClick()
            {
                // Получаем текущие координаты курсора и передаем их центру объекта прицела
                mouseState = Mouse.GetState();
                cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
                cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
        
                // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
                for (int i = 0; i < bb.Length; i++)
                {
                    bb[i].Center = ball[i].Position;
                    bb[i].Radius = ball[i].Radius;
                }
        
                // Определяем попадание и при успехе 
                // устанавливаем мяч в начальную позицию
                if (mouseState.LeftButton == ButtonState.Pressed  mouseFree)
                {
                    // Пересчитываем координаты курсора на трехмерный объем
                    Ray pickRay = GetPickRay();
        
                    // Проверяем попадание
                    for (int i = 0; i < ball.Length; i++)
                    {
                        Nullable<float> result = pickRay.Intersects(bb[i]);
                        if (result.HasValue == true)
                        {
                            ball[i].Position = new Vector3(
                                rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                                rand.Next(0, 80),
                                //-rand.Next(20, 150));
                                rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                            scores[i] += 1;
                        }
                    }
        
                    // Проверяем победу
                    if (scores[0] >= resultScreen.MaxScore  scores[1] >= resultScreen.MaxScore 
                         scores[2] >= resultScreen.MaxScore)
                    {
                        gameState = GameState.VictotyScreen;
                        BeginPosition();
                    }
                }
        
                // Ждем освобождения кнопки мыши
                mouseFree = mouseState.LeftButton == ButtonState.Released;
            }
        
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из AboutScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.GameOverScreen:
                        // Выход из режима
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                        {
                            // Сброс очков
                            for (int i = 0; i < scores.Length; i++)
                                scores[i] = 0;
                            gameState = GameState.GameScreen;
                        }
                        break;
                    case GameState.GameScreen:
                        // Выход из GameScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из HelpScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.MenuScreen:
                        // Выход из MenuScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.SplashScreen;
        
                        // Вход в другие состояния
                        if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                        {
                            if ((MenuState)menuState == MenuState.Game)
                                gameState = GameState.GameScreen;
                            else if ((MenuState)menuState == MenuState.Help)
                                gameState = GameState.HelpScreen;
                            else if ((MenuState)menuState == MenuState.About)
                                gameState = GameState.AboutScreen;
                            else if ((MenuState)menuState == MenuState.Esc)
                                gameState = GameState.SplashScreen;
                        }
        
                        // Управление пунктами меню
                        if (keyboardState.IsKeyDown(Keys.Up)  upFree)
                        {
                            menuState--;
                            if (menuState < 0)
                                menuState = 3;
                        }
                        else if (keyboardState.IsKeyDown(Keys.Down)  downFree)
                        {
                            menuState++;
                            menuState %= 4; // Деление по модулю
                        }
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            this.Exit();
                        else if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.VictotyScreen:
                        // Выход из режима
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                        {
                            // Сброс очков
                            for (int i = 0; i < scores.Length; i++)
                                scores[i] = 0;
                            if (level < maxLevel)
                            {
                                level++;
                                gameState = GameState.GameScreen;
                            }
                            else
                            {
                                level = 1;
                                gameState = GameState.MenuScreen;
                            }
                        }
                        break;
                }
        
                // Устанавливаем флаги освобождения клавиш
                enterFree = keyboardState.IsKeyUp(Keys.Enter);
                upFree = keyboardState.IsKeyUp(Keys.Up);
                downFree = keyboardState.IsKeyUp(Keys.Down);
                escFree = keyboardState.IsKeyUp(Keys.Escape);
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        GraphicsDevice.Clear(Color.LightSalmon);
                        resultScreen.DrawResult(300, 200, gameState);
                        break;
                    case GameState.GameScreen:
                        // Нарисовать фон самым первым
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        background.DrawSprite(spriteBatch);
                        spriteBatch.End();
        
                        // Включить буфер глубины 
                        graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                        // Установить камеру
                        view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                        // Задать проекционную матрицу
                        proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                            aspectRatio, nearClip, farClip);
        
                        // Нарисовать стадион
                        world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                        stadium.DrawModel(world, view, proj);
        
                        // Рисуем табло до мячей и курсора
                        resultScreen.DrawResult(scores, level);
        
                        // Нарисовать модели мячей
                        for (int i = 0; i < ball.Length; i++)
                        {
                            // Определить матрицу преобразования
                            world = Matrix.CreateTranslation(ball[i].Position);
                            // Нарисовать модель в начальном состоянии
                            ball[i].DrawModel(world, view, proj);
                        }
        
                        // Нарисовать курсор-прицел
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        cursor.DrawSprite(spriteBatch);
                        spriteBatch.End();
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        menu.DrawScreen(
                            this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        GraphicsDevice.Clear(Color.PaleGreen);
                        resultScreen.DrawResult(300, 200, gameState);
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            // Метод пересчета координат курсора в трехмерную сцену игры
            Ray GetPickRay()
            {
                // Читаем стандартные координаты курсора
                mouseState = Mouse.GetState();
        
                // Вспомогательные переменные
                int mouseX = mouseState.X;
                int mouseY = mouseState.Y;
                Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
                Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
        
                // Обнуляем мировую матрицу
                world = Matrix.CreateTranslation(0, 0, 0);
                // Матрица вида, как в трехмерной сцене
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Матрица проекции, как в трехмерной сцене
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                // Проекция курсора на ближнюю плоскость отсечения объема 
                Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
                // Проекция курсора на дальнюю плоскость отсечения объема
                Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
                Vector3 direction = farPoint - nearPoint;
                direction.Normalize();
        
                return new Ray(nearPoint, direction);
            }
        }
    }

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

  • Запустите проект на выполнение и убедитесь в работе введенного механизма смены уровней игры и подсчета очков
  • Ниже приведены уменьшенные экраны для введенных режимов, чтобы можно было сравнить результаты.

    Добавление звукового оформления

    Для тренировки мы добавим в программу звуковые эффекты для следующих случаев:

  • Попадание в мяч - файл shot.wav
  • Смена пункта меню - файл menu_select.wav
  • Выигрыш уровня - файл victory.wav (замкнем в петлю loop)
  • Проигрыш уровня - файл over.wav
  • Через проводник решений добавьте к проекту в папку Content подпапку Sound и скопируйте в нее из прилагаемого каталога Source файлы shot.wav, menu_select.wav, victory.wav, over.wav
  • Добавьте в корневой узел проекта из предыдущей работы Lab58 файл Sound.cs и модифицируйте его следующим образом
  • using System;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
        
    namespace Game3D
    {
        // Перечисление
        enum SoundList
        {
            Shot,       // Попали в мяч
            MenuSelect, // Сменили пункт меню
            Victory,    // Выиграли уровень
            Over        // Проиграли уровень
        }
        
        static class Sound
        {
            static SoundEffect shot, menuSelect, victory, over;
        
            // Для внешнего управления
            public static SoundEffectInstance victoryInstance, overInstance;
        
            public static void Load(ContentManager content)
            {
                shot = content.Load<SoundEffect>("Sound\\shot");
                menuSelect = content.Load<SoundEffect>("Sound\\menu_select");
                victory = content.Load<SoundEffect>("Sound\\victory");
                over = content.Load<SoundEffect>("Sound\\over");
            }
        
            // Воспроизведение звука
            public static void Play(SoundList sound)
            {
                switch (sound)
                {
                    case SoundList.Shot:
                        shot.Play();
                        break;
                    case SoundList.MenuSelect:
                        menuSelect.Play();
                        break;
                    case SoundList.Victory:
                        victoryInstance = victory.Play();
                        break;
                    case SoundList.Over:
                        overInstance = over.Play();
                        break;
                }
            }
        }
    }
  • Включите вызов звука в окончательный вид класса StartGame3D следующим образом
  • 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.Media;
    using Microsoft.Xna.Framework.Net;
    using Microsoft.Xna.Framework.Storage;
        
    namespace Game3D
    {
        // Объявление состояний экранов
        enum GameState
        {
            AboutScreen,    // Экран с информацией об игре
            GameOverScreen, // Экран проигрыша
            GameScreen,     // Экран игрового процесса
            HelpScreen,     // Инструкция о правилах
            MenuScreen,     // Меню игры
            SplashScreen,   // Первая игровая заставка 
            VictotyScreen   // Экран выигрыша
        }
        
        public class StartGame3D : Microsoft.Xna.Framework.Game
        {    
            #region Поля класса 
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            int screenWidth, screenHeight;  // Размеры экрана
            KeyboardState keyboardState;    // Буфер клавиатуры 
            GameState gameState = GameState.SplashScreen;   // Переменная состояния игры
        
            Matrix world;                   // Мировая матрица
            Matrix view;                    // Матрица вида
            Matrix proj;                    // Проекционная матрица
            float aspectRatio;              // Коэффициент искажения проекции
            float FOV = MathHelper.PiOver4; // Ракурс
            float nearClip = 1.0f;          // Ближняя отсекающая плоскость перспективы
            float farClip = 1000.0f;        // Дальняя отсекающая плоскость перспективы
            //Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
            Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
            //ModelClass ball;   
            ModelClass[] ball = new ModelClass[3];  
            MouseState mouseState;                  
            Random rand = new Random();   
        
            // Создание объекта курсора-прицела
            Game2D.Sprite cursor = new Game2D.Sprite();
        
            // Создание объекта модели стадиона
            ModelClass stadium = new ModelClass();
        
            // Создание объекта для рисунка пейзажа
            Game2D.Sprite background = new Game2D.Sprite();
        
            // Создание объектов для других состояний экранов
            SplashScreen splash = new SplashScreen();
            AboutScreen about = new AboutScreen();
            HelpScreen help = new HelpScreen();
            MenuScreen menu = new MenuScreen();
            int menuState = (int)MenuState.Game;
        
            // Флаги освобождения задействованных клавиш 
            bool enterFree = true;  // Enter
            bool upFree=true;       // Стрелка вверх
            bool downFree = true;   // Стрелка вниз
            bool escFree = true;    // Esc
            // Флаг освобождения мыши
            bool mouseFree = true;
        
            // Результаты игры
            ResultScreen resultScreen;
            int[] scores = { 0, 0, 0 };// Порядок индексов: Blue, Green, Red
            int level = 1;
            int maxLevel = 8;
            #endregion
        
            public StartGame3D()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
        
                // Создание экземпляров класса ModelClass   
                //ball = new ModelClass();                  
                for (int i = 0; i < ball.Length; i++)       
                {                                           
                    ball[i] = new ModelClass();             
                }           
        
                // Включаем стандартный курсор мыши
                //this.IsMouseVisible = true;        
            }
        
            protected override void Initialize()
            {
                GraphicsAdapter adapter = graphics.GraphicsDevice.
                    CreationParameters.Adapter;                     // Получить ссылку на параметры адаптера
                screenWidth = adapter.CurrentDisplayMode.Width;     // Сохранить текущие размеры экрана
                screenHeight = adapter.CurrentDisplayMode.Height;   
                graphics.PreferredBackBufferWidth = screenWidth;    // Настроить размеры экранного буфера
                graphics.PreferredBackBufferHeight = screenHeight;  
                graphics.IsFullScreen = true;                       // Перевести в полноэкранный режим
                graphics.ApplyChanges();                            // Применить настройки
                aspectRatio = (float)screenWidth / screenHeight;    // Коэффициент искажения проекции
        
                base.Initialize();
            }
        
            protected override void LoadContent()
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
        
                //ball.Load(this.Content, "Models\\Soccerball");            
                // Загружаем модели мячей
                ball[0].Load(this.Content, "Models\\Soccerball");           
                ball[1].Load(this.Content, "Models\\SoccerballGreen");      
                ball[2].Load(this.Content, "Models\\SoccerballRed");   
        
                // Загружаем трехмерную модель стадиона
                stadium.Load(this.Content, "Models\\stadium 1");
        
                // Устанавливаем начальную позицию объектов 
                BeginPosition();    
        
                // Загружаем рисунок курсора-прицела 
                cursor.Load(this.Content, "Textures\\cursor");
        
                // Загружаем рисунок заднего фона
                background.Load(this.Content, "Textures\\hallake001");
                // Чуть приподнимем вверх экрана 
                background.spritePosition = new Vector2(0, -50);
        
                // Инициализация объектов других состояний экранов
                splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
                menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
        
                // Создаем объект с результатами игры
                resultScreen = new ResultScreen(this.Content, spriteBatch);
                resultScreen.Load();
        
                // Загружаем звук
                Sound.Load(this.Content);
            }
        
            // Устанавливаем начальную позицию объекта
            void BeginPosition()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    ball[i].Position = new Vector3(
                        rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                        rand.Next(0, 80),
                        //-rand.Next(20, 150));
                        rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                }                                                           
            }
        
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
            }
        
            void MoveBalls()
            {
                for (int i = 0; i < ball.Length; i++)
                {
                    if (ball[i].Position.Y > -32)
                        ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
                    else
                    {
                        gameState = GameState.GameOverScreen;// Присуждаем поражение
                        BeginPosition();
                    }
                }
            }
        
            // Механизм стрельбы по целям 
            BoundingSphere[] bb = new BoundingSphere[3];
            void MouseClick()
            {
                // Получаем текущие координаты курсора и передаем их центру объекта прицела
                mouseState = Mouse.GetState();
                cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
                cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
        
                // Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
                for (int i = 0; i < bb.Length; i++)
                {
                    bb[i].Center = ball[i].Position;
                    bb[i].Radius = ball[i].Radius;
                }
        
                // Определяем попадание и при успехе 
                // устанавливаем мяч в начальную позицию
                if (mouseState.LeftButton == ButtonState.Pressed  mouseFree)
                {
                    // Пересчитываем координаты курсора на трехмерный объем
                    Ray pickRay = GetPickRay();
        
                    // Проверяем попадание
                    for (int i = 0; i < ball.Length; i++)
                    {
                        Nullable<float> result = pickRay.Intersects(bb[i]);
                        if (result.HasValue == true)
                        {
                            ball[i].Position = new Vector3(
                                rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
                                rand.Next(0, 80),
                                //-rand.Next(20, 150));
                                rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
                            scores[i] += 1;
                            Sound.Play(SoundList.Shot); // Попадания по мячу
                        }
                    }
        
                    // Проверяем победу
                    if (scores[0] >= resultScreen.MaxScore  scores[1] >= resultScreen.MaxScore 
                         scores[2] >= resultScreen.MaxScore)
                    {
                        gameState = GameState.VictotyScreen;
                        BeginPosition();
                    }
                }
        
                // Ждем освобождения кнопки мыши
                mouseFree = mouseState.LeftButton == ButtonState.Released;
            }
        
            bool soundFree = true;  // Предохранитель от повторного выполнения звука
            protected override void Update(GameTime gameTime)
            {
                // Читать буфер клавиатуры 
                keyboardState = Keyboard.GetState();
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        // Выход из AboutScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.GameOverScreen:
                        if (soundFree)
                        {
                            Sound.Play(SoundList.Over);
                            soundFree = false;
                        }
                        // Выход из режима
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                        {
                            // Сброс очков
                            for (int i = 0; i < scores.Length; i++)
                                scores[i] = 0;
                            gameState = GameState.GameScreen;
        
                            soundFree = true;
                            Sound.overInstance.Stop();// Остановить немедленно
                        }
                        break;
                    case GameState.GameScreen:
                        // Выход из GameScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
        
                        MoveBalls();
                        MouseClick();
                        break;
                    case GameState.HelpScreen:
                        // Выход из HelpScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.MenuScreen:
                        // Выход из MenuScreen
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            gameState = GameState.SplashScreen;
        
                        // Вход в другие состояния
                        if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                        {
                            if ((MenuState)menuState == MenuState.Game)
                                gameState = GameState.GameScreen;
                            else if ((MenuState)menuState == MenuState.Help)
                                gameState = GameState.HelpScreen;
                            else if ((MenuState)menuState == MenuState.About)
                                gameState = GameState.AboutScreen;
                            else if ((MenuState)menuState == MenuState.Esc)
                                gameState = GameState.SplashScreen;
                        }
        
                        // Управление пунктами меню
                        if (keyboardState.IsKeyDown(Keys.Up)  upFree)
                        {
                            menuState--;
                            if (menuState < 0)
                                menuState = 3;
                            
                            Sound.Play(SoundList.MenuSelect);// Звук
                        }
                        else if (keyboardState.IsKeyDown(Keys.Down)  downFree)
                        {
                            menuState++;
                            menuState %= 4; // Деление по модулю
                            
                            Sound.Play(SoundList.MenuSelect);// Звук
                        }
                        break;
                    case GameState.SplashScreen:
                        // Выход из игрового процесса
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                            this.Exit();
                        else if (keyboardState.IsKeyDown(Keys.Enter)  enterFree)
                            gameState = GameState.MenuScreen;
                        break;
                    case GameState.VictotyScreen:
                        if (soundFree || Sound.victoryInstance.State == SoundState.Stopped)
                        {
                            // Повторять по кругу
                            Sound.Play(SoundList.Victory);
                            soundFree = false;
                        }
                        // Выход из режима
                        if (keyboardState.IsKeyDown(Keys.Escape)  escFree)
                        {
                            // Сброс очков
                            for (int i = 0; i < scores.Length; i++)
                                scores[i] = 0;
                            if (level < maxLevel)
                            {
                                level++;
                                gameState = GameState.GameScreen;
                            }
                            else
                            {
                                level = 1;
                                gameState = GameState.MenuScreen;
                            }
        
                            soundFree = true;
                            Sound.victoryInstance.Stop();// Остановить немедленно
                        }
                        break;
                }
        
                // Устанавливаем флаги освобождения клавиш
                enterFree = keyboardState.IsKeyUp(Keys.Enter);
                upFree = keyboardState.IsKeyUp(Keys.Up);
                downFree = keyboardState.IsKeyUp(Keys.Down);
                escFree = keyboardState.IsKeyUp(Keys.Escape);
        
                base.Update(gameTime);
            }
        
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
        
                switch (gameState)
                {
                    case GameState.AboutScreen:
                        about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.GameOverScreen:
                        GraphicsDevice.Clear(Color.LightSalmon);
                        resultScreen.DrawResult(300, 200, gameState);
                        break;
                    case GameState.GameScreen:
                        // Нарисовать фон самым первым
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        background.DrawSprite(spriteBatch);
                        spriteBatch.End();
        
                        // Включить буфер глубины 
                        graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
                        // Установить камеру
                        view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                        // Задать проекционную матрицу
                        proj = Matrix.CreatePerspectiveFieldOfView(FOV, 
                            aspectRatio, nearClip, farClip);
        
                        // Нарисовать стадион
                        world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
                        stadium.DrawModel(world, view, proj);
        
                        // Рисуем табло до мячей и курсора
                        resultScreen.DrawResult(scores, level);
        
                        // Нарисовать модели мячей
                        for (int i = 0; i < ball.Length; i++)
                        {
                            // Определить матрицу преобразования
                            world = Matrix.CreateTranslation(ball[i].Position);
                            // Нарисовать модель в начальном состоянии
                            ball[i].DrawModel(world, view, proj);
                        }
        
                        // Нарисовать курсор-прицел
                        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                        cursor.DrawSprite(spriteBatch);
                        spriteBatch.End();
                        break;
                    case GameState.HelpScreen:
                        help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.MenuScreen:
                        menu.DrawScreen(
                            this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
                        break;
                    case GameState.SplashScreen:
                        splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
                        break;
                    case GameState.VictotyScreen:
                        GraphicsDevice.Clear(Color.PaleGreen);
                        resultScreen.DrawResult(300, 200, gameState);
                        break;
                }
        
                base.Draw(gameTime);
            }
        
            // Метод пересчета координат курсора в трехмерную сцену игры
            Ray GetPickRay()
            {
                // Читаем стандартные координаты курсора
                mouseState = Mouse.GetState();
        
                // Вспомогательные переменные
                int mouseX = mouseState.X;
                int mouseY = mouseState.Y;
                Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
                Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
        
                // Обнуляем мировую матрицу
                world = Matrix.CreateTranslation(0, 0, 0);
                // Матрица вида, как в трехмерной сцене
                view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
                // Матрица проекции, как в трехмерной сцене
                proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
                // Проекция курсора на ближнюю плоскость отсечения объема 
                Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
                // Проекция курсора на дальнюю плоскость отсечения объема
                Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
                Vector3 direction = farPoint - nearPoint;
                direction.Normalize();
        
                return new Ray(nearPoint, direction);
            }
        }
    }
  • Запустите приложение и убедитесь в правильной работе звуковых эффектов
  • Откомпилируйте проект в режиме Release и сформируйте установочный пакет готового приложения, как это было показано в предыдущей работе Lab58
  • Сформируйте установочный диск со всеми необходимыми библиотеками для распространения программы, в том числе для предоставления преподавателю
  • Запуск внешних процессов

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

  • В конструкторе класса StartGame3D() зарегистрируйте обработчик для события Exiting следующим образом
  • public StartGame3D()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
        
                // Создание экземпляров класса ModelClass   
                //ball = new ModelClass();                  
                for (int i = 0; i < ball.Length; i++)       
                {                                           
                    ball[i] = new ModelClass();             
                }           
        
                // Включаем стандартный курсор мыши
                //this.IsMouseVisible = true;     
        
                // Регистрируем обработчик события Exiting
                this.Exiting += new EventHandler(StartGame3D_Exiting);
            }
        
            void StartGame3D_Exiting(object sender, EventArgs e)
            {
                // Внимание!!! Для тренировки...
                // При завершении приложения выключим компьютер
                System.Diagnostics.Process exe = new System.Diagnostics.Process();
                exe.StartInfo.FileName = @"cmd.exe"; //Имя файла для запуска
                exe.StartInfo.Arguments = "/c shutdown -s"; //Аргументы
                exe.Start();
            }
  • Откомпилируйте проект командой Build/Rebuild Solution и закройте все программы, включая Visual Studio
  • Запустите проводник Windows Explorer, зайдите в каталог проекта bin\x86\Debug и запустите файл Game3D приложения
  • После выхода из приложения компьютер выключится!!!

  • Запустите компьютер, откройте проект и удалите только что введенный код, показанный красным цветом
  • На этом выполнение работы закончено, можете 5 минут отдохнуть.

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