| Невозможно пройти тесты, в окне с вопросами пусто |
Искусственный интеллект в играх
18.3. Реализация алгоритма перемещения с обходом препятствий
Существует немало алгоритмов поиска пути на карте с препятствиями. Один из них заключается в следующем – объект двигается по карте, "держась рукой" за стену. Объект перемещается вдоль стен, выполняя повороты лишь в одну сторону, таким образом он гарантированно обойдет все места на карте, вдоль которых находятся стены или другие границы.
Это достаточно простой алгоритм, подходящий для несложных игр. В играх более сложных его применение может вызвать отрицательные эмоции у игрока – поэтому в таких играх следует применять более сложные алгоритмы. Например, для поиска кратчайшего пути между двумя точками можно применить популярный алгоритм A*, для исследования игрового мира – алгоритм на основе "сенсоров", которыми обладает игровой персонаж.
Мы используем комбинированный алгоритм – он рассчитан на действия в трех ситуациях. Во-первых, если объект-преследователь "видит" объект-цель – он перемещается к ней в свободном пространстве – так же, как в вышеописанном примере. Если объект-преследователь теряет цель – например – она ушла из пределов прямой досягаемости – он действует различным образом в зависимости от того, находится ли он в свободном пространстве или около стены или границы экрана. Если преследователь находится в свободном пространстве – он перемещается в нем на случайные расстояния, делая повороты. Если он находится около стены – он продолжает обход игрового мира. Как правило, объект, находящийся в свободном пространстве некоторое время "блуждает" по нему – если он снова "увидит" объект-цель – он начнет преследовать его, если он столкнется со стеной или с границей экрана – он продолжит обход игрового мира.
Создадим новый проект 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.6.
Код класса Game1
На рис. 18.3 вы можете видеть карту этого проекта.
