При выполнении в лабораторной работе упражнения №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" или один из зависимых от них компонентов. Не удается найти указанный файл. Делаю все пунктуально. В чем может быть проблема? |
Компьютерная графика 3D в XNA
Введение
Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Сценарий игры состоит в том, что сверху вниз экрана падают мячи, а задача игрока не допустить, чтобы хотя бы один из них упал на площадь футбольного поля. С помощью выстрелов, которые должны попасть в мячик, игрок должен удерживать мячи на лету определенное время. Удачный игрок переходит на новый уровень.
В трехмерной графике используется мировая система координат, ориентированная так, что плоскость xOy направлена параллельно экрану монитора, а ось Oz направлена либо к пользователю перпендикулярно экрану (правая система координат), либо от пользователя вглубь экрана (левая система координат). Если вытянуть в разные стороны три пальца правой руки (большой, указательный и средний) под прямыми углами, то большой будет указывать направление оси x, указательный - оси y, а средний - оси z. В DirectX используется левая система координат, а в OpenGL и XNA Framework - правая.
Поверхность любого объемного тела описывается с помощью примитивов - простых плоских геометрических фигур, чаще всего треугольников. Каждый треугольник задан координатами своих вершин в мировой системе координат. Относительно этой системы задаются координаты вершин всех примитивов каждого объекта ( модели ). Совокупность трехмерных объектов называется сценой. Над трехмерными моделями могут выполняться следующие операции, которые называются модельными преобразованиями:
- Поворот или ротация (на любой угол относительно любой произвольной оси, чаще складывается из последовательности поворотов относительно осей системы координат)
- Сдвиг или трансляция (параллельное перемещение в пространстве)
- Масштабирование (увеличение или уменьшение)
Чтобы выполнить одно из указанных действий для объекта в целом, нужно применить его к вершине каждого примитива, описывающего модель. Для выполнения таких действий служит мировая матрица преобразования. Это матрица размером 4x4, которая может принимать одно из следующих значений, в зависимости от выполняемых действий:
Начальная расстановка моделей на сцене определяется начальными координатами примитивов модели, последовательно умноженными на одну или несколько матриц модельного преобразования. Применение одной и той же матрицы преобразования ко всем моделям меняет всю сцену вместе с предыдущими координатами вершин примитивов.
Кроме перечисленных модельных матриц имееются еще две матрицы:
- Матрица вида
- Проекционная матрица
Матрица вида содержит в себе координаты размещения камеры в трехмерном пространстве. У камеры есть вертикальная ось, совпадающая с координатой 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 и запустите его на выполнение. Должен получиться рисунок с начальным положением мяча