Опубликован: 05.08.2010 | Доступ: свободный | Студентов: 2009 / 47 | Оценка: 4.50 / 4.40 | Длительность: 60:26:00
Самостоятельная работа 2:

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

Расширение кода до трех разных моделей мячей и обеспечение их движения

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

  • Добавьте в папку 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));
            }
        }
    }
}
  • Запустите приложение и проверьте механизм стрельбы по целям
Алексей Бабушкин
Алексей Бабушкин

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

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