Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Сценарий игры состоит в том, что сверху вниз экрана падают мячи, а задача игрока не допустить, чтобы хотя бы один из них упал на площадь футбольного поля. С помощью выстрелов, которые должны попасть в мячик, игрок должен удерживать мячи на лету определенное время. Удачный игрок переходит на новый уровень.
В трехмерной графике используется мировая система координат, ориентированная так, что плоскость xOy направлена параллельно экрану монитора, а ось Oz направлена либо к пользователю перпендикулярно экрану (правая система координат), либо от пользователя вглубь экрана (левая система координат). Если вытянуть в разные стороны три пальца правой руки (большой, указательный и средний) под прямыми углами, то большой будет указывать направление оси x, указательный - оси y, а средний - оси z. В DirectX используется левая система координат, а в OpenGL и XNA Framework - правая.
Поверхность любого объемного тела описывается с помощью примитивов - простых плоских геометрических фигур, чаще всего треугольников. Каждый треугольник задан координатами своих вершин в мировой системе координат. Относительно этой системы задаются координаты вершин всех примитивов каждого объекта ( модели ). Совокупность трехмерных объектов называется сценой. Над трехмерными моделями могут выполняться следующие операции, которые называются модельными преобразованиями:
Чтобы выполнить одно из указанных действий для объекта в целом, нужно применить его к вершине каждого примитива, описывающего модель. Для выполнения таких действий служит мировая матрица преобразования. Это матрица размером 4x4, которая может принимать одно из следующих значений, в зависимости от выполняемых действий:
| Поворот относительно оси Ox | |||
| 1 | 0 | 0 | 0 |
| 0 | cos(a) | sin(a) | 0 |
| 0 | -sin(a) | cos(a) | 0 |
| 0 | 0 | 0 | 1 |
| Поворот относительно оси Oy | |||
| cos(a) | 0 | -sin(a) | 0 |
| 0 | 1 | 0 | 0 |
| sin(a) | 0 | cos(a) | 0 |
| 0 | 0 | 0 | 1 |
| Поворот относительно оси Oz | |||
| cos(a) | sin(a) | 0 | 0 |
| -sin(a) | cos(a) | 0 | 0 |
| 0 | 0 | 1 | 0 |
| 0 | 0 | 0 | 1 |
| Смещение на Tx, Ty, Tz | |||
| 1 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 |
| 0 | 0 | 1 | 0 |
| Tx | Ty | Tz | 1 |
| Масштабирование на Sx, Sy, Sz | |||
| Sx | 0 | 0 | 0 |
| 0 | Sy | 0 | 0 |
| 0 | 0 | Sz | 0 |
| 0 | 0 | 0 | 1 |
Начальная расстановка моделей на сцене определяется начальными координатами примитивов модели, последовательно умноженными на одну или несколько матриц модельного преобразования. Применение одной и той же матрицы преобразования ко всем моделям меняет всю сцену вместе с предыдущими координатами вершин примитивов.
Кроме перечисленных модельных матриц имееются еще две матрицы:
Матрица вида содержит в себе координаты размещения камеры в трехмерном пространстве. У камеры есть вертикальная ось, совпадающая с координатой Oy мировой системы координат. Обычно ограничивают операции преобразования для координат камеры поворотами относительно этой оси и параллельными смещениями вправо/влево вверх/вниз, а также отдалением/приближением камеры к сцене.
Проекционная матрица создает проекцию трехмерной сцены на экран плоского монитора. Она строит прямоугольную пирамиду с вершиной в камере и обеспечивает переднюю и заднюю стенки отсечения трехмерного пространства. Это позволяет вычленять только определенные куски пространства и снижает нагрузку на процессор компьютера.
Новый проект мы разместим в уже существующей папке XNAProjects, созданной нами ранее при выполнении работы Lab58.
Откройте решение XNAProjects работы Lab58 и
в панели Solution Explorer вызовите контекстное меню для узла самого
решения Solution
Командой Add/New Project вызовите
окно мастера и настройте его, с учетом версии библиотеки .NET Framework 3.0,
так
Переименуйте файл Game1.cs нового
проекта в StartGame3D.csКак видно из панели Solution Explorer, теперь в решении присутствуют
два проекта: Game2D и новый проект Game3D.
Обратите внимание, что Game2D выделен жирным шрифтом. Это значит, что
он по умолчанию в оболочке установлен как стартовый. Если сейчас запустить
выполнение решения, то будет выполнен выделенный проект Game2D. В дальнейшем
есть два пути: либо назначить новый проект стартовым, либо исключить из решения
старый проект, тогда новый проект автоматически станет стартовым. Для тренировки,
мы вначале назначим новый проект стартовым, а потом исключим старый проект.
В панели Solution
Explorer вызовите контекстное меню для узла Game3D и выполните
команду Set as StartUp Project, чтобы сделать новый
проект стартовым
Добавьте в конструктор
класса заготовки нового проекта игры код установки размеров окнаpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 300;
graphics.PreferredBackBufferHeight = 200;
}
Щелкните на кнопке Start
Debugging (F5) и убедитесь, что запустилась именно заготовка приложения
нового проекта Game3D
В панели Solution
Explorer вызовите контекстное меню для узла Game2D и выполните
команду Remove, чтобы исключить из решения старый проект, который
после этого останется существовать в пассивном режимеВ прошлой игре мы устанавливали жестко размер экрана, и если это разрешение не совпадало с текущим перед запуском игры, то после запуска приложения система некоторое время настраивала требуемое разрешение, то есть была некоторая задержка в появлении экрана. В этом приложении мы оставим неизменными текущие размеры экрана, но определим их из видеоадаптера и запомним в полях класса игры для дальнейшего использования.
Удалите из конструктора
класса игры код жесткой установки размеров экранаpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
//graphics.PreferredBackBufferWidth = 300;
//graphics.PreferredBackBufferHeight = 200;
}
Поля класса для удобства разместим в секции #region.
Дополните основной
класс игры и его методы кодом автоматической установки размеров
экрана и выхода из игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
#endregion
.....................................................
protected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
base.Initialize();
}
.....................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
base.Update(gameTime);
}
.....................................................
}
В текущее
пространство имен перед классом StartGame3D добавьте объявление типа
перечисления GameState с
элементами обозначений игровых состояний, а в классе создайте поле
этого типа и инициализируйте ее заставкой начала игрыnamespace Game3D
{
// Объявление состояний экранов
enum GameState
{
AboutScreen, // Экран с информацией об игре
GameOverScreen, // Экран проигрыша
GameScreen, // Экран игрового процесса
HelpScreen, // Инструкция о правилах
MenuScreen, // Меню игры
GameScreen, // Первая игровая заставка
VictotyScreen // Экран выигрыша
}
public class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.GameScreen; // Переменная состояния игры
#endregion
..................................................
}
}
Для работы нужного
блока кода в соответствии с установленным состоянием введите переключатель switch в
функциях Update() и Draw()public class StartGame3D : Microsoft.Xna.Framework.Game
{
...................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
}
}
В 3D-задачах используются уже не спрайты, а модели. Трехмерная модель в памяти
компьютера называется мэш-объектом и хранится до загрузки в файле с расширением .x, возможно, вместе со своей текстурой. В трехмерной модели содержится
набор координат примитивов (простых плоских фигур, чаще всего треугольников),
с помощью которых конструируется сложная поверхность модели. Чем сложнее объемная
фигура, тем больше треугольников требуется для ее точного описания.
Создадим класс, который будет загружать модель мяча и обеспечивать вывод его изображения на экран.
Командой Project/Add Class добавьте к проекту класс с именем ModelClass
Скопируйте из файла StartGame3D.cs в новый файл ModelClass.cs секцию using с подключениями пространств
имен
Заполните файл ModelClass.cs следующим
кодом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class ModelClass
{
// Поля
Model model;
Vector3 position;
// Свойства
public Vector3 Position
{
get { return position; }
set { position = value; }
}
// Конструктор
public ModelClass()
{
position = new Vector3(0, 0, 0);
}
// Загрузка модели
public void Load(ContentManager content, String strModel)
{
model = content.Load<Model>(strModel);
}
// Рисуем модель на экране
public void DrawModel(Matrix world, Matrix view, Matrix proj)
{
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index] * world;
effect.View = view;
effect.Projection = proj;
}
mesh.Draw();
}
}
}
}
Добавьте в узел Content проекта папку Models командой Add/New Folder и скопируйте в нее командой Add/Existing Item файл Soccerball.x из прилагаемого каталога Source
Добавьте в секцию #region
Поля класса файла StartGame3D.cs следующие поляpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.GameScreen; // Переменная состояния игры
Matrix world;
Matrix view;
Matrix proj;
float aspectRatio;
float FOV = MathHelper.PiOver4;
float nearClip = 1.0f;
float farClip = 1000.0f;
Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
ModelClass ball;
#endregion
..........................................................
}
Добавьте в конструктор
класса StartGame3D код создания экземпляра класса ModelClasspublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляра класса ModelClass
ball = new ModelClass();
}
Добавьте в функцию Initialize() класса StartGame3D код вычисления коэффициента aspectRatio для
компенсации искажения в проекционной матрицеprotected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
aspectRatio = (float)screenWidth / screenHeight; // Коэффициент искажения проекции
base.Initialize();
}
Коэффициент aspectRatio служит для предотвращения искажения изображения,
проецируемого на плоскость экрана. Поскольку областью просмотра в полноэкранном
режиме служит весь экран и он не имеет равных сторон, то круглый мяч в трехмерном
пространстве будет проецироваться на экран aspectRatio как раз и компенсирует это искажение.
Добавьте в функцию LoadContent() класса StartGame3D код загрузки в программу модели мячаprotected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
ball.Load(this.Content, "Models\\Soccerball");
}
Добавьте в вариант GameState.SplashScreen переключателя switch функции Draw() код отображения мяча
на экранеprotected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
ball.DrawModel(world, view, proj);
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Откомпилируйте проект
командой Rebuild и запустите его на выполнение. Должен получиться рисунок
с начальным положением мяча
Здесь мы выведем на экран три разных мяча и будем перемещать их в пространстве.
Добавьте
в папку Models проекта командой Add/New Item из прилагаемого
каталога Source файлы SoccerballGreen.x и SoccerballRed.x
Добавьте в класс ModelClass поле
и свойство доступа, определяющие скорость падения мячей сверху внизclass ModelClass
{
// Поля
Model model;
Vector3 position;
float speed = 20.0f / 60.0f;
// Свойства
public Vector3 Position
{
get { return position; }
set { position = value; }
}
public float Speed
{ get { return speed; } }
// Конструктор
public ModelClass()
{
position = new Vector3(0, 0, 0);
}
// Загрузка модели
public void Load(ContentManager content, String strModel)
{
model = content.Load<Model>(strModel);
}
// Рисуем модель на экране
public void DrawModel(Matrix world, Matrix view, Matrix proj)
{
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index] * world;
effect.View = view;
effect.Projection = proj;
}
mesh.Draw();
}
}
}
Переменная speed инициализируется значением 20.0f
/ 60.0f, где 60 означает количество секунд в одной минуте.
Добавьте в основной
класс игры код загрузки трех моделей мяча и установки их начальной позицииpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.GameScreen; // Переменная состояния игры
Matrix world;
Matrix view;
Matrix proj;
float aspectRatio;
float FOV = MathHelper.PiOver4;
float nearClip = 1.0f;
float farClip = 1000.0f;
Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
#endregion
public StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
}
protected override void Initialize()
{
.......................................................
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Устанавливаем начальную позицию объектов
BeginPosition();
}
// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
-rand.Next(20, 150));
}
}
.......................................................
}
Модифицируйте метод Draw() основного класса игры так, чтобы отображались все загруженные
мячиprotected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Откомпилируйте проект
командой Rebuild и выполните его. Должно получиться примерно следующее
Для имитации движения мячей сверху вниз нам нужно уменьшать координату y в
методе обновленных вычислений Update(), поскольку ось Oy направлена
снизу вверх. Кроме того, на данном этапе предусмотрим временный MouseClick(). Ну, и временно для этого включим стандартный курсор
в полноэкранном режиме.
Добавьте в класс StartGame3D код
включения курсора, а также методы MoveBalls() и MouseClick() со следующим содержимымpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
this.IsMouseVisible = true;
}
.........................................................
void MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
}
}
void MouseClick()
{
mouseState = Mouse.GetState();
if (mouseState.LeftButton == ButtonState.Pressed)
BeginPosition();
}
Добавьте в вычислительный
метод Update() основного класса игры вызов функций MoveBalls() и MouseClick()protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
Запустите проект и
убедитесь в работоспособности добавленных в программу механизмовНа данном этапе мы добавим к программе вместо стандартного курсора прицел для стрельбы по мячам и создадим механизм попаданий выстрелов в цель.
В любой трехмерной игре могут на равных применяться и двухмерные изображения.
Наша программа не исключение и мы вместо курсора будем использовать прицел,
который реализуем в виде спрайта. Управление спрайтом и его отображение на
экране упакуем в класс. Но чтобы не повторять заново уже разработанное,
используем для этих целей класс Sprite из предыдущей лабораторной работы.
Командой Project/Add Existing Item скопируйте из предыдущей лабораторной работы или прилагаемого каталога Source файл Sprite.cs в корневой каталог текущего проектаВ этом разделе мы будем использовать
библиотечный класс BoundingSphere для определения столкновений моделей
и курсора и нам понадобится радиус ограничивающей мячи сферы. Чтобы не измерять
этот радиус, введем переменную radius и
определим ее значение, которое
немного больше радиуса мячей.
Добавьте в класс ModelClass поле и свойство доступа для работы с радиусом попадания в цельclass ModelClass
{
// Поля
Model model;
Vector3 position;
float speed = 20.0f / 60.0f;
float radius = 8.0F;
// Свойства
public Vector3 Position
{
get { return position; }
set { position = value; }
}
public float Speed
{ get { return speed; } }
public float Radius
{ get { return radius; } }
...........................................
}
В конструкторе класса StartGame3D отключите стандартный курсор мыши, закомментировав соответствующий
кодpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
}
Через Проводник решений
создайте для узла Content командой Add/New Folder контекстного
меню папку Textures и
скопируйте в нее из прилагаемого каталога Source файл рисунка прицела cursor.png
В классе StartGame3D создайте поле для хранения ссылки на объект курсора и инициализируйте ее
экземпляром класса Sprite#region Поля класса GraphicsDeviceManager graphics; SpriteBatch spriteBatch; int screenWidth, screenHeight; // Размеры экрана KeyboardState keyboardState; // Буфер клавиатуры GameState gameState = GameState.GameScreen; // Переменная состояния игры Matrix world; Matrix view; Matrix proj; float aspectRatio; float FOV = MathHelper.PiOver4; float nearClip = 1.0f; float farClip = 1000.0f; Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f); //ModelClass ball; ModelClass[] ball = new ModelClass[3]; MouseState mouseState; Random rand = new Random(); // Создание объекта курсора-прицела Game2D.Sprite cursor = new Game2D.Sprite(); #endregion
В методе LoadContent() класса StartGame3D загрузите
спрайт курсора в объект, а в методе Draw() поместите
код рисования курсора последним - после рисования мячей, чтобы он оказался
самым ближним к пользователю на экране монитораprotected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
}
.......................................................
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Система координат двухмерного и трехмерного пространств в XNA Framework существенно отличаются. Поэтому нужен особый механизм перевода двухмерных координат курсора, генерируемых операционной системой, в трехмерное пространство, построенное XNA. Мы воспользуемся готовым решением, приведенным в справочной информации по XNA Game Studio. Главным условием достоверности пересчета координат курсора на трехмерную сцену является совпадение значений матрицы вида и проекционной матрицы, применяемых для построения сцены, с теми же матрицами, определения координат курсора на этой сцене.
Добавьте к классу StartGame3D метод GetPickRay() пересчета координат курсора в трехмерное пространство
сценыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
............................................................
// Метод пересчета координат курсора в трехмерную сцену игры
Ray GetPickRay()
{
// Читаем стандартные координаты курсора
mouseState = Mouse.GetState();
// Вспомогательные переменные
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
// Обнуляем мировую матрицу
world = Matrix.CreateTranslation(0, 0, 0);
// Матрица вида, как в трехмерной сцене
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Матрица проекции, как в трехмерной сцене
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
// Проекция курсора на ближнюю плоскость отсечения объема
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
// Проекция курсора на дальнюю плоскость отсечения объема
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
return new Ray(nearPoint, direction);
}
}
Теперь нужно применить этот метод для создания механизма перемещения спрайта
курсора по экрану и определения попаданий в движущиеся объекты мячей. Спусковым
крючком выстрела по мячу будет служить левая кнопка мыши, поэтому весь механизм
поместим в уже созданную нами ранее заготовку метода MouseClick().
Добавьте в класс StartGame3D поле
массива объектов типа BoundingSphere и модифицируйте метод MouseClick() следующим
образом// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
-rand.Next(20, 150));
}
}
}
}
Запустите приложение
и проверьте механизм стрельбы по целямВ этом разделе мы загрузим в игру модель стадиона, на фоне которой будут летать мячи, и установим камеру под определенным углом, одинаковым и для мячей и для стадиона.
В классе StartGame3D установите
новое значение поля camera для выбора наилучшего ракурса (угла зрения)
всей сцены игры и добавьте объект стадиона, в который мы позднее загрузим
трехмерный мэшpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.GameScreen; // Переменная состояния игры
Matrix world;
Matrix view;
Matrix proj;
float aspectRatio;
float FOV = MathHelper.PiOver4;
float nearClip = 1.0f;
float farClip = 1000.0f;
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
#endregion
..................................................
}
Добавьте из прилагаемого
каталога Source в папку Models проекта файл с именем stadium 1.x
В методе LoadContent() основного
класса игры загрузите модель стадиона в созданный ранее объект stadium программыprotected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
}
В методе Draw() основного
класса игры вставьте код вывода изображения стадиона на экран монитора (раньше
кода вывода других объектов)protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
// Нарисовать модель стадиона самой первой
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Запустите игру и убедитесь,
что начальные позиции мячей нуждаются в корректировкеОбратите внимание на позиции установки всех моделей игры, включая камеру.
Если бы камера была совмещена с экраном монитора, то начало мировой системы
координат находилось бы в плоскости экрана. Позиции мячей мы формируем в
отрицательном направлении оси Oz в диапазоне [-20,-150] пикселов.
Сам стадион рисуем
относительно начала мировой системы координат. И для того, чтобы увидеть его
целиком, мы камеру отодвинули в положительном направлении на 250 пикселов.
В результате мячи удалились от наблюдателя и к тому же падают на дальней границе
стадиона. Поэтому нужно приблизить мячи к наблюдателю, скорректировав их начальные
координаты по оси Oz в новом диапазоне [-50,150].
Введите изменения в
код функций BeginPosition() и MouseClick(), где задается координата z полета мячей// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
.....................................................
// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
}
}
Запустите приложение
и убедитесь, что мячи теперь падают над поверхностью поля стадионаНам нужно сделать, чтобы мячи останавливались на поверхности поля, а не проваливались
сквозь него. Поверхность поля стадиона проходит через ось Oy мировой
системы координат на расстоянии ( -32 ) пиксела, поэтому двигать мячи
в вертикальном направлении следует только до этой величины.
Внесите следующие изменения
в функцию MoveBalls() основного класса игрыvoid MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
if (ball[i].Position.Y > -32)
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
}
}
Запустите приложение
и убедитесь, что мячи теперь останавливаются на поверхности поля стадиона
Дополним сцену подходящим задним фоном, чтобы стадион и вся сцена смотрелись более красочно.
Добавьте в папку Textures проекта файл hallake001.png из прилагаемого каталога Source
В классе StartGame3D добавьте поле-ссылку background для хранения объекта текстуры, а в
методах LoadContent() и Draw() - код загрузки и вывода рисунка на экран#region Поля класса
..................................................
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
#endregion
..................................................
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
}
..................................................
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Нарисовать фон самым первым
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
background.DrawSprite(spriteBatch);
spriteBatch.End();
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
// Нарисовать стадион
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Запустите приложение
и полюбуйтесь на солидную сцену, сделанную своими руками
В самом начале построения приложения мы в методах LoadContent() и Draw() с
помощью оператора switch заложили несколько состояний игры, определенных
в перечислении GameState
AboutScreen, // Экран с информацией об игреGameOverScreen, // Экран проигрышаGameScreen, // Экран игрового процессаHelpScreen, // Инструкция
о правилахMenuScreen, // Меню игрыSplashScreen, // Первая игровая заставкаVictotyScreen, // Экран выигрышаДо сих пор мы реализовали только одно из них - саму
игру. В этом разделе закодируем еще три состояния: SplashScreen, AboutScreen, HelpScreen. Смену режимов закрепим
за клавишами Enter и Esc.
Первой будет появляться игровая заставка, из которой по клавише Enter попадем в меню, а по клавише Esc выйдем из игры. Игровая заставка будет состоять из нескольких отдельных компонентов:
splash.pngtitle.pngenter.pnghole .png, которое мы разбросаем по
экрану заставки случайным образом в 30 экземплярахSoccerball.x посередине экрана на переднем плане
заставкиПрежде всего нужно скопировать в проект эти компоненты заставки.
В панели Solution
Explorer вызовите контекстное меню для каталога проекта Textures и дабавьте
командой Add/Existing Item из прилагаемого каталога Source файлы
с рисунками splash.png, title.png, enter.png, hole .pngКод игровой заставки упакуем в класс SplashScreen.
Командой Project/Add Class меню
оболочки добавьте к проекту файл SplashScreen.cs и
скопируйте в него все инструкции using из файла StartGame3D
Заполните файл SplashScreen.cs следующим
кодом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class SplashScreen
{
#region Поля класса
Texture2D splash, title, enter, hole; // Объекты рисунков
Vector2[] posHole = new Vector2[30]; // Массив позиций рисунка пули
Random rand = new Random(); // Генератор случайных чисел
ModelClass ball = new ModelClass(); // Объект мяча
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс камеры 45 градусов
float nearClip = 1.0f; // Ближняя отсекающая плоскость
float farClip = 1000.0f; // Дальняя отсекающай плоскость
float angle = 0; // Угол поворота
Matrix world, view, proj; // Матрицы преобразований
#endregion
// Инициализация объектов
// content - контент экземпляра основного класса игры
// width - текущаяя ширина экрана
// height - текущая высота экрана
public void InitializeSplashScreen(ContentManager content, int width, int height)
{
// Загружаем в объекты изображения и модель мяча
splash = content.Load<Texture2D>("Textures\\splash");
title = content.Load<Texture2D>("Textures\\title");
enter = content.Load<Texture2D>("Textures\\enter");
hole = content.Load<Texture2D>("Textures\\hole");
ball.Load(content, "Models\\Soccerball");
ball.Position = new Vector3(0, 0, 0);// В центре экрана, где начало мировой системы координат
// Разбрасываем по экрану пулевые отверстия
for (int i = 0; i < posHole.Length; i++)
{
posHole[i].X = rand.Next(20, width - 60);
posHole[i].Y = rand.Next(100, height - 60);
}
// Коэффициент искажения проекции
aspectRatio = (float)width / height;
}
// Вывод заставки на экран
// spriteBatch - объект рисования спрайтов
// graphics - объект графического устройства GDI
// width - текущаяя ширина экрана
// height - текущая высота экрана
// gameTime - длительность одного такта игры
public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
int width, int height, GameTime gameTime)
{
// Очищаем экран своим цветом
graphics.GraphicsDevice.Clear(Color.DarkGreen);
// Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(splash, new Vector2(width / 2 - splash.Width / 2,
height / 2 - splash.Height / 2), Color.White);
spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
30), // Чуть опустили
Color.White);
spriteBatch.Draw(enter, new Vector2(width / 2 - enter.Width / 2,
height - enter.Height - 30), // Чуть приподняли
Color.White);
for (int i = 0; i < posHole.Length; i++)
spriteBatch.Draw(hole, posHole[i], Color.White);
spriteBatch.End();
// Рисуем объемный мяч на переднем плане
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 150.0f), Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
world = Matrix.CreateTranslation(ball.Position);
angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 2.0f);// Увеличиваем угол вращения мяча
world *= Matrix.CreateRotationY(angle);
ball.DrawModel(world, view, proj);
}
}
}
Для тестирования разработанного
класса SplashScreen введите временно следующие изменения в
основной класс игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
//GameState gameState = GameState.GameScreen; // Переменная состояния игры
GameState gameState = GameState.SplashScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
#endregion
protected override void LoadContent()
{
....................................................
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
.......................................................
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
}
Запустите приложение
и убедитесь, что код класса SplashScreen работает нормально в основном
цикле игры. Получится картинка, приведенная ниже, с вращающимся в центре
мячом
Код этой заставки мы спроектируем на основе только что созданного класса SplashScreen,
внеся в него незначительные коррективы. Эти изменения будет связаны в основном
с тем, что вместо одного мяча, вращающегося в центре экрана, мы введем три
мяча, которые будут вращаться как и прежде - относительно оси y, и одновременно
- относительно оси z. В итоге получим сложное вращение мячей в плоскости экрана
относительно его центра.
Загрузите в папку Textures проекта
файлы изображений about.png и esc.png из прилагаемого к работе каталога Source
В панели Solution
Explorer сделайте копию файла SplashScreen.cs и переименуйте
ее в файл AboutScreen.cs
Модифицируйте файл AboutScreen.cs так,
чтобы он выглядел следующим образом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class AboutScreen
{
#region Поля класса
Texture2D about, title, esc, hole; // Объекты рисунков
Vector2[] posHole = new Vector2[30]; // Массив позиций рисунка пули
Random rand = new Random(); // Генератор случайных чисел
ModelClass [] ball = new ModelClass[3]; // Объекты мячей
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс камеры 45 градусов
float nearClip = 1.0f; // Ближняя отсекающая плоскость
float farClip = 1000.0f; // Дальняя отсекающай плоскость
float angle = 0; // Угол поворота
Matrix world, view, proj; // Матрицы преобразований
#endregion
// Инициализация объектов
// content - контент экземпляра основного класса игры
// width - текущаяя ширина экрана
// height - текущая высота экрана
public void InitializeSplashScreen(ContentManager content, int width, int height)
{
// Создаем объекты для мячей
for (int i = 0; i < ball.Length; i++)
ball[i] = new ModelClass();
// Загружаем в объекты изображения и модель мяча
about = content.Load<Texture2D>("Textures\\about");
title = content.Load<Texture2D>("Textures\\title");
esc = content.Load<Texture2D>("Textures\\esc");
hole = content.Load<Texture2D>("Textures\\hole");
ball[0].Load(content, "Models\\Soccerball");
ball[0].Position = new Vector3(-30, 40, -30);
ball[1].Load(content, "Models\\SoccerballGreen");
ball[1].Position = new Vector3(50, 30, -50);
ball[2].Load(content, "Models\\SoccerballRed");
ball[2].Position = new Vector3(-50, -30, 40);
// Разбрасываем по экрану пулевые отверстия
for (int i = 0; i < posHole.Length; i++)
{
posHole[i].X = rand.Next(20, width - 60);
posHole[i].Y = rand.Next(100, height - 60);
}
// Коэффициент искажения проекции
aspectRatio = (float)width / height;
}
// Вывод заставки на экран
// spriteBatch - объект рисования спрайтов
// graphics - объект графического устройства GDI
// width - текущаяя ширина экрана
// height - текущая высота экрана
// gameTime - длительность одного такта игры
public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
int width, int height, GameTime gameTime)
{
// Очищаем экран своим цветом
graphics.GraphicsDevice.Clear(Color.DarkGreen);
// Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(about, new Vector2(width / 2 - about.Width / 2,
height / 2 - about.Height / 2), Color.White);
spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
30), // Чуть опустили
Color.White);
spriteBatch.Draw(esc, new Vector2(width / 2 - esc.Width / 2,
height - esc.Height - 30), // Чуть приподняли
Color.White);
for (int i = 0; i < posHole.Length; i++)
spriteBatch.Draw(hole, posHole[i], Color.White);
spriteBatch.End();
// Рисуем мячи на переднем плане
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 260.0f), Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
for (int i = 0; i < ball.Length; i++)
{
world = Matrix.CreateTranslation(ball[i].Position);
ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
}
}
}
}
Для тестирования
разработанного класса AboutScreen введите временно следующие изменения
в основной класс игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.AboutScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
#endregion
..............................................................
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
..............................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
..............................................................
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
..............................................................
}
Запустите приложение
и убедитесь, что код класса AboutScreen работает нормально в основном
цикле игры. Получится картинка, приведенная ниже, с вращающимися в плоскости
экрана относительно его центра мячами
Код этой заставки мы спроектируем на основе только что созданного класса AboutScreen,
внеся в него вместо загрузки файла about.png загрузку файла help.png. Здесь
получится немного нерационально, потому что остальной код класса будет неизменным.
Было бы проще добавить в конструктор класса AboutScreen параметр с именем загружаемого
файла. Но учитывая, что у нас подобных заставок будет всего две, просто повторим
этот код еще раз.
Загрузите в папку Textures проекта
файл изображений help.png из
прилагаемого к работе каталога Source
В панели Solution
Explorer сделайте копию файла AboutScreen.cs и переименуйте
ее в файл HelpScreen.cs
Перейдите в режим редактирования
файла HelpScreen.cs,
вызовите комбинацией клавиш Ctrl-H окно замены редактора кода и замените
все вхождения about на help документа.
После этого файл должен стать таким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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class HelpScreen
{
#region Поля класса
Texture2D help, title, esc, hole; // Объекты рисунков
Vector2[] posHole = new Vector2[30]; // Массив позиций рисунка пули
Random rand = new Random(); // Генератор случайных чисел
ModelClass [] ball = new ModelClass[3]; // Объекты мячей
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс камеры 45 градусов
float nearClip = 1.0f; // Ближняя отсекающая плоскость
float farClip = 1000.0f; // Дальняя отсекающай плоскость
float angle = 0; // Угол поворота
Matrix world, view, proj; // Матрицы преобразований
#endregion
// Инициализация объектов
// content - контент экземпляра основного класса игры
// width - текущаяя ширина экрана
// height - текущая высота экрана
public void InitializeSplashScreen(ContentManager content, int width, int height)
{
// Создаем объекты для мячей
for (int i = 0; i < ball.Length; i++)
ball[i] = new ModelClass();
// Загружаем в объекты изображения и модель мяча
help = content.Load<Texture2D>("Textures\\help");
title = content.Load<Texture2D>("Textures\\title");
esc = content.Load<Texture2D>("Textures\\esc");
hole = content.Load<Texture2D>("Textures\\hole");
ball[0].Load(content, "Models\\Soccerball");
ball[0].Position = new Vector3(-30, 40, -30);
ball[1].Load(content, "Models\\SoccerballGreen");
ball[1].Position = new Vector3(50, 30, -50);
ball[2].Load(content, "Models\\SoccerballRed");
ball[2].Position = new Vector3(-50, -30, 40);
// Разбрасываем по экрану пулевые отверстия
for (int i = 0; i < posHole.Length; i++)
{
posHole[i].X = rand.Next(20, width - 60);
posHole[i].Y = rand.Next(100, height - 60);
}
// Коэффициент искажения проекции
aspectRatio = (float)width / height;
}
// Вывод заставки на экран
// spriteBatch - объект рисования спрайтов
// graphics - объект графического устройства GDI
// width - текущаяя ширина экрана
// height - текущая высота экрана
// gameTime - длительность одного такта игры
public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
int width, int height, GameTime gameTime)
{
// Очищаем экран своим цветом
graphics.GraphicsDevice.Clear(Color.DarkGreen);
// Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(help, new Vector2(width / 2 - help.Width / 2,
height / 2 - help.Height / 2), Color.White);
spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
30), // Чуть опустили
Color.White);
spriteBatch.Draw(esc, new Vector2(width / 2 - esc.Width / 2,
height - esc.Height - 30), // Чуть приподняли
Color.White);
for (int i = 0; i < posHole.Length; i++)
spriteBatch.Draw(hole, posHole[i], Color.White);
spriteBatch.End();
// Рисуем мячи на переднем плане
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 260.0f), Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
for (int i = 0; i < ball.Length; i++)
{
world = Matrix.CreateTranslation(ball[i].Position);
ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
}
}
}
}
Для тестирования разработанного
класса HelpScreen введите временно следующие изменения в основной
класс игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.HelpScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
#endregion
........................................................
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
............................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
.........................................................
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
.............................................................
}
Запустите приложение
и убедитесь, что код класса HelpScreen работает нормально в основном
цикле игры. Получится картинка, приведенная ниже, с вращающимися в плоскости
экрана относительно его центра мячами
Для формирования заставки с элементами меню создадим игровой класс MenuScreen.
Меню будет содержать фоновую заставку, титульный заголовок, подсказку действий
пользователя по выбору вариантов и аналоги кнопок выбора команд. Как и в двух
предыдущих заставках оставим три мяча, вращающихся относительно центра в плоскости
экрана.
В центре экрана разместим команды:
GameScreenHelpScreenAboutScreenКаждую команду будем представлять текстовой надписью, под которую будем подкладывать
желтый прямоугольник, если она имеет фокус ввода. Для этого придется подключить
кириллический шрифт требуемого размера и гарнитуры. По краям экрана для украшательства
разместим фигуры ' girl ' свободного поведения. И еще уберем отображение
пуль.
Добавьте из
прилагаемого к работе каталога Source в папку Textures проекта файлы menu.jpg, cursorMenu.png, girlLeft.png, girlRight.png
Добавьте к папке Content подпапку Fonts
Добавьте в папку Fonts файл font.spritefont
Откорректируйте файл font.spritefont следующим
образом<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<FontName>Arial</FontName>
<Size>24</Size>
<Spacing>0</Spacing>
<UseKerning>true</UseKerning>
<Style>Bold</Style>
<CharacterRegions>
<CharacterRegion>
<Start>#32;</Start>
<End>#126;</End>
</CharacterRegion>
<CharacterRegion>
<Start>#1040;</Start>
<End>#1103;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>
Скопируйте файл HelpScreen.cs и
назовите копию MenuScreen.cs
Откорректируйте файл
следующим образом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
// Объявление состояний меню
enum MenuState
{
Game, // Меню установлено на первой позиции
Help,
About,
Esc
}
class MenuScreen
{
#region Поля класса
// Объекты рисунков
Texture2D menu, title, enter, cursorMenu, girlLeft, girlRight;
// Объекты мячей
ModelClass [] ball = new ModelClass[3];
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс камеры 45 градусов
float nearClip = 1.0f; // Ближняя отсекающая плоскость
float farClip = 1000.0f; // Дальняя отсекающай плоскость
float angle = 0; // Угол поворота
Matrix world, view, proj; // Матрицы преобразований
SpriteFont font; // Для загрузки шрифта Arial 24 Bold
String[] menuCommant = { // Команды меню
"Игра",
"Помощь",
"О программе",
"Выход"
};
#endregion
// Инициализация объектов
// content - контент экземпляра основного класса игры
// width - текущаяя ширина экрана
// height - текущая высота экрана
public void InitializeSplashScreen(ContentManager content, int width, int height)
{
// Создаем объекты для мячей
for (int i = 0; i < ball.Length; i++)
ball[i] = new ModelClass();
// Загружаем в объекты изображения и модели мячей
menu = content.Load<Texture2D>("Textures\\menu");
cursorMenu = content.Load<Texture2D>("Textures\\cursorMenu");
title = content.Load<Texture2D>("Textures\\title");
enter = content.Load<Texture2D>("Textures\\enter");
girlLeft = content.Load<Texture2D>("Textures\\girlLeft");
girlRight = content.Load<Texture2D>("Textures\\girlRight");
ball[0].Load(content, "Models\\Soccerball");
ball[0].Position = new Vector3(-30, 40, -30);
ball[1].Load(content, "Models\\SoccerballGreen");
ball[1].Position = new Vector3(50, 30, -50);
ball[2].Load(content, "Models\\SoccerballRed");
ball[2].Position = new Vector3(-50, -30, 40);
// Коэффициент искажения проекции
aspectRatio = (float)width / height;
// Загружаем шрифт
font = content.Load<SpriteFont>("Fonts\\font");
}
// Вывод заставки на экран
// spriteBatch - объект рисования спрайтов
// graphics - объект графического устройства GDI
// width - текущаяя ширина экрана
// height - текущая высота экрана
// gameTime - длительность одного такта игры
public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
int width, int height, GameTime gameTime, int menuState)
{
// Очищаем экран своим цветом
graphics.GraphicsDevice.Clear(Color.DarkGreen);
// Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(menu, new Vector2(width / 2 - menu.Width / 2,
height / 2 - menu.Height / 2), Color.White);
spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
30), // Чуть опустили
Color.White);
spriteBatch.Draw(enter, new Vector2(width / 2 - enter.Width / 2,
height - enter.Height - 30), // Чуть приподняли
Color.White);
// Рисуем girls
spriteBatch.Draw(girlLeft, new Vector2(
0, height / 2 - girlLeft.Height / 2), Color.White);
spriteBatch.Draw(girlRight, new Vector2(
width - girlRight.Width, height / 2 - girlRight.Height / 2), Color.White);
spriteBatch.End();
// Рисуем мячи
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 250.0f), Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
for (int i = 0; i < ball.Length; i++)
{
world = Matrix.CreateTranslation(ball[i].Position);
ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
}
// Рисуем надписи меню и фон
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
int posX=width / 2 + width / 7;
int posY= height / 2;
int shift = 50;
spriteBatch.Draw(cursorMenu,
new Vector2(posX - 7, posY + shift * menuState),
Color.White);
for (int i = 0; i < menuCommant.Length; i++)
{
spriteBatch.DrawString(font,
menuCommant[i],
new Vector2(posX, posY),
Color.Red);
posY += shift;
}
spriteBatch.End();
}
}
}
Обратите внимание, что расчет координат вывода графических компонентов мы ведем относительно центра экрана с учетом текущих значений его ширины и высоты. Разрешение экрана у разных пользователях может отличаться, а такой подход позволяет сохранить компоновку графических элементов на экране при разном разрешении. Для этого мы сами жестко и не устанавливаем размеры экрана, а пользуемся текущими настройками.
Для тестирования разработанного
класса MenuScreen введите временно следующие изменения в основной
класс игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.MenuScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
MenuScreen menu = new MenuScreen();
int menuState = (int)MenuState.Game;
#endregion
......................................................................
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
........................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.MenuScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
..................................................
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
menu.DrawScreen(
this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
}
Запустите приложение
и убедитесь, что код класса MenuScreen работает нормально в основном
цикле игры. Должна получиться примерно такая картинка
В предыдущих разделах мы подготовили графические заставки, включая саму игру,
которые нам нужно показать пользователю в зависимости от его действий. При
запуске программы мы автоматически покажем титульную заставку, определенную
классом SplashScreen. Из этой заставки есть два пути: нажать клавишу Esc, чтобы завершить работу приложения, или нажать клавишу Enter. После
нажатия клавиши Enter пользователь
должен попасть на заставку меню, поддерживаемую классом MenuScreen.
А далее после его выбора программа должна перейти в одно из состояний, определяемых
основным классом игры StartGame3D или классами HelpScreen и AboutScreen.
По клавише Esc программа должна вернуться в состояние меню, откуда при повторном нажатии Esc или выбора пункта ' Выход ' - перейти в первую экранную заставку. В режиме меню для смены пунктов задействуем клавиши Up и Down (Стрелка вверх/Стрелка вниз), а для исполнения выбранной команды - клавишу Enter в соответствии с подсказкой в нижней части заставки меню.
Такую логику управления состояниями программы мы должны реализовать в этом разделе. Но здесь есть одна проблема, которую сразу нужно решить. Дело в том, что любая клавиша клавиатуры, кроме расширяющих клавиш Ctrl, Shift и Alt, при ее нажатии начинает непрерывно генерировать соответствующий ей код, пока не будет опущена. По этой причине в большинстве офисных программ, когда смена состояний закреплена за какими-то клавишами, ее связывают не с нажатием, а с отпусканием соответствующей клавиши.
В нашем игровом случае проблема смены состояния связана с тем, что пользователь ожидает ответных действий программы именно от нажатия командной клавиши, которое должно обеспечить одиночную реакцию программы. Для надежного блокирования эффекта генерации применим прием, который напоминает решение, используемое в гениальном создании под названием автомат Калашникова. В нем для гарантированного ведения огня одиночными выстрелами используется специальный запорный крючок - ' шептало одиночного огня '. Это 'шептало' после произведенного выстрела блокирует ударный механизм до тех пор, пока стрелок не отпустит спусковой крючок в исходное состояние. Только после этого можно производить следующий выстрел.
В нашей программе режим меню использует клавиши Enter, Up, Down и Esc.
Поэтому в качестве запорного механизма будем использовать флаги enterFree, upFree, downFree и escFree. В исходном состоянии эти флаги
подняты, но стоит пользователю нажать на
Аналогичная проблема существует и с длительностью удержания кнопки мыши в
нажатом состоянии. Если сейчас запустить приложение в режиме GameScreen,
нажать левую кнопку мыши и, не отпуская ее, направлять курсор на мячи, то мы
увидим этот эффект. В качестве запорного механизма для мыши будем использовать
флаг mouseFree.
Пока мы разработали состояния программы, определяемые следующими значениями
перечисления GameState:
AboutScreen, // Экран с информацией об игреGameScreen, // Экран игрового процессаHelpScreen, // Инструкция о правилахMenuScreen, // Меню игрыSplashScreen // Первая игровая заставка
Для реализации устойчивого
механизма управления этими состояниями внесите необходимые изменения
в код класса StartGame3D, как показано в следующем его полном,
на данный момент, листинге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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
// Объявление состояний экранов
enum GameState
{
AboutScreen, // Экран с информацией об игре
GameOverScreen, // Экран проигрыша
GameScreen, // Экран игрового процесса
HelpScreen, // Инструкция о правилах
MenuScreen, // Меню игры
SplashScreen, // Первая игровая заставка
VictotyScreen // Экран выигрыша
}
public class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.SplashScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
MenuScreen menu = new MenuScreen();
int menuState = (int)MenuState.Game;
// Флаги освобождения задействованных клавиш
bool enterFree = true; // Enter
bool upFree=true; // Стрелка вверх
bool downFree = true; // Стрелка вниз
bool escFree = true; // Esc
// Флаг освобождения мыши
bool mouseFree = true;
#endregion
public StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
}
protected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
aspectRatio = (float)screenWidth / screenHeight; // Коэффициент искажения проекции
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
void MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
if (ball[i].Position.Y > -32)
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
}
}
// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed mouseFree)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
}
// Ждем освобождения кнопки мыши
mouseFree = mouseState.LeftButton == ButtonState.Released;
}
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из AboutScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из MenuScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из HelpScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.MenuScreen:
// Выход из MenuScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.SplashScreen;
// Вход в другие состояния
if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
{
if ((MenuState)menuState == MenuState.Game)
gameState = GameState.GameScreen;
else if ((MenuState)menuState == MenuState.Help)
gameState = GameState.HelpScreen;
else if ((MenuState)menuState == MenuState.About)
gameState = GameState.AboutScreen;
else if ((MenuState)menuState == MenuState.Esc)
gameState = GameState.SplashScreen;
}
// Управление пунктами меню
if (keyboardState.IsKeyDown(Keys.Up) upFree)
{
menuState--;
if (menuState < 0)
menuState = 3;
}
else if (keyboardState.IsKeyDown(Keys.Down) downFree)
{
menuState++;
menuState %= 4; // Деление по модулю
}
break;
case GameState.SplashScreen:
// Выход из SplashScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
this.Exit();
else if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
gameState = GameState.MenuScreen;
break;
case GameState.VictotyScreen:
break;
}
// Устанавливаем флаги освобождения клавиш
enterFree = keyboardState.IsKeyUp(Keys.Enter);
upFree = keyboardState.IsKeyUp(Keys.Up);
downFree = keyboardState.IsKeyUp(Keys.Down);
escFree = keyboardState.IsKeyUp(Keys.Escape);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Нарисовать фон самым первым
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
background.DrawSprite(spriteBatch);
spriteBatch.End();
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
// Нарисовать стадион
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
menu.DrawScreen(
this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
// Метод пересчета координат курсора в трехмерную сцену игры
Ray GetPickRay()
{
// Читаем стандартные координаты курсора
mouseState = Mouse.GetState();
// Вспомогательные переменные
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
// Обнуляем мировую матрицу
world = Matrix.CreateTranslation(0, 0, 0);
// Матрица вида, как в трехмерной сцене
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Матрица проекции, как в трехмерной сцене
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
// Проекция курсора на ближнюю плоскость отсечения объема
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
// Проекция курсора на дальнюю плоскость отсечения объема
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
return new Ray(nearPoint, direction);
}
}
}
Запустите приложение
и убедитесь, что механизм смены состояний работает нормальноОпишем схему, по которой мы будем вести подсчет очков и изменять уровни сложности игры. Под уровнями сложности будем понимать последовательные этапы в игре, в которых нарастает скорость падения мячей. Ключом для перехода на новый уровень будет служить количество попаданий для каждого мяча (установим 20 попаданий), набранных на текущем уровне. При этом ни один мяч не должен коснуться поля стадиона, в противном случае все очки текущего уровня сгорают.
Текущий уровень и количество попаданий для каждого мяча будем отображать в табло, размещенном в левом верхнем углу экрана режима игры. В табло будут нарисованы три мяча, используемых в игре, над которыми расположим счетчики попаданий, а внизу отобразим текущий уровень игры. Если по какому-то мячу выполнено 20 попаданий, то на нем рисуется крестик, но мяч с экрана не исчезает и все равно продолжает участвовать в игре. Когда по всем мячам будет выполнена норма, то очки сбрасываются и увеличивается уровень.
Всего предусмотрим 8 уровней. При успешном прохождении очередного уровня будем выдавать заставку выигрыша, а в случае падения мяча на поле стадиона - заставку проигрыша. В любом из этих двух режимов при нажатии клавиши Esc пользователь вновь попадает в режим игры. Но при выигрыше на последнем уровне пользователь попадает в меню игры.
Добавьте в папку Textures проекта
из прилагаемого каталога Source оставшиеся файлы с рисунками:bad.png - проигрышdagger.png -
крестикg1_over.png -
girls слева при проигрышеg1_victory.png -
girls слева при выигрышеg2_over .png -
girls справа при проигрышеg2_victory.png -
girls справа при выигрышеgood.png -
отличноtablo.png -
таблоДля управления всеми этими ресурсами создадим специальный класс.
Командой Project/Add Class добавьте
в корневой узел проекта Game3D новый файл с именем ResultScreen.cs и
скопируйте в него из файла StartGame3D.cs секцию using
Создайте в каталоге Fonts файл font1.spritefont со
следующими параметрами<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<FontName>Arial</FontName>
<Size>12</Size>
<Spacing>0</Spacing>
<UseKerning>true</UseKerning>
<Style>Bold</Style>
<CharacterRegions>
<CharacterRegion>
<Start>#32;</Start>
<End>#126;</End>
</CharacterRegion>
<CharacterRegion>
<Start>#1040;</Start>
<End>#1103;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>
Дополните класс ResultScreen следующим
кодом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class ResultScreen
{
// Внешние данные
ContentManager content;
SpriteBatch spriteBatch;
int maxScore = 20; // Флаг для рисования крестика
SpriteFont font;
// Рисунки
Texture2D bad, dagger, g1_over, g1_victory, g2_over, g2_victory, good, tablo;
// Свойство доступа
public int MaxScore
{
get { return maxScore; }
set { maxScore = value; }
}
public ResultScreen() { } // Конструктор по умолчанию
public ResultScreen(ContentManager content, SpriteBatch spriteBatch)
{
this.content = content;
this.spriteBatch = spriteBatch;
}
public void Load()
{
// Загружаем шрифт
font = content.Load<SpriteFont>("Fonts\\font1");
// Загружаем рисунки
bad = content.Load<Texture2D>("Textures\\bad");
dagger = content.Load<Texture2D>("Textures\\dagger");
g1_over = content.Load<Texture2D>("Textures\\g1_over");
g1_victory = content.Load<Texture2D>("Textures\\g1_victory");
g2_over = content.Load<Texture2D>("Textures\\g2_over");
g2_victory = content.Load<Texture2D>("Textures\\g2_victory");
good = content.Load<Texture2D>("Textures\\good");
tablo = content.Load<Texture2D>("Textures\\tablo");
}
public void DrawResult(int[] scores, int level)
{
int x = 0, y = 0;
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(tablo, new Vector2(x, y), Color.White);
int shift = 33;
for (int i = 0; i < scores.Length; i++)
{
spriteBatch.DrawString(font, scores[i].ToString(), new Vector2(x + shift, y + 5), Color.White);
if (scores[i] >= maxScore)
spriteBatch.Draw(dagger, new Vector2(x + shift - 23, y + 37), Color.White);
shift += 65;
}
spriteBatch.DrawString(font, level.ToString(), new Vector2(x + 142, y + 108), Color.White);
spriteBatch.End();
}
public void DrawResult(int x, int y, GameState gameState)
{
//x = 300;
//y = 200;
switch (gameState)
{
case GameState.VictotyScreen:
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(g1_victory, new Vector2(x, y), Color.White);
spriteBatch.Draw(good, new Vector2(x + g1_victory.Width + 20, y + 50), Color.White);
spriteBatch.Draw(g2_victory, new Vector2(x + g1_victory.Width + good.Width, y), Color.White);
spriteBatch.End();
break;
case GameState.GameOverScreen:
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(g1_over, new Vector2(x, y), Color.White);
spriteBatch.Draw(bad, new Vector2(x + g1_over.Width - 20, y + 50), Color.White);
spriteBatch.Draw(g2_over, new Vector2(x + g2_over.Width + bad.Width, y), Color.White);
spriteBatch.End();
break;
}
}
}
}
Измените код класса StartGame3D для
введения механизма подсчета очков и реализации логики игры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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
// Объявление состояний экранов
enum GameState
{
AboutScreen, // Экран с информацией об игре
GameOverScreen, // Экран проигрыша
GameScreen, // Экран игрового процесса
HelpScreen, // Инструкция о правилах
MenuScreen, // Меню игры
SplashScreen, // Первая игровая заставка
VictotyScreen // Экран выигрыша
}
public class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.SplashScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
MenuScreen menu = new MenuScreen();
int menuState = (int)MenuState.Game;
// Флаги освобождения задействованных клавиш
bool enterFree = true; // Enter
bool upFree=true; // Стрелка вверх
bool downFree = true; // Стрелка вниз
bool escFree = true; // Esc
// Флаг освобождения мыши
bool mouseFree = true;
// Результаты игры
ResultScreen resultScreen;
int[] scores = { 0, 0, 0 };// Порядок индексов: Blue, Green, Red
int level = 1;
int maxLevel = 8;
#endregion
public StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
}
protected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
aspectRatio = (float)screenWidth / screenHeight; // Коэффициент искажения проекции
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
// Создаем объект с результатами игры
resultScreen = new ResultScreen(this.Content, spriteBatch);
resultScreen.Load();
}
// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
void MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
if (ball[i].Position.Y > -32)
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
else
{
gameState = GameState.GameOverScreen;// Присуждаем поражение
BeginPosition();
}
}
}
// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed mouseFree)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
scores[i] += 1;
}
}
// Проверяем победу
if (scores[0] >= resultScreen.MaxScore scores[1] >= resultScreen.MaxScore
scores[2] >= resultScreen.MaxScore)
{
gameState = GameState.VictotyScreen;
BeginPosition();
}
}
// Ждем освобождения кнопки мыши
mouseFree = mouseState.LeftButton == ButtonState.Released;
}
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из AboutScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.GameOverScreen:
// Выход из режима
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
{
// Сброс очков
for (int i = 0; i < scores.Length; i++)
scores[i] = 0;
gameState = GameState.GameScreen;
}
break;
case GameState.GameScreen:
// Выход из GameScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из HelpScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.MenuScreen:
// Выход из MenuScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.SplashScreen;
// Вход в другие состояния
if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
{
if ((MenuState)menuState == MenuState.Game)
gameState = GameState.GameScreen;
else if ((MenuState)menuState == MenuState.Help)
gameState = GameState.HelpScreen;
else if ((MenuState)menuState == MenuState.About)
gameState = GameState.AboutScreen;
else if ((MenuState)menuState == MenuState.Esc)
gameState = GameState.SplashScreen;
}
// Управление пунктами меню
if (keyboardState.IsKeyDown(Keys.Up) upFree)
{
menuState--;
if (menuState < 0)
menuState = 3;
}
else if (keyboardState.IsKeyDown(Keys.Down) downFree)
{
menuState++;
menuState %= 4; // Деление по модулю
}
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
this.Exit();
else if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
gameState = GameState.MenuScreen;
break;
case GameState.VictotyScreen:
// Выход из режима
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
{
// Сброс очков
for (int i = 0; i < scores.Length; i++)
scores[i] = 0;
if (level < maxLevel)
{
level++;
gameState = GameState.GameScreen;
}
else
{
level = 1;
gameState = GameState.MenuScreen;
}
}
break;
}
// Устанавливаем флаги освобождения клавиш
enterFree = keyboardState.IsKeyUp(Keys.Enter);
upFree = keyboardState.IsKeyUp(Keys.Up);
downFree = keyboardState.IsKeyUp(Keys.Down);
escFree = keyboardState.IsKeyUp(Keys.Escape);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
GraphicsDevice.Clear(Color.LightSalmon);
resultScreen.DrawResult(300, 200, gameState);
break;
case GameState.GameScreen:
// Нарисовать фон самым первым
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
background.DrawSprite(spriteBatch);
spriteBatch.End();
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Нарисовать стадион
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Рисуем табло до мячей и курсора
resultScreen.DrawResult(scores, level);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
menu.DrawScreen(
this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
GraphicsDevice.Clear(Color.PaleGreen);
resultScreen.DrawResult(300, 200, gameState);
break;
}
base.Draw(gameTime);
}
// Метод пересчета координат курсора в трехмерную сцену игры
Ray GetPickRay()
{
// Читаем стандартные координаты курсора
mouseState = Mouse.GetState();
// Вспомогательные переменные
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
// Обнуляем мировую матрицу
world = Matrix.CreateTranslation(0, 0, 0);
// Матрица вида, как в трехмерной сцене
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Матрица проекции, как в трехмерной сцене
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
// Проекция курсора на ближнюю плоскость отсечения объема
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
// Проекция курсора на дальнюю плоскость отсечения объема
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
return new Ray(nearPoint, direction);
}
}
}
В данном коде не производится усложнение игры путем увеличения скорости при переходе на новый уровень, а просто символически обозначается этот переход. В реальной игре такое усложнение должно быть.
Запустите проект на
выполнение и убедитесь в работе введенного механизма смены уровней игры и
подсчета очковНиже приведены уменьшенные экраны для введенных режимов, чтобы можно было сравнить результаты.
![]() |
![]() |
![]() |
Для тренировки мы добавим в программу звуковые эффекты для следующих случаев:
shot .wavmenu_select.wavvictory.wav (замкнем в петлю loop)over.wav
Через проводник решений
добавьте к проекту в папку Content подпапку Sound и скопируйте
в нее из прилагаемого каталога Source файлы shot .wav, menu_select.wav, victory.wav, over.wav
Добавьте в корневой
узел проекта из предыдущей работы Lab58 файл Sound.cs и модифицируйте
его следующим образомusing System;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
namespace Game3D
{
// Перечисление
enum SoundList
{
Shot, // Попали в мяч
MenuSelect, // Сменили пункт меню
Victory, // Выиграли уровень
Over // Проиграли уровень
}
static class Sound
{
static SoundEffect shot, menuSelect, victory, over;
// Для внешнего управления
public static SoundEffectInstance victoryInstance, overInstance;
public static void Load(ContentManager content)
{
shot = content.Load<SoundEffect>("Sound\\shot");
menuSelect = content.Load<SoundEffect>("Sound\\menu_select");
victory = content.Load<SoundEffect>("Sound\\victory");
over = content.Load<SoundEffect>("Sound\\over");
}
// Воспроизведение звука
public static void Play(SoundList sound)
{
switch (sound)
{
case SoundList.Shot:
shot.Play();
break;
case SoundList.MenuSelect:
menuSelect.Play();
break;
case SoundList.Victory:
victoryInstance = victory.Play();
break;
case SoundList.Over:
overInstance = over.Play();
break;
}
}
}
}
Включите вызов звука
в окончательный вид класса StartGame3D следующим
образом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
// Объявление состояний экранов
enum GameState
{
AboutScreen, // Экран с информацией об игре
GameOverScreen, // Экран проигрыша
GameScreen, // Экран игрового процесса
HelpScreen, // Инструкция о правилах
MenuScreen, // Меню игры
SplashScreen, // Первая игровая заставка
VictotyScreen // Экран выигрыша
}
public class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.SplashScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
MenuScreen menu = new MenuScreen();
int menuState = (int)MenuState.Game;
// Флаги освобождения задействованных клавиш
bool enterFree = true; // Enter
bool upFree=true; // Стрелка вверх
bool downFree = true; // Стрелка вниз
bool escFree = true; // Esc
// Флаг освобождения мыши
bool mouseFree = true;
// Результаты игры
ResultScreen resultScreen;
int[] scores = { 0, 0, 0 };// Порядок индексов: Blue, Green, Red
int level = 1;
int maxLevel = 8;
#endregion
public StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
}
protected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
aspectRatio = (float)screenWidth / screenHeight; // Коэффициент искажения проекции
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
// Создаем объект с результатами игры
resultScreen = new ResultScreen(this.Content, spriteBatch);
resultScreen.Load();
// Загружаем звук
Sound.Load(this.Content);
}
// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
void MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
if (ball[i].Position.Y > -32)
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
else
{
gameState = GameState.GameOverScreen;// Присуждаем поражение
BeginPosition();
}
}
}
// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed mouseFree)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
scores[i] += 1;
Sound.Play(SoundList.Shot); // Попадания по мячу
}
}
// Проверяем победу
if (scores[0] >= resultScreen.MaxScore scores[1] >= resultScreen.MaxScore
scores[2] >= resultScreen.MaxScore)
{
gameState = GameState.VictotyScreen;
BeginPosition();
}
}
// Ждем освобождения кнопки мыши
mouseFree = mouseState.LeftButton == ButtonState.Released;
}
bool soundFree = true; // Предохранитель от повторного выполнения звука
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из AboutScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.GameOverScreen:
if (soundFree)
{
Sound.Play(SoundList.Over);
soundFree = false;
}
// Выход из режима
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
{
// Сброс очков
for (int i = 0; i < scores.Length; i++)
scores[i] = 0;
gameState = GameState.GameScreen;
soundFree = true;
Sound.overInstance.Stop();// Остановить немедленно
}
break;
case GameState.GameScreen:
// Выход из GameScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из HelpScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.MenuScreen:
// Выход из MenuScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.SplashScreen;
// Вход в другие состояния
if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
{
if ((MenuState)menuState == MenuState.Game)
gameState = GameState.GameScreen;
else if ((MenuState)menuState == MenuState.Help)
gameState = GameState.HelpScreen;
else if ((MenuState)menuState == MenuState.About)
gameState = GameState.AboutScreen;
else if ((MenuState)menuState == MenuState.Esc)
gameState = GameState.SplashScreen;
}
// Управление пунктами меню
if (keyboardState.IsKeyDown(Keys.Up) upFree)
{
menuState--;
if (menuState < 0)
menuState = 3;
Sound.Play(SoundList.MenuSelect);// Звук
}
else if (keyboardState.IsKeyDown(Keys.Down) downFree)
{
menuState++;
menuState %= 4; // Деление по модулю
Sound.Play(SoundList.MenuSelect);// Звук
}
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
this.Exit();
else if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
gameState = GameState.MenuScreen;
break;
case GameState.VictotyScreen:
if (soundFree || Sound.victoryInstance.State == SoundState.Stopped)
{
// Повторять по кругу
Sound.Play(SoundList.Victory);
soundFree = false;
}
// Выход из режима
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
{
// Сброс очков
for (int i = 0; i < scores.Length; i++)
scores[i] = 0;
if (level < maxLevel)
{
level++;
gameState = GameState.GameScreen;
}
else
{
level = 1;
gameState = GameState.MenuScreen;
}
soundFree = true;
Sound.victoryInstance.Stop();// Остановить немедленно
}
break;
}
// Устанавливаем флаги освобождения клавиш
enterFree = keyboardState.IsKeyUp(Keys.Enter);
upFree = keyboardState.IsKeyUp(Keys.Up);
downFree = keyboardState.IsKeyUp(Keys.Down);
escFree = keyboardState.IsKeyUp(Keys.Escape);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
GraphicsDevice.Clear(Color.LightSalmon);
resultScreen.DrawResult(300, 200, gameState);
break;
case GameState.GameScreen:
// Нарисовать фон самым первым
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
background.DrawSprite(spriteBatch);
spriteBatch.End();
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Нарисовать стадион
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Рисуем табло до мячей и курсора
resultScreen.DrawResult(scores, level);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
menu.DrawScreen(
this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
GraphicsDevice.Clear(Color.PaleGreen);
resultScreen.DrawResult(300, 200, gameState);
break;
}
base.Draw(gameTime);
}
// Метод пересчета координат курсора в трехмерную сцену игры
Ray GetPickRay()
{
// Читаем стандартные координаты курсора
mouseState = Mouse.GetState();
// Вспомогательные переменные
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
// Обнуляем мировую матрицу
world = Matrix.CreateTranslation(0, 0, 0);
// Матрица вида, как в трехмерной сцене
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Матрица проекции, как в трехмерной сцене
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
// Проекция курсора на ближнюю плоскость отсечения объема
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
// Проекция курсора на дальнюю плоскость отсечения объема
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
return new Ray(nearPoint, direction);
}
}
}
Запустите приложение
и убедитесь в правильной работе звуковых эффектов
Откомпилируйте проект
в режиме Release и сформируйте установочный пакет готового приложения,
как это было показано в предыдущей работе Lab58
Сформируйте установочный
диск со всеми необходимыми библиотеками для распространения программы, в
том числе для предоставления преподавателюТолько для тренировки, чтобы запомнить эффектную возможность, введем фукциональность, по которой при завершении работы нашего приложения выключится компьютер (или запустится иное указанное нами внешнее приложение). Тот код, который мы сейчас добавим, после проверки его работы нужно удалить. И перед испытанием закройте все запущенные приложения, данные которых жалко потерять.
В конструкторе класса StartGame3D() зарегистрируйте обработчик для события Exiting следующим образомpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
// Регистрируем обработчик события Exiting
this.Exiting += new EventHandler(StartGame3D_Exiting);
}
void StartGame3D_Exiting(object sender, EventArgs e)
{
// Внимание!!! Для тренировки...
// При завершении приложения выключим компьютер
System.Diagnostics.Process exe = new System.Diagnostics.Process();
exe.StartInfo.FileName = @"cmd.exe"; //Имя файла для запуска
exe.StartInfo.Arguments = "/c shutdown -s"; //Аргументы
exe.Start();
}
Откомпилируйте проект
командой Build/Rebuild Solution и закройте все программы, включая Visual Studio
Запустите проводник Windows Explorer, зайдите в каталог проекта bin\x86\Debug и запустите
файл Game3D приложенияПосле выхода из приложения компьютер выключится!!!
Запустите компьютер,
откройте проект и удалите только что введенный код, показанный красным цветомНа этом выполнение работы закончено, можете 5 минут отдохнуть.

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Сценарий игры состоит в том, что сверху вниз экрана падают мячи, а задача игрока не допустить, чтобы хотя бы один из них упал на площадь футбольного поля. С помощью выстрелов, которые должны попасть в мячик, игрок должен удерживать мячи на лету определенное время. Удачный игрок переходит на новый уровень.
В трехмерной графике используется мировая система координат, ориентированная так, что плоскость xOy направлена параллельно экрану монитора, а ось Oz направлена либо к пользователю перпендикулярно экрану (правая система координат), либо от пользователя вглубь экрана (левая система координат). Если вытянуть в разные стороны три пальца правой руки (большой, указательный и средний) под прямыми углами, то большой будет указывать направление оси x, указательный - оси y, а средний - оси z. В DirectX используется левая система координат, а в OpenGL и XNA Framework - правая.
Поверхность любого объемного тела описывается с помощью примитивов - простых плоских геометрических фигур, чаще всего треугольников. Каждый треугольник задан координатами своих вершин в мировой системе координат. Относительно этой системы задаются координаты вершин всех примитивов каждого объекта ( модели ). Совокупность трехмерных объектов называется сценой. Над трехмерными моделями могут выполняться следующие операции, которые называются модельными преобразованиями:
Чтобы выполнить одно из указанных действий для объекта в целом, нужно применить его к вершине каждого примитива, описывающего модель. Для выполнения таких действий служит мировая матрица преобразования. Это матрица размером 4x4, которая может принимать одно из следующих значений, в зависимости от выполняемых действий:
| Поворот относительно оси Ox | |||
| 1 | 0 | 0 | 0 |
| 0 | cos(a) | sin(a) | 0 |
| 0 | -sin(a) | cos(a) | 0 |
| 0 | 0 | 0 | 1 |
| Поворот относительно оси Oy | |||
| cos(a) | 0 | -sin(a) | 0 |
| 0 | 1 | 0 | 0 |
| sin(a) | 0 | cos(a) | 0 |
| 0 | 0 | 0 | 1 |
| Поворот относительно оси Oz | |||
| cos(a) | sin(a) | 0 | 0 |
| -sin(a) | cos(a) | 0 | 0 |
| 0 | 0 | 1 | 0 |
| 0 | 0 | 0 | 1 |
| Смещение на Tx, Ty, Tz | |||
| 1 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 |
| 0 | 0 | 1 | 0 |
| Tx | Ty | Tz | 1 |
| Масштабирование на Sx, Sy, Sz | |||
| Sx | 0 | 0 | 0 |
| 0 | Sy | 0 | 0 |
| 0 | 0 | Sz | 0 |
| 0 | 0 | 0 | 1 |
Начальная расстановка моделей на сцене определяется начальными координатами примитивов модели, последовательно умноженными на одну или несколько матриц модельного преобразования. Применение одной и той же матрицы преобразования ко всем моделям меняет всю сцену вместе с предыдущими координатами вершин примитивов.
Кроме перечисленных модельных матриц имееются еще две матрицы:
Матрица вида содержит в себе координаты размещения камеры в трехмерном пространстве. У камеры есть вертикальная ось, совпадающая с координатой Oy мировой системы координат. Обычно ограничивают операции преобразования для координат камеры поворотами относительно этой оси и параллельными смещениями вправо/влево вверх/вниз, а также отдалением/приближением камеры к сцене.
Проекционная матрица создает проекцию трехмерной сцены на экран плоского монитора. Она строит прямоугольную пирамиду с вершиной в камере и обеспечивает переднюю и заднюю стенки отсечения трехмерного пространства. Это позволяет вычленять только определенные куски пространства и снижает нагрузку на процессор компьютера.
Новый проект мы разместим в уже существующей папке XNAProjects, созданной нами ранее при выполнении работы Lab58.
Откройте решение XNAProjects работы Lab58 и
в панели Solution Explorer вызовите контекстное меню для узла самого
решения Solution
Командой Add/New Project вызовите
окно мастера и настройте его, с учетом версии библиотеки .NET Framework 3.0,
так
Переименуйте файл Game1.cs нового
проекта в StartGame3D.csКак видно из панели Solution Explorer, теперь в решении присутствуют
два проекта: Game2D и новый проект Game3D.
Обратите внимание, что Game2D выделен жирным шрифтом. Это значит, что
он по умолчанию в оболочке установлен как стартовый. Если сейчас запустить
выполнение решения, то будет выполнен выделенный проект Game2D. В дальнейшем
есть два пути: либо назначить новый проект стартовым, либо исключить из решения
старый проект, тогда новый проект автоматически станет стартовым. Для тренировки,
мы вначале назначим новый проект стартовым, а потом исключим старый проект.
В панели Solution
Explorer вызовите контекстное меню для узла Game3D и выполните
команду Set as StartUp Project, чтобы сделать новый
проект стартовым
Добавьте в конструктор
класса заготовки нового проекта игры код установки размеров окнаpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 300;
graphics.PreferredBackBufferHeight = 200;
}
Щелкните на кнопке Start
Debugging (F5) и убедитесь, что запустилась именно заготовка приложения
нового проекта Game3D
В панели Solution
Explorer вызовите контекстное меню для узла Game2D и выполните
команду Remove, чтобы исключить из решения старый проект, который
после этого останется существовать в пассивном режимеВ прошлой игре мы устанавливали жестко размер экрана, и если это разрешение не совпадало с текущим перед запуском игры, то после запуска приложения система некоторое время настраивала требуемое разрешение, то есть была некоторая задержка в появлении экрана. В этом приложении мы оставим неизменными текущие размеры экрана, но определим их из видеоадаптера и запомним в полях класса игры для дальнейшего использования.
Удалите из конструктора
класса игры код жесткой установки размеров экранаpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
//graphics.PreferredBackBufferWidth = 300;
//graphics.PreferredBackBufferHeight = 200;
}
Поля класса для удобства разместим в секции #region.
Дополните основной
класс игры и его методы кодом автоматической установки размеров
экрана и выхода из игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
#endregion
.....................................................
protected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
base.Initialize();
}
.....................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
base.Update(gameTime);
}
.....................................................
}
В текущее
пространство имен перед классом StartGame3D добавьте объявление типа
перечисления GameState с
элементами обозначений игровых состояний, а в классе создайте поле
этого типа и инициализируйте ее заставкой начала игрыnamespace Game3D
{
// Объявление состояний экранов
enum GameState
{
AboutScreen, // Экран с информацией об игре
GameOverScreen, // Экран проигрыша
GameScreen, // Экран игрового процесса
HelpScreen, // Инструкция о правилах
MenuScreen, // Меню игры
GameScreen, // Первая игровая заставка
VictotyScreen // Экран выигрыша
}
public class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.GameScreen; // Переменная состояния игры
#endregion
..................................................
}
}
Для работы нужного
блока кода в соответствии с установленным состоянием введите переключатель switch в
функциях Update() и Draw()public class StartGame3D : Microsoft.Xna.Framework.Game
{
...................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
}
}
В 3D-задачах используются уже не спрайты, а модели. Трехмерная модель в памяти
компьютера называется мэш-объектом и хранится до загрузки в файле с расширением .x, возможно, вместе со своей текстурой. В трехмерной модели содержится
набор координат примитивов (простых плоских фигур, чаще всего треугольников),
с помощью которых конструируется сложная поверхность модели. Чем сложнее объемная
фигура, тем больше треугольников требуется для ее точного описания.
Создадим класс, который будет загружать модель мяча и обеспечивать вывод его изображения на экран.
Командой Project/Add Class добавьте к проекту класс с именем ModelClass
Скопируйте из файла StartGame3D.cs в новый файл ModelClass.cs секцию using с подключениями пространств
имен
Заполните файл ModelClass.cs следующим
кодом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class ModelClass
{
// Поля
Model model;
Vector3 position;
// Свойства
public Vector3 Position
{
get { return position; }
set { position = value; }
}
// Конструктор
public ModelClass()
{
position = new Vector3(0, 0, 0);
}
// Загрузка модели
public void Load(ContentManager content, String strModel)
{
model = content.Load<Model>(strModel);
}
// Рисуем модель на экране
public void DrawModel(Matrix world, Matrix view, Matrix proj)
{
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index] * world;
effect.View = view;
effect.Projection = proj;
}
mesh.Draw();
}
}
}
}
Добавьте в узел Content проекта папку Models командой Add/New Folder и скопируйте в нее командой Add/Existing Item файл Soccerball.x из прилагаемого каталога Source
Добавьте в секцию #region
Поля класса файла StartGame3D.cs следующие поляpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.GameScreen; // Переменная состояния игры
Matrix world;
Matrix view;
Matrix proj;
float aspectRatio;
float FOV = MathHelper.PiOver4;
float nearClip = 1.0f;
float farClip = 1000.0f;
Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
ModelClass ball;
#endregion
..........................................................
}
Добавьте в конструктор
класса StartGame3D код создания экземпляра класса ModelClasspublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляра класса ModelClass
ball = new ModelClass();
}
Добавьте в функцию Initialize() класса StartGame3D код вычисления коэффициента aspectRatio для
компенсации искажения в проекционной матрицеprotected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
aspectRatio = (float)screenWidth / screenHeight; // Коэффициент искажения проекции
base.Initialize();
}
Коэффициент aspectRatio служит для предотвращения искажения изображения,
проецируемого на плоскость экрана. Поскольку областью просмотра в полноэкранном
режиме служит весь экран и он не имеет равных сторон, то круглый мяч в трехмерном
пространстве будет проецироваться на экран aspectRatio как раз и компенсирует это искажение.
Добавьте в функцию LoadContent() класса StartGame3D код загрузки в программу модели мячаprotected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
ball.Load(this.Content, "Models\\Soccerball");
}
Добавьте в вариант GameState.SplashScreen переключателя switch функции Draw() код отображения мяча
на экранеprotected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
ball.DrawModel(world, view, proj);
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Откомпилируйте проект
командой Rebuild и запустите его на выполнение. Должен получиться рисунок
с начальным положением мяча
Здесь мы выведем на экран три разных мяча и будем перемещать их в пространстве.
Добавьте
в папку Models проекта командой Add/New Item из прилагаемого
каталога Source файлы SoccerballGreen.x и SoccerballRed.x
Добавьте в класс ModelClass поле
и свойство доступа, определяющие скорость падения мячей сверху внизclass ModelClass
{
// Поля
Model model;
Vector3 position;
float speed = 20.0f / 60.0f;
// Свойства
public Vector3 Position
{
get { return position; }
set { position = value; }
}
public float Speed
{ get { return speed; } }
// Конструктор
public ModelClass()
{
position = new Vector3(0, 0, 0);
}
// Загрузка модели
public void Load(ContentManager content, String strModel)
{
model = content.Load<Model>(strModel);
}
// Рисуем модель на экране
public void DrawModel(Matrix world, Matrix view, Matrix proj)
{
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = transforms[mesh.ParentBone.Index] * world;
effect.View = view;
effect.Projection = proj;
}
mesh.Draw();
}
}
}
Переменная speed инициализируется значением 20.0f
/ 60.0f, где 60 означает количество секунд в одной минуте.
Добавьте в основной
класс игры код загрузки трех моделей мяча и установки их начальной позицииpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.GameScreen; // Переменная состояния игры
Matrix world;
Matrix view;
Matrix proj;
float aspectRatio;
float FOV = MathHelper.PiOver4;
float nearClip = 1.0f;
float farClip = 1000.0f;
Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
#endregion
public StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
}
protected override void Initialize()
{
.......................................................
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Устанавливаем начальную позицию объектов
BeginPosition();
}
// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
-rand.Next(20, 150));
}
}
.......................................................
}
Модифицируйте метод Draw() основного класса игры так, чтобы отображались все загруженные
мячиprotected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Откомпилируйте проект
командой Rebuild и выполните его. Должно получиться примерно следующее
Для имитации движения мячей сверху вниз нам нужно уменьшать координату y в
методе обновленных вычислений Update(), поскольку ось Oy направлена
снизу вверх. Кроме того, на данном этапе предусмотрим временный MouseClick(). Ну, и временно для этого включим стандартный курсор
в полноэкранном режиме.
Добавьте в класс StartGame3D код
включения курсора, а также методы MoveBalls() и MouseClick() со следующим содержимымpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
this.IsMouseVisible = true;
}
.........................................................
void MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
}
}
void MouseClick()
{
mouseState = Mouse.GetState();
if (mouseState.LeftButton == ButtonState.Pressed)
BeginPosition();
}
Добавьте в вычислительный
метод Update() основного класса игры вызов функций MoveBalls() и MouseClick()protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
Запустите проект и
убедитесь в работоспособности добавленных в программу механизмовНа данном этапе мы добавим к программе вместо стандартного курсора прицел для стрельбы по мячам и создадим механизм попаданий выстрелов в цель.
В любой трехмерной игре могут на равных применяться и двухмерные изображения.
Наша программа не исключение и мы вместо курсора будем использовать прицел,
который реализуем в виде спрайта. Управление спрайтом и его отображение на
экране упакуем в класс. Но чтобы не повторять заново уже разработанное,
используем для этих целей класс Sprite из предыдущей лабораторной работы.
Командой Project/Add Existing Item скопируйте из предыдущей лабораторной работы или прилагаемого каталога Source файл Sprite.cs в корневой каталог текущего проектаВ этом разделе мы будем использовать
библиотечный класс BoundingSphere для определения столкновений моделей
и курсора и нам понадобится радиус ограничивающей мячи сферы. Чтобы не измерять
этот радиус, введем переменную radius и
определим ее значение, которое
немного больше радиуса мячей.
Добавьте в класс ModelClass поле и свойство доступа для работы с радиусом попадания в цельclass ModelClass
{
// Поля
Model model;
Vector3 position;
float speed = 20.0f / 60.0f;
float radius = 8.0F;
// Свойства
public Vector3 Position
{
get { return position; }
set { position = value; }
}
public float Speed
{ get { return speed; } }
public float Radius
{ get { return radius; } }
...........................................
}
В конструкторе класса StartGame3D отключите стандартный курсор мыши, закомментировав соответствующий
кодpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
}
Через Проводник решений
создайте для узла Content командой Add/New Folder контекстного
меню папку Textures и
скопируйте в нее из прилагаемого каталога Source файл рисунка прицела cursor.png
В классе StartGame3D создайте поле для хранения ссылки на объект курсора и инициализируйте ее
экземпляром класса Sprite#region Поля класса GraphicsDeviceManager graphics; SpriteBatch spriteBatch; int screenWidth, screenHeight; // Размеры экрана KeyboardState keyboardState; // Буфер клавиатуры GameState gameState = GameState.GameScreen; // Переменная состояния игры Matrix world; Matrix view; Matrix proj; float aspectRatio; float FOV = MathHelper.PiOver4; float nearClip = 1.0f; float farClip = 1000.0f; Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f); //ModelClass ball; ModelClass[] ball = new ModelClass[3]; MouseState mouseState; Random rand = new Random(); // Создание объекта курсора-прицела Game2D.Sprite cursor = new Game2D.Sprite(); #endregion
В методе LoadContent() класса StartGame3D загрузите
спрайт курсора в объект, а в методе Draw() поместите
код рисования курсора последним - после рисования мячей, чтобы он оказался
самым ближним к пользователю на экране монитораprotected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
}
.......................................................
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Система координат двухмерного и трехмерного пространств в XNA Framework существенно отличаются. Поэтому нужен особый механизм перевода двухмерных координат курсора, генерируемых операционной системой, в трехмерное пространство, построенное XNA. Мы воспользуемся готовым решением, приведенным в справочной информации по XNA Game Studio. Главным условием достоверности пересчета координат курсора на трехмерную сцену является совпадение значений матрицы вида и проекционной матрицы, применяемых для построения сцены, с теми же матрицами, определения координат курсора на этой сцене.
Добавьте к классу StartGame3D метод GetPickRay() пересчета координат курсора в трехмерное пространство
сценыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
............................................................
// Метод пересчета координат курсора в трехмерную сцену игры
Ray GetPickRay()
{
// Читаем стандартные координаты курсора
mouseState = Mouse.GetState();
// Вспомогательные переменные
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
// Обнуляем мировую матрицу
world = Matrix.CreateTranslation(0, 0, 0);
// Матрица вида, как в трехмерной сцене
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Матрица проекции, как в трехмерной сцене
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
// Проекция курсора на ближнюю плоскость отсечения объема
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
// Проекция курсора на дальнюю плоскость отсечения объема
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
return new Ray(nearPoint, direction);
}
}
Теперь нужно применить этот метод для создания механизма перемещения спрайта
курсора по экрану и определения попаданий в движущиеся объекты мячей. Спусковым
крючком выстрела по мячу будет служить левая кнопка мыши, поэтому весь механизм
поместим в уже созданную нами ранее заготовку метода MouseClick().
Добавьте в класс StartGame3D поле
массива объектов типа BoundingSphere и модифицируйте метод MouseClick() следующим
образом// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
-rand.Next(20, 150));
}
}
}
}
Запустите приложение
и проверьте механизм стрельбы по целямВ этом разделе мы загрузим в игру модель стадиона, на фоне которой будут летать мячи, и установим камеру под определенным углом, одинаковым и для мячей и для стадиона.
В классе StartGame3D установите
новое значение поля camera для выбора наилучшего ракурса (угла зрения)
всей сцены игры и добавьте объект стадиона, в который мы позднее загрузим
трехмерный мэшpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.GameScreen; // Переменная состояния игры
Matrix world;
Matrix view;
Matrix proj;
float aspectRatio;
float FOV = MathHelper.PiOver4;
float nearClip = 1.0f;
float farClip = 1000.0f;
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
#endregion
..................................................
}
Добавьте из прилагаемого
каталога Source в папку Models проекта файл с именем stadium 1.x
В методе LoadContent() основного
класса игры загрузите модель стадиона в созданный ранее объект stadium программыprotected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
}
В методе Draw() основного
класса игры вставьте код вывода изображения стадиона на экран монитора (раньше
кода вывода других объектов)protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
// Нарисовать модель стадиона самой первой
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Запустите игру и убедитесь,
что начальные позиции мячей нуждаются в корректировкеОбратите внимание на позиции установки всех моделей игры, включая камеру.
Если бы камера была совмещена с экраном монитора, то начало мировой системы
координат находилось бы в плоскости экрана. Позиции мячей мы формируем в
отрицательном направлении оси Oz в диапазоне [-20,-150] пикселов.
Сам стадион рисуем
относительно начала мировой системы координат. И для того, чтобы увидеть его
целиком, мы камеру отодвинули в положительном направлении на 250 пикселов.
В результате мячи удалились от наблюдателя и к тому же падают на дальней границе
стадиона. Поэтому нужно приблизить мячи к наблюдателю, скорректировав их начальные
координаты по оси Oz в новом диапазоне [-50,150].
Введите изменения в
код функций BeginPosition() и MouseClick(), где задается координата z полета мячей// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
.....................................................
// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
}
}
Запустите приложение
и убедитесь, что мячи теперь падают над поверхностью поля стадионаНам нужно сделать, чтобы мячи останавливались на поверхности поля, а не проваливались
сквозь него. Поверхность поля стадиона проходит через ось Oy мировой
системы координат на расстоянии ( -32 ) пиксела, поэтому двигать мячи
в вертикальном направлении следует только до этой величины.
Внесите следующие изменения
в функцию MoveBalls() основного класса игрыvoid MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
if (ball[i].Position.Y > -32)
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
}
}
Запустите приложение
и убедитесь, что мячи теперь останавливаются на поверхности поля стадиона
Дополним сцену подходящим задним фоном, чтобы стадион и вся сцена смотрелись более красочно.
Добавьте в папку Textures проекта файл hallake001.png из прилагаемого каталога Source
В классе StartGame3D добавьте поле-ссылку background для хранения объекта текстуры, а в
методах LoadContent() и Draw() - код загрузки и вывода рисунка на экран#region Поля класса
..................................................
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
#endregion
..................................................
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
}
..................................................
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Нарисовать фон самым первым
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
background.DrawSprite(spriteBatch);
spriteBatch.End();
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
// Нарисовать стадион
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
Запустите приложение
и полюбуйтесь на солидную сцену, сделанную своими руками
В самом начале построения приложения мы в методах LoadContent() и Draw() с
помощью оператора switch заложили несколько состояний игры, определенных
в перечислении GameState
AboutScreen, // Экран с информацией об игреGameOverScreen, // Экран проигрышаGameScreen, // Экран игрового процессаHelpScreen, // Инструкция
о правилахMenuScreen, // Меню игрыSplashScreen, // Первая игровая заставкаVictotyScreen, // Экран выигрышаДо сих пор мы реализовали только одно из них - саму
игру. В этом разделе закодируем еще три состояния: SplashScreen, AboutScreen, HelpScreen. Смену режимов закрепим
за клавишами Enter и Esc.
Первой будет появляться игровая заставка, из которой по клавише Enter попадем в меню, а по клавише Esc выйдем из игры. Игровая заставка будет состоять из нескольких отдельных компонентов:
splash.pngtitle.pngenter.pnghole .png, которое мы разбросаем по
экрану заставки случайным образом в 30 экземплярахSoccerball.x посередине экрана на переднем плане
заставкиПрежде всего нужно скопировать в проект эти компоненты заставки.
В панели Solution
Explorer вызовите контекстное меню для каталога проекта Textures и дабавьте
командой Add/Existing Item из прилагаемого каталога Source файлы
с рисунками splash.png, title.png, enter.png, hole .pngКод игровой заставки упакуем в класс SplashScreen.
Командой Project/Add Class меню
оболочки добавьте к проекту файл SplashScreen.cs и
скопируйте в него все инструкции using из файла StartGame3D
Заполните файл SplashScreen.cs следующим
кодом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class SplashScreen
{
#region Поля класса
Texture2D splash, title, enter, hole; // Объекты рисунков
Vector2[] posHole = new Vector2[30]; // Массив позиций рисунка пули
Random rand = new Random(); // Генератор случайных чисел
ModelClass ball = new ModelClass(); // Объект мяча
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс камеры 45 градусов
float nearClip = 1.0f; // Ближняя отсекающая плоскость
float farClip = 1000.0f; // Дальняя отсекающай плоскость
float angle = 0; // Угол поворота
Matrix world, view, proj; // Матрицы преобразований
#endregion
// Инициализация объектов
// content - контент экземпляра основного класса игры
// width - текущаяя ширина экрана
// height - текущая высота экрана
public void InitializeSplashScreen(ContentManager content, int width, int height)
{
// Загружаем в объекты изображения и модель мяча
splash = content.Load<Texture2D>("Textures\\splash");
title = content.Load<Texture2D>("Textures\\title");
enter = content.Load<Texture2D>("Textures\\enter");
hole = content.Load<Texture2D>("Textures\\hole");
ball.Load(content, "Models\\Soccerball");
ball.Position = new Vector3(0, 0, 0);// В центре экрана, где начало мировой системы координат
// Разбрасываем по экрану пулевые отверстия
for (int i = 0; i < posHole.Length; i++)
{
posHole[i].X = rand.Next(20, width - 60);
posHole[i].Y = rand.Next(100, height - 60);
}
// Коэффициент искажения проекции
aspectRatio = (float)width / height;
}
// Вывод заставки на экран
// spriteBatch - объект рисования спрайтов
// graphics - объект графического устройства GDI
// width - текущаяя ширина экрана
// height - текущая высота экрана
// gameTime - длительность одного такта игры
public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
int width, int height, GameTime gameTime)
{
// Очищаем экран своим цветом
graphics.GraphicsDevice.Clear(Color.DarkGreen);
// Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(splash, new Vector2(width / 2 - splash.Width / 2,
height / 2 - splash.Height / 2), Color.White);
spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
30), // Чуть опустили
Color.White);
spriteBatch.Draw(enter, new Vector2(width / 2 - enter.Width / 2,
height - enter.Height - 30), // Чуть приподняли
Color.White);
for (int i = 0; i < posHole.Length; i++)
spriteBatch.Draw(hole, posHole[i], Color.White);
spriteBatch.End();
// Рисуем объемный мяч на переднем плане
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 150.0f), Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
world = Matrix.CreateTranslation(ball.Position);
angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 2.0f);// Увеличиваем угол вращения мяча
world *= Matrix.CreateRotationY(angle);
ball.DrawModel(world, view, proj);
}
}
}
Для тестирования разработанного
класса SplashScreen введите временно следующие изменения в
основной класс игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
//GameState gameState = GameState.GameScreen; // Переменная состояния игры
GameState gameState = GameState.SplashScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
#endregion
protected override void LoadContent()
{
....................................................
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
.......................................................
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
}
Запустите приложение
и убедитесь, что код класса SplashScreen работает нормально в основном
цикле игры. Получится картинка, приведенная ниже, с вращающимся в центре
мячом
Код этой заставки мы спроектируем на основе только что созданного класса SplashScreen,
внеся в него незначительные коррективы. Эти изменения будет связаны в основном
с тем, что вместо одного мяча, вращающегося в центре экрана, мы введем три
мяча, которые будут вращаться как и прежде - относительно оси y, и одновременно
- относительно оси z. В итоге получим сложное вращение мячей в плоскости экрана
относительно его центра.
Загрузите в папку Textures проекта
файлы изображений about.png и esc.png из прилагаемого к работе каталога Source
В панели Solution
Explorer сделайте копию файла SplashScreen.cs и переименуйте
ее в файл AboutScreen.cs
Модифицируйте файл AboutScreen.cs так,
чтобы он выглядел следующим образом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class AboutScreen
{
#region Поля класса
Texture2D about, title, esc, hole; // Объекты рисунков
Vector2[] posHole = new Vector2[30]; // Массив позиций рисунка пули
Random rand = new Random(); // Генератор случайных чисел
ModelClass [] ball = new ModelClass[3]; // Объекты мячей
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс камеры 45 градусов
float nearClip = 1.0f; // Ближняя отсекающая плоскость
float farClip = 1000.0f; // Дальняя отсекающай плоскость
float angle = 0; // Угол поворота
Matrix world, view, proj; // Матрицы преобразований
#endregion
// Инициализация объектов
// content - контент экземпляра основного класса игры
// width - текущаяя ширина экрана
// height - текущая высота экрана
public void InitializeSplashScreen(ContentManager content, int width, int height)
{
// Создаем объекты для мячей
for (int i = 0; i < ball.Length; i++)
ball[i] = new ModelClass();
// Загружаем в объекты изображения и модель мяча
about = content.Load<Texture2D>("Textures\\about");
title = content.Load<Texture2D>("Textures\\title");
esc = content.Load<Texture2D>("Textures\\esc");
hole = content.Load<Texture2D>("Textures\\hole");
ball[0].Load(content, "Models\\Soccerball");
ball[0].Position = new Vector3(-30, 40, -30);
ball[1].Load(content, "Models\\SoccerballGreen");
ball[1].Position = new Vector3(50, 30, -50);
ball[2].Load(content, "Models\\SoccerballRed");
ball[2].Position = new Vector3(-50, -30, 40);
// Разбрасываем по экрану пулевые отверстия
for (int i = 0; i < posHole.Length; i++)
{
posHole[i].X = rand.Next(20, width - 60);
posHole[i].Y = rand.Next(100, height - 60);
}
// Коэффициент искажения проекции
aspectRatio = (float)width / height;
}
// Вывод заставки на экран
// spriteBatch - объект рисования спрайтов
// graphics - объект графического устройства GDI
// width - текущаяя ширина экрана
// height - текущая высота экрана
// gameTime - длительность одного такта игры
public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
int width, int height, GameTime gameTime)
{
// Очищаем экран своим цветом
graphics.GraphicsDevice.Clear(Color.DarkGreen);
// Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(about, new Vector2(width / 2 - about.Width / 2,
height / 2 - about.Height / 2), Color.White);
spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
30), // Чуть опустили
Color.White);
spriteBatch.Draw(esc, new Vector2(width / 2 - esc.Width / 2,
height - esc.Height - 30), // Чуть приподняли
Color.White);
for (int i = 0; i < posHole.Length; i++)
spriteBatch.Draw(hole, posHole[i], Color.White);
spriteBatch.End();
// Рисуем мячи на переднем плане
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 260.0f), Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
for (int i = 0; i < ball.Length; i++)
{
world = Matrix.CreateTranslation(ball[i].Position);
ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
}
}
}
}
Для тестирования
разработанного класса AboutScreen введите временно следующие изменения
в основной класс игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.AboutScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
#endregion
..............................................................
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
..............................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
..............................................................
break;
case GameState.HelpScreen:
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
..............................................................
}
Запустите приложение
и убедитесь, что код класса AboutScreen работает нормально в основном
цикле игры. Получится картинка, приведенная ниже, с вращающимися в плоскости
экрана относительно его центра мячами
Код этой заставки мы спроектируем на основе только что созданного класса AboutScreen,
внеся в него вместо загрузки файла about.png загрузку файла help.png. Здесь
получится немного нерационально, потому что остальной код класса будет неизменным.
Было бы проще добавить в конструктор класса AboutScreen параметр с именем загружаемого
файла. Но учитывая, что у нас подобных заставок будет всего две, просто повторим
этот код еще раз.
Загрузите в папку Textures проекта
файл изображений help.png из
прилагаемого к работе каталога Source
В панели Solution
Explorer сделайте копию файла AboutScreen.cs и переименуйте
ее в файл HelpScreen.cs
Перейдите в режим редактирования
файла HelpScreen.cs,
вызовите комбинацией клавиш Ctrl-H окно замены редактора кода и замените
все вхождения about на help документа.
После этого файл должен стать таким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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class HelpScreen
{
#region Поля класса
Texture2D help, title, esc, hole; // Объекты рисунков
Vector2[] posHole = new Vector2[30]; // Массив позиций рисунка пули
Random rand = new Random(); // Генератор случайных чисел
ModelClass [] ball = new ModelClass[3]; // Объекты мячей
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс камеры 45 градусов
float nearClip = 1.0f; // Ближняя отсекающая плоскость
float farClip = 1000.0f; // Дальняя отсекающай плоскость
float angle = 0; // Угол поворота
Matrix world, view, proj; // Матрицы преобразований
#endregion
// Инициализация объектов
// content - контент экземпляра основного класса игры
// width - текущаяя ширина экрана
// height - текущая высота экрана
public void InitializeSplashScreen(ContentManager content, int width, int height)
{
// Создаем объекты для мячей
for (int i = 0; i < ball.Length; i++)
ball[i] = new ModelClass();
// Загружаем в объекты изображения и модель мяча
help = content.Load<Texture2D>("Textures\\help");
title = content.Load<Texture2D>("Textures\\title");
esc = content.Load<Texture2D>("Textures\\esc");
hole = content.Load<Texture2D>("Textures\\hole");
ball[0].Load(content, "Models\\Soccerball");
ball[0].Position = new Vector3(-30, 40, -30);
ball[1].Load(content, "Models\\SoccerballGreen");
ball[1].Position = new Vector3(50, 30, -50);
ball[2].Load(content, "Models\\SoccerballRed");
ball[2].Position = new Vector3(-50, -30, 40);
// Разбрасываем по экрану пулевые отверстия
for (int i = 0; i < posHole.Length; i++)
{
posHole[i].X = rand.Next(20, width - 60);
posHole[i].Y = rand.Next(100, height - 60);
}
// Коэффициент искажения проекции
aspectRatio = (float)width / height;
}
// Вывод заставки на экран
// spriteBatch - объект рисования спрайтов
// graphics - объект графического устройства GDI
// width - текущаяя ширина экрана
// height - текущая высота экрана
// gameTime - длительность одного такта игры
public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
int width, int height, GameTime gameTime)
{
// Очищаем экран своим цветом
graphics.GraphicsDevice.Clear(Color.DarkGreen);
// Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(help, new Vector2(width / 2 - help.Width / 2,
height / 2 - help.Height / 2), Color.White);
spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
30), // Чуть опустили
Color.White);
spriteBatch.Draw(esc, new Vector2(width / 2 - esc.Width / 2,
height - esc.Height - 30), // Чуть приподняли
Color.White);
for (int i = 0; i < posHole.Length; i++)
spriteBatch.Draw(hole, posHole[i], Color.White);
spriteBatch.End();
// Рисуем мячи на переднем плане
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 260.0f), Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
for (int i = 0; i < ball.Length; i++)
{
world = Matrix.CreateTranslation(ball[i].Position);
ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
}
}
}
}
Для тестирования разработанного
класса HelpScreen введите временно следующие изменения в основной
класс игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.HelpScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
#endregion
........................................................
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
............................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
.........................................................
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
.............................................................
}
Запустите приложение
и убедитесь, что код класса HelpScreen работает нормально в основном
цикле игры. Получится картинка, приведенная ниже, с вращающимися в плоскости
экрана относительно его центра мячами
Для формирования заставки с элементами меню создадим игровой класс MenuScreen.
Меню будет содержать фоновую заставку, титульный заголовок, подсказку действий
пользователя по выбору вариантов и аналоги кнопок выбора команд. Как и в двух
предыдущих заставках оставим три мяча, вращающихся относительно центра в плоскости
экрана.
В центре экрана разместим команды:
GameScreenHelpScreenAboutScreenКаждую команду будем представлять текстовой надписью, под которую будем подкладывать
желтый прямоугольник, если она имеет фокус ввода. Для этого придется подключить
кириллический шрифт требуемого размера и гарнитуры. По краям экрана для украшательства
разместим фигуры ' girl ' свободного поведения. И еще уберем отображение
пуль.
Добавьте из
прилагаемого к работе каталога Source в папку Textures проекта файлы menu.jpg, cursorMenu.png, girlLeft.png, girlRight.png
Добавьте к папке Content подпапку Fonts
Добавьте в папку Fonts файл font.spritefont
Откорректируйте файл font.spritefont следующим
образом<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<FontName>Arial</FontName>
<Size>24</Size>
<Spacing>0</Spacing>
<UseKerning>true</UseKerning>
<Style>Bold</Style>
<CharacterRegions>
<CharacterRegion>
<Start>#32;</Start>
<End>#126;</End>
</CharacterRegion>
<CharacterRegion>
<Start>#1040;</Start>
<End>#1103;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>
Скопируйте файл HelpScreen.cs и
назовите копию MenuScreen.cs
Откорректируйте файл
следующим образом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
// Объявление состояний меню
enum MenuState
{
Game, // Меню установлено на первой позиции
Help,
About,
Esc
}
class MenuScreen
{
#region Поля класса
// Объекты рисунков
Texture2D menu, title, enter, cursorMenu, girlLeft, girlRight;
// Объекты мячей
ModelClass [] ball = new ModelClass[3];
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс камеры 45 градусов
float nearClip = 1.0f; // Ближняя отсекающая плоскость
float farClip = 1000.0f; // Дальняя отсекающай плоскость
float angle = 0; // Угол поворота
Matrix world, view, proj; // Матрицы преобразований
SpriteFont font; // Для загрузки шрифта Arial 24 Bold
String[] menuCommant = { // Команды меню
"Игра",
"Помощь",
"О программе",
"Выход"
};
#endregion
// Инициализация объектов
// content - контент экземпляра основного класса игры
// width - текущаяя ширина экрана
// height - текущая высота экрана
public void InitializeSplashScreen(ContentManager content, int width, int height)
{
// Создаем объекты для мячей
for (int i = 0; i < ball.Length; i++)
ball[i] = new ModelClass();
// Загружаем в объекты изображения и модели мячей
menu = content.Load<Texture2D>("Textures\\menu");
cursorMenu = content.Load<Texture2D>("Textures\\cursorMenu");
title = content.Load<Texture2D>("Textures\\title");
enter = content.Load<Texture2D>("Textures\\enter");
girlLeft = content.Load<Texture2D>("Textures\\girlLeft");
girlRight = content.Load<Texture2D>("Textures\\girlRight");
ball[0].Load(content, "Models\\Soccerball");
ball[0].Position = new Vector3(-30, 40, -30);
ball[1].Load(content, "Models\\SoccerballGreen");
ball[1].Position = new Vector3(50, 30, -50);
ball[2].Load(content, "Models\\SoccerballRed");
ball[2].Position = new Vector3(-50, -30, 40);
// Коэффициент искажения проекции
aspectRatio = (float)width / height;
// Загружаем шрифт
font = content.Load<SpriteFont>("Fonts\\font");
}
// Вывод заставки на экран
// spriteBatch - объект рисования спрайтов
// graphics - объект графического устройства GDI
// width - текущаяя ширина экрана
// height - текущая высота экрана
// gameTime - длительность одного такта игры
public void DrawScreen(SpriteBatch spriteBatch, GraphicsDeviceManager graphics,
int width, int height, GameTime gameTime, int menuState)
{
// Очищаем экран своим цветом
graphics.GraphicsDevice.Clear(Color.DarkGreen);
// Рисуем в GDI плоские компоненты относительно центра экрана и в Z-последовательности
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(menu, new Vector2(width / 2 - menu.Width / 2,
height / 2 - menu.Height / 2), Color.White);
spriteBatch.Draw(title, new Vector2(width / 2 - title.Width / 2,
30), // Чуть опустили
Color.White);
spriteBatch.Draw(enter, new Vector2(width / 2 - enter.Width / 2,
height - enter.Height - 30), // Чуть приподняли
Color.White);
// Рисуем girls
spriteBatch.Draw(girlLeft, new Vector2(
0, height / 2 - girlLeft.Height / 2), Color.White);
spriteBatch.Draw(girlRight, new Vector2(
width - girlRight.Width, height / 2 - girlRight.Height / 2), Color.White);
spriteBatch.End();
// Рисуем мячи
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;// Включаем буфер глубины
view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 250.0f), Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
angle += (float)(gameTime.ElapsedGameTime.TotalSeconds * 1.0f);
Matrix rotationMatrixY = Matrix.CreateRotationY(angle);
Matrix rotationMatrixZ = Matrix.CreateRotationZ(angle);
for (int i = 0; i < ball.Length; i++)
{
world = Matrix.CreateTranslation(ball[i].Position);
ball[i].DrawModel(rotationMatrixY * world * rotationMatrixZ, view, proj);
}
// Рисуем надписи меню и фон
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
int posX=width / 2 + width / 7;
int posY= height / 2;
int shift = 50;
spriteBatch.Draw(cursorMenu,
new Vector2(posX - 7, posY + shift * menuState),
Color.White);
for (int i = 0; i < menuCommant.Length; i++)
{
spriteBatch.DrawString(font,
menuCommant[i],
new Vector2(posX, posY),
Color.Red);
posY += shift;
}
spriteBatch.End();
}
}
}
Обратите внимание, что расчет координат вывода графических компонентов мы ведем относительно центра экрана с учетом текущих значений его ширины и высоты. Разрешение экрана у разных пользователях может отличаться, а такой подход позволяет сохранить компоновку графических элементов на экране при разном разрешении. Для этого мы сами жестко и не устанавливаем размеры экрана, а пользуемся текущими настройками.
Для тестирования разработанного
класса MenuScreen введите временно следующие изменения в основной
класс игрыpublic class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.MenuScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
MenuScreen menu = new MenuScreen();
int menuState = (int)MenuState.Game;
#endregion
......................................................................
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
........................................................
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.MenuScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape))
this.Exit();
break;
case GameState.VictotyScreen:
break;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
..................................................
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
menu.DrawScreen(
this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
}
Запустите приложение
и убедитесь, что код класса MenuScreen работает нормально в основном
цикле игры. Должна получиться примерно такая картинка
В предыдущих разделах мы подготовили графические заставки, включая саму игру,
которые нам нужно показать пользователю в зависимости от его действий. При
запуске программы мы автоматически покажем титульную заставку, определенную
классом SplashScreen. Из этой заставки есть два пути: нажать клавишу Esc, чтобы завершить работу приложения, или нажать клавишу Enter. После
нажатия клавиши Enter пользователь
должен попасть на заставку меню, поддерживаемую классом MenuScreen.
А далее после его выбора программа должна перейти в одно из состояний, определяемых
основным классом игры StartGame3D или классами HelpScreen и AboutScreen.
По клавише Esc программа должна вернуться в состояние меню, откуда при повторном нажатии Esc или выбора пункта ' Выход ' - перейти в первую экранную заставку. В режиме меню для смены пунктов задействуем клавиши Up и Down (Стрелка вверх/Стрелка вниз), а для исполнения выбранной команды - клавишу Enter в соответствии с подсказкой в нижней части заставки меню.
Такую логику управления состояниями программы мы должны реализовать в этом разделе. Но здесь есть одна проблема, которую сразу нужно решить. Дело в том, что любая клавиша клавиатуры, кроме расширяющих клавиш Ctrl, Shift и Alt, при ее нажатии начинает непрерывно генерировать соответствующий ей код, пока не будет опущена. По этой причине в большинстве офисных программ, когда смена состояний закреплена за какими-то клавишами, ее связывают не с нажатием, а с отпусканием соответствующей клавиши.
В нашем игровом случае проблема смены состояния связана с тем, что пользователь ожидает ответных действий программы именно от нажатия командной клавиши, которое должно обеспечить одиночную реакцию программы. Для надежного блокирования эффекта генерации применим прием, который напоминает решение, используемое в гениальном создании под названием автомат Калашникова. В нем для гарантированного ведения огня одиночными выстрелами используется специальный запорный крючок - ' шептало одиночного огня '. Это 'шептало' после произведенного выстрела блокирует ударный механизм до тех пор, пока стрелок не отпустит спусковой крючок в исходное состояние. Только после этого можно производить следующий выстрел.
В нашей программе режим меню использует клавиши Enter, Up, Down и Esc.
Поэтому в качестве запорного механизма будем использовать флаги enterFree, upFree, downFree и escFree. В исходном состоянии эти флаги
подняты, но стоит пользователю нажать на
Аналогичная проблема существует и с длительностью удержания кнопки мыши в
нажатом состоянии. Если сейчас запустить приложение в режиме GameScreen,
нажать левую кнопку мыши и, не отпуская ее, направлять курсор на мячи, то мы
увидим этот эффект. В качестве запорного механизма для мыши будем использовать
флаг mouseFree.
Пока мы разработали состояния программы, определяемые следующими значениями
перечисления GameState:
AboutScreen, // Экран с информацией об игреGameScreen, // Экран игрового процессаHelpScreen, // Инструкция о правилахMenuScreen, // Меню игрыSplashScreen // Первая игровая заставка
Для реализации устойчивого
механизма управления этими состояниями внесите необходимые изменения
в код класса StartGame3D, как показано в следующем его полном,
на данный момент, листинге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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
// Объявление состояний экранов
enum GameState
{
AboutScreen, // Экран с информацией об игре
GameOverScreen, // Экран проигрыша
GameScreen, // Экран игрового процесса
HelpScreen, // Инструкция о правилах
MenuScreen, // Меню игры
SplashScreen, // Первая игровая заставка
VictotyScreen // Экран выигрыша
}
public class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.SplashScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
MenuScreen menu = new MenuScreen();
int menuState = (int)MenuState.Game;
// Флаги освобождения задействованных клавиш
bool enterFree = true; // Enter
bool upFree=true; // Стрелка вверх
bool downFree = true; // Стрелка вниз
bool escFree = true; // Esc
// Флаг освобождения мыши
bool mouseFree = true;
#endregion
public StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
}
protected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
aspectRatio = (float)screenWidth / screenHeight; // Коэффициент искажения проекции
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
}
// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
void MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
if (ball[i].Position.Y > -32)
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
}
}
// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed mouseFree)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
}
// Ждем освобождения кнопки мыши
mouseFree = mouseState.LeftButton == ButtonState.Released;
}
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из AboutScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Выход из MenuScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из HelpScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.MenuScreen:
// Выход из MenuScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.SplashScreen;
// Вход в другие состояния
if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
{
if ((MenuState)menuState == MenuState.Game)
gameState = GameState.GameScreen;
else if ((MenuState)menuState == MenuState.Help)
gameState = GameState.HelpScreen;
else if ((MenuState)menuState == MenuState.About)
gameState = GameState.AboutScreen;
else if ((MenuState)menuState == MenuState.Esc)
gameState = GameState.SplashScreen;
}
// Управление пунктами меню
if (keyboardState.IsKeyDown(Keys.Up) upFree)
{
menuState--;
if (menuState < 0)
menuState = 3;
}
else if (keyboardState.IsKeyDown(Keys.Down) downFree)
{
menuState++;
menuState %= 4; // Деление по модулю
}
break;
case GameState.SplashScreen:
// Выход из SplashScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
this.Exit();
else if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
gameState = GameState.MenuScreen;
break;
case GameState.VictotyScreen:
break;
}
// Устанавливаем флаги освобождения клавиш
enterFree = keyboardState.IsKeyUp(Keys.Enter);
upFree = keyboardState.IsKeyUp(Keys.Up);
downFree = keyboardState.IsKeyUp(Keys.Down);
escFree = keyboardState.IsKeyUp(Keys.Escape);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
break;
case GameState.GameScreen:
// Нарисовать фон самым первым
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
background.DrawSprite(spriteBatch);
spriteBatch.End();
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Определить матрицу преобразования
//world = Matrix.CreateTranslation(ball.Position);
// Нарисовать модель в начальном состоянии
//ball.DrawModel(world, view, proj);
// Нарисовать стадион
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
menu.DrawScreen(
this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
break;
}
base.Draw(gameTime);
}
// Метод пересчета координат курсора в трехмерную сцену игры
Ray GetPickRay()
{
// Читаем стандартные координаты курсора
mouseState = Mouse.GetState();
// Вспомогательные переменные
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
// Обнуляем мировую матрицу
world = Matrix.CreateTranslation(0, 0, 0);
// Матрица вида, как в трехмерной сцене
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Матрица проекции, как в трехмерной сцене
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
// Проекция курсора на ближнюю плоскость отсечения объема
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
// Проекция курсора на дальнюю плоскость отсечения объема
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
return new Ray(nearPoint, direction);
}
}
}
Запустите приложение
и убедитесь, что механизм смены состояний работает нормальноОпишем схему, по которой мы будем вести подсчет очков и изменять уровни сложности игры. Под уровнями сложности будем понимать последовательные этапы в игре, в которых нарастает скорость падения мячей. Ключом для перехода на новый уровень будет служить количество попаданий для каждого мяча (установим 20 попаданий), набранных на текущем уровне. При этом ни один мяч не должен коснуться поля стадиона, в противном случае все очки текущего уровня сгорают.
Текущий уровень и количество попаданий для каждого мяча будем отображать в табло, размещенном в левом верхнем углу экрана режима игры. В табло будут нарисованы три мяча, используемых в игре, над которыми расположим счетчики попаданий, а внизу отобразим текущий уровень игры. Если по какому-то мячу выполнено 20 попаданий, то на нем рисуется крестик, но мяч с экрана не исчезает и все равно продолжает участвовать в игре. Когда по всем мячам будет выполнена норма, то очки сбрасываются и увеличивается уровень.
Всего предусмотрим 8 уровней. При успешном прохождении очередного уровня будем выдавать заставку выигрыша, а в случае падения мяча на поле стадиона - заставку проигрыша. В любом из этих двух режимов при нажатии клавиши Esc пользователь вновь попадает в режим игры. Но при выигрыше на последнем уровне пользователь попадает в меню игры.
Добавьте в папку Textures проекта
из прилагаемого каталога Source оставшиеся файлы с рисунками:bad.png - проигрышdagger.png -
крестикg1_over.png -
girls слева при проигрышеg1_victory.png -
girls слева при выигрышеg2_over .png -
girls справа при проигрышеg2_victory.png -
girls справа при выигрышеgood.png -
отличноtablo.png -
таблоДля управления всеми этими ресурсами создадим специальный класс.
Командой Project/Add Class добавьте
в корневой узел проекта Game3D новый файл с именем ResultScreen.cs и
скопируйте в него из файла StartGame3D.cs секцию using
Создайте в каталоге Fonts файл font1.spritefont со
следующими параметрами<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<FontName>Arial</FontName>
<Size>12</Size>
<Spacing>0</Spacing>
<UseKerning>true</UseKerning>
<Style>Bold</Style>
<CharacterRegions>
<CharacterRegion>
<Start>#32;</Start>
<End>#126;</End>
</CharacterRegion>
<CharacterRegion>
<Start>#1040;</Start>
<End>#1103;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>
Дополните класс ResultScreen следующим
кодом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
class ResultScreen
{
// Внешние данные
ContentManager content;
SpriteBatch spriteBatch;
int maxScore = 20; // Флаг для рисования крестика
SpriteFont font;
// Рисунки
Texture2D bad, dagger, g1_over, g1_victory, g2_over, g2_victory, good, tablo;
// Свойство доступа
public int MaxScore
{
get { return maxScore; }
set { maxScore = value; }
}
public ResultScreen() { } // Конструктор по умолчанию
public ResultScreen(ContentManager content, SpriteBatch spriteBatch)
{
this.content = content;
this.spriteBatch = spriteBatch;
}
public void Load()
{
// Загружаем шрифт
font = content.Load<SpriteFont>("Fonts\\font1");
// Загружаем рисунки
bad = content.Load<Texture2D>("Textures\\bad");
dagger = content.Load<Texture2D>("Textures\\dagger");
g1_over = content.Load<Texture2D>("Textures\\g1_over");
g1_victory = content.Load<Texture2D>("Textures\\g1_victory");
g2_over = content.Load<Texture2D>("Textures\\g2_over");
g2_victory = content.Load<Texture2D>("Textures\\g2_victory");
good = content.Load<Texture2D>("Textures\\good");
tablo = content.Load<Texture2D>("Textures\\tablo");
}
public void DrawResult(int[] scores, int level)
{
int x = 0, y = 0;
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(tablo, new Vector2(x, y), Color.White);
int shift = 33;
for (int i = 0; i < scores.Length; i++)
{
spriteBatch.DrawString(font, scores[i].ToString(), new Vector2(x + shift, y + 5), Color.White);
if (scores[i] >= maxScore)
spriteBatch.Draw(dagger, new Vector2(x + shift - 23, y + 37), Color.White);
shift += 65;
}
spriteBatch.DrawString(font, level.ToString(), new Vector2(x + 142, y + 108), Color.White);
spriteBatch.End();
}
public void DrawResult(int x, int y, GameState gameState)
{
//x = 300;
//y = 200;
switch (gameState)
{
case GameState.VictotyScreen:
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(g1_victory, new Vector2(x, y), Color.White);
spriteBatch.Draw(good, new Vector2(x + g1_victory.Width + 20, y + 50), Color.White);
spriteBatch.Draw(g2_victory, new Vector2(x + g1_victory.Width + good.Width, y), Color.White);
spriteBatch.End();
break;
case GameState.GameOverScreen:
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(g1_over, new Vector2(x, y), Color.White);
spriteBatch.Draw(bad, new Vector2(x + g1_over.Width - 20, y + 50), Color.White);
spriteBatch.Draw(g2_over, new Vector2(x + g2_over.Width + bad.Width, y), Color.White);
spriteBatch.End();
break;
}
}
}
}
Измените код класса StartGame3D для
введения механизма подсчета очков и реализации логики игры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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
// Объявление состояний экранов
enum GameState
{
AboutScreen, // Экран с информацией об игре
GameOverScreen, // Экран проигрыша
GameScreen, // Экран игрового процесса
HelpScreen, // Инструкция о правилах
MenuScreen, // Меню игры
SplashScreen, // Первая игровая заставка
VictotyScreen // Экран выигрыша
}
public class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.SplashScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
MenuScreen menu = new MenuScreen();
int menuState = (int)MenuState.Game;
// Флаги освобождения задействованных клавиш
bool enterFree = true; // Enter
bool upFree=true; // Стрелка вверх
bool downFree = true; // Стрелка вниз
bool escFree = true; // Esc
// Флаг освобождения мыши
bool mouseFree = true;
// Результаты игры
ResultScreen resultScreen;
int[] scores = { 0, 0, 0 };// Порядок индексов: Blue, Green, Red
int level = 1;
int maxLevel = 8;
#endregion
public StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
}
protected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
aspectRatio = (float)screenWidth / screenHeight; // Коэффициент искажения проекции
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
// Создаем объект с результатами игры
resultScreen = new ResultScreen(this.Content, spriteBatch);
resultScreen.Load();
}
// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
void MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
if (ball[i].Position.Y > -32)
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
else
{
gameState = GameState.GameOverScreen;// Присуждаем поражение
BeginPosition();
}
}
}
// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed mouseFree)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
scores[i] += 1;
}
}
// Проверяем победу
if (scores[0] >= resultScreen.MaxScore scores[1] >= resultScreen.MaxScore
scores[2] >= resultScreen.MaxScore)
{
gameState = GameState.VictotyScreen;
BeginPosition();
}
}
// Ждем освобождения кнопки мыши
mouseFree = mouseState.LeftButton == ButtonState.Released;
}
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из AboutScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.GameOverScreen:
// Выход из режима
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
{
// Сброс очков
for (int i = 0; i < scores.Length; i++)
scores[i] = 0;
gameState = GameState.GameScreen;
}
break;
case GameState.GameScreen:
// Выход из GameScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из HelpScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.MenuScreen:
// Выход из MenuScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.SplashScreen;
// Вход в другие состояния
if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
{
if ((MenuState)menuState == MenuState.Game)
gameState = GameState.GameScreen;
else if ((MenuState)menuState == MenuState.Help)
gameState = GameState.HelpScreen;
else if ((MenuState)menuState == MenuState.About)
gameState = GameState.AboutScreen;
else if ((MenuState)menuState == MenuState.Esc)
gameState = GameState.SplashScreen;
}
// Управление пунктами меню
if (keyboardState.IsKeyDown(Keys.Up) upFree)
{
menuState--;
if (menuState < 0)
menuState = 3;
}
else if (keyboardState.IsKeyDown(Keys.Down) downFree)
{
menuState++;
menuState %= 4; // Деление по модулю
}
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
this.Exit();
else if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
gameState = GameState.MenuScreen;
break;
case GameState.VictotyScreen:
// Выход из режима
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
{
// Сброс очков
for (int i = 0; i < scores.Length; i++)
scores[i] = 0;
if (level < maxLevel)
{
level++;
gameState = GameState.GameScreen;
}
else
{
level = 1;
gameState = GameState.MenuScreen;
}
}
break;
}
// Устанавливаем флаги освобождения клавиш
enterFree = keyboardState.IsKeyUp(Keys.Enter);
upFree = keyboardState.IsKeyUp(Keys.Up);
downFree = keyboardState.IsKeyUp(Keys.Down);
escFree = keyboardState.IsKeyUp(Keys.Escape);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
GraphicsDevice.Clear(Color.LightSalmon);
resultScreen.DrawResult(300, 200, gameState);
break;
case GameState.GameScreen:
// Нарисовать фон самым первым
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
background.DrawSprite(spriteBatch);
spriteBatch.End();
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Нарисовать стадион
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Рисуем табло до мячей и курсора
resultScreen.DrawResult(scores, level);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
menu.DrawScreen(
this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
GraphicsDevice.Clear(Color.PaleGreen);
resultScreen.DrawResult(300, 200, gameState);
break;
}
base.Draw(gameTime);
}
// Метод пересчета координат курсора в трехмерную сцену игры
Ray GetPickRay()
{
// Читаем стандартные координаты курсора
mouseState = Mouse.GetState();
// Вспомогательные переменные
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
// Обнуляем мировую матрицу
world = Matrix.CreateTranslation(0, 0, 0);
// Матрица вида, как в трехмерной сцене
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Матрица проекции, как в трехмерной сцене
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
// Проекция курсора на ближнюю плоскость отсечения объема
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
// Проекция курсора на дальнюю плоскость отсечения объема
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
return new Ray(nearPoint, direction);
}
}
}
В данном коде не производится усложнение игры путем увеличения скорости при переходе на новый уровень, а просто символически обозначается этот переход. В реальной игре такое усложнение должно быть.
Запустите проект на
выполнение и убедитесь в работе введенного механизма смены уровней игры и
подсчета очковНиже приведены уменьшенные экраны для введенных режимов, чтобы можно было сравнить результаты.
![]() |
![]() |
![]() |
Для тренировки мы добавим в программу звуковые эффекты для следующих случаев:
shot .wavmenu_select.wavvictory.wav (замкнем в петлю loop)over.wav
Через проводник решений
добавьте к проекту в папку Content подпапку Sound и скопируйте
в нее из прилагаемого каталога Source файлы shot .wav, menu_select.wav, victory.wav, over.wav
Добавьте в корневой
узел проекта из предыдущей работы Lab58 файл Sound.cs и модифицируйте
его следующим образомusing System;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
namespace Game3D
{
// Перечисление
enum SoundList
{
Shot, // Попали в мяч
MenuSelect, // Сменили пункт меню
Victory, // Выиграли уровень
Over // Проиграли уровень
}
static class Sound
{
static SoundEffect shot, menuSelect, victory, over;
// Для внешнего управления
public static SoundEffectInstance victoryInstance, overInstance;
public static void Load(ContentManager content)
{
shot = content.Load<SoundEffect>("Sound\\shot");
menuSelect = content.Load<SoundEffect>("Sound\\menu_select");
victory = content.Load<SoundEffect>("Sound\\victory");
over = content.Load<SoundEffect>("Sound\\over");
}
// Воспроизведение звука
public static void Play(SoundList sound)
{
switch (sound)
{
case SoundList.Shot:
shot.Play();
break;
case SoundList.MenuSelect:
menuSelect.Play();
break;
case SoundList.Victory:
victoryInstance = victory.Play();
break;
case SoundList.Over:
overInstance = over.Play();
break;
}
}
}
}
Включите вызов звука
в окончательный вид класса StartGame3D следующим
образом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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Game3D
{
// Объявление состояний экранов
enum GameState
{
AboutScreen, // Экран с информацией об игре
GameOverScreen, // Экран проигрыша
GameScreen, // Экран игрового процесса
HelpScreen, // Инструкция о правилах
MenuScreen, // Меню игры
SplashScreen, // Первая игровая заставка
VictotyScreen // Экран выигрыша
}
public class StartGame3D : Microsoft.Xna.Framework.Game
{
#region Поля класса
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
int screenWidth, screenHeight; // Размеры экрана
KeyboardState keyboardState; // Буфер клавиатуры
GameState gameState = GameState.SplashScreen; // Переменная состояния игры
Matrix world; // Мировая матрица
Matrix view; // Матрица вида
Matrix proj; // Проекционная матрица
float aspectRatio; // Коэффициент искажения проекции
float FOV = MathHelper.PiOver4; // Ракурс
float nearClip = 1.0f; // Ближняя отсекающая плоскость перспективы
float farClip = 1000.0f; // Дальняя отсекающая плоскость перспективы
//Vector3 camera = new Vector3(0.0f, 0.0f, 150.0f);
Vector3 camera = new Vector3(0.0f, 20.0f, 250.0f);
//ModelClass ball;
ModelClass[] ball = new ModelClass[3];
MouseState mouseState;
Random rand = new Random();
// Создание объекта курсора-прицела
Game2D.Sprite cursor = new Game2D.Sprite();
// Создание объекта модели стадиона
ModelClass stadium = new ModelClass();
// Создание объекта для рисунка пейзажа
Game2D.Sprite background = new Game2D.Sprite();
// Создание объектов для других состояний экранов
SplashScreen splash = new SplashScreen();
AboutScreen about = new AboutScreen();
HelpScreen help = new HelpScreen();
MenuScreen menu = new MenuScreen();
int menuState = (int)MenuState.Game;
// Флаги освобождения задействованных клавиш
bool enterFree = true; // Enter
bool upFree=true; // Стрелка вверх
bool downFree = true; // Стрелка вниз
bool escFree = true; // Esc
// Флаг освобождения мыши
bool mouseFree = true;
// Результаты игры
ResultScreen resultScreen;
int[] scores = { 0, 0, 0 };// Порядок индексов: Blue, Green, Red
int level = 1;
int maxLevel = 8;
#endregion
public StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
}
protected override void Initialize()
{
GraphicsAdapter adapter = graphics.GraphicsDevice.
CreationParameters.Adapter; // Получить ссылку на параметры адаптера
screenWidth = adapter.CurrentDisplayMode.Width; // Сохранить текущие размеры экрана
screenHeight = adapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = screenWidth; // Настроить размеры экранного буфера
graphics.PreferredBackBufferHeight = screenHeight;
graphics.IsFullScreen = true; // Перевести в полноэкранный режим
graphics.ApplyChanges(); // Применить настройки
aspectRatio = (float)screenWidth / screenHeight; // Коэффициент искажения проекции
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//ball.Load(this.Content, "Models\\Soccerball");
// Загружаем модели мячей
ball[0].Load(this.Content, "Models\\Soccerball");
ball[1].Load(this.Content, "Models\\SoccerballGreen");
ball[2].Load(this.Content, "Models\\SoccerballRed");
// Загружаем трехмерную модель стадиона
stadium.Load(this.Content, "Models\\stadium 1");
// Устанавливаем начальную позицию объектов
BeginPosition();
// Загружаем рисунок курсора-прицела
cursor.Load(this.Content, "Textures\\cursor");
// Загружаем рисунок заднего фона
background.Load(this.Content, "Textures\\hallake001");
// Чуть приподнимем вверх экрана
background.spritePosition = new Vector2(0, -50);
// Инициализация объектов других состояний экранов
splash.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
about.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
help.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
menu.InitializeSplashScreen(this.Content, screenWidth, screenHeight);
// Создаем объект с результатами игры
resultScreen = new ResultScreen(this.Content, spriteBatch);
resultScreen.Load();
// Загружаем звук
Sound.Load(this.Content);
}
// Устанавливаем начальную позицию объекта
void BeginPosition()
{
for (int i = 0; i < ball.Length; i++)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
}
}
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
void MoveBalls()
{
for (int i = 0; i < ball.Length; i++)
{
if (ball[i].Position.Y > -32)
ball[i].Position -= new Vector3(0, ball[i].Speed, 0);
else
{
gameState = GameState.GameOverScreen;// Присуждаем поражение
BeginPosition();
}
}
}
// Механизм стрельбы по целям
BoundingSphere[] bb = new BoundingSphere[3];
void MouseClick()
{
// Получаем текущие координаты курсора и передаем их центру объекта прицела
mouseState = Mouse.GetState();
cursor.spritePosition.X = mouseState.X - cursor.spriteTexture.Width / 2;
cursor.spritePosition.Y = mouseState.Y - cursor.spriteTexture.Height / 2;
// Одеваем на каждый мячик ограничивающую сферу с заданным радиусом
for (int i = 0; i < bb.Length; i++)
{
bb[i].Center = ball[i].Position;
bb[i].Radius = ball[i].Radius;
}
// Определяем попадание и при успехе
// устанавливаем мяч в начальную позицию
if (mouseState.LeftButton == ButtonState.Pressed mouseFree)
{
// Пересчитываем координаты курсора на трехмерный объем
Ray pickRay = GetPickRay();
// Проверяем попадание
for (int i = 0; i < ball.Length; i++)
{
Nullable<float> result = pickRay.Intersects(bb[i]);
if (result.HasValue == true)
{
ball[i].Position = new Vector3(
rand.Next(-rand.Next(0, 80), rand.Next(0, 80)),
rand.Next(0, 80),
//-rand.Next(20, 150));
rand.Next(-rand.Next(0, 50), rand.Next(0, 150)));
scores[i] += 1;
Sound.Play(SoundList.Shot); // Попадания по мячу
}
}
// Проверяем победу
if (scores[0] >= resultScreen.MaxScore scores[1] >= resultScreen.MaxScore
scores[2] >= resultScreen.MaxScore)
{
gameState = GameState.VictotyScreen;
BeginPosition();
}
}
// Ждем освобождения кнопки мыши
mouseFree = mouseState.LeftButton == ButtonState.Released;
}
bool soundFree = true; // Предохранитель от повторного выполнения звука
protected override void Update(GameTime gameTime)
{
// Читать буфер клавиатуры
keyboardState = Keyboard.GetState();
switch (gameState)
{
case GameState.AboutScreen:
// Выход из AboutScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.GameOverScreen:
if (soundFree)
{
Sound.Play(SoundList.Over);
soundFree = false;
}
// Выход из режима
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
{
// Сброс очков
for (int i = 0; i < scores.Length; i++)
scores[i] = 0;
gameState = GameState.GameScreen;
soundFree = true;
Sound.overInstance.Stop();// Остановить немедленно
}
break;
case GameState.GameScreen:
// Выход из GameScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
MoveBalls();
MouseClick();
break;
case GameState.HelpScreen:
// Выход из HelpScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.MenuScreen;
break;
case GameState.MenuScreen:
// Выход из MenuScreen
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
gameState = GameState.SplashScreen;
// Вход в другие состояния
if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
{
if ((MenuState)menuState == MenuState.Game)
gameState = GameState.GameScreen;
else if ((MenuState)menuState == MenuState.Help)
gameState = GameState.HelpScreen;
else if ((MenuState)menuState == MenuState.About)
gameState = GameState.AboutScreen;
else if ((MenuState)menuState == MenuState.Esc)
gameState = GameState.SplashScreen;
}
// Управление пунктами меню
if (keyboardState.IsKeyDown(Keys.Up) upFree)
{
menuState--;
if (menuState < 0)
menuState = 3;
Sound.Play(SoundList.MenuSelect);// Звук
}
else if (keyboardState.IsKeyDown(Keys.Down) downFree)
{
menuState++;
menuState %= 4; // Деление по модулю
Sound.Play(SoundList.MenuSelect);// Звук
}
break;
case GameState.SplashScreen:
// Выход из игрового процесса
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
this.Exit();
else if (keyboardState.IsKeyDown(Keys.Enter) enterFree)
gameState = GameState.MenuScreen;
break;
case GameState.VictotyScreen:
if (soundFree || Sound.victoryInstance.State == SoundState.Stopped)
{
// Повторять по кругу
Sound.Play(SoundList.Victory);
soundFree = false;
}
// Выход из режима
if (keyboardState.IsKeyDown(Keys.Escape) escFree)
{
// Сброс очков
for (int i = 0; i < scores.Length; i++)
scores[i] = 0;
if (level < maxLevel)
{
level++;
gameState = GameState.GameScreen;
}
else
{
level = 1;
gameState = GameState.MenuScreen;
}
soundFree = true;
Sound.victoryInstance.Stop();// Остановить немедленно
}
break;
}
// Устанавливаем флаги освобождения клавиш
enterFree = keyboardState.IsKeyUp(Keys.Enter);
upFree = keyboardState.IsKeyUp(Keys.Up);
downFree = keyboardState.IsKeyUp(Keys.Down);
escFree = keyboardState.IsKeyUp(Keys.Escape);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
switch (gameState)
{
case GameState.AboutScreen:
about.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.GameOverScreen:
GraphicsDevice.Clear(Color.LightSalmon);
resultScreen.DrawResult(300, 200, gameState);
break;
case GameState.GameScreen:
// Нарисовать фон самым первым
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
background.DrawSprite(spriteBatch);
spriteBatch.End();
// Включить буфер глубины
graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
// Установить камеру
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Задать проекционную матрицу
proj = Matrix.CreatePerspectiveFieldOfView(FOV,
aspectRatio, nearClip, farClip);
// Нарисовать стадион
world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
stadium.DrawModel(world, view, proj);
// Рисуем табло до мячей и курсора
resultScreen.DrawResult(scores, level);
// Нарисовать модели мячей
for (int i = 0; i < ball.Length; i++)
{
// Определить матрицу преобразования
world = Matrix.CreateTranslation(ball[i].Position);
// Нарисовать модель в начальном состоянии
ball[i].DrawModel(world, view, proj);
}
// Нарисовать курсор-прицел
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
cursor.DrawSprite(spriteBatch);
spriteBatch.End();
break;
case GameState.HelpScreen:
help.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.MenuScreen:
menu.DrawScreen(
this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime, menuState);
break;
case GameState.SplashScreen:
splash.DrawScreen(this.spriteBatch, this.graphics, screenWidth, screenHeight, gameTime);
break;
case GameState.VictotyScreen:
GraphicsDevice.Clear(Color.PaleGreen);
resultScreen.DrawResult(300, 200, gameState);
break;
}
base.Draw(gameTime);
}
// Метод пересчета координат курсора в трехмерную сцену игры
Ray GetPickRay()
{
// Читаем стандартные координаты курсора
mouseState = Mouse.GetState();
// Вспомогательные переменные
int mouseX = mouseState.X;
int mouseY = mouseState.Y;
Vector3 nearSource = new Vector3((float)mouseX, (float)mouseY, 0.0f);
Vector3 farSource = new Vector3((float)mouseX, (float)mouseY, 1.0f);
// Обнуляем мировую матрицу
world = Matrix.CreateTranslation(0, 0, 0);
// Матрица вида, как в трехмерной сцене
view = Matrix.CreateLookAt(camera, Vector3.Zero, Vector3.Up);
// Матрица проекции, как в трехмерной сцене
proj = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio, nearClip, farClip);
// Проекция курсора на ближнюю плоскость отсечения объема
Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource, proj, view, world);
// Проекция курсора на дальнюю плоскость отсечения объема
Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource, proj, view, world);
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
return new Ray(nearPoint, direction);
}
}
}
Запустите приложение
и убедитесь в правильной работе звуковых эффектов
Откомпилируйте проект
в режиме Release и сформируйте установочный пакет готового приложения,
как это было показано в предыдущей работе Lab58
Сформируйте установочный
диск со всеми необходимыми библиотеками для распространения программы, в
том числе для предоставления преподавателюТолько для тренировки, чтобы запомнить эффектную возможность, введем фукциональность, по которой при завершении работы нашего приложения выключится компьютер (или запустится иное указанное нами внешнее приложение). Тот код, который мы сейчас добавим, после проверки его работы нужно удалить. И перед испытанием закройте все запущенные приложения, данные которых жалко потерять.
В конструкторе класса StartGame3D() зарегистрируйте обработчик для события Exiting следующим образомpublic StartGame3D()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// Создание экземпляров класса ModelClass
//ball = new ModelClass();
for (int i = 0; i < ball.Length; i++)
{
ball[i] = new ModelClass();
}
// Включаем стандартный курсор мыши
//this.IsMouseVisible = true;
// Регистрируем обработчик события Exiting
this.Exiting += new EventHandler(StartGame3D_Exiting);
}
void StartGame3D_Exiting(object sender, EventArgs e)
{
// Внимание!!! Для тренировки...
// При завершении приложения выключим компьютер
System.Diagnostics.Process exe = new System.Diagnostics.Process();
exe.StartInfo.FileName = @"cmd.exe"; //Имя файла для запуска
exe.StartInfo.Arguments = "/c shutdown -s"; //Аргументы
exe.Start();
}
Откомпилируйте проект
командой Build/Rebuild Solution и закройте все программы, включая Visual Studio
Запустите проводник Windows Explorer, зайдите в каталог проекта bin\x86\Debug и запустите
файл Game3D приложенияПосле выхода из приложения компьютер выключится!!!
Запустите компьютер,
откройте проект и удалите только что введенный код, показанный красным цветомНа этом выполнение работы закончено, можете 5 минут отдохнуть.

Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.