Невозможно пройти тесты, в окне с вопросами пусто |
Искусственный интеллект в играх
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 вы можете видеть карту этого проекта.