вторник, 6 марта 2012 г.

Практическое применение XNA. Часть 9.

Камера  (XNA Camera)


Вы не могли не заметить, что все примеры рассмотренные ранее - неподвижны, то есть в отличии от привычных нам компьютерных игр мы пока не могли перемещаться среди созданых нами обьектов. Теперь пришло время это исправить. В современных играх роль головы игрока или наблюдателя играет так называемая камера - аналог кино или видео камеры. Она должна стать нашими "глазами" в игре. Вообще в компьютерных играх существует несколько видов камер. Зависит это от жанра игры и в зависимости от этого камера наделяется определенными свойствами. Например во всех стрелялках от первого лица должна использоваться камера от первого лица имитируя поведение человека от лица которого вы и играете. Такая камера должна уметь поворачиваться влево вправо вверх вниз, прыгать приседать и ползать. Если же вы решили написать космический симулятор, то ваша камера должна уметь летать так как требует обьект который собственно и будет играть роль летательного аппарата.

     Вы уже должны знать, что за отображение обьектов на экране монитора главным образом отвечают две матрицы. Матрица вида и проекции. А если говорить точнее, то определив однажды матрицу проекции вам останется управлять лишь матрицой вида, если вы не ставите себе цель изменять настройки проекции. Напомню, что матрица проекции может управлять такими эффектами как Zoom (приближение или удаление обьектов), или изменять угол обзора сцены. Итак вырисовуется следующая картина. Для создания собственного приложения с использованием камеры, нам надо отобразить модель любым известным нам способом и написать некоторый код который позволит нам управлять матрицей вида. Для этого создадим новый проект под названием myCamera. И игровой компонент с названием Camera. Он позволит нам отделить код камеры от общего кода и даст нам возможность многократного его использования в других приложениях.

using System;
using System.Collections.Generic;
using System.Linq;
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 myCamera
{
  public class Game1 : Microsoft.Xna.Framework.Game
  {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    Model _model;
    Camera _camera;
    public Game1()
    {
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Content";
    }
    protected override void Initialize()
    {
      _camera = new Camera(this);
      Components.Add(_camera);
      base.Initialize();
    }
    protected override void LoadContent()
    {    
      spriteBatch = new SpriteBatch(GraphicsDevice);
      _model = Content.Load<Model>("torknot");
    }
    protected override void UnloadContent()
    {
     
    }
    protected override void Update(GameTime gameTime)
    {
      if (Keyboard.GetState().IsKeyDown(Keys.Escape))
      {
        this.Exit();
      }
      base.Update(gameTime);
    }
    protected override void Draw(GameTime gameTime)
    {
      GraphicsDevice.Clear(Color.CornflowerBlue);
      foreach (ModelMesh mesh in _model.Meshes)
      {
        foreach (BasicEffect effect in mesh.Effects)
        {
          effect.World = Matrix.CreateTranslation(new Vector3(0, 0, 0));
          effect.View = _camera.viewMatrix;
          effect.Projection = _camera.projectionMatrix;
          effect.EnableDefaultLighting();
        }
        mesh.Draw();
      }
      base.Draw(gameTime);
    }
  }
}


Этот код вам должен быть понятен. Здесь мы лишь вывели модель на экран, но в оличии от предыдущих примеров матрицы вида и проекции взяты из класса камеры. Вот о нем дальше реч и пойдет. Для начала мы обьявили переменные обеих матриц.

public Matrix viewMatrix;
public Matrix projectionMatrix;

Они, как вы могли догадаться, будут хранить вид и проекцию.

float _Pitch;
float _Yaw;

Будут хранить угол поворота камеры вверх вниз и влево вправо

 float distancePerFrame = 10;

Расстояние которое будет проходить наша камера за один кадр. Меняя это значение мы сможем управлять её скоростью перемещения.

Vector2 CenterScreen;

Хранит позицию середины экрана

private void CenteredCursor()
{
  Mouse.SetPosition((int)CenterScreen.X, (int)CenterScreen.Y);
}

