Цель работы: Научиться применять
Тема
Очень сложно найти компьютерную игру, которая обходится без ИИ. Классический пример "умных" игр – это программы для игры в шашки, шахматы и прочие настольные игры. Каждая игра, в которой компьютер играет против пользователя, оснащена ИИ.
Игровой ИИ, в первом приближении, можно разделить на два вида. Первый – наиболее очевидный – это
Второй уровень ИИ – это групповой
Если бы действия армии в StarCraft никак не контролировались, а ИИ присутствовал лишь на уровне отдельного юнита – игра превратилась бы в скучный поиск и уничтожение врагов. А StarCraft, несмотря на серьёзный возраст остаётся увлекательной игрой. Даже в однопользовательской кампании StarCraft способна очень сильно "затянуть" игрока, не говоря уже о сетевых баталиях.
Кстати, несложно заметить, что в том же StarCraft индивидуальный ИИ есть и у юнитов, которыми управляет пользователь. Например, та же команда "патрулировать", отданная пользователем, заставит существо из StarCraft послушно ходить по указанному пути. Но если на пути появится препятствие (например, игрок построит там здание, преграждающее путь) – юнит сам решит, что ему делать. Аналогично, он самостоятельно примет решение об атаке, если в поле его видимости появятся враги.
Системы ИИ, применяемые в компьютерных играх, можно разделить на два основных вида. Во-первых – это так называемые детерминированные системы. Они отличаются
Как мы уже сказали, индивидуальный ИИ юнитов играет подчинённую роль в сравнении с групповым ИИ. А может ли ИИ отдельного юнита повлиять на игру вцелом? Может – в том случае, если предусмотрено распространение успехов отдельного юнита на всех схожих. Например, какой-то юнит столкнулся с сильным противником и чудом вышел победителем в схватке. Этот юнит набрался опыта, который, благодаря групповому ИИ, может быть распространён на других юнитов. Т.е. если один юнит чему-то научился, другие, благодаря групповому ИИ, смогут перенять у него новые умения. Таким образом, индивидуальный и групповой ИИ взаимосвязаны, а в некоторых случаях и взаимозависимы.
Персонаж, оснащённый недетерминированным ИИ, отличается непредсказуемостью поведения, большей "живостью". Играть против таких персонажей обычно гораздо интереснее, чем против жёстко детерминированных. Популярным в последнее время способом реализации недетерминированного ИИ является технология
На практике находят применение как детерминированные, так и недетерминированные виды ИИ. Обычно они действуют совместно. Например, для выполнения каких-то простых однозначных действий (скажем, при приближении к стене свернуть) могут применяться простые и быстрые детерминированные алгоритмы. В более сложных случаях (например – купить ли
Реализуем игру, использующую алгоритм преследования. Сущность этого алгоритма заключается в следующем. Объект-преследователь сравнивает свои координаты в игровом мире с координатами объекта-жертвы и корректирует свои координаты таким образом, чтобы приблизиться к жертве. В простейшем случае преследование осуществляется на открытом пространстве.
Создадим новый игровой проект P12_1 на основе проекта P10_1. Будем использовать два объекта – преследователя и жертву. Преследователь будет перемещаться в сторону жертвы со скоростью, на 1 меньше, чем скорость жертвы. Если объекты столкнуться – жертва будет уничтожена.
На рис. 18.1 приведено окно Обозреватель решений игрового проекта P12_1.
(рис 18.1) Обозреватель решений, P12_1
Мы используем базовый класс gBaseClass, класс для объекта-преследователя (Enemy), класс объекта-жертвы (Me) и класс для объекта-стены. Класс объекта-стены будет нужен нам для того, чтобы изучить поведение объекта, реализующего алгоритм преследования, при столкновении с непреодолимым препятствием на пути к жертве. Мы значительно модифицировали код классов в сравнении с исходным проектом P5_1, поэтому ниже вы найдете полный код классов игры. В листинге 18.1 вы можете найти код класса Game1.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
namespace P12_1
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D txtBackground;
Texture2D txtEnemy;
Texture2D txtMe;
Texture2D txtWall;
Texture2D txtArrows;
SpriteFont myFont;
//Массив для конструирования уровня
public int[,] Layer;
Rectangle recBackround = new Rectangle(16, 0, 768, 448);
Rectangle recSprite = new Rectangle(0, 0, 64, 64);
Rectangle recArrows = new Rectangle(500, 280, 300, 200);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
Layer = new int[7, 12] {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
Services.AddService(typeof(SpriteBatch), spriteBatch);
txtBackground = Content.Load<Texture2D>("background");
txtEnemy = Content.Load<Texture2D>("enemy");
txtMe = Content.Load<Texture2D>("me");
txtWall = Content.Load<Texture2D>("wall");
txtArrows = Content.Load<Texture2D>("arrows");
myFont = Content.Load<SpriteFont>("MyFont");
//Вызываем процедуру расстановки объектов в игровом окне
AddSprites();
}
void AddSprites()
{
//Переменные для временного хранения адреса
//объекта-игрока
int a = 0, b = 0;
//Просматриваем массив Layer
for (int i = 0; i < 7; i++)
{
for (int j = 0; j < 12; j++)
{
//Если элемент с индексом (i,j) равен 1 -
//устанавливаем в соответствующую позицию элемент с
//номером 1, то есть - стену
if (Layer[i, j] == 1)
Components.Add(new GameObjects.Wall(this, ref txtWall, new Vector2(j, i), recSprite, myFont));
if (Layer[i, j] == 5)
Components.Add(new GameObjects.Enemy(this, ref txtEnemy, new Vector2(j, i),
new Rectangle(0, 0, 32, 32), myFont));
//Если обнаружен объект игрока - запишем его координаты
if (Layer[i, j] == 6)
{
a = i;
b = j;
}
}
}
//Последним установим объект игрока - так он гарантированно
//расположен поверх всех остальных объектов
Components.Add(new GameObjects.Me(this, ref txtMe, new Vector2(b, a), new Rectangle(0, 0, 32, 32), myFont));
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
txtBackground.Dispose();
txtEnemy.Dispose();
txtMe.Dispose();
txtWall.Dispose();
spriteBatch.Dispose();
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// ЗАДАЧА: добавьте здесь логику обновления
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// ЗАДАЧА: добавьте здесь код отрисовки
spriteBatch.Begin();
//выведем фоновое изображение
spriteBatch.Draw(txtBackground, recBackround, Color.White);
//Выведем игровые объекты
base.Draw(gameTime);
//Стрелки
spriteBatch.Draw(txtArrows, recArrows, Color.White);
spriteBatch.End();
}
}
}
В данном классе производится инициализация матрицы игрового мира и расстановка объектов. Здесь мы, помимо объекта-игрока и объекта-преследователя, разместили на игровом поле стену (рис.12.2.).
(рис 18.2) Преследователь не умеет обходить препятствия
С её помощью можно заметить, что объект-преследователь может выполнять свои функции лишь на открытом пространстве. Если между преследователем и игроком находится стена – преследователь упирается в нее, перемещаясь вдоль стены до тех пор, пока либо не окажется на
В листинге 18.2 приведен код класса gBaseClass.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_1.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent
{
Texture2D sprTexture;
public Vector2 sprPosition;
public Rectangle sprRectangle;
public SpriteFont myFont;
public string message="";
//Прямоугольник, представляющий игровое окно
public Rectangle scrBounds;
//Скорость объекта
public float sprSpeed;
public gBaseClass(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game)
{
sprTexture = _sprTexture;
//Именно здесь производится перевод индекса элемента массива
//в координаты на игровом экране
sprPosition = _sprPosition * 64;
sprPosition.X += 16;
sprRectangle = _sprRectangle;
myFont = _myFont;
scrBounds = new Rectangle(16, 0, 768, 448);
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// ЗАДАЧА: добавьте здесь код обновления
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
SpriteBatch sprBatch =
(SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
sprBatch.Draw(sprTexture, sprPosition, Color.White);
//Если строка сообщения не пуста
if (this.message != "")
{
sprBatch.DrawString(myFont, message, new Vector2(0, 455), Color.Red);
}
base.Draw(gameTime);
}
//Проверка на столкновение с каким-либо объектом
public bool IsCollideWithObject(gBaseClass spr)
{
return (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X
this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width
this.sprPosition.Y + this.sprRectangle.Height > spr.sprPosition.Y
this.sprPosition.Y < spr.sprPosition.Y + spr.sprRectangle.Height);
}
//Процедуры для перемещения объекта
public void MoveUp(float speed)
{
this.sprPosition.Y -= speed;
}
public void MoveDown(float speed)
{
this.sprPosition.Y += speed;
}
public void MoveLeft(float speed)
{
this.sprPosition.X -= speed;
}
public void MoveRight(float speed)
{
this.sprPosition.X += speed;
}
//Проверка на столкновение со стеной
public bool IsCollideWithWall()
{
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Wall)))
{
if (IsCollideWithObject(spr)) return true;
}
}
return false;
}
//Проверка на столкновение с границами экрана
public void Check()
{
if (sprPosition.X <= scrBounds.Left)
{
sprPosition.X = scrBounds.Left;
}
if (sprPosition.X >= scrBounds.Width - sprRectangle.Width)
{
sprPosition.X = scrBounds.Width - sprRectangle.Width;
}
if (sprPosition.Y <= scrBounds.Top)
{
sprPosition.Y = scrBounds.Top;
}
if (sprPosition.Y >= scrBounds.Height - sprRectangle.Height)
{
sprPosition.Y = scrBounds.Height - sprRectangle.Height;
}
}
}
}
Мы включили в этот класс методы и свойства, которые будем использовать и для объекта игрока и для объекта-преследователя. Они знакомы вам по проекту P10_1.
Теперь рассмотрим код класса Wall – (листинг 18.3.) мы приводим его здесь лишь для
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_1.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Wall : gBaseClass
{
public Wall(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
// ЗАДАЧА: здесь создаются дочерние компоненты
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// ЗАДАЧА: добавьте здесь код обновления
base.Update(gameTime);
}
}
}
Листинг 18.4. содержит код класса Me.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
namespace P12_1.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Me : gBaseClass
{
public Me(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
sprSpeed = 2;
}
public void IsCollideWithAny()
{
//Заводим переменную для временного хранения
//ссылки на объект, с которым столкнулся игровой объект
gBaseClass FindObj = null;
//Проверка на столкновение с объектами Enemy
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Enemy)))
{
if (IsCollideWithObject(spr))
{
FindObj = spr;
}
}
}
if (FindObj != null)
{
this.Dispose();
}
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
//Перемещение объекта
void Move(int Direction)
{
//Вверх
if (Direction == 1)
{
MoveUp(sprSpeed);
//При прыжке проводится проверка на контакт со стеной
//Которая может быть расположена над объектом
//при необходимости его координаты корректируются
while (IsCollideWithWall())
{
MoveDown((sprSpeed / 10));
}
}
//Вниз
//Происходит обычная процедура перемещения
//объекта с проверкой
if (Direction == 2)
{
MoveDown(sprSpeed);
while (IsCollideWithWall())
{
MoveUp((sprSpeed / 10));
}
}
//Точно так же обрабатывается
//движение Влево
if (Direction == 3)
{
MoveLeft(sprSpeed);
while (IsCollideWithWall())
{
MoveRight((sprSpeed / 10));
}
}
//Аналогично - перемещение вправо
if (Direction == 4)
{
MoveRight(sprSpeed);
while (IsCollideWithWall())
{
MoveLeft((sprSpeed / 10));
}
}
}
//Обработка сенсорного ввода
void TouchInput()
{
//Реализуем управление объектом
//Получаем коллекцию объектов, содержащих информацию о касаниях экрана
TouchCollection touchLocations = TouchPanel.GetState();
//Перебираем коллекцию, присваивая объекту координаты касания
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Стрелка "Влево"
if (touchLocation.Position.X > 500 touchLocation.Position.X < 600
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(3);
}
//Стрелка "Вправо"
if (touchLocation.Position.X > 700 touchLocation.Position.X < 800
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(4);
}
//Стрелка "Вниз"
if (touchLocation.Position.X > 600 touchLocation.Position.X < 700
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(2);
}
//Стрелка "Вверх"
if (touchLocation.Position.X > 600 touchLocation.Position.X < 700
touchLocation.Position.Y > 280 touchLocation.Position.Y < 380)
{
Move(1);
}
}
}
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
//Обработаем касания экрана для реализации перемещения объекта
TouchInput();
//Проверим, не вышел ли он за границы экрана, если надо
//исправим его позицию
Check();
//Применим к объекту "силу тяжести"
//Проверим на другие столкновения
IsCollideWithAny();
message = "Try to escape!";
base.Update(gameTime);
}
}
}
Объект класса Me умеет перемещаться по командам, полученных от сенсорной панели, не выходя за границы игровой области экрана, останавливаться, "натыкаясь" на стену, а при столкновении с объектом-преследователем объект класса Me уничтожается. Алгоритм преследования реализован в коде класса Enemy (листинг 18.5).
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_1.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Enemy : gBaseClass
{
Vector2 Direction;
public Enemy(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
sprSpeed = 1;
Direction = new Vector2(0, 0);
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
//Вычисление направления на объект-игрока и передвижение в
//этом направлении
void SeekAndDestroy()
{
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Me)))
{
//найдем разницу между координатами преследователя и
//игрока
Direction = (this.sprPosition - spr.sprPosition);
//Если разность по X положительная
//переместим преследователя влево
//с учетом того, что стены для него
//непроницаемы.
if (Direction.X > 0)
{
MoveLeft(sprSpeed);
while (IsCollideWithWall())
{
MoveRight((sprSpeed / 10));
}
}
//При отрицательной разности по X
//переместим объект вправо
if (Direction.X < 0)
{
MoveRight(sprSpeed);
while (IsCollideWithWall())
{
MoveLeft((sprSpeed / 10));
}
}
//При положительной разности по Y
//переместим объект вверх
if (Direction.Y > 0)
{
MoveUp(sprSpeed);
while (IsCollideWithWall())
{
MoveDown((sprSpeed / 10));
}
}
//При отрицательной разности по Y переместим
//объект вниз
if (Direction.Y < 0)
{
MoveDown(sprSpeed);
while (IsCollideWithWall())
{
MoveUp((sprSpeed / 10));
}
}
}
}
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// Перемещение к объекту игрока
SeekAndDestroy();
//Проверка на столкновение с границами экрана
Check();
base.Update(gameTime);
}
}
}
На каждом шаге игрового цикла объект-преследователь сравнивает свои координаты и координаты объекта игрока и проводит необходимую коррекцию своей позиции.
Как мы уже отмечали, наш объект-преследователь не умеет обходить препятствия. Рассмотрим реализацию алгоритма преследования с обходом препятствий.
Существует немало алгоритмов поиска пути на карте с препятствиями. Один из них заключается в следующем – объект двигается по карте, "держась рукой" за стену. Объект перемещается вдоль стен, выполняя повороты лишь в одну сторону, таким образом он гарантированно обойдет все места на карте, вдоль которых находятся стены или другие границы.
Это достаточно простой алгоритм, подходящий для несложных игр. В играх более сложных его применение может вызвать отрицательные эмоции у игрока – поэтому в таких играх следует применять более сложные алгоритмы. Например, для поиска
Мы используем комбинированный алгоритм – он рассчитан на действия в трех ситуациях. Во-первых, если объект-преследователь "видит" объект-цель – он перемещается к ней в
Создадим новый проект P12_2 на основе проекта P12_1. Ранее объект-преследователь перемещался в направлении объекта-цели, мог передвигаться вдоль вертикальных "стен", но даже простейшая конфигурация из препятствий была способна задержать его. Теперь же мы реализуем в объекте-преследователе алгоритм обхода препятствий
Состав проекта, по сравнению с проектом P12_1, не изменился, однако код объектов и основного игрового класса претерпел некоторые изменения. Ниже приведен полный код проекта P12_2.
В листинге 18.6 приведен код класса Game1.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
namespace P12_2
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D txtBackground;
Texture2D txtEnemy;
Texture2D txtMe;
Texture2D txtWall;
Texture2D txtArrows;
SpriteFont myFont;
//Массив для конструирования уровня
public int[,] Layer;
Rectangle recBackround = new Rectangle(16, 0, 768, 448);
Rectangle recSprite = new Rectangle(0, 0, 64, 64);
Rectangle recArrows = new Rectangle(500, 280, 300, 200);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
Layer = new int[7, 12] {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 },
{ 0, 5, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 6 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
Services.AddService(typeof(SpriteBatch), spriteBatch);
txtBackground = Content.Load<Texture2D>("background");
txtEnemy = Content.Load<Texture2D>("enemy");
txtMe = Content.Load<Texture2D>("me");
txtWall = Content.Load<Texture2D>("wall");
txtArrows = Content.Load<Texture2D>("arrows");
myFont = Content.Load<SpriteFont>("MyFont");
//Вызываем процедуру расстановки объектов в игровом окне
AddSprites();
}
void AddSprites()
{
//Переменные для временного хранения адреса
//объекта-игрока
int a = 0, b = 0;
//Просматриваем массив Layer
for (int i = 0; i < 7; i++)
{
for (int j = 0; j < 12; j++)
{
//Если элемент с индексом (i,j) равен 1 -
//устанавливаем в соответствующую позицию элемент с
//номером 1, то есть - стену
if (Layer[i, j] == 1)
Components.Add(new GameObjects.Wall(this, ref txtWall, new Vector2(j, i), recSprite, myFont));
if (Layer[i, j] == 5)
Components.Add(new GameObjects.Enemy(this, ref txtEnemy, new Vector2(j, i), new Rectangle(0, 0, 32, 32), myFont));
//Если обнаружен объект игрока - запишем его координаты
if (Layer[i, j] == 6)
{
a = i;
b = j;
}
}
}
//Последним установим объект игрока - так он гарантированно
//расположен поверх всех остальных объектов
Components.Add(new GameObjects.Me(this, ref txtMe, new Vector2(b, a), new Rectangle(0, 0, 32, 32), myFont));
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
txtBackground.Dispose();
txtEnemy.Dispose();
txtMe.Dispose();
txtWall.Dispose();
spriteBatch.Dispose();
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// ЗАДАЧА: добавьте здесь логику обновления
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// ЗАДАЧА: добавьте здесь код отрисовки
spriteBatch.Begin();
//выведем фоновое изображение
spriteBatch.Draw(txtBackground, recBackround, Color.White);
//Выведем игровые объекты
base.Draw(gameTime);
//Стрелки
spriteBatch.Draw(txtArrows, recArrows, Color.White);
spriteBatch.End();
}
}
}На рис. 18.3 вы можете видеть карту этого проекта.
(рис 18.3) Карта проекта P12_2
В листинге 18.7 вы можете найти код класса gBaseClass
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_2.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent
{
Texture2D sprTexture;
public Vector2 sprPosition;
public Rectangle sprRectangle;
public SpriteFont myFont;
public string message="";
//Прямоугольник, представляющий игровое окно
public Rectangle scrBounds;
//Скорость объекта
public float sprSpeed;
public gBaseClass(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game)
{
sprTexture = _sprTexture;
//Именно здесь производится перевод индекса элемента массива
//в координаты на игровом экране
sprPosition = _sprPosition * 64;
sprPosition.X += 16;
sprRectangle = _sprRectangle;
myFont = _myFont;
scrBounds = new Rectangle(16, 0, 768, 448);
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// ЗАДАЧА: добавьте здесь код обновления
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
SpriteBatch sprBatch =
(SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
sprBatch.Draw(sprTexture, sprPosition, Color.White);
//Если строка сообщения не пуста
if (this.message != "")
{
sprBatch.DrawString(myFont, message, new Vector2(0, 455), Color.Red);
}
base.Draw(gameTime);
}
//Проверка на столкновение с каким-либо объектом
public bool IsCollideWithObject(gBaseClass spr)
{
return (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X
this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width
this.sprPosition.Y + this.sprRectangle.Height > spr.sprPosition.Y
this.sprPosition.Y < spr.sprPosition.Y + spr.sprRectangle.Height);
}
//Процедуры для перемещения объекта
public void MoveUp(float speed)
{
this.sprPosition.Y -= speed;
}
public void MoveDown(float speed)
{
this.sprPosition.Y += speed;
}
public void MoveLeft(float speed)
{
this.sprPosition.X -= speed;
}
public void MoveRight(float speed)
{
this.sprPosition.X += speed;
}
//Проверка на столкновение со стеной
public bool IsCollideWithWall()
{
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Wall)))
{
if (IsCollideWithObject(spr)) return true;
}
}
return false;
}
//Проверка на столкновение с границами экрана
public void Check()
{
if (sprPosition.X <= scrBounds.Left)
{
sprPosition.X = scrBounds.Left;
}
if (sprPosition.X >= scrBounds.Width - sprRectangle.Width)
{
sprPosition.X = scrBounds.Width - sprRectangle.Width;
}
if (sprPosition.Y <= scrBounds.Top)
{
sprPosition.Y = scrBounds.Top;
}
if (sprPosition.Y >= scrBounds.Height - sprRectangle.Height)
{
sprPosition.Y = scrBounds.Height - sprRectangle.Height;
}
}
public bool CheckBounds()
{
return ((sprPosition.X <= scrBounds.Left |
sprPosition.X >= scrBounds.Width - sprRectangle.Width |
sprPosition.Y <= scrBounds.Top |
sprPosition.Y >= scrBounds.Height - sprRectangle.Height));
}
}
}
Листинг 18.8. содержит код класса Wall.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_2.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Wall : gBaseClass
{
public Wall(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
// ЗАДАЧА: здесь создаются дочерние компоненты
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// ЗАДАЧА: добавьте здесь код обновления
base.Update(gameTime);
}
}
}
Листинг 18.9. содержит код класса Me.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
namespace P12_2.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Me : gBaseClass
{
public Me(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
sprSpeed = 2;
}
public void IsCollideWithAny()
{
//Заводим переменную для временного хранения
//ссылки на объект, с которым столкнулся игровой объект
gBaseClass FindObj = null;
//Проверка на столкновение с объектами Enemy
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Enemy)))
{
if (IsCollideWithObject(spr))
{
FindObj = spr;
}
}
}
if (FindObj != null)
{
this.Dispose();
}
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
//Перемещение объекта
void Move(int Direction)
{
//Вверх
if (Direction == 1)
{
MoveUp(sprSpeed);
//При прыжке проводится проверка на контакт со стеной
//Которая может быть расположена над объектом
//при необходимости его координаты корректируются
while (IsCollideWithWall())
{
MoveDown((sprSpeed / 10));
}
}
//Вниз
//Происходит обычная процедура перемещения
//объекта с проверкой
if (Direction == 2)
{
MoveDown(sprSpeed);
while (IsCollideWithWall())
{
MoveUp((sprSpeed / 10));
}
}
//Точно так же обрабатывается
//движение Влево
if (Direction == 3)
{
MoveLeft(sprSpeed);
while (IsCollideWithWall())
{
MoveRight((sprSpeed / 10));
}
}
//Аналогично - перемещение вправо
if (Direction == 4)
{
MoveRight(sprSpeed);
while (IsCollideWithWall())
{
MoveLeft((sprSpeed / 10));
}
}
}
//Обработка сенсорного ввода
void TouchInput()
{
//Реализуем управление объектом
//Получаем коллекцию объектов, содержащих информацию о касаниях экрана
TouchCollection touchLocations = TouchPanel.GetState();
//Перебираем коллекцию, присваивая объекту координаты касания
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Стрелка "Влево"
if (touchLocation.Position.X > 500 touchLocation.Position.X < 600
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(3);
}
//Стрелка "Вправо"
if (touchLocation.Position.X > 700 touchLocation.Position.X < 800
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(4);
}
//Стрелка "Вниз"
if (touchLocation.Position.X > 600 touchLocation.Position.X < 700
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(2);
}
//Стрелка "Вверх"
if (touchLocation.Position.X > 600 touchLocation.Position.X < 700
touchLocation.Position.Y > 280 touchLocation.Position.Y < 380)
{
Move(1);
}
}
}
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
//Обработаем касания экрана для реализации перемещения объекта
TouchInput();
//Проверим, не вышел ли он за границы экрана, если надо
//исправим его позицию
Check();
//Применим к объекту "силу тяжести"
//Проверим на другие столкновения
IsCollideWithAny();
message = "Try to escape!";
base.Update(gameTime);
}
}
}
Комментарии к коду раскрывают особенности алгоритма.
Задача реализации
Результаты такого упрощения обычно выражаются в некоторых странностях в поведении персонажей. Например, персонаж может "застрять" в двери или в каком-нибудь другом месте карты, при прохождении которого ИИ, встроенный в игру, не предусматривает однозначного решения. При разработке системы ИИ широко применяется предварительный просчет возможных действий персонажей, после чего полученные данные применяются в ходе игры.
Предварительно может быть составлена карта обхода местности, которая предусматривает указание путей прохождения и точек, в которых требуются какие-то особые действия персонажа – прыжок для преодоления препятствия, поворот, выполнение определенной манипуляции с другими объектами игрового мира. Обычно игровые персонажи имеют комбинированный ИИ.
Например, заранее могут быть просчитаны возможные пути прохождения карты, а если при прохождении персонаж сталкивается с какими-либо подвижными объектами, он ведет себя уже не в соответствии с общей картой прохождения, а в соответствии с правилами обхода локальных препятствий.
Разработайте игровой проект – клон игры "Battle City" - симулятор танкового боя на карте, подобной карте, применяемой в наших примерах. Создайте следующие объекты карты:
Два вида танков врага
Систему бонусных объектов
Снаряд
К данной лабораторной работе прилагается видеоурок
и демонстрационный проект.
Цель работы: Научиться применять
Тема
Очень сложно найти компьютерную игру, которая обходится без ИИ. Классический пример "умных" игр – это программы для игры в шашки, шахматы и прочие настольные игры. Каждая игра, в которой компьютер играет против пользователя, оснащена ИИ.
Игровой ИИ, в первом приближении, можно разделить на два вида. Первый – наиболее очевидный – это
Второй уровень ИИ – это групповой
Если бы действия армии в StarCraft никак не контролировались, а ИИ присутствовал лишь на уровне отдельного юнита – игра превратилась бы в скучный поиск и уничтожение врагов. А StarCraft, несмотря на серьёзный возраст остаётся увлекательной игрой. Даже в однопользовательской кампании StarCraft способна очень сильно "затянуть" игрока, не говоря уже о сетевых баталиях.
Кстати, несложно заметить, что в том же StarCraft индивидуальный ИИ есть и у юнитов, которыми управляет пользователь. Например, та же команда "патрулировать", отданная пользователем, заставит существо из StarCraft послушно ходить по указанному пути. Но если на пути появится препятствие (например, игрок построит там здание, преграждающее путь) – юнит сам решит, что ему делать. Аналогично, он самостоятельно примет решение об атаке, если в поле его видимости появятся враги.
Системы ИИ, применяемые в компьютерных играх, можно разделить на два основных вида. Во-первых – это так называемые детерминированные системы. Они отличаются
Как мы уже сказали, индивидуальный ИИ юнитов играет подчинённую роль в сравнении с групповым ИИ. А может ли ИИ отдельного юнита повлиять на игру вцелом? Может – в том случае, если предусмотрено распространение успехов отдельного юнита на всех схожих. Например, какой-то юнит столкнулся с сильным противником и чудом вышел победителем в схватке. Этот юнит набрался опыта, который, благодаря групповому ИИ, может быть распространён на других юнитов. Т.е. если один юнит чему-то научился, другие, благодаря групповому ИИ, смогут перенять у него новые умения. Таким образом, индивидуальный и групповой ИИ взаимосвязаны, а в некоторых случаях и взаимозависимы.
Персонаж, оснащённый недетерминированным ИИ, отличается непредсказуемостью поведения, большей "живостью". Играть против таких персонажей обычно гораздо интереснее, чем против жёстко детерминированных. Популярным в последнее время способом реализации недетерминированного ИИ является технология
На практике находят применение как детерминированные, так и недетерминированные виды ИИ. Обычно они действуют совместно. Например, для выполнения каких-то простых однозначных действий (скажем, при приближении к стене свернуть) могут применяться простые и быстрые детерминированные алгоритмы. В более сложных случаях (например – купить ли
Реализуем игру, использующую алгоритм преследования. Сущность этого алгоритма заключается в следующем. Объект-преследователь сравнивает свои координаты в игровом мире с координатами объекта-жертвы и корректирует свои координаты таким образом, чтобы приблизиться к жертве. В простейшем случае преследование осуществляется на открытом пространстве.
Создадим новый игровой проект P12_1 на основе проекта P10_1. Будем использовать два объекта – преследователя и жертву. Преследователь будет перемещаться в сторону жертвы со скоростью, на 1 меньше, чем скорость жертвы. Если объекты столкнуться – жертва будет уничтожена.
На рис. 18.1 приведено окно Обозреватель решений игрового проекта P12_1.
(рис 18.1) Обозреватель решений, P12_1
Мы используем базовый класс gBaseClass, класс для объекта-преследователя (Enemy), класс объекта-жертвы (Me) и класс для объекта-стены. Класс объекта-стены будет нужен нам для того, чтобы изучить поведение объекта, реализующего алгоритм преследования, при столкновении с непреодолимым препятствием на пути к жертве. Мы значительно модифицировали код классов в сравнении с исходным проектом P5_1, поэтому ниже вы найдете полный код классов игры. В листинге 18.1 вы можете найти код класса Game1.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
namespace P12_1
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D txtBackground;
Texture2D txtEnemy;
Texture2D txtMe;
Texture2D txtWall;
Texture2D txtArrows;
SpriteFont myFont;
//Массив для конструирования уровня
public int[,] Layer;
Rectangle recBackround = new Rectangle(16, 0, 768, 448);
Rectangle recSprite = new Rectangle(0, 0, 64, 64);
Rectangle recArrows = new Rectangle(500, 280, 300, 200);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
Layer = new int[7, 12] {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
Services.AddService(typeof(SpriteBatch), spriteBatch);
txtBackground = Content.Load<Texture2D>("background");
txtEnemy = Content.Load<Texture2D>("enemy");
txtMe = Content.Load<Texture2D>("me");
txtWall = Content.Load<Texture2D>("wall");
txtArrows = Content.Load<Texture2D>("arrows");
myFont = Content.Load<SpriteFont>("MyFont");
//Вызываем процедуру расстановки объектов в игровом окне
AddSprites();
}
void AddSprites()
{
//Переменные для временного хранения адреса
//объекта-игрока
int a = 0, b = 0;
//Просматриваем массив Layer
for (int i = 0; i < 7; i++)
{
for (int j = 0; j < 12; j++)
{
//Если элемент с индексом (i,j) равен 1 -
//устанавливаем в соответствующую позицию элемент с
//номером 1, то есть - стену
if (Layer[i, j] == 1)
Components.Add(new GameObjects.Wall(this, ref txtWall, new Vector2(j, i), recSprite, myFont));
if (Layer[i, j] == 5)
Components.Add(new GameObjects.Enemy(this, ref txtEnemy, new Vector2(j, i),
new Rectangle(0, 0, 32, 32), myFont));
//Если обнаружен объект игрока - запишем его координаты
if (Layer[i, j] == 6)
{
a = i;
b = j;
}
}
}
//Последним установим объект игрока - так он гарантированно
//расположен поверх всех остальных объектов
Components.Add(new GameObjects.Me(this, ref txtMe, new Vector2(b, a), new Rectangle(0, 0, 32, 32), myFont));
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
txtBackground.Dispose();
txtEnemy.Dispose();
txtMe.Dispose();
txtWall.Dispose();
spriteBatch.Dispose();
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// ЗАДАЧА: добавьте здесь логику обновления
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// ЗАДАЧА: добавьте здесь код отрисовки
spriteBatch.Begin();
//выведем фоновое изображение
spriteBatch.Draw(txtBackground, recBackround, Color.White);
//Выведем игровые объекты
base.Draw(gameTime);
//Стрелки
spriteBatch.Draw(txtArrows, recArrows, Color.White);
spriteBatch.End();
}
}
}
В данном классе производится инициализация матрицы игрового мира и расстановка объектов. Здесь мы, помимо объекта-игрока и объекта-преследователя, разместили на игровом поле стену (рис.12.2.).
(рис 18.2) Преследователь не умеет обходить препятствия
С её помощью можно заметить, что объект-преследователь может выполнять свои функции лишь на открытом пространстве. Если между преследователем и игроком находится стена – преследователь упирается в нее, перемещаясь вдоль стены до тех пор, пока либо не окажется на
В листинге 18.2 приведен код класса gBaseClass.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_1.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent
{
Texture2D sprTexture;
public Vector2 sprPosition;
public Rectangle sprRectangle;
public SpriteFont myFont;
public string message="";
//Прямоугольник, представляющий игровое окно
public Rectangle scrBounds;
//Скорость объекта
public float sprSpeed;
public gBaseClass(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game)
{
sprTexture = _sprTexture;
//Именно здесь производится перевод индекса элемента массива
//в координаты на игровом экране
sprPosition = _sprPosition * 64;
sprPosition.X += 16;
sprRectangle = _sprRectangle;
myFont = _myFont;
scrBounds = new Rectangle(16, 0, 768, 448);
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// ЗАДАЧА: добавьте здесь код обновления
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
SpriteBatch sprBatch =
(SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
sprBatch.Draw(sprTexture, sprPosition, Color.White);
//Если строка сообщения не пуста
if (this.message != "")
{
sprBatch.DrawString(myFont, message, new Vector2(0, 455), Color.Red);
}
base.Draw(gameTime);
}
//Проверка на столкновение с каким-либо объектом
public bool IsCollideWithObject(gBaseClass spr)
{
return (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X
this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width
this.sprPosition.Y + this.sprRectangle.Height > spr.sprPosition.Y
this.sprPosition.Y < spr.sprPosition.Y + spr.sprRectangle.Height);
}
//Процедуры для перемещения объекта
public void MoveUp(float speed)
{
this.sprPosition.Y -= speed;
}
public void MoveDown(float speed)
{
this.sprPosition.Y += speed;
}
public void MoveLeft(float speed)
{
this.sprPosition.X -= speed;
}
public void MoveRight(float speed)
{
this.sprPosition.X += speed;
}
//Проверка на столкновение со стеной
public bool IsCollideWithWall()
{
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Wall)))
{
if (IsCollideWithObject(spr)) return true;
}
}
return false;
}
//Проверка на столкновение с границами экрана
public void Check()
{
if (sprPosition.X <= scrBounds.Left)
{
sprPosition.X = scrBounds.Left;
}
if (sprPosition.X >= scrBounds.Width - sprRectangle.Width)
{
sprPosition.X = scrBounds.Width - sprRectangle.Width;
}
if (sprPosition.Y <= scrBounds.Top)
{
sprPosition.Y = scrBounds.Top;
}
if (sprPosition.Y >= scrBounds.Height - sprRectangle.Height)
{
sprPosition.Y = scrBounds.Height - sprRectangle.Height;
}
}
}
}
Мы включили в этот класс методы и свойства, которые будем использовать и для объекта игрока и для объекта-преследователя. Они знакомы вам по проекту P10_1.
Теперь рассмотрим код класса Wall – (листинг 18.3.) мы приводим его здесь лишь для
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_1.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Wall : gBaseClass
{
public Wall(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
// ЗАДАЧА: здесь создаются дочерние компоненты
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// ЗАДАЧА: добавьте здесь код обновления
base.Update(gameTime);
}
}
}
Листинг 18.4. содержит код класса Me.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
namespace P12_1.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Me : gBaseClass
{
public Me(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
sprSpeed = 2;
}
public void IsCollideWithAny()
{
//Заводим переменную для временного хранения
//ссылки на объект, с которым столкнулся игровой объект
gBaseClass FindObj = null;
//Проверка на столкновение с объектами Enemy
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Enemy)))
{
if (IsCollideWithObject(spr))
{
FindObj = spr;
}
}
}
if (FindObj != null)
{
this.Dispose();
}
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
//Перемещение объекта
void Move(int Direction)
{
//Вверх
if (Direction == 1)
{
MoveUp(sprSpeed);
//При прыжке проводится проверка на контакт со стеной
//Которая может быть расположена над объектом
//при необходимости его координаты корректируются
while (IsCollideWithWall())
{
MoveDown((sprSpeed / 10));
}
}
//Вниз
//Происходит обычная процедура перемещения
//объекта с проверкой
if (Direction == 2)
{
MoveDown(sprSpeed);
while (IsCollideWithWall())
{
MoveUp((sprSpeed / 10));
}
}
//Точно так же обрабатывается
//движение Влево
if (Direction == 3)
{
MoveLeft(sprSpeed);
while (IsCollideWithWall())
{
MoveRight((sprSpeed / 10));
}
}
//Аналогично - перемещение вправо
if (Direction == 4)
{
MoveRight(sprSpeed);
while (IsCollideWithWall())
{
MoveLeft((sprSpeed / 10));
}
}
}
//Обработка сенсорного ввода
void TouchInput()
{
//Реализуем управление объектом
//Получаем коллекцию объектов, содержащих информацию о касаниях экрана
TouchCollection touchLocations = TouchPanel.GetState();
//Перебираем коллекцию, присваивая объекту координаты касания
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Стрелка "Влево"
if (touchLocation.Position.X > 500 touchLocation.Position.X < 600
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(3);
}
//Стрелка "Вправо"
if (touchLocation.Position.X > 700 touchLocation.Position.X < 800
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(4);
}
//Стрелка "Вниз"
if (touchLocation.Position.X > 600 touchLocation.Position.X < 700
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(2);
}
//Стрелка "Вверх"
if (touchLocation.Position.X > 600 touchLocation.Position.X < 700
touchLocation.Position.Y > 280 touchLocation.Position.Y < 380)
{
Move(1);
}
}
}
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
//Обработаем касания экрана для реализации перемещения объекта
TouchInput();
//Проверим, не вышел ли он за границы экрана, если надо
//исправим его позицию
Check();
//Применим к объекту "силу тяжести"
//Проверим на другие столкновения
IsCollideWithAny();
message = "Try to escape!";
base.Update(gameTime);
}
}
}
Объект класса Me умеет перемещаться по командам, полученных от сенсорной панели, не выходя за границы игровой области экрана, останавливаться, "натыкаясь" на стену, а при столкновении с объектом-преследователем объект класса Me уничтожается. Алгоритм преследования реализован в коде класса Enemy (листинг 18.5).
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_1.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Enemy : gBaseClass
{
Vector2 Direction;
public Enemy(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
sprSpeed = 1;
Direction = new Vector2(0, 0);
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
//Вычисление направления на объект-игрока и передвижение в
//этом направлении
void SeekAndDestroy()
{
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Me)))
{
//найдем разницу между координатами преследователя и
//игрока
Direction = (this.sprPosition - spr.sprPosition);
//Если разность по X положительная
//переместим преследователя влево
//с учетом того, что стены для него
//непроницаемы.
if (Direction.X > 0)
{
MoveLeft(sprSpeed);
while (IsCollideWithWall())
{
MoveRight((sprSpeed / 10));
}
}
//При отрицательной разности по X
//переместим объект вправо
if (Direction.X < 0)
{
MoveRight(sprSpeed);
while (IsCollideWithWall())
{
MoveLeft((sprSpeed / 10));
}
}
//При положительной разности по Y
//переместим объект вверх
if (Direction.Y > 0)
{
MoveUp(sprSpeed);
while (IsCollideWithWall())
{
MoveDown((sprSpeed / 10));
}
}
//При отрицательной разности по Y переместим
//объект вниз
if (Direction.Y < 0)
{
MoveDown(sprSpeed);
while (IsCollideWithWall())
{
MoveUp((sprSpeed / 10));
}
}
}
}
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// Перемещение к объекту игрока
SeekAndDestroy();
//Проверка на столкновение с границами экрана
Check();
base.Update(gameTime);
}
}
}
На каждом шаге игрового цикла объект-преследователь сравнивает свои координаты и координаты объекта игрока и проводит необходимую коррекцию своей позиции.
Как мы уже отмечали, наш объект-преследователь не умеет обходить препятствия. Рассмотрим реализацию алгоритма преследования с обходом препятствий.
Существует немало алгоритмов поиска пути на карте с препятствиями. Один из них заключается в следующем – объект двигается по карте, "держась рукой" за стену. Объект перемещается вдоль стен, выполняя повороты лишь в одну сторону, таким образом он гарантированно обойдет все места на карте, вдоль которых находятся стены или другие границы.
Это достаточно простой алгоритм, подходящий для несложных игр. В играх более сложных его применение может вызвать отрицательные эмоции у игрока – поэтому в таких играх следует применять более сложные алгоритмы. Например, для поиска
Мы используем комбинированный алгоритм – он рассчитан на действия в трех ситуациях. Во-первых, если объект-преследователь "видит" объект-цель – он перемещается к ней в
Создадим новый проект P12_2 на основе проекта P12_1. Ранее объект-преследователь перемещался в направлении объекта-цели, мог передвигаться вдоль вертикальных "стен", но даже простейшая конфигурация из препятствий была способна задержать его. Теперь же мы реализуем в объекте-преследователе алгоритм обхода препятствий
Состав проекта, по сравнению с проектом P12_1, не изменился, однако код объектов и основного игрового класса претерпел некоторые изменения. Ниже приведен полный код проекта P12_2.
В листинге 18.6 приведен код класса Game1.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
namespace P12_2
{
/// <summary>
/// Это главный тип игры
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D txtBackground;
Texture2D txtEnemy;
Texture2D txtMe;
Texture2D txtWall;
Texture2D txtArrows;
SpriteFont myFont;
//Массив для конструирования уровня
public int[,] Layer;
Rectangle recBackround = new Rectangle(16, 0, 768, 448);
Rectangle recSprite = new Rectangle(0, 0, 64, 64);
Rectangle recArrows = new Rectangle(500, 280, 300, 200);
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
// Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
TargetElapsedTime = TimeSpan.FromTicks(333333);
// Дополнительный заряд аккумулятора заблокирован.
InactiveSleepTime = TimeSpan.FromSeconds(1);
}
/// <summary>
/// Позволяет игре выполнить инициализацию, необходимую перед запуском.
/// Здесь можно запросить нужные службы и загрузить неграфический
/// контент. Вызов base.Initialize приведет к перебору всех компонентов и
/// их инициализации.
/// </summary>
protected override void Initialize()
{
Layer = new int[7, 12] {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 },
{ 0, 5, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 6 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
base.Initialize();
}
/// <summary>
/// LoadContent будет вызываться в игре один раз; здесь загружается
/// весь контент.
/// </summary>
protected override void LoadContent()
{
// Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
spriteBatch = new SpriteBatch(GraphicsDevice);
Services.AddService(typeof(SpriteBatch), spriteBatch);
txtBackground = Content.Load<Texture2D>("background");
txtEnemy = Content.Load<Texture2D>("enemy");
txtMe = Content.Load<Texture2D>("me");
txtWall = Content.Load<Texture2D>("wall");
txtArrows = Content.Load<Texture2D>("arrows");
myFont = Content.Load<SpriteFont>("MyFont");
//Вызываем процедуру расстановки объектов в игровом окне
AddSprites();
}
void AddSprites()
{
//Переменные для временного хранения адреса
//объекта-игрока
int a = 0, b = 0;
//Просматриваем массив Layer
for (int i = 0; i < 7; i++)
{
for (int j = 0; j < 12; j++)
{
//Если элемент с индексом (i,j) равен 1 -
//устанавливаем в соответствующую позицию элемент с
//номером 1, то есть - стену
if (Layer[i, j] == 1)
Components.Add(new GameObjects.Wall(this, ref txtWall, new Vector2(j, i), recSprite, myFont));
if (Layer[i, j] == 5)
Components.Add(new GameObjects.Enemy(this, ref txtEnemy, new Vector2(j, i), new Rectangle(0, 0, 32, 32), myFont));
//Если обнаружен объект игрока - запишем его координаты
if (Layer[i, j] == 6)
{
a = i;
b = j;
}
}
}
//Последним установим объект игрока - так он гарантированно
//расположен поверх всех остальных объектов
Components.Add(new GameObjects.Me(this, ref txtMe, new Vector2(b, a), new Rectangle(0, 0, 32, 32), myFont));
}
/// <summary>
/// UnloadContent будет вызываться в игре один раз; здесь выгружается
/// весь контент.
/// </summary>
protected override void UnloadContent()
{
txtBackground.Dispose();
txtEnemy.Dispose();
txtMe.Dispose();
txtWall.Dispose();
spriteBatch.Dispose();
}
/// <summary>
/// Позволяет игре запускать логику обновления мира,
/// проверки столкновений, получения ввода и воспроизведения звуков.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Update(GameTime gameTime)
{
// Позволяет выйти из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// ЗАДАЧА: добавьте здесь логику обновления
base.Update(gameTime);
}
/// <summary>
/// Вызывается, когда игра отрисовывается.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// ЗАДАЧА: добавьте здесь код отрисовки
spriteBatch.Begin();
//выведем фоновое изображение
spriteBatch.Draw(txtBackground, recBackround, Color.White);
//Выведем игровые объекты
base.Draw(gameTime);
//Стрелки
spriteBatch.Draw(txtArrows, recArrows, Color.White);
spriteBatch.End();
}
}
}На рис. 18.3 вы можете видеть карту этого проекта.
(рис 18.3) Карта проекта P12_2
В листинге 18.7 вы можете найти код класса gBaseClass
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_2.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent
{
Texture2D sprTexture;
public Vector2 sprPosition;
public Rectangle sprRectangle;
public SpriteFont myFont;
public string message="";
//Прямоугольник, представляющий игровое окно
public Rectangle scrBounds;
//Скорость объекта
public float sprSpeed;
public gBaseClass(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game)
{
sprTexture = _sprTexture;
//Именно здесь производится перевод индекса элемента массива
//в координаты на игровом экране
sprPosition = _sprPosition * 64;
sprPosition.X += 16;
sprRectangle = _sprRectangle;
myFont = _myFont;
scrBounds = new Rectangle(16, 0, 768, 448);
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// ЗАДАЧА: добавьте здесь код обновления
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
SpriteBatch sprBatch =
(SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
sprBatch.Draw(sprTexture, sprPosition, Color.White);
//Если строка сообщения не пуста
if (this.message != "")
{
sprBatch.DrawString(myFont, message, new Vector2(0, 455), Color.Red);
}
base.Draw(gameTime);
}
//Проверка на столкновение с каким-либо объектом
public bool IsCollideWithObject(gBaseClass spr)
{
return (this.sprPosition.X + this.sprRectangle.Width > spr.sprPosition.X
this.sprPosition.X < spr.sprPosition.X + spr.sprRectangle.Width
this.sprPosition.Y + this.sprRectangle.Height > spr.sprPosition.Y
this.sprPosition.Y < spr.sprPosition.Y + spr.sprRectangle.Height);
}
//Процедуры для перемещения объекта
public void MoveUp(float speed)
{
this.sprPosition.Y -= speed;
}
public void MoveDown(float speed)
{
this.sprPosition.Y += speed;
}
public void MoveLeft(float speed)
{
this.sprPosition.X -= speed;
}
public void MoveRight(float speed)
{
this.sprPosition.X += speed;
}
//Проверка на столкновение со стеной
public bool IsCollideWithWall()
{
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Wall)))
{
if (IsCollideWithObject(spr)) return true;
}
}
return false;
}
//Проверка на столкновение с границами экрана
public void Check()
{
if (sprPosition.X <= scrBounds.Left)
{
sprPosition.X = scrBounds.Left;
}
if (sprPosition.X >= scrBounds.Width - sprRectangle.Width)
{
sprPosition.X = scrBounds.Width - sprRectangle.Width;
}
if (sprPosition.Y <= scrBounds.Top)
{
sprPosition.Y = scrBounds.Top;
}
if (sprPosition.Y >= scrBounds.Height - sprRectangle.Height)
{
sprPosition.Y = scrBounds.Height - sprRectangle.Height;
}
}
public bool CheckBounds()
{
return ((sprPosition.X <= scrBounds.Left |
sprPosition.X >= scrBounds.Width - sprRectangle.Width |
sprPosition.Y <= scrBounds.Top |
sprPosition.Y >= scrBounds.Height - sprRectangle.Height));
}
}
}
Листинг 18.8. содержит код класса Wall.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace P12_2.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Wall : gBaseClass
{
public Wall(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
// ЗАДАЧА: здесь создаются дочерние компоненты
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
// ЗАДАЧА: добавьте здесь код обновления
base.Update(gameTime);
}
}
}
Листинг 18.9. содержит код класса Me.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
namespace P12_2.GameObjects
{
/// <summary>
/// Это игровой компонент, реализующий интерфейс IUpdateable.
/// </summary>
public class Me : gBaseClass
{
public Me(Game game, ref Texture2D _sprTexture,
Vector2 _sprPosition, Rectangle _sprRectangle, SpriteFont _myFont)
: base(game, ref _sprTexture, _sprPosition, _sprRectangle, _myFont)
{
sprSpeed = 2;
}
public void IsCollideWithAny()
{
//Заводим переменную для временного хранения
//ссылки на объект, с которым столкнулся игровой объект
gBaseClass FindObj = null;
//Проверка на столкновение с объектами Enemy
foreach (gBaseClass spr in Game.Components)
{
if (spr.GetType() == (typeof(Enemy)))
{
if (IsCollideWithObject(spr))
{
FindObj = spr;
}
}
}
if (FindObj != null)
{
this.Dispose();
}
}
/// <summary>
/// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском.
Здесь можно запросить нужные службы и загрузить контент.
///
/// </summary>
public override void Initialize()
{
// ЗАДАЧА: добавьте здесь код инициализации
base.Initialize();
}
//Перемещение объекта
void Move(int Direction)
{
//Вверх
if (Direction == 1)
{
MoveUp(sprSpeed);
//При прыжке проводится проверка на контакт со стеной
//Которая может быть расположена над объектом
//при необходимости его координаты корректируются
while (IsCollideWithWall())
{
MoveDown((sprSpeed / 10));
}
}
//Вниз
//Происходит обычная процедура перемещения
//объекта с проверкой
if (Direction == 2)
{
MoveDown(sprSpeed);
while (IsCollideWithWall())
{
MoveUp((sprSpeed / 10));
}
}
//Точно так же обрабатывается
//движение Влево
if (Direction == 3)
{
MoveLeft(sprSpeed);
while (IsCollideWithWall())
{
MoveRight((sprSpeed / 10));
}
}
//Аналогично - перемещение вправо
if (Direction == 4)
{
MoveRight(sprSpeed);
while (IsCollideWithWall())
{
MoveLeft((sprSpeed / 10));
}
}
}
//Обработка сенсорного ввода
void TouchInput()
{
//Реализуем управление объектом
//Получаем коллекцию объектов, содержащих информацию о касаниях экрана
TouchCollection touchLocations = TouchPanel.GetState();
//Перебираем коллекцию, присваивая объекту координаты касания
foreach (TouchLocation touchLocation in touchLocations)
{
if (touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved)
{
//Стрелка "Влево"
if (touchLocation.Position.X > 500 touchLocation.Position.X < 600
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(3);
}
//Стрелка "Вправо"
if (touchLocation.Position.X > 700 touchLocation.Position.X < 800
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(4);
}
//Стрелка "Вниз"
if (touchLocation.Position.X > 600 touchLocation.Position.X < 700
touchLocation.Position.Y > 380 touchLocation.Position.Y < 480)
{
Move(2);
}
//Стрелка "Вверх"
if (touchLocation.Position.X > 600 touchLocation.Position.X < 700
touchLocation.Position.Y > 280 touchLocation.Position.Y < 380)
{
Move(1);
}
}
}
}
/// <summary>
/// Позволяет игровому компоненту обновиться.
/// </summary>
/// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
public override void Update(GameTime gameTime)
{
//Обработаем касания экрана для реализации перемещения объекта
TouchInput();
//Проверим, не вышел ли он за границы экрана, если надо
//исправим его позицию
Check();
//Применим к объекту "силу тяжести"
//Проверим на другие столкновения
IsCollideWithAny();
message = "Try to escape!";
base.Update(gameTime);
}
}
}
Комментарии к коду раскрывают особенности алгоритма.
Задача реализации
Результаты такого упрощения обычно выражаются в некоторых странностях в поведении персонажей. Например, персонаж может "застрять" в двери или в каком-нибудь другом месте карты, при прохождении которого ИИ, встроенный в игру, не предусматривает однозначного решения. При разработке системы ИИ широко применяется предварительный просчет возможных действий персонажей, после чего полученные данные применяются в ходе игры.
Предварительно может быть составлена карта обхода местности, которая предусматривает указание путей прохождения и точек, в которых требуются какие-то особые действия персонажа – прыжок для преодоления препятствия, поворот, выполнение определенной манипуляции с другими объектами игрового мира. Обычно игровые персонажи имеют комбинированный ИИ.
Например, заранее могут быть просчитаны возможные пути прохождения карты, а если при прохождении персонаж сталкивается с какими-либо подвижными объектами, он ведет себя уже не в соответствии с общей картой прохождения, а в соответствии с правилами обхода локальных препятствий.
Разработайте игровой проект – клон игры "Battle City" - симулятор танкового боя на карте, подобной карте, применяемой в наших примерах. Создайте следующие объекты карты:
Два вида танков врага
Систему бонусных объектов
Снаряд
К данной лабораторной работе прилагается видеоурок
и демонстрационный проект.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.