Свободная камера
В рамках этой статьи мы создадим простейшую камеру, которая позже также немного сэкономит нам времени.
Мы будем создавать «свободную камеру», то есть такую камеру, которая не привязана к какому-либо объекту, может свободно перемещать в трехмерном пространстве, а также поворачиваться на произвольный угол вокруг каждой координатной оси.
Камеру мы будет реализовывать в виде игрового компонента, но посколько нам не нужно никаким образом рисовать камеру, мы будем наследовать ее от GameComponent.
Основными параметрами, которые нам нужно будет получать от камеры, являются позиция камеры (она необходима для некоторых моделей освещения) и матрица вида, которую мы будем использовать везде в наших дальнейших приложениях.
Можно также было переместить матрицу проекции в класс камеры, но я не буду так делать, поскольку считаю это не совсем логичным (хотя я могу ошибаться).
Класс камеры будет выглядеть следующим образом:
class
FreeCamera : GameComponent
{
public
Matrix View;
public
Vector3 Position;
Vector3 angle;
float speed = 1;
float turnSpeed = 1;
public FreeCamera(Game game, Vector3 position, Vector3 lookAt) : base(game)
{
this.Position = position;
View = Matrix.CreateLookAt(position, lookAt, Vector3.Up);
}
public
override
void Update(GameTime gameTime)
{
int centerX = Game.GraphicsDevice.Viewport.Width / 2;
int centerY = Game.GraphicsDevice.Viewport.Height / 2;
MouseState mouse = Mouse.GetState();
Mouse.SetPosition(centerX, centerY);
float seconds = (float)(gameTime.ElapsedGameTime.TotalSeconds);
float yaw = MathHelper.ToRadians((mouse.X — centerX) * turnSpeed * seconds);
float pitch = MathHelper.ToRadians((mouse.Y — centerY) * turnSpeed * seconds);
angle = new
Vector3(angle.X + pitch, angle.Y + yaw, angle.Z);
Vector3 forward = —Vector3.Normalize(new
Vector3(
(float)Math.Sin(-angle.Y) * (float)Math.Cos(angle.X),
(float)Math.Sin(angle.X),
(float)Math.Cos(-angle.Y) * (float)Math.Cos(angle.X))
);
Vector3 left = —Vector3.Normalize(new
Vector3(
(float)Math.Cos(angle.Y),
0f,
(float)Math.Sin(angle.Y))
);
KeyboardState state = Keyboard.GetState();
if (state.IsKeyDown(Keys.Up))
Position += forward * speed * seconds;
if (state.IsKeyDown(Keys.Down))
Position -= forward * speed * seconds;
if (state.IsKeyDown(Keys.Left))
Position += left * speed * seconds;
if (state.IsKeyDown(Keys.Right))
Position -= left * speed * seconds;
View = Matrix.CreateTranslation(-Position) *
Matrix.CreateRotationZ(angle.Z) *
Matrix.CreateRotationY(angle.Y) *
Matrix.CreateRotationX(angle.X);
base.Update(gameTime);
}
}
Подробно реализация такого класса описана в моем курсе «XNA для начинающих», здесь отметим лишь некоторые основные моменты:
Задаются две переменные, отвечающие за скорость перемещения и поворота камеры – speed и turnSpeed соответственно.
Сначала определяется смещение угла поворота камеры в зависимости от смещения указывателя мыши от центра экрана (после этого курсор снова перемещается в центр экрана) и определяется новый угол поворота относительно всех координатных осей. По этим углам определяются векторы Вперед и Влево для текущего направления камеры. В зависимости от нажатия клавищ на клавиатуре происходит смещение позиции камеры в направлении векторов Вперед и Влево.
Зачет вычисляется матрица вида.
Теперь можно подключить новый компонент в игре и использовать матрицу вида, которая содержится в камере для рендеринга сцены.
Сложно будет проверять нашу камеру, когда в цвене присутствует всего одна модель, напишем простой код, который будет рисовать множество моделей в различных местах трехмерного мира.
FreeCamera camera;
protected
override
void Initialize()
{
// TODO: Add your initialization logic here
Components.Add(new
FPSCounter(this));
light = new
Light(this);
Components.Add(light);
camera = new
FreeCamera(this, new
Vector3(0, 0, 3), Vector3.Zero);
Components.Add(camera);
basicEffect = new
BasicEffect(GraphicsDevice, null);
teapot = new
TeapotPrimitive(GraphicsDevice);
base.Initialize();
}
protected
override
void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
Matrix view = camera.View;
Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);
// TODO: Add your drawing code here
basicEffect.World = Matrix.Identity;
basicEffect.View = view;
basicEffect.Projection = proj;
basicEffect.AmbientLightColor = new
Vector3(0.1f, 0.1f, 0.1f);
basicEffect.DirectionalLight0.Direction = Vector3.Zero-light.Position;
basicEffect.DirectionalLight0.DiffuseColor = new
Vector3(0.6f, 0, 0.4f);
basicEffect.LightingEnabled = true;
basicEffect.DirectionalLight0.Enabled = true;
for (int i = -2; i < 3; i++)
{
for (int j = -2; j < 3; j++)
{
for (int k = -2; k < 3; k++)
{
Vector3 pos = new
Vector3(i,j,k) * 2;
basicEffect.World = Matrix.CreateTranslation(pos);
basicEffect.DirectionalLight0.Direction = pos — light.Position;
teapot.Draw(basicEffect);
}
}
}
light.Draw(view, proj);
base.Draw(gameTime);
}
Обратите внимание на то, что, поскольку источник света находится на расстоянии 2 от начала координат, источник света находится ближе к началу координат, чем некоторые чайники. Хотя обычно считается, что направленный источник света (а пока мы будем рассматривать именно направленный источник света) бесконечно удален от сцены. В принципе, в этом нет ничего страшного, но, если хочется исправить этот недочет, то можно просто изменить начальную позициб источника света на большую (например на Vector3(0,0,100)).
Также стоит отметить то, что при наших начальных условиях камера по умолчанию находится внутри одного из чайников.
Исходный код:
FreeCamera.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace MyGame
{
class
FreeCamera : GameComponent
{
public
Matrix View;
public
Vector3 Position;
Vector3 angle;
float speed = 1;
float turnSpeed = 1;
public FreeCamera(Game game, Vector3 position, Vector3 lookAt) : base(game)
{
this.Position = position;
View = Matrix.CreateLookAt(position, lookAt, Vector3.Up);
}
public
override
void Update(GameTime gameTime)
{
int centerX = Game.GraphicsDevice.Viewport.Width / 2;
int centerY = Game.GraphicsDevice.Viewport.Height / 2;
MouseState mouse = Mouse.GetState();
Mouse.SetPosition(centerX, centerY);
float seconds = (float)(gameTime.ElapsedGameTime.TotalSeconds);
float yaw = MathHelper.ToRadians((mouse.X — centerX) * turnSpeed * seconds);
float pitch = MathHelper.ToRadians((mouse.Y — centerY) * turnSpeed * seconds);
angle = new
Vector3(angle.X + pitch, angle.Y + yaw, angle.Z);
Vector3 forward = —Vector3.Normalize(new
Vector3(
(float)Math.Sin(-angle.Y) * (float)Math.Cos(angle.X),
(float)Math.Sin(angle.X),
(float)Math.Cos(-angle.Y) * (float)Math.Cos(angle.X))
);
Vector3 left = —Vector3.Normalize(new
Vector3(
(float)Math.Cos(angle.Y),
0f,
(float)Math.Sin(angle.Y))
);
KeyboardState state = Keyboard.GetState();
if (state.IsKeyDown(Keys.Up))
Position += forward * speed * seconds;
if (state.IsKeyDown(Keys.Down))
Position -= forward * speed * seconds;
if (state.IsKeyDown(Keys.Left))
Position += left * speed * seconds;
if (state.IsKeyDown(Keys.Right))
Position -= left * speed * seconds;
View = Matrix.CreateTranslation(-Position) *
Matrix.CreateRotationZ(angle.Z) *
Matrix.CreateRotationY(angle.Y) *
Matrix.CreateRotationX(angle.X);
base.Update(gameTime);
}
}
}
Game1.cs
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;
using Primitives3D;
namespace MyGame
{
///
<summary>
/// This is the main type for your game
///
</summary>
public
class
Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Light light;
BasicEffect basicEffect;
TeapotPrimitive teapot;
FreeCamera camera;
public Game1()
{
graphics = new
GraphicsDeviceManager(this);
Content.RootDirectory = «Content»;
graphics.SynchronizeWithVerticalRetrace = false;
IsFixedTimeStep = false;
}
///
<summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
///
</summary>
protected
override
void Initialize()
{
// TODO: Add your initialization logic here
Components.Add(new
FPSCounter(this));
light = new
Light(this);
Components.Add(light);
camera = new
FreeCamera(this, new
Vector3(0, 0, 3), Vector3.Zero);
Components.Add(camera);
basicEffect = new
BasicEffect(GraphicsDevice, null);
teapot = new
TeapotPrimitive(GraphicsDevice);
base.Initialize();
}
///
<summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
///
</summary>
protected
override
void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new
SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
}
///
<summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
///
</summary>
protected
override
void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
///
<summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
///
</summary>
///
<param name=»gameTime»>Provides a snapshot of timing values.</param>
protected
override
void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
base.Update(gameTime);
}
///
<summary>
/// This is called when the game should draw itself.
///
</summary>
///
<param name=»gameTime»>Provides a snapshot of timing values.</param>
protected
override
void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
Matrix view = camera.View;
Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);
// TODO: Add your drawing code here
basicEffect.World = Matrix.Identity;
basicEffect.View = view;
basicEffect.Projection = proj;
basicEffect.AmbientLightColor = new
Vector3(0.1f, 0.1f, 0.1f);
basicEffect.DirectionalLight0.Direction = Vector3.Zero-light.Position;
basicEffect.DirectionalLight0.DiffuseColor = new
Vector3(0.6f, 0, 0.4f);
basicEffect.LightingEnabled = true;
basicEffect.DirectionalLight0.Enabled = true;
for (int i = -2; i < 3; i++)
{
for (int j = -2; j < 3; j++)
{
for (int k = -2; k < 3; k++)
{
Vector3 pos = new
Vector3(i,j,k) * 2;
basicEffect.World = Matrix.CreateTranslation(pos);
basicEffect.DirectionalLight0.Direction = pos — light.Position;
teapot.Draw(basicEffect);
}
}
}
light.Draw(view, proj);
base.Draw(gameTime);
}
}
}
Здраствуйте Иван! Я создал 3D модель и загрузил ее в XNA, вы не моглибы мне подсказать как реализовать столкновения камеры со стенами?
Я так понял, что на xnadev уже ответили…
В этом вопросе все зависит от самой игры. Если стен не много, и каждая описывается отдельным объектом, то можно просто проверять пересечение BoundingSphere, в которой находится камера с каждым BoundingBox, в котором содержится стена.
У вас такого простого примерчика не будед где проверяется сфера и бох……..
Вообще я так делал когда-то, но простого примера нет…
А что не понятно в таком способе?
Алгоритм такой:
1. Сделать BoundingSphere для камеры
2. Сделать BoundingBox для каждой стены
3. Проверить пересечения сферы с каждым боксом
мне интересно как в данном свособе все меши загнать в BoundingBox ?
А как задаются стены?
BoundingSphere s1 = ball.Meshes[0].BoundingSphere;
s1.Center = avatarPosition;
s1.Radius = 60.0f;
BoundingBox s2 = new BoundingBox(
new Vector3(modelPos.X, modelPos.Y, -50),
new Vector3(modelPos.X , modelPos.Y, 50));
if (s1.Intersects(s2))
{
this.Window.Title = «Collision!»;
return true;
}
Ну если есть модель, то хорошо.
Вот первое, что нашел, должно помочь
http://www.toymaker.info/Games/XNA/html/xna_bounding_box.html
Сделал перемещение сферы по лабиринту….пытаюсь сделать проверку на столкновение вроде бы все записал…только ни как изменений…не посмотрите мой проект может где что то пропустил
Вот мой проект:
http://narod.ru/disk/9066974001/лабиринт_с_камерой.rar.html
http://narod.ru/disk/9066974001/лабиринт_с_камерой.rar.html
Хм.. получается, что я не могу нормально посмотреть проект, у меня сейчас есть только XNA 4.0
По коду не понял некоторых вещей:
1. У Вас весь уровень в одном большой модели? Просто если так, то такой способ не сработает. В этом способе для каждой стены строится бокс (нужно переносить его в соответствующее место)
2. Там есть проверка на пересечение с какой-то непонятной сферой, а надо сферу строить вокруг камеры
Рещил воспользоваться библиотеокой все получилось Ziggyware.Utility.dll…. вы ни когода не пользовались?…..и еще меня интересует какие параметры имеет базовый источник света
Библиотекой не пользовался, посмотрю, спасибо.
По поводу источника света основное: направление, цвета (Diffuse, Specular)
Не могли бы вы посмотреть мой проект и оценить его……..
http://depositfiles.com/files/qnqqxkf5h
В проекте использую меню из вашего видео урока….
Идея прикольная, мне в целом понравилось
Только у меня почему-то текстуры криво наложились на стены…
Думаю создать дополнительный пункт в меню…где можно менять разрешение экрана….и хотел еще реализовать таймер….т.е назначить определенное время и что бы оно шло в обратном порядке…вы мне не подскажите как это можно лучше сделать….и если сможите пришлите мне скрин где текстуры криво лежат, а то может что то я пропустил…..
Таймер — ну просто переменная float (сколько секунд осталось)
в Update кажый раз нужно вычитать из нее gameTime.ElapsedTime.TotalSeconds. При выводе на экран приводить к int для округления
баг: http://savepic.ru/2461195.png
Текстуры криво лежат почти везде…
У меня Radeon HD5650
Подработал код……вот посмотрите:
http://depositfiles.com/files/g0qhtjgmt
освещение такое нормально?
Текстуры починились.
А с освещением есть баги, например вот этот этот (из первой комнаты налево)
Добрый день Иван а не подскажете как правильно стоить матрицу допустим у меня есть некая сфера большого радиуса ю Надо расположить камеру на сфере и врашать ее в зависимости от того где она находииться тее чтобы камера какбы двигалась по поверхности сферы и низ камеры был на сфере постоянно .
Так сходу код не могу написать. Вот ссылка, например, http://forums.create.msdn.com/forums/t/3259.aspx
Подходит?
немного не то у меня есть модель земли тее рандомно генерируемая радиусом так этак 100кF передвежение свободной камеры разобрался как делать но как привязаться к земле чтобы у меня низ камыры всегда был обращен к земле тее псебда эмуляция что я постоянно иду по земле но пофагту могу находиться верх ногами по отношению к оси
Ну то есть высота в каждой точке сферы известна?
Да известна. но может меняться тераформом . Но сохраняеться в бд. просто не совсем понятно как камеру переворачивать в зависимости от того гда неходишся тее пускать Ray от позиции камеры до центра как вариант посмотреть где сопрекаснулся с точкой сферы но как дальше перевернуть? И еще вопросик есть ли в XNA какой либо инструмет позволяющий расчитывать динамически Индексы для вершин? а то както накладно выходить для 64 милионов вершин 384 милиона индексов держать и расчитывать зону видимости и выводить нужные триугольники
Можно попробовать сначала повернуть камеру, пока она не передвинута на поверхность сферы. Тогда нужен только угол относительно Y. Потом уже переносить.
Второй вопрос не понял. Вообще что-то похожее было на xnadev.ru
Приеду домой попробую. По второму вопросу вот как можно создовать индексы для треугольников если известны только вершины. Можно ли их создать на лету или как таковое не возможно т.к не известно правильный обход точек
Пока не представляю, как это можно сделать автоматически. Только если это равномерная сетка, тогда можно.
Спасибо за помощь буду думать
А можно спросить как создать 2ю камеру и поместить ее чуть выше 1й и выводить в ней то что она видит в прямоугольнике, в правом верхнему углу, на подобие карты пытаюсь сделать. Но нигде неописано как вывод камеры в прямоугольник запихнуть особенно если она нужна как карта в 2д играх стратегии
Да, можно поставить камеру сверху и рендерить кадр с нее на отдельную плоскость.
Получится примерно как вот в этой статье:
https://wordpress.com/post/iandreev.wordpress.com/889
То есть в Draw задаете матрицы View (тут можно камеру просто над сценой), Projection (для миникарты тут лучше использовать ортогональную проекцию), ставите другой рендер таргет GraphicsDevice.SetRenderTarget() и рисуете.
После этого в рендертаргете будет миникарта. Ну точнее что-то похожее )