Для создания игрового мира трехмерной игры используют несколько подходов.
Рассмотрим работу с игровым миром, который представлен в виде отдельной модели, содержащей все его элементы.
В качестве объекта игрового мира, который мы использовали для демонстрации, применен готовый игровой объект – .
Создадим новый игровой проект P17_1. На рис. 22.1. вы можете видеть его окно Project Explorer.
(рис 22.1) Окно Project Explorer проекта P17_1В качестве игрового объекта мы используем модель ball.fbx, для построения игрового мира применим модель дома house1.x и плоскость plane.fbx. В этом проекте мы используем уже знакомый вам по предыдущим проектам класс modCls – он применяется для вывода моделей.
В листинге 22.1. вы можете видеть код класса Game1.
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.Net;
using Microsoft.Xna.Framework.Storage;
namespace P17_1
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модели
Model ball, house, plane;
// Позиция объекта, поворот
Vector3 avatarPosition = new Vector3(0, 0, -10);
float avatarlRotation;
// Положение камеры
Vector3 cameraReference = new Vector3(0, 0, 10);
Vector3 thirdPersonReference = new Vector3(0, 20, 100);
// Скорости поворота и движения
float rotationSpeed = 1f / 60f;
float forwardSpeed = 10f / 60f;
//Поле зрения камеры
float viewAngle = MathHelper.ToRadians(45.0f);
//Расстояние от камеры до переднего и заднего плана
float nearClip = 1.0f;
float farClip = 2000.0f;
//Объекты
modCls ballObj;
modCls mHouse;
modCls mPlane;
//Соотношение сторон экрана
float aspectRatio;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
protected override void LoadContent()
{
//Загрузка моделей
ball = Content.Load<Model>("ball");
house = Content.Load<Model>("house1");
plane = Content.Load<Model>("plane");
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
protected override void Update(GameTime gameTime)
{
//обновить положение объекта
UpdateAvatarPosition();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
UpdateCameraThirdPerson();
//вывести объекты сцены
DrawScene();
base.Draw(gameTime);
}
//Обновляем состояние объекта
void UpdateAvatarPosition()
{
KeyboardState keyboardState = Keyboard.GetState();
//Поворот влево
if (keyboardState.IsKeyDown(Keys.Left))
{
avatarlRotation += rotationSpeed;
}
//Поворот вправо
if (keyboardState.IsKeyDown(Keys.Right))
{
avatarlRotation -= rotationSpeed;
}
//Движение вперед
if (keyboardState.IsKeyDown(Keys.Up))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, 0, -forwardSpeed);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Z += v.Z;
avatarPosition.X += v.X;
}
//Движение назад
if (keyboardState.IsKeyDown(Keys.Down))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, 0, forwardSpeed);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Z += v.Z;
avatarPosition.X += v.X;
}
//Движение вверх
if (keyboardState.IsKeyDown(Keys.W))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
}
//Движение вниз
if (keyboardState.IsKeyDown(Keys.S))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, -forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
}
//Уменьшение угла обзора камеры
if (keyboardState.IsKeyDown(Keys.R))
{
viewAngle -= MathHelper.ToRadians(1.0f);
}
//Увеличение угла обзора камеры
if (keyboardState.IsKeyDown(Keys.F))
{
viewAngle += MathHelper.ToRadians(1.0f);
}
//Если новый угол обзора вышел за дозволенные пределы
//изменяем его
if (viewAngle > MathHelper.ToRadians(180.0f)) viewAngle = MathHelper.ToRadians(179.9f);
if (viewAngle < MathHelper.ToRadians(0.0f)) viewAngle = MathHelper.ToRadians(0.1f);
}
//Обновляем положение камеры при выбранном виде от третьего лица
void UpdateCameraThirdPerson()
{
//Поворот камеры
Matrix rotationMatrix = Matrix.CreateRotationY(avatarlRotation);
// Направление камеры
Vector3 transformedReference = Vector3.Transform(thirdPersonReference, rotationMatrix);
// Позиция камеры
Vector3 cameraPosition = transformedReference + avatarPosition;
//Матрица вида
viewMatrix = Matrix.CreateLookAt(cameraPosition, avatarPosition, new Vector3(0.0f, 1.0f, 0.0f));
//Проецкионная матрица
projMatrix = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearClip, farClip);
}
//Вывод объектов сцены
void DrawScene()
{
//очистить коллекцию компонентов
Components.Clear();
//Вывести дом
mHouse = new modCls(this, house, graphics);
mHouse .WorldMatrix = Matrix.CreateTranslation(0, 0, -5)*Matrix .CreateScale (5f);
mHouse .ViewMatrix = viewMatrix;
mHouse .ProjectMatrix = projMatrix;
Components .Add (mHouse );
//выведем игровой объект
ballObj = new modCls(this, ball, graphics);
ballObj.WorldMatrix = Matrix.CreateRotationY(avatarlRotation) * Matrix.CreateTranslation(avatarPosition);
ballObj.ViewMatrix = viewMatrix;
ballObj.ProjectMatrix = projMatrix;
Components.Add(ballObj);
//Вывести плоскость
mPlane = new modCls(this, plane, graphics);
mPlane.WorldMatrix = Matrix.CreateScale(700) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *
Matrix.CreateTranslation(0, -2, 0);
mPlane.ViewMatrix = viewMatrix;
mPlane.ProjectMatrix = projMatrix;
Components.Add(mPlane);
}
}
}
На рис. 22.2. вы можете видеть игровой экран проекта P17_1.
(рис 22.2) Игровой экран проекта P17_1Рассмотрим управление освещением и отображением теней в XNA. Существуют различные техники работы с тенями, мы рассмотрим отрисовку теней с использованием так называемого буфера трафаретов, или, по-английски -
Для создания тени мы воспользуемся методом CreateShadow объекта Matrix. Он позволяет создавать тень от объекта на основе информации об источнике освещения и плоскости, на которую должна проецироваться тень.
После того, как создана матрица, представляющая собой тень, мы используем эту матрицу для вывода тени. Причем, техника работы такова: сначала вывести сцену, освещенную так, как нужно, после этого соответствующим образом настроить буфер трафаретов и вывести ту же сцену без освещения, модифицировав мировую матрицу с помощью полученной матрицы тени.
В этом же примере мы рассмотрим настройку источника света. В частности, мы применим для рисования объектов один направленный источник света, направление которого можно менять с помощью клавиш клавиатуры – координата Z изменяется с помощью клавиш-стрелок вверх-вниз, координата X – клавишами вправо-влево, координата Y – клавишами W и S. Изменение направления освещения влияет не только на освещение объектов, но и на тень.
Создадим новый проект – P17_2.
На рис. 22.3. вы можете видеть его окно Project Explorer.
(рис 22.3) Окно Project Explorer проекта P17_2Для представления моделей, выводимых на экран, мы используем класс modCls, в качестве ресурсов игры используем шар ball2 и плоскость plane.
Рассмотрим код класса modCls (листинг. 22.2.) В проекте P16_2 этот класс отвечал за вывод изображений на экран. Сейчас он выполняет ту же функцию, дополнив вывод изображений визуализацией теней.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
namespace P17_2
{
/// <summary>
/// This is a game component that implements IUpdateable.
/// </summary>
public class modCls : Microsoft.Xna.Framework.DrawableGameComponent
{
//Модель
public Model myModel;
//Мировая матрица, матрицы вида и проекции
public Matrix WorldMatrix;
public Matrix ViewMatrix;
public Matrix ProjectMatrix;
//Направление света
public Vector3 LightDirection;
//Матрица для отображения тени
Matrix shadow;
//Плоскость, на которой отображается тень
Plane sPlane;
//Соотношение сторон экрана
public float aspectRatio;
//Для управления графическим устройством
GraphicsDeviceManager graphics;
//Конструктор получает на вход
//игровой класс, модель, объект для управления графическим устройством и плоскость для вывода тени
public modCls(Game game, Model mod, GraphicsDeviceManager grf, Plane pl)
: base(game)
{
myModel = mod;
graphics = grf;
sPlane = pl;
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
LightDirection = new Vector3();
}
public override void Initialize()
{
// TODO: Add your initialization code here
base.Initialize();
}
public override void Draw(GameTime gameTime)
{
//Выводим объект
foreach (ModelMesh mesh in myModel.Meshes)
{
//Для каждого эффекта в сети
foreach (BasicEffect effect in mesh.Effects)
{
//Включить источник направленного света №0
effect.DirectionalLight0.Enabled = true;
//Настроить параметры
effect.DirectionalLight0.DiffuseColor = Vector3.One;
effect.DirectionalLight0.SpecularColor = Vector3.One;
//Направление света - в класса Game1 мы меняем направление
//по клавиатурным командам
effect.DirectionalLight0.Direction = Vector3.Normalize(LightDirection);
//Включить освещение
effect.LightingEnabled = true;
//Установка матриц
effect.World = WorldMatrix;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
}
mesh.Draw();
}
//Создать матрицу тени
shadow = Matrix.CreateShadow(-LightDirection, sPlane);
//Очистить буфер трафаретов
graphics.GraphicsDevice.Clear(ClearOptions.Stencil, Color.Black, 0, 0);
//Включть буфер трафаретов
graphics.GraphicsDevice.RenderState.StencilEnable = true;
// Вывод на экран элемента буфера в том случае, если он установлен в 0
graphics.GraphicsDevice.RenderState.ReferenceStencil = 0;
graphics.GraphicsDevice.RenderState.StencilFunction = CompareFunction.Equal;
//При выводе применяем операцию увеличения
graphics.GraphicsDevice.RenderState.StencilPass = StencilOperation.Increment;
//Включаем альфа-смешивание для того, чтобы сделать тень полупрозрачной
graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
//Выводим тень
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.AmbientLightColor = Vector3.Zero;
effect.Alpha = 0.5f;
effect.DirectionalLight0.Enabled = false;
effect.DirectionalLight1.Enabled = false;
effect.DirectionalLight2.Enabled = false;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
//При выводе тени умножаем мировую матрицу
//на матрицу вывода тени
effect.World = WorldMatrix*shadow;
}
mesh.Draw();
}
//Отключить буфер
graphics.GraphicsDevice.RenderState.StencilEnable = false;
//Отключить альфа-смешивание
graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
base.Draw(gameTime);
}
}
}
В листинге 22.3. вы можете найти код класса Game1.
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.Net;
using Microsoft.Xna.Framework.Storage;
namespace P17_2
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модели
modCls[] cls;
//Положение моделей в пространстве
Vector3[] offset;
//Соотношение сторон окна вывода
float aspectRatio;
//Плоскость
modCls plane;
//Направление света
Vector3 LightDirection;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
//Установить формат буфера трафаретов
graphics.PreferredDepthStencilFormat = SelectStencilMode();
}
//Проверка доступного буфера трафаретов и возврат соответствующей переменной типа
//DepthFormat. Если буфер трафаретов не найден - выдадим исключение
private static DepthFormat SelectStencilMode()
{
//Графическая карта
GraphicsAdapter adapter = GraphicsAdapter.DefaultAdapter;
//Формат поверхности
SurfaceFormat format = adapter.CurrentDisplayMode.Format;
//Проверка имеющихся технических средств карты
if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8))
return DepthFormat.Depth24Stencil8;
else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8Single))
return DepthFormat.Depth24Stencil8Single;
else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil4))
return DepthFormat.Depth24Stencil4;
else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth15Stencil1))
return DepthFormat.Depth15Stencil1;
else
throw new InvalidOperationException("Could Not Find Stencil Buffer for Default Adapter");
}
protected override void Initialize()
{
//Отобразим указатель мыши
IsMouseVisible = true;
base.Initialize();
}
protected override void LoadContent()
{
//Вычислим соотношение сторон
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
//Матрица вида
viewMatrix = Matrix.CreateLookAt(new Vector3(0, 10, 15),
new Vector3(0, 0, 0),
new Vector3(0.0f, 1.0f, 0.0f));
//Матрица проекции
projMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),
aspectRatio, 1.0f, 1000.0f);
LightDirection = new Vector3(-2, -2, 0);
//Создаем плоскость
plane = new modCls(this, Content.Load<Model>("plane"), graphics, new Plane ());
//Настраиваем параметры плоскости
plane.WorldMatrix = Matrix.CreateScale(25) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *
Matrix.CreateTranslation(0, -4, 0);
plane.ViewMatrix = viewMatrix;
plane.ProjectMatrix = projMatrix;
plane.LightDirection = LightDirection;
Components.Add(plane);
//Плоскость, на которую проецируется тень
//Она немного выше плоскости, которую мы выводим на экран
//для того, чтобы тень была видна
Plane pl1 = new Plane(new Vector3(0, -3.9f, 0), new Vector3(2, -3.9f, 1), new Vector3(-1, -3.9f, -2));
//Три элемента в массиве объектов
cls = new modCls[3];
//Три элемента в массиве положений объектов
offset = new Vector3[3];
//Зададим начальные положения моделей
offset[0] = new Vector3(0, 2, 0);
offset[1] = new Vector3(-6, 3, -4);
offset[2] = new Vector3(6, 4, -6);
//Создадим модели
cls[0] = new modCls(this, Content.Load<Model>("ball2"), graphics,pl1);
cls[1] = new modCls(this, Content.Load<Model>("ball2"), graphics, pl1);
cls[2] = new modCls(this, Content.Load<Model>("ball2"), graphics, pl1);
//Задаем параметры матриц и направления света каждой из моделей
for (int i = 0; i < 3; i++)
{
cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
cls[i].ProjectMatrix = projMatrix;
cls[i].ViewMatrix = viewMatrix;
cls[i].LightDirection = LightDirection;
Components.Add(cls[i]);
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
protected override void Update(GameTime gameTime)
{
//Для случайных колебаний моделей
Random rand = new Random();
//В каждом проходе цикла
//Смещаем каждую модель вниз на 0.04 единицы
//И на небольшое случайное число вдоль оси X и Y - модели
//слегка колеблются при добавлении этого изменения
//Мы не ввели ограничения на изменение X и Z -
//При практической реалиации данного примера это
//желательно сделать
for (int i = 0; i < 3; i++)
{
offset[i].Y -= 0.04f;
offset[i].X -= (0.08f * (float)rand.NextDouble() - 0.08f * (float)rand.NextDouble());
offset[i].Z -= (0.08f * (float)rand.NextDouble() - 0.08f * (float)rand.NextDouble());
cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
}
//Проверяем попадание мыши по объекту
CheckMouseClick();
//Проверяем пересечение с плоскостью
CheckPlane();
//Меняем позицию источника света по нажатию клавиш
LightSourceControl();
base.Update(gameTime);
}
//Изменение позиции источника света
void LightSourceControl()
{
//Получим состояние клавиатуры
KeyboardState key = Keyboard.GetState();
//Клавиша "вверх" - уменьшим позицию по Z
if (key.IsKeyDown(Keys .Up ))
{
LightDirection.Z -= 0.5f;
}
//Клавиша "вниз" - увеличим Z
if (key.IsKeyDown(Keys.Down))
{
LightDirection.Z += 0.5f;
}
//"Влево" - уменьшим X
if (key.IsKeyDown(Keys.Left))
{
LightDirection.X -= 0.5f;
}
//"Вправо" - увеличим X
if (key.IsKeyDown(Keys.Right))
{
LightDirection.X += 0.5f;
}
//"W" - увеличим Y
if (key.IsKeyDown(Keys.W))
{
LightDirection.Y += 0.5f;
}
//"S" - уменьшим Y
if (key.IsKeyDown(Keys.S))
{
LightDirection.Y -= 0.5f;
}
//Изменим направление источника света для плоскости
plane.LightDirection = LightDirection;
//Изменим направление источника света для моделей
for (int i = 0; i < 3; i++)
{
cls[i].LightDirection = LightDirection;
}
//Выведем в заголовок окна информацию о направлении
this.Window.Title = "Light source: " + LightDirection.ToString();
}
//Проверка на нажатие клавиши мыши
void CheckMouseClick()
{
//получим состояние мыши
MouseState mouseState = Mouse.GetState();
//если нажата левая кнопка
if (mouseState.LeftButton == ButtonState.Pressed)
{
//Получим луч, идущий от позиции мыши на экране в пространство
Ray pickRay = GetPickRay();
//Переменная для хранения сферы, соответствующей объекту
BoundingSphere b1;
//Переменная для хранения вектора размера модели
Vector3 scale;
//Переменная для хранения информации о повороте модели
Quaternion rotation;
//Переменая для хранения информации о позиции модели
Vector3 translation;
for (int i = 0; i < 3; i++)
{
//Получить BoundingSphere для текущего объекта
b1 = cls[i].myModel.Meshes[0].BoundingSphere;
//Получить параметры - размер, поворот, позицию для объекта
cls[i].WorldMatrix.Decompose(out scale, out rotation, out translation);
//Установить центр сферы в соответствии с позицией объекта
b1.Center = translation;
//Получить результат пересечения луча и сферы
Nullable<float> result = pickRay.Intersects(b1);
//Если луч и сфера пересеклись - "поднять" объект до позиции Y=4
if (result.HasValue)
{
offset[i].Y = 4;
cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
}
}
}
}
//Проверка на столкновение с плоскостью
void CheckPlane()
{
//Создадим плоскость по трем точкам - она пересекает ось Y в точке -4 и не пересекает
//другие оси
Plane pl = new Plane(new Vector3(0, -4, 0), new Vector3(2, -4, 1), new Vector3(-1, -4, -2));
//Переменная для хранения типа пересечения с плоскостью
//Она может сигнализировать о нахождении объекта перед плоскостью (выше в нашем случае)
//за плоскостью и о пересечении с плоскостью
PlaneIntersectionType plType;
//Сфера для объекта
BoundingSphere b1;
//Переменная для хранения вектора размера модели
Vector3 scale;
//Переменная для хранения информации о повороте модели
Quaternion rotation;
//Переменая для хранения информации о позиции модели
Vector3 translation;
for (int i = 0; i < 3; i++)
{
//Получить BoundingSphere для текущего объекта
b1 = cls[i].myModel.Meshes[0].BoundingSphere;
//Получить параметры - размер, поворот, позицию для объекта
cls[i].WorldMatrix.Decompose(out scale, out rotation, out translation);
//Установить центр сферы в соответствии с позицией объекта
b1.Center = translation;
//Получить тип пересечения сферы объекта и плоскости
plType = pl.Intersects(b1);
//Если сфера пересекла плоскость
if (plType == PlaneIntersectionType.Intersecting)
{
//Удалить соответствующий игровой объект из коллекции
Components.Remove(cls[i]);
}
}
}
//Функция для вычисления луча, исходящего из точки, в которой
//находился указатель мыши в момент щелчка
Ray GetPickRay()
{
//получить состояние мыши
MouseState mouseState = Mouse.GetState();
//Сохранить координаты
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
//Точки для вычисления направления луча - одна, соответствующая координате мыши
//вторая - направленная по оси Z
Vector3 nearsource = new Vector3((float)mouseX, (float)mouseY, 0f);
Vector3 farsource = new Vector3((float)mouseX, (float)mouseY, 1f);
//Мировая матрица для вычислений
Matrix world = Matrix.CreateTranslation(0, 0, 0);
//Переведем координаты мыши в координаты трехмерного пространства
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearsource, projMatrix, viewMatrix, world);
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farsource, projMatrix, viewMatrix, world);
// Получим направление луча
Vector3 direction = farPoint - nearPoint;
//Нормализуем его (преобразуем к единичному вектору)
direction.Normalize();
//Создадим новый луч, начинающийся в точке, соответствующей координате
//указателя мыши в объектном пространстве, с направлением,
//соответствующим вычисленному направлению
Ray pickRay = new Ray(nearPoint, direction);
//возвратим луч в процедуру, вызывавшую данную функцию
return pickRay;
}
/// <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)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
base.Draw(gameTime);
}
}
}
На рис. 22.4. вы можете видеть игровой экран проекта P17_2.
(рис 22.4) Игровой экран проекта P17_2Разработайте на основе проекта P17_2 собственную игру, которая реализует следующие возможности:
Для создания игрового мира трехмерной игры используют несколько подходов.
Рассмотрим работу с игровым миром, который представлен в виде отдельной модели, содержащей все его элементы.
В качестве объекта игрового мира, который мы использовали для демонстрации, применен готовый игровой объект – .
Создадим новый игровой проект P17_1. На рис. 22.1. вы можете видеть его окно Project Explorer.
(рис 22.1) Окно Project Explorer проекта P17_1В качестве игрового объекта мы используем модель ball.fbx, для построения игрового мира применим модель дома house1.x и плоскость plane.fbx. В этом проекте мы используем уже знакомый вам по предыдущим проектам класс modCls – он применяется для вывода моделей.
В листинге 22.1. вы можете видеть код класса Game1.
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.Net;
using Microsoft.Xna.Framework.Storage;
namespace P17_1
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модели
Model ball, house, plane;
// Позиция объекта, поворот
Vector3 avatarPosition = new Vector3(0, 0, -10);
float avatarlRotation;
// Положение камеры
Vector3 cameraReference = new Vector3(0, 0, 10);
Vector3 thirdPersonReference = new Vector3(0, 20, 100);
// Скорости поворота и движения
float rotationSpeed = 1f / 60f;
float forwardSpeed = 10f / 60f;
//Поле зрения камеры
float viewAngle = MathHelper.ToRadians(45.0f);
//Расстояние от камеры до переднего и заднего плана
float nearClip = 1.0f;
float farClip = 2000.0f;
//Объекты
modCls ballObj;
modCls mHouse;
modCls mPlane;
//Соотношение сторон экрана
float aspectRatio;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
protected override void LoadContent()
{
//Загрузка моделей
ball = Content.Load<Model>("ball");
house = Content.Load<Model>("house1");
plane = Content.Load<Model>("plane");
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
protected override void Update(GameTime gameTime)
{
//обновить положение объекта
UpdateAvatarPosition();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
UpdateCameraThirdPerson();
//вывести объекты сцены
DrawScene();
base.Draw(gameTime);
}
//Обновляем состояние объекта
void UpdateAvatarPosition()
{
KeyboardState keyboardState = Keyboard.GetState();
//Поворот влево
if (keyboardState.IsKeyDown(Keys.Left))
{
avatarlRotation += rotationSpeed;
}
//Поворот вправо
if (keyboardState.IsKeyDown(Keys.Right))
{
avatarlRotation -= rotationSpeed;
}
//Движение вперед
if (keyboardState.IsKeyDown(Keys.Up))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, 0, -forwardSpeed);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Z += v.Z;
avatarPosition.X += v.X;
}
//Движение назад
if (keyboardState.IsKeyDown(Keys.Down))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, 0, forwardSpeed);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Z += v.Z;
avatarPosition.X += v.X;
}
//Движение вверх
if (keyboardState.IsKeyDown(Keys.W))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
}
//Движение вниз
if (keyboardState.IsKeyDown(Keys.S))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, -forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
}
//Уменьшение угла обзора камеры
if (keyboardState.IsKeyDown(Keys.R))
{
viewAngle -= MathHelper.ToRadians(1.0f);
}
//Увеличение угла обзора камеры
if (keyboardState.IsKeyDown(Keys.F))
{
viewAngle += MathHelper.ToRadians(1.0f);
}
//Если новый угол обзора вышел за дозволенные пределы
//изменяем его
if (viewAngle > MathHelper.ToRadians(180.0f)) viewAngle = MathHelper.ToRadians(179.9f);
if (viewAngle < MathHelper.ToRadians(0.0f)) viewAngle = MathHelper.ToRadians(0.1f);
}
//Обновляем положение камеры при выбранном виде от третьего лица
void UpdateCameraThirdPerson()
{
//Поворот камеры
Matrix rotationMatrix = Matrix.CreateRotationY(avatarlRotation);
// Направление камеры
Vector3 transformedReference = Vector3.Transform(thirdPersonReference, rotationMatrix);
// Позиция камеры
Vector3 cameraPosition = transformedReference + avatarPosition;
//Матрица вида
viewMatrix = Matrix.CreateLookAt(cameraPosition, avatarPosition, new Vector3(0.0f, 1.0f, 0.0f));
//Проецкионная матрица
projMatrix = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearClip, farClip);
}
//Вывод объектов сцены
void DrawScene()
{
//очистить коллекцию компонентов
Components.Clear();
//Вывести дом
mHouse = new modCls(this, house, graphics);
mHouse .WorldMatrix = Matrix.CreateTranslation(0, 0, -5)*Matrix .CreateScale (5f);
mHouse .ViewMatrix = viewMatrix;
mHouse .ProjectMatrix = projMatrix;
Components .Add (mHouse );
//выведем игровой объект
ballObj = new modCls(this, ball, graphics);
ballObj.WorldMatrix = Matrix.CreateRotationY(avatarlRotation) * Matrix.CreateTranslation(avatarPosition);
ballObj.ViewMatrix = viewMatrix;
ballObj.ProjectMatrix = projMatrix;
Components.Add(ballObj);
//Вывести плоскость
mPlane = new modCls(this, plane, graphics);
mPlane.WorldMatrix = Matrix.CreateScale(700) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *
Matrix.CreateTranslation(0, -2, 0);
mPlane.ViewMatrix = viewMatrix;
mPlane.ProjectMatrix = projMatrix;
Components.Add(mPlane);
}
}
}
На рис. 22.2. вы можете видеть игровой экран проекта P17_1.
(рис 22.2) Игровой экран проекта P17_1Рассмотрим управление освещением и отображением теней в XNA. Существуют различные техники работы с тенями, мы рассмотрим отрисовку теней с использованием так называемого буфера трафаретов, или, по-английски -
Для создания тени мы воспользуемся методом CreateShadow объекта Matrix. Он позволяет создавать тень от объекта на основе информации об источнике освещения и плоскости, на которую должна проецироваться тень.
После того, как создана матрица, представляющая собой тень, мы используем эту матрицу для вывода тени. Причем, техника работы такова: сначала вывести сцену, освещенную так, как нужно, после этого соответствующим образом настроить буфер трафаретов и вывести ту же сцену без освещения, модифицировав мировую матрицу с помощью полученной матрицы тени.
В этом же примере мы рассмотрим настройку источника света. В частности, мы применим для рисования объектов один направленный источник света, направление которого можно менять с помощью клавиш клавиатуры – координата Z изменяется с помощью клавиш-стрелок вверх-вниз, координата X – клавишами вправо-влево, координата Y – клавишами W и S. Изменение направления освещения влияет не только на освещение объектов, но и на тень.
Создадим новый проект – P17_2.
На рис. 22.3. вы можете видеть его окно Project Explorer.
(рис 22.3) Окно Project Explorer проекта P17_2Для представления моделей, выводимых на экран, мы используем класс modCls, в качестве ресурсов игры используем шар ball2 и плоскость plane.
Рассмотрим код класса modCls (листинг. 22.2.) В проекте P16_2 этот класс отвечал за вывод изображений на экран. Сейчас он выполняет ту же функцию, дополнив вывод изображений визуализацией теней.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
namespace P17_2
{
/// <summary>
/// This is a game component that implements IUpdateable.
/// </summary>
public class modCls : Microsoft.Xna.Framework.DrawableGameComponent
{
//Модель
public Model myModel;
//Мировая матрица, матрицы вида и проекции
public Matrix WorldMatrix;
public Matrix ViewMatrix;
public Matrix ProjectMatrix;
//Направление света
public Vector3 LightDirection;
//Матрица для отображения тени
Matrix shadow;
//Плоскость, на которой отображается тень
Plane sPlane;
//Соотношение сторон экрана
public float aspectRatio;
//Для управления графическим устройством
GraphicsDeviceManager graphics;
//Конструктор получает на вход
//игровой класс, модель, объект для управления графическим устройством и плоскость для вывода тени
public modCls(Game game, Model mod, GraphicsDeviceManager grf, Plane pl)
: base(game)
{
myModel = mod;
graphics = grf;
sPlane = pl;
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
LightDirection = new Vector3();
}
public override void Initialize()
{
// TODO: Add your initialization code here
base.Initialize();
}
public override void Draw(GameTime gameTime)
{
//Выводим объект
foreach (ModelMesh mesh in myModel.Meshes)
{
//Для каждого эффекта в сети
foreach (BasicEffect effect in mesh.Effects)
{
//Включить источник направленного света №0
effect.DirectionalLight0.Enabled = true;
//Настроить параметры
effect.DirectionalLight0.DiffuseColor = Vector3.One;
effect.DirectionalLight0.SpecularColor = Vector3.One;
//Направление света - в класса Game1 мы меняем направление
//по клавиатурным командам
effect.DirectionalLight0.Direction = Vector3.Normalize(LightDirection);
//Включить освещение
effect.LightingEnabled = true;
//Установка матриц
effect.World = WorldMatrix;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
}
mesh.Draw();
}
//Создать матрицу тени
shadow = Matrix.CreateShadow(-LightDirection, sPlane);
//Очистить буфер трафаретов
graphics.GraphicsDevice.Clear(ClearOptions.Stencil, Color.Black, 0, 0);
//Включть буфер трафаретов
graphics.GraphicsDevice.RenderState.StencilEnable = true;
// Вывод на экран элемента буфера в том случае, если он установлен в 0
graphics.GraphicsDevice.RenderState.ReferenceStencil = 0;
graphics.GraphicsDevice.RenderState.StencilFunction = CompareFunction.Equal;
//При выводе применяем операцию увеличения
graphics.GraphicsDevice.RenderState.StencilPass = StencilOperation.Increment;
//Включаем альфа-смешивание для того, чтобы сделать тень полупрозрачной
graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
//Выводим тень
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.AmbientLightColor = Vector3.Zero;
effect.Alpha = 0.5f;
effect.DirectionalLight0.Enabled = false;
effect.DirectionalLight1.Enabled = false;
effect.DirectionalLight2.Enabled = false;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
//При выводе тени умножаем мировую матрицу
//на матрицу вывода тени
effect.World = WorldMatrix*shadow;
}
mesh.Draw();
}
//Отключить буфер
graphics.GraphicsDevice.RenderState.StencilEnable = false;
//Отключить альфа-смешивание
graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
base.Draw(gameTime);
}
}
}
В листинге 22.3. вы можете найти код класса Game1.
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.Net;
using Microsoft.Xna.Framework.Storage;
namespace P17_2
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модели
modCls[] cls;
//Положение моделей в пространстве
Vector3[] offset;
//Соотношение сторон окна вывода
float aspectRatio;
//Плоскость
modCls plane;
//Направление света
Vector3 LightDirection;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
//Установить формат буфера трафаретов
graphics.PreferredDepthStencilFormat = SelectStencilMode();
}
//Проверка доступного буфера трафаретов и возврат соответствующей переменной типа
//DepthFormat. Если буфер трафаретов не найден - выдадим исключение
private static DepthFormat SelectStencilMode()
{
//Графическая карта
GraphicsAdapter adapter = GraphicsAdapter.DefaultAdapter;
//Формат поверхности
SurfaceFormat format = adapter.CurrentDisplayMode.Format;
//Проверка имеющихся технических средств карты
if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8))
return DepthFormat.Depth24Stencil8;
else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8Single))
return DepthFormat.Depth24Stencil8Single;
else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil4))
return DepthFormat.Depth24Stencil4;
else if (adapter.CheckDepthStencilMatch(DeviceType.Hardware, format, format, DepthFormat.Depth15Stencil1))
return DepthFormat.Depth15Stencil1;
else
throw new InvalidOperationException("Could Not Find Stencil Buffer for Default Adapter");
}
protected override void Initialize()
{
//Отобразим указатель мыши
IsMouseVisible = true;
base.Initialize();
}
protected override void LoadContent()
{
//Вычислим соотношение сторон
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
//Матрица вида
viewMatrix = Matrix.CreateLookAt(new Vector3(0, 10, 15),
new Vector3(0, 0, 0),
new Vector3(0.0f, 1.0f, 0.0f));
//Матрица проекции
projMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),
aspectRatio, 1.0f, 1000.0f);
LightDirection = new Vector3(-2, -2, 0);
//Создаем плоскость
plane = new modCls(this, Content.Load<Model>("plane"), graphics, new Plane ());
//Настраиваем параметры плоскости
plane.WorldMatrix = Matrix.CreateScale(25) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *
Matrix.CreateTranslation(0, -4, 0);
plane.ViewMatrix = viewMatrix;
plane.ProjectMatrix = projMatrix;
plane.LightDirection = LightDirection;
Components.Add(plane);
//Плоскость, на которую проецируется тень
//Она немного выше плоскости, которую мы выводим на экран
//для того, чтобы тень была видна
Plane pl1 = new Plane(new Vector3(0, -3.9f, 0), new Vector3(2, -3.9f, 1), new Vector3(-1, -3.9f, -2));
//Три элемента в массиве объектов
cls = new modCls[3];
//Три элемента в массиве положений объектов
offset = new Vector3[3];
//Зададим начальные положения моделей
offset[0] = new Vector3(0, 2, 0);
offset[1] = new Vector3(-6, 3, -4);
offset[2] = new Vector3(6, 4, -6);
//Создадим модели
cls[0] = new modCls(this, Content.Load<Model>("ball2"), graphics,pl1);
cls[1] = new modCls(this, Content.Load<Model>("ball2"), graphics, pl1);
cls[2] = new modCls(this, Content.Load<Model>("ball2"), graphics, pl1);
//Задаем параметры матриц и направления света каждой из моделей
for (int i = 0; i < 3; i++)
{
cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
cls[i].ProjectMatrix = projMatrix;
cls[i].ViewMatrix = viewMatrix;
cls[i].LightDirection = LightDirection;
Components.Add(cls[i]);
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
protected override void Update(GameTime gameTime)
{
//Для случайных колебаний моделей
Random rand = new Random();
//В каждом проходе цикла
//Смещаем каждую модель вниз на 0.04 единицы
//И на небольшое случайное число вдоль оси X и Y - модели
//слегка колеблются при добавлении этого изменения
//Мы не ввели ограничения на изменение X и Z -
//При практической реалиации данного примера это
//желательно сделать
for (int i = 0; i < 3; i++)
{
offset[i].Y -= 0.04f;
offset[i].X -= (0.08f * (float)rand.NextDouble() - 0.08f * (float)rand.NextDouble());
offset[i].Z -= (0.08f * (float)rand.NextDouble() - 0.08f * (float)rand.NextDouble());
cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
}
//Проверяем попадание мыши по объекту
CheckMouseClick();
//Проверяем пересечение с плоскостью
CheckPlane();
//Меняем позицию источника света по нажатию клавиш
LightSourceControl();
base.Update(gameTime);
}
//Изменение позиции источника света
void LightSourceControl()
{
//Получим состояние клавиатуры
KeyboardState key = Keyboard.GetState();
//Клавиша "вверх" - уменьшим позицию по Z
if (key.IsKeyDown(Keys .Up ))
{
LightDirection.Z -= 0.5f;
}
//Клавиша "вниз" - увеличим Z
if (key.IsKeyDown(Keys.Down))
{
LightDirection.Z += 0.5f;
}
//"Влево" - уменьшим X
if (key.IsKeyDown(Keys.Left))
{
LightDirection.X -= 0.5f;
}
//"Вправо" - увеличим X
if (key.IsKeyDown(Keys.Right))
{
LightDirection.X += 0.5f;
}
//"W" - увеличим Y
if (key.IsKeyDown(Keys.W))
{
LightDirection.Y += 0.5f;
}
//"S" - уменьшим Y
if (key.IsKeyDown(Keys.S))
{
LightDirection.Y -= 0.5f;
}
//Изменим направление источника света для плоскости
plane.LightDirection = LightDirection;
//Изменим направление источника света для моделей
for (int i = 0; i < 3; i++)
{
cls[i].LightDirection = LightDirection;
}
//Выведем в заголовок окна информацию о направлении
this.Window.Title = "Light source: " + LightDirection.ToString();
}
//Проверка на нажатие клавиши мыши
void CheckMouseClick()
{
//получим состояние мыши
MouseState mouseState = Mouse.GetState();
//если нажата левая кнопка
if (mouseState.LeftButton == ButtonState.Pressed)
{
//Получим луч, идущий от позиции мыши на экране в пространство
Ray pickRay = GetPickRay();
//Переменная для хранения сферы, соответствующей объекту
BoundingSphere b1;
//Переменная для хранения вектора размера модели
Vector3 scale;
//Переменная для хранения информации о повороте модели
Quaternion rotation;
//Переменая для хранения информации о позиции модели
Vector3 translation;
for (int i = 0; i < 3; i++)
{
//Получить BoundingSphere для текущего объекта
b1 = cls[i].myModel.Meshes[0].BoundingSphere;
//Получить параметры - размер, поворот, позицию для объекта
cls[i].WorldMatrix.Decompose(out scale, out rotation, out translation);
//Установить центр сферы в соответствии с позицией объекта
b1.Center = translation;
//Получить результат пересечения луча и сферы
Nullable<float> result = pickRay.Intersects(b1);
//Если луч и сфера пересеклись - "поднять" объект до позиции Y=4
if (result.HasValue)
{
offset[i].Y = 4;
cls[i].WorldMatrix = Matrix.CreateTranslation(offset[i]);
}
}
}
}
//Проверка на столкновение с плоскостью
void CheckPlane()
{
//Создадим плоскость по трем точкам - она пересекает ось Y в точке -4 и не пересекает
//другие оси
Plane pl = new Plane(new Vector3(0, -4, 0), new Vector3(2, -4, 1), new Vector3(-1, -4, -2));
//Переменная для хранения типа пересечения с плоскостью
//Она может сигнализировать о нахождении объекта перед плоскостью (выше в нашем случае)
//за плоскостью и о пересечении с плоскостью
PlaneIntersectionType plType;
//Сфера для объекта
BoundingSphere b1;
//Переменная для хранения вектора размера модели
Vector3 scale;
//Переменная для хранения информации о повороте модели
Quaternion rotation;
//Переменая для хранения информации о позиции модели
Vector3 translation;
for (int i = 0; i < 3; i++)
{
//Получить BoundingSphere для текущего объекта
b1 = cls[i].myModel.Meshes[0].BoundingSphere;
//Получить параметры - размер, поворот, позицию для объекта
cls[i].WorldMatrix.Decompose(out scale, out rotation, out translation);
//Установить центр сферы в соответствии с позицией объекта
b1.Center = translation;
//Получить тип пересечения сферы объекта и плоскости
plType = pl.Intersects(b1);
//Если сфера пересекла плоскость
if (plType == PlaneIntersectionType.Intersecting)
{
//Удалить соответствующий игровой объект из коллекции
Components.Remove(cls[i]);
}
}
}
//Функция для вычисления луча, исходящего из точки, в которой
//находился указатель мыши в момент щелчка
Ray GetPickRay()
{
//получить состояние мыши
MouseState mouseState = Mouse.GetState();
//Сохранить координаты
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
//Точки для вычисления направления луча - одна, соответствующая координате мыши
//вторая - направленная по оси Z
Vector3 nearsource = new Vector3((float)mouseX, (float)mouseY, 0f);
Vector3 farsource = new Vector3((float)mouseX, (float)mouseY, 1f);
//Мировая матрица для вычислений
Matrix world = Matrix.CreateTranslation(0, 0, 0);
//Переведем координаты мыши в координаты трехмерного пространства
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearsource, projMatrix, viewMatrix, world);
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farsource, projMatrix, viewMatrix, world);
// Получим направление луча
Vector3 direction = farPoint - nearPoint;
//Нормализуем его (преобразуем к единичному вектору)
direction.Normalize();
//Создадим новый луч, начинающийся в точке, соответствующей координате
//указателя мыши в объектном пространстве, с направлением,
//соответствующим вычисленному направлению
Ray pickRay = new Ray(nearPoint, direction);
//возвратим луч в процедуру, вызывавшую данную функцию
return pickRay;
}
/// <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)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
base.Draw(gameTime);
}
}
}
На рис. 22.4. вы можете видеть игровой экран проекта P17_2.
(рис 22.4) Игровой экран проекта P17_2Разработайте на основе проекта P17_2 собственную игру, которая реализует следующие возможности:
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.