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

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

Аннотация: В данной лабораторной работе мы познакомимся с программированием 3D-приложений на примере простой игры " Футбольный стрелок ". Материал основан на источнике Горнаков С.Г. Разработка компьютерных игр под Windows в XNA Game Studio Express. - М.: ДМК Пресс, 2007, - 384 с.

Введение

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

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

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


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

При выполнении в лабораторной работе упражнения №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