Кроме предусмотренных игровым компонентом методов мы создадим еще один вспомогательный. Он будет возвращать позицию мыши на середину экрана

public Camera(Game game) : base(game)
{
 CenterScreen =
    new Vector2(game.GraphicsDevice.Viewport.Width / 2,
                game.GraphicsDevice.Viewport.Height / 2);
 viewMatrix = Matrix.CreateLookAt(new Vector3(5, 5, 500),
                                       new Vector3(0, 0, 0),
                                       new Vector3(0, 1, 0));
 projectionMatrix =         Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),
                       game.GraphicsDevice.Viewport.Width /
                       game.GraphicsDevice.Viewport.Height,
                                            0.1f, 1000.0f);
 CenteredCursor();
}


В конструкторе класса мы определии центр игрового экрана, создали матрицы вида и проекции и поставили указатель мыши на середину экрана. Далее в методе Update()

Vector2 cursorPosition = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
Vector2 deltaDampened = (cursorPosition - CenterScreen) * 0.0015f;

мы находим текущее положение указателя мыши, оно могло измениться с момента определения. Вычитаем это значение из значения центра экрана и умножаем результат на 0.0015 для того что бы подобрать подходящую нам скорость поворотов камеры. Движение мышью в этом примере будет управлять вращением камеры соответственно. То есть если перемещать мышь вправо, то камера будет поворачиваться тоже вправо. Аналогично влево, вверх, вниз, по диагоналям да и вообще в любом направлении.

_Yaw -= deltaDampened.X;
_Pitch -= deltaDampened.Y;

переменные _Yaw и _Pitch получают значения в зависимости от манипуляций мышью.

if (_Pitch < -1.55f)
{
  _Pitch = -1.55f;
}
if (_Pitch > 1.55f)
{
  _Pitch = 1.55f;
}

Здесь мы установили пределы поворотов камеры по вертикали, чтобы она поднималась и опускалась подобно человеческой голове.

 Matrix cameraRotation = Matrix.CreateFromYawPitchRoll(_Yaw,_Pitch,0.0f);
Vector3 translateDirection = Vector3.Zero;

Здесь мы создали матрицу вращения и вектор перемещения камеры, заметим что при создании cameraRotation мы воспользовались _Yaw и _Pitch переменными.

KeyboardState states = Keyboard.GetState();
if (states.IsKeyDown(Keys.W))
    translateDirection +=
    Vector3.TransformNormal(Vector3.Forward, cameraRotation);
if (states.IsKeyDown(Keys.S))
    translateDirection +=
    Vector3.TransformNormal(Vector3.Backward, cameraRotation);
if (states.IsKeyDown(Keys.A))
    translateDirection +=
    Vector3.TransformNormal(Vector3.Left, cameraRotation);
if (states.IsKeyDown(Keys.D))
    translateDirection +=
    Vector3.TransformNormal(Vector3.Right, cameraRotation);
if (states.IsKeyDown(Keys.Q))
    translateDirection +=
    Vector3.TransformNormal(Vector3.Up, cameraRotation);
if (states.IsKeyDown(Keys.E))
    translateDirection +=
    Vector3.TransformNormal(Vector3.Down, cameraRotation);

Здесь мы определили клавиши на клавиатуре которые будут отвечать за перемещение камеры: W - движение вперед, S - движение назад, A - движение влево, D - движение вправо, Q - движение вверх, E - движение вниз. Повороты сюда не входят, за них отвечает позиция мыши.

Vector3 newPosition = Matrix.Invert(viewMatrix).Translation;
if (translateDirection != Vector3.Zero)
{
  newPosition += Vector3.Normalize(translateDirection) *     distancePerFrame;
}
Vector3 newForward = Vector3.TransformNormal(Vector3.Forward, cameraRotation);
viewMatrix = Matrix.CreateLookAt(newPosition, newPosition + newForward, Vector3.Up);

Здесь мы инвертируем текущую позицию камеры,  проверяем изменилось ли направление движения камеры, вычисляем новое направление "взгляда" камеры и на основании этих данных, создаётся новая матрица вида.

