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

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

Управление состояниями программы

В предыдущих разделах мы подготовили графические заставки, включая саму игру, которые нам нужно показать пользователю в зависимости от его действий. При запуске программы мы автоматически покажем титульную заставку, определенную классом 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);
        }
    }
}
  • Запустите приложение и убедитесь, что механизм смены состояний работает нормально
Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Dmitriy Ivanchenko
Dmitriy Ivanchenko
Украина, Кировоград, Виктория-П, 2011
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974