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

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

Добавление меню

Для формирования заставки с элементами меню создадим игровой класс 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 работает нормально в основном цикле игры. Должна получиться примерно такая картинка

Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №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" или один из зависимых от них компонентов. Не удается найти указанный файл.

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

Иван Циферблат
Иван Циферблат
Россия, Таганрог, 36, 2000