CenteredCursor();

И напоследок вызываем метод возврата курсора на середину экрана. Этим завершается метод Update(). Если теперь запустить приложение, то вы увидите на экране модель и сможете "полетать" вокруг нее с помощью нашей камеры.

На этом мы заканчиваем главу посвященную камере. Осталось добавить, что получившаяся камера носит название "летающая камера", потому что она может перемещаться во всех направлениях имитируя тем самым полет. Немного поразмыслив вы догадаетесь какие парамерры камеры можно ограничичть, что бы "летающая камера" превратилась в камеру от первого лица. 

Весь код класса камера:

using System;
using System.Collections.Generic;
using System.Linq;
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 myCamera
{
  public class Camera : Microsoft.Xna.Framework.GameComponent
  {
    public Matrix viewMatrix;
    public Matrix projectionMatrix;
    Vector2 CenterScreen;
    float _Pitch;
    float _Yaw;
    float distancePerFrame = 10;
    public Camera(Game game) : base(game)
    {
      CenterScreen = new Vector2(game.GraphicsDevice.Viewport.Width / 2,
                                 game.GraphicsDevice.Viewport.Height / 2);
      viewMatrix = Matrix.CreateLookAt(new Vector3(5, 5, 500),
                                       new Vector3(0, 0, 0),
                                       new Vector3(0, 1, 0));
      projectionMatrix =
      Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),
                                          game.GraphicsDevice.Viewport.Width /
                                          game.GraphicsDevice.Viewport.Height,
                                          0.1f, 1000.0f);
      CenteredCursor();
    }
    public override void Update(GameTime gameTime)
    {
        Vector2 cursorPosition = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
        Vector2 deltaDampened = (cursorPosition - CenterScreen) * 0.0015f;
        _Yaw -= deltaDampened.X;
        _Pitch -= deltaDampened.Y;
        if (_Pitch < -1.55f)
        {
          _Pitch = -1.55f;
        }
        if (_Pitch > 1.55f)
        {
          _Pitch = 1.55f;
        }

        Matrix cameraRotation = Matrix.CreateFromYawPitchRoll(_Yaw, _Pitch, 0.0f);
        Vector3 translateDirection = Vector3.Zero;
        KeyboardState states = Keyboard.GetState();
       
        if (states.IsKeyDown(Keys.W))
          translateDirection +=
            Vector3.TransformNormal(Vector3.Forward, cameraRotation);
       
        if (states.IsKeyDown(Keys.S))
          translateDirection +=
            Vector3.TransformNormal(Vector3.Backward, cameraRotation);
       
        if (states.IsKeyDown(Keys.A))
          translateDirection +=
            Vector3.TransformNormal(Vector3.Left, cameraRotation);
       
        if (states.IsKeyDown(Keys.D))
          translateDirection +=
            Vector3.TransformNormal(Vector3.Right, cameraRotation);
      
        if (states.IsKeyDown(Keys.Q))
          translateDirection +=
            Vector3.TransformNormal(Vector3.Up, cameraRotation);
      
        if (states.IsKeyDown(Keys.E))
          translateDirection +=
            Vector3.TransformNormal(Vector3.Down, cameraRotation);
      
        Vector3 newPosition = Matrix.Invert(viewMatrix).Translation;
        if (translateDirection != Vector3.Zero)
        {
          newPosition += Vector3.Normalize(translateDirection) * distancePerFrame;
        }
        Vector3 newForward = Vector3.TransformNormal(Vector3.Forward, cameraRotation);
        viewMatrix = Matrix.CreateLookAt(newPosition, newPosition + newForward, Vector3.Up);
        CenteredCursor();
      base.Update(gameTime);
    }

    private void CenteredCursor()
    {
      Mouse.SetPosition((int)CenterScreen.X, (int)CenterScreen.Y);
    }
  }
}

Результат полета камеры.


Удачи!





Комментариев нет:

Отправить комментарий

Physically Based Rendering