Спонсор: Microsoft
Опубликован: 10.04.2009 | Доступ: свободный | Студентов: 3248 / 243 | Оценка: 4.49 / 4.39 | Длительность: 17:21:00
Специальности: Программист
Самостоятельная работа 8:

Методы искусственного интеллекта (ИИ) в компьютерных играх

Реализация алгоритма перемещения с обходом препятствий

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

Игровое окно проекта P8_2

увеличить изображение
Рис. 12.3. Игровое окно проекта P8_2

В листинге 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
Alina Lasskaja
Alina Lasskaja

Быть может кто-то из Вас знает игру Sims, к какому жанру она относиться? Жизненная симуляция, ролевая игра, там можно и дома строить.....