Цель работы: Научиться создавать игровой мир, применять эффекты к его объектам
Для создания игрового мира трехмерной игры используют несколько подходов.
Игровой мир можно конструировать из отдельных объектов. Например, для того, чтобы создать комнату, можно использовать плоскости для строительства стен, пола и потолка, различные объекты для создания предметов, находящихся в комнате. При таком подходе для обработки взаимодействия игрового объекта и окружения можно применить уже знакомый вам подход проверки столкновений объектов. Этот подход мы использовали в предыдущих проектах – в примере с передвижением объекта среди группы других объектов, в примере, где нужно было касаться объектов для того, чтобы не допустить их падения.
Игровой мир можно сконструировать в трехмерном редакторе – например – ту же комнату, или целое здание, или ландшафт – и загрузить в игру в виде отдельной модели.
Мир можно сконструировать средствами XNA
Рассмотрим работу с игровым миром, который представлен в виде отдельной модели, содержащей все его элементы.
В качестве объекта игрового мира, который мы использовали для демонстрации, применен готовый игровой объект –
Создадим новый игровой проект P19_1. На рис. 26.1 вы можете видеть его окно Обозреватель решений.
(рис 26.1) Обозреватель решений для проекта P19_1
В качестве игрового объекта мы используем модель modCls – он применяется для вывода моделей. Для управления игровым объектом мы применяем экранные стрелки, представленные файлом
В листинге 26.1 вы можете видеть код класса Game1.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
namespace P19_1
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
//Матрицы
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;
//Стрелки
Texture2D txtArrows;
Rectangle keyForward = new Rectangle(500, 280, 100, 100);
Rectangle keyUp = new Rectangle(600, 280, 100, 100);
Rectangle keyBack = new Rectangle(700, 280, 100, 100);
Rectangle keyLeft = new Rectangle(500, 380, 100, 100);
Rectangle keyDown = new Rectangle(600, 380, 100, 100);
Rectangle keyRight = new Rectangle(700, 380, 100, 100);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
TouchPanel.EnabledGestures = GestureType.Pinch;
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Загрузка моделей
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;
txtArrows = Content.Load<Texture2D>("Arrows");
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
//Проверка попадания касания в область, занимаемую одним из элементов управления
private bool MenuSelect(Rectangle m, Vector2 p)
{
bool res = false;
if (p.X > m.X p.X < m.X + m.Width p.Y > m.Y p.Y < m.Y + m.Height)
{
res = true;
}
return res;
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//обновить положение объекта
UpdateAvatarPosition();
//Обновить камеру
UpdateCameraThirdPerson();
//Сформировать объекты сцены
DrawScene();
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
//Вывод стрелок
spriteBatch.Begin();
spriteBatch.Draw(txtArrows, new Rectangle(500, 280, 300, 200), Color.White);
spriteBatch.End();
//Для нормального отображение 3D-сцены после работы spriteBatch
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
}
//Обновляем состояние объекта
void UpdateAvatarPosition()
{
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Движение вверх
if (MenuSelect(keyForward, touchLocation.Position))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
}
//Вперед
if (MenuSelect(keyUp, touchLocation.Position))
{
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 (MenuSelect(keyBack, touchLocation.Position))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, -forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
}
//Поворот влево
if (MenuSelect(keyLeft, touchLocation.Position))
{
avatarlRotation += rotationSpeed;
}
//Назад
if (MenuSelect(keyDown, touchLocation.Position))
{
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 (MenuSelect(keyRight, touchLocation.Position))
{
avatarlRotation -= rotationSpeed;
}
}
}
//До тех пор, пока разрешена обработка жестов
while (TouchPanel.IsGestureAvailable)
{
//прочитаем жест
GestureSample gs = TouchPanel.ReadGesture();
if (gs.GestureType == GestureType.Pinch)
{
//Найдем предыдущие координаты, вычтя из текущих координат приращение
Vector2 oldPos = gs.Position - gs.Delta;
Vector2 oldPos2 = gs.Position2 - gs.Delta2;
//Получим расстояние между текущими точками касания
float currentDist = Vector2.Distance(gs.Position, gs.Position2);
//Получим расстояние между предыдущими точками касания
float oldDist = Vector2.Distance(oldPos, oldPos2);
//Умножим переменную, отвечающую за масштабирование спрайта
//на результат деления текущего расстояния на предыдущее
if (currentDist / oldDist > 1)
{
viewAngle -= MathHelper.ToRadians(0.5f);
}
else
{
viewAngle += MathHelper.ToRadians(0.5f);
}
}
}
//Если новый угол обзора вышел за дозволенные пределы
//изменяем его
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);
}
}
}
В листинге 26.2 приведен код класса modCls.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace P19_1
{
public class modCls : Microsoft.Xna.Framework.DrawableGameComponent
{
//Модель
public Model myModel;
//Мировая матрица, матрицы вида и проекции
public Matrix WorldMatrix;
public Matrix ViewMatrix;
public Matrix ProjectMatrix;
//Соотношение сторон экрана
public float aspectRatio;
//Для управления графическим устройством
GraphicsDeviceManager graphics;
//Конструктор получает на вход
//игровой класс, модель, объект для управления графическим устройством
public modCls(Game game, Model mod, GraphicsDeviceManager grf)
: base(game)
{
myModel = mod;
graphics = grf;
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
}
public override void Initialize()
{
base.Initialize();
}
public override void Draw(GameTime gameTime)
{
foreach (ModelMesh mesh in myModel.Meshes)
{
//Для каждого эффекта в сети
foreach (BasicEffect effect in mesh.Effects)
{
//Установить освещение по умолчанию
effect.LightingEnabled = true;
effect.EnableDefaultLighting();
//установить матрицы
effect.World = WorldMatrix;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
На рис. 26.2 и рис. 26.3 вы можете видеть различные состояния игрового экрана проекта.
(рис 26.2) Игровой экран проекта P19_1
(рис 26.3) Игровой экран проекта P19_1
Рассмотрим управление освещением и отображением теней в XNA. Существуют различные техники работы с тенями, мы рассмотрим отрисовку теней. В частности, мы модифицируем проект P18_2 таким образом, чтобы шарики, падающие на плоскость, отбрасывали тени.
Для создания тени мы воспользуемся методом CreateShadow объекта . Он позволяет создавать тень от объекта на основе информации об источнике освещения и плоскости, на которую должна проецироваться тень.
После того, как сознана матрица, представляющая собой тень, мы используем эту матрицу для вывода тени. Предварительно мы включаем альфа-смешение, и, выводя объекты, указываем, что они непрозрачны, выводя тень – настраиваем её прозрачность.
В этом же примере мы рассмотрим настройку источника света. В частности, мы применим для рисования объектов один направленный источник света, направление которого можно менять с помощью экранныэ элементов управления. Изменение направления освещения влияет не только на освещение объектов, но и на тень.
Создадим новый проект – P19_2.
На рис. 26.4 вы можете видеть его окно Обозреватель решений.
(рис 26.4) Обозреватель решений, P19_2
Для представления моделей, выводимых на экран, мы используем класс modCls, в качестве ресурсов игры используем шар ball2 и плоскость .
Рассмотрим код класса modCls (листинг. 26.3.) В проекте P18_2 этот класс отвечал за вывод изображений на экран. Сейчас он выполняет ту же функцию, дополнив вывод изображений визуализацией теней.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace P19_2
{
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();
//Включаем альфа-смешение для эффекта полупрозрачности теней
graphics.GraphicsDevice.BlendState = BlendState.AlphaBlend;
}
public override void Initialize()
{
base.Initialize();
}
public override void Draw(GameTime gameTime)
{
//Выводим объект
foreach (ModelMesh mesh in myModel.Meshes)
{
//Для каждого эффекта в сети
foreach (BasicEffect effect in mesh.Effects)
{
//Объекты непрозрачны
effect.Alpha = 1.0f;
effect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
//Включить освещение
effect.LightingEnabled = true;
//Включить источник направленного света №0
effect.DirectionalLight0.Enabled = true;
//Настроить параметры
effect.DirectionalLight0.DiffuseColor = Vector3.One;
effect.DirectionalLight0.SpecularColor = Vector3.One;
//Направление света - в класса Game1 мы меняем направление
//по клавиатурным командам
effect.DirectionalLight0.Direction = Vector3.Normalize(LightDirection);
//Установка матриц
effect.World = WorldMatrix;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
}
mesh.Draw();
}
//Создать матрицу тени
shadow = Matrix.CreateShadow(-LightDirection, sPlane);
//Выводим тень
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
//Тень полупрозрачна
effect.Alpha = 0.4f;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
//При выводе тени умножаем мировую матрицу
//на матрицу вывода тени
effect.World = WorldMatrix * shadow;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
В листинге 26.4 вы можете найти код класса Game1.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
namespace P19_2
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модели
modCls[] cls;
//Положение моделей в пространстве
Vector3[] offset;
//Соотношение сторон окна вывода
float aspectRatio;
//Плоскость
modCls plane;
//Направление света
Vector3 LightDirection;
//Стрелки
Texture2D txtArrows;
Rectangle keyForward = new Rectangle(500, 280, 100, 100);
Rectangle keyUp = new Rectangle(600, 280, 100, 100);
Rectangle keyBack = new Rectangle(700, 280, 100, 100);
Rectangle keyLeft = new Rectangle(500, 380, 100, 100);
Rectangle keyDown = new Rectangle(600, 380, 100, 100);
Rectangle keyRight = new Rectangle(700, 380, 100, 100);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Вычислим соотношение сторон
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]);
}
txtArrows = Content.Load<Texture2D>("Arrows");
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
//Проверка попадания касания в область, занимаемую одним из элементов управления
private bool MenuSelect(Rectangle m, Vector2 p)
{
bool res = false;
if (p.X > m.X p.X < m.X + m.Width p.Y > m.Y p.Y < m.Y + m.Height)
{
res = true;
}
return res;
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Для случайных колебаний моделей
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]);
}
//Проверяем попадание мыши по объекту
CheckTouch();
//Проверяем пересечение с плоскостью
CheckPlane();
//Меняем позицию источника света по нажатию клавиш
LightSourceControl();
base.Update(gameTime);
}
//Изменение позиции источника света
void LightSourceControl()
{
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Увеличим позицию Y
if (MenuSelect(keyForward, touchLocation.Position))
{
LightDirection.Y += 0.5f;
}
//Уменьшим позицию по Z
if (MenuSelect(keyUp, touchLocation.Position))
{
LightDirection.Z -= 0.5f;
}
//Уменьшим позицию Y
if (MenuSelect(keyBack, touchLocation.Position))
{
LightDirection.Y -= 0.5f;
}
//Уменьшим позицию X
if (MenuSelect(keyLeft, touchLocation.Position))
{
LightDirection.X -= 0.5f;
}
//Увеличим позицию Z
if (MenuSelect(keyDown, touchLocation.Position))
{
LightDirection.Z += 0.5f;
}
//Увеличим позицию X
if (MenuSelect(keyRight, touchLocation.Position))
{
LightDirection.X += 0.5f;
}
}
}
//Изменим направление источника света для плоскости
plane.LightDirection = LightDirection;
//Изменим направление источника света для моделей
for (int i = 0; i < 3; i++)
{
cls[i].LightDirection = LightDirection;
}
}
//Обработка касаний
void CheckTouch()
{
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed)
{
//Получим луч, идущий от позиции мыши на экране в пространство
Ray pickRay = GetPickRay(touchLocation.Position);
//Переменная для хранения сферы, соответствующей объекту
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(Vector2 touchPos)
{
//Точки для вычисления направления луча - одна, соответствующая координате мыши
//вторая - направленная по оси Z
Vector3 nearsource = new Vector3(touchPos.X, touchPos.Y, 0f);
Vector3 farsource = new Vector3(touchPos.X, touchPos.Y, 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>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
//Вывод стрелок
spriteBatch.Begin();
spriteBatch.Draw(txtArrows, new Rectangle(500, 280, 300, 200), Color.White);
spriteBatch.End();
//Для нормального отображение 3D-сцены после работы spriteBatch
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
}
}
}
На рис. 26.5 вы можете видеть игровой экран проекта.
(рис 26.5) Игровой экран проекта P19_2
В данной лабораторной работе мы рассмотрели подходы к созданию игрового мира, рассмотрели некоторые особенности освещения объектов.
Разработайте на основе проекта P19_2 собственную игру, которая реализует следующие возможности:
К данной лабораторной работе прилагается видеоурок
и демонстрационный проект.
Цель работы: Научиться создавать игровой мир, применять эффекты к его объектам
Для создания игрового мира трехмерной игры используют несколько подходов.
Игровой мир можно конструировать из отдельных объектов. Например, для того, чтобы создать комнату, можно использовать плоскости для строительства стен, пола и потолка, различные объекты для создания предметов, находящихся в комнате. При таком подходе для обработки взаимодействия игрового объекта и окружения можно применить уже знакомый вам подход проверки столкновений объектов. Этот подход мы использовали в предыдущих проектах – в примере с передвижением объекта среди группы других объектов, в примере, где нужно было касаться объектов для того, чтобы не допустить их падения.
Игровой мир можно сконструировать в трехмерном редакторе – например – ту же комнату, или целое здание, или ландшафт – и загрузить в игру в виде отдельной модели.
Мир можно сконструировать средствами XNA
Рассмотрим работу с игровым миром, который представлен в виде отдельной модели, содержащей все его элементы.
В качестве объекта игрового мира, который мы использовали для демонстрации, применен готовый игровой объект –
Создадим новый игровой проект P19_1. На рис. 26.1 вы можете видеть его окно Обозреватель решений.
(рис 26.1) Обозреватель решений для проекта P19_1
В качестве игрового объекта мы используем модель modCls – он применяется для вывода моделей. Для управления игровым объектом мы применяем экранные стрелки, представленные файлом
В листинге 26.1 вы можете видеть код класса Game1.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
namespace P19_1
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
//Матрицы
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;
//Стрелки
Texture2D txtArrows;
Rectangle keyForward = new Rectangle(500, 280, 100, 100);
Rectangle keyUp = new Rectangle(600, 280, 100, 100);
Rectangle keyBack = new Rectangle(700, 280, 100, 100);
Rectangle keyLeft = new Rectangle(500, 380, 100, 100);
Rectangle keyDown = new Rectangle(600, 380, 100, 100);
Rectangle keyRight = new Rectangle(700, 380, 100, 100);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
TouchPanel.EnabledGestures = GestureType.Pinch;
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Загрузка моделей
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;
txtArrows = Content.Load<Texture2D>("Arrows");
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
//Проверка попадания касания в область, занимаемую одним из элементов управления
private bool MenuSelect(Rectangle m, Vector2 p)
{
bool res = false;
if (p.X > m.X p.X < m.X + m.Width p.Y > m.Y p.Y < m.Y + m.Height)
{
res = true;
}
return res;
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//обновить положение объекта
UpdateAvatarPosition();
//Обновить камеру
UpdateCameraThirdPerson();
//Сформировать объекты сцены
DrawScene();
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
//Вывод стрелок
spriteBatch.Begin();
spriteBatch.Draw(txtArrows, new Rectangle(500, 280, 300, 200), Color.White);
spriteBatch.End();
//Для нормального отображение 3D-сцены после работы spriteBatch
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
}
//Обновляем состояние объекта
void UpdateAvatarPosition()
{
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Движение вверх
if (MenuSelect(keyForward, touchLocation.Position))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
}
//Вперед
if (MenuSelect(keyUp, touchLocation.Position))
{
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 (MenuSelect(keyBack, touchLocation.Position))
{
Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);
Vector3 v = new Vector3(0, -forwardSpeed, 0);
v = Vector3.Transform(v, forwardMovement);
avatarPosition.Y += v.Y;
}
//Поворот влево
if (MenuSelect(keyLeft, touchLocation.Position))
{
avatarlRotation += rotationSpeed;
}
//Назад
if (MenuSelect(keyDown, touchLocation.Position))
{
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 (MenuSelect(keyRight, touchLocation.Position))
{
avatarlRotation -= rotationSpeed;
}
}
}
//До тех пор, пока разрешена обработка жестов
while (TouchPanel.IsGestureAvailable)
{
//прочитаем жест
GestureSample gs = TouchPanel.ReadGesture();
if (gs.GestureType == GestureType.Pinch)
{
//Найдем предыдущие координаты, вычтя из текущих координат приращение
Vector2 oldPos = gs.Position - gs.Delta;
Vector2 oldPos2 = gs.Position2 - gs.Delta2;
//Получим расстояние между текущими точками касания
float currentDist = Vector2.Distance(gs.Position, gs.Position2);
//Получим расстояние между предыдущими точками касания
float oldDist = Vector2.Distance(oldPos, oldPos2);
//Умножим переменную, отвечающую за масштабирование спрайта
//на результат деления текущего расстояния на предыдущее
if (currentDist / oldDist > 1)
{
viewAngle -= MathHelper.ToRadians(0.5f);
}
else
{
viewAngle += MathHelper.ToRadians(0.5f);
}
}
}
//Если новый угол обзора вышел за дозволенные пределы
//изменяем его
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);
}
}
}
В листинге 26.2 приведен код класса modCls.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace P19_1
{
public class modCls : Microsoft.Xna.Framework.DrawableGameComponent
{
//Модель
public Model myModel;
//Мировая матрица, матрицы вида и проекции
public Matrix WorldMatrix;
public Matrix ViewMatrix;
public Matrix ProjectMatrix;
//Соотношение сторон экрана
public float aspectRatio;
//Для управления графическим устройством
GraphicsDeviceManager graphics;
//Конструктор получает на вход
//игровой класс, модель, объект для управления графическим устройством
public modCls(Game game, Model mod, GraphicsDeviceManager grf)
: base(game)
{
myModel = mod;
graphics = grf;
aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height;
}
public override void Initialize()
{
base.Initialize();
}
public override void Draw(GameTime gameTime)
{
foreach (ModelMesh mesh in myModel.Meshes)
{
//Для каждого эффекта в сети
foreach (BasicEffect effect in mesh.Effects)
{
//Установить освещение по умолчанию
effect.LightingEnabled = true;
effect.EnableDefaultLighting();
//установить матрицы
effect.World = WorldMatrix;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
На рис. 26.2 и рис. 26.3 вы можете видеть различные состояния игрового экрана проекта.
(рис 26.2) Игровой экран проекта P19_1
(рис 26.3) Игровой экран проекта P19_1
Рассмотрим управление освещением и отображением теней в XNA. Существуют различные техники работы с тенями, мы рассмотрим отрисовку теней. В частности, мы модифицируем проект P18_2 таким образом, чтобы шарики, падающие на плоскость, отбрасывали тени.
Для создания тени мы воспользуемся методом CreateShadow объекта . Он позволяет создавать тень от объекта на основе информации об источнике освещения и плоскости, на которую должна проецироваться тень.
После того, как сознана матрица, представляющая собой тень, мы используем эту матрицу для вывода тени. Предварительно мы включаем альфа-смешение, и, выводя объекты, указываем, что они непрозрачны, выводя тень – настраиваем её прозрачность.
В этом же примере мы рассмотрим настройку источника света. В частности, мы применим для рисования объектов один направленный источник света, направление которого можно менять с помощью экранныэ элементов управления. Изменение направления освещения влияет не только на освещение объектов, но и на тень.
Создадим новый проект – P19_2.
На рис. 26.4 вы можете видеть его окно Обозреватель решений.
(рис 26.4) Обозреватель решений, P19_2
Для представления моделей, выводимых на экран, мы используем класс modCls, в качестве ресурсов игры используем шар ball2 и плоскость .
Рассмотрим код класса modCls (листинг. 26.3.) В проекте P18_2 этот класс отвечал за вывод изображений на экран. Сейчас он выполняет ту же функцию, дополнив вывод изображений визуализацией теней.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace P19_2
{
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();
//Включаем альфа-смешение для эффекта полупрозрачности теней
graphics.GraphicsDevice.BlendState = BlendState.AlphaBlend;
}
public override void Initialize()
{
base.Initialize();
}
public override void Draw(GameTime gameTime)
{
//Выводим объект
foreach (ModelMesh mesh in myModel.Meshes)
{
//Для каждого эффекта в сети
foreach (BasicEffect effect in mesh.Effects)
{
//Объекты непрозрачны
effect.Alpha = 1.0f;
effect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
//Включить освещение
effect.LightingEnabled = true;
//Включить источник направленного света №0
effect.DirectionalLight0.Enabled = true;
//Настроить параметры
effect.DirectionalLight0.DiffuseColor = Vector3.One;
effect.DirectionalLight0.SpecularColor = Vector3.One;
//Направление света - в класса Game1 мы меняем направление
//по клавиатурным командам
effect.DirectionalLight0.Direction = Vector3.Normalize(LightDirection);
//Установка матриц
effect.World = WorldMatrix;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
}
mesh.Draw();
}
//Создать матрицу тени
shadow = Matrix.CreateShadow(-LightDirection, sPlane);
//Выводим тень
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
//Тень полупрозрачна
effect.Alpha = 0.4f;
effect.View = ViewMatrix;
effect.Projection = ProjectMatrix;
//При выводе тени умножаем мировую матрицу
//на матрицу вывода тени
effect.World = WorldMatrix * shadow;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
В листинге 26.4 вы можете найти код класса Game1.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
namespace P19_2
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
//Матрицы
Matrix viewMatrix;
Matrix projMatrix;
//Модели
modCls[] cls;
//Положение моделей в пространстве
Vector3[] offset;
//Соотношение сторон окна вывода
float aspectRatio;
//Плоскость
modCls plane;
//Направление света
Vector3 LightDirection;
//Стрелки
Texture2D txtArrows;
Rectangle keyForward = new Rectangle(500, 280, 100, 100);
Rectangle keyUp = new Rectangle(600, 280, 100, 100);
Rectangle keyBack = new Rectangle(700, 280, 100, 100);
Rectangle keyLeft = new Rectangle(500, 380, 100, 100);
Rectangle keyDown = new Rectangle(600, 380, 100, 100);
Rectangle keyRight = new Rectangle(700, 380, 100, 100);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
// ЗАДАЧА: добавьте здесь логику инициализации
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Вычислим соотношение сторон
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]);
}
txtArrows = Content.Load<Texture2D>("Arrows");
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
// ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
}
//Проверка попадания касания в область, занимаемую одним из элементов управления
private bool MenuSelect(Rectangle m, Vector2 p)
{
bool res = false;
if (p.X > m.X p.X < m.X + m.Width p.Y > m.Y p.Y < m.Y + m.Height)
{
res = true;
}
return res;
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Для случайных колебаний моделей
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]);
}
//Проверяем попадание мыши по объекту
CheckTouch();
//Проверяем пересечение с плоскостью
CheckPlane();
//Меняем позицию источника света по нажатию клавиш
LightSourceControl();
base.Update(gameTime);
}
//Изменение позиции источника света
void LightSourceControl()
{
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Увеличим позицию Y
if (MenuSelect(keyForward, touchLocation.Position))
{
LightDirection.Y += 0.5f;
}
//Уменьшим позицию по Z
if (MenuSelect(keyUp, touchLocation.Position))
{
LightDirection.Z -= 0.5f;
}
//Уменьшим позицию Y
if (MenuSelect(keyBack, touchLocation.Position))
{
LightDirection.Y -= 0.5f;
}
//Уменьшим позицию X
if (MenuSelect(keyLeft, touchLocation.Position))
{
LightDirection.X -= 0.5f;
}
//Увеличим позицию Z
if (MenuSelect(keyDown, touchLocation.Position))
{
LightDirection.Z += 0.5f;
}
//Увеличим позицию X
if (MenuSelect(keyRight, touchLocation.Position))
{
LightDirection.X += 0.5f;
}
}
}
//Изменим направление источника света для плоскости
plane.LightDirection = LightDirection;
//Изменим направление источника света для моделей
for (int i = 0; i < 3; i++)
{
cls[i].LightDirection = LightDirection;
}
}
//Обработка касаний
void CheckTouch()
{
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed)
{
//Получим луч, идущий от позиции мыши на экране в пространство
Ray pickRay = GetPickRay(touchLocation.Position);
//Переменная для хранения сферы, соответствующей объекту
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(Vector2 touchPos)
{
//Точки для вычисления направления луча - одна, соответствующая координате мыши
//вторая - направленная по оси Z
Vector3 nearsource = new Vector3(touchPos.X, touchPos.Y, 0f);
Vector3 farsource = new Vector3(touchPos.X, touchPos.Y, 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>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
//Вывод стрелок
spriteBatch.Begin();
spriteBatch.Draw(txtArrows, new Rectangle(500, 280, 300, 200), Color.White);
spriteBatch.End();
//Для нормального отображение 3D-сцены после работы spriteBatch
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
}
}
}
На рис. 26.5 вы можете видеть игровой экран проекта.
(рис 26.5) Игровой экран проекта P19_2
В данной лабораторной работе мы рассмотрели подходы к созданию игрового мира, рассмотрели некоторые особенности освещения объектов.
Разработайте на основе проекта P19_2 собственную игру, которая реализует следующие возможности:
К данной лабораторной работе прилагается видеоурок
и демонстрационный проект.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.