Быть может кто-то из Вас знает игру Sims, к какому жанру она относиться? Жизненная симуляция, ролевая игра, там можно и дома строить..... |
Методы искусственного интеллекта (ИИ) в компьютерных играх
Реализация алгоритма перемещения с обходом препятствий
Существует немало алгоритмов поиска пути на карте с препятствиями. Один из них заключается в следующем – объект двигается по карте, "держась рукой" за стену. Объект перемещается вдоль стен, выполняя повороты лишь в одну сторону, таким образом он гарантированно обойдет все места на карте, вдоль которых находятся стены или другие границы. Это достаточно простой алгоритм, подходящий для несложных игр. В играх более сложных его применение может вызвать отрицательные эмоции у игрока – поэтому в таких играх следует применять более сложные алгоритмы. Например, для поиска кратчайшего пути между двумя точками можно применить популярный алгоритм A*, для исследования игрового мира – алгоритм на основе "сенсоров", которыми обладает игровой персонаж.
Мы используем комбинированный алгоритм – он рассчитан на действия в трех ситуациях. Во-первых, если объект-преследователь "видит" объект-цель – он перемещается к ней в свободном пространстве – так же, как в вышеописанном примере. Если объект-преследователь теряет цель – например – она ушла из пределов прямой досягаемости – он действует различным образом в зависимости от того, находится ли он в свободном пространстве или около стены или границы экрана. Если преследователь находится в свободном пространстве – он перемещается в нем на случайные расстояния, делая повороты. Если он находится около стены – он продолжает обход игрового мира. Как правило, объект, находящийся в свободном пространстве некоторое время "блуждает" по нему – если он снова "увидит" объект-цель – он начнет преследовать его, если он столкнется со стеной или с границей экрана – он продолжит обход игрового мира.
Создадим новый проект P8_2 на основе проекта P8_1. Ранее объект-преследователь перемещался в направлении объекта-цели, мог передвигаться вдоль вертикальных "стен", но даже простейшая конфигурация из препятствий была способна задержать его. Теперь же мы реализуем в объекте-преследователе алгоритм обхода препятствий
Состав проекта, по сравнению с проектом P8_1, не изменился, однако код объектов и основного игрового класса претерпел некоторые изменения. Ниже приведен полный код проекта P8_2.
В листинге 12.6. приведен код класса Game1.
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.Net; using Microsoft.Xna.Framework.Storage; namespace P8_2 { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Texture2D txtBackground; Texture2D txtEnemy; Texture2D txtMe; Texture2D txtWall; //Массив для конструирования уровня public int[,] Layer; Rectangle recBackround = new Rectangle(0, 0, 640, 512); Rectangle recSprite = new Rectangle(0, 0, 64, 64); public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { // TODO: Add your initialization logic here Layer = new int[8, 10] { { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, { 0, 1, 1, 1, 1, 1, 0, 1, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 1, 0 }, { 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 }, { 0, 5, 0, 0, 1, 0, 1, 0, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 1, 1, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 6 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }; //Устанавливаем разрешение игрового окна //640х512 graphics.PreferredBackBufferWidth = 640; graphics.PreferredBackBufferHeight = 512; graphics.ApplyChanges(); base.Initialize(); } protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. 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"); //Вызываем процедуру расстановки объектов в игровом окне AddSprites(); // TODO: use this.Content to load your game content here } //Процедура расстановки объектов в игровом окне void AddSprites() { //Переменные для временного хранения адреса //объекта-игрока int a = 0, b = 0; //Просматриваем массив Layer for (int i = 0; i < 8; i++) { for (int j = 0; j < 10; j++) { //Если элемент с индексом (i,j) равен 1 - //устанавливаем в соответствующую позицию элемент с //номером 1, то есть - стену if (Layer[i, j] == 1) Components.Add(new GameObj.Wall(this, ref txtWall, new Vector2(j, i), recSprite)); if (Layer[i, j] == 5) Components.Add(new GameObj.Enemy(this, ref txtEnemy, new Vector2(j, i), new Rectangle(0, 0, 32, 32))); //Если обнаружен объект игрока - запишем его координаты if (Layer[i, j] == 6) { a = i; b = j; } } } //Последним установим объект игрока - так он гарантированно //расположен поверх всех остальных объектов Components.Add(new GameObj.Me(this, ref txtMe, new Vector2(b, a), new Rectangle(0, 0, 32, 32))); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { base.Update(gameTime); } protected override void Draw(GameTime gameTime) { spriteBatch.Begin(); //выведем фоновое изображение spriteBatch.Draw(txtBackground, recBackround, Color.White); //Выведем игровые объекты base.Draw(gameTime); spriteBatch.End(); } } }Листинг 12.6. Код класса Game1
На рис. 12.3. вы можете видеть карту этого проекта.
В листинге 12.7. вы можете найти код класса gBaseClass
using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; using Microsoft.Xna.Framework.Content; namespace P8_2.GameObj { /// <summary> /// This is a game component that implements IUpdateable. /// </summary> public class gBaseClass : Microsoft.Xna.Framework.DrawableGameComponent { Texture2D sprTexture; public Vector2 sprPosition; public Rectangle sprRectangle; //Прямоугольник, представляющий игровое окно public Rectangle scrBounds; //Скорость объекта public float sprSpeed; public gBaseClass(Game game, ref Texture2D _sprTexture, Vector2 _sprPosition, Rectangle _sprRectangle) : base(game) { sprTexture = _sprTexture; //Именно здесь производится перевод индекса элемента массива //в координаты на игровом экране sprPosition = _sprPosition * 64; sprRectangle = _sprRectangle; scrBounds = new Rectangle(0, 0, game.Window.ClientBounds.Width, game.Window.ClientBounds.Height); } public override void Initialize() { base.Initialize(); } 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); 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)); } } }Листинг 12.7. Код класса gBaseClass
Листинг 12.8. содержит код класса Wall.
using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; using Microsoft.Xna.Framework.Content; namespace P8_2.GameObj { public class Wall : gBaseClass { public Wall(Game game, ref Texture2D _sprTexture, Vector2 _sprPosition, Rectangle _sprRectangle) : base(game, ref _sprTexture, _sprPosition, _sprRectangle) { // TODO: Construct any child components here } public override void Initialize() { // TODO: Add your initialization code here base.Initialize(); } public override void Update(GameTime gameTime) { // TODO: Add your update code here base.Update(gameTime); } } }Листинг 12.8. Код класса Wall
Листинг 12.9. содержит код класса Me.
using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; using Microsoft.Xna.Framework.Content; namespace P8_2.GameObj { public class Me : gBaseClass { public Me(Game game, ref Texture2D _sprTexture, Vector2 _sprPosition, Rectangle _sprRectangle) : base(game, ref _sprTexture, _sprPosition, _sprRectangle) { sprSpeed = 2; // TODO: Construct any child components here } public override void Initialize() { base.Initialize(); } 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(); } } //Процедура, отвечающая за перемещение игрового объекта void Move() { KeyboardState kbState = Keyboard.GetState(); //При нажатии кнопки "Вверх" if (kbState.IsKeyDown(Keys.Up)) { MoveUp(sprSpeed); while (IsCollideWithWall()) { MoveDown((sprSpeed / 10)); } } //При нажатии кнопки "Вниз" //Происходит обычная процедура перемещения //объекта с проверкой if (kbState.IsKeyDown(Keys.Down)) { MoveDown(sprSpeed); while (IsCollideWithWall()) { MoveUp((sprSpeed / 10)); } } //Точно так же обрабатывается //нажатие кнопки "Влево" if (kbState.IsKeyDown(Keys.Left)) { MoveLeft(sprSpeed); while (IsCollideWithWall()) { MoveRight((sprSpeed / 10)); } } //Аналогично - перемещение вправо if (kbState.IsKeyDown(Keys.Right)) { MoveRight(sprSpeed); while (IsCollideWithWall()) { MoveLeft((sprSpeed / 10)); } } } public override void Update(GameTime gameTime) { // TODO: Add your update code here //Вызовем процедуру перемещения объекта по клавиатурным командам Move(); //Проверим, не вышел ли он за границы экрана, если надо //исправим его позицию Check(); IsCollideWithAny(); base.Update(gameTime); } } }Листинг 12.9. Код класса Me