Спонсор: Microsoft
Опубликован: 10.04.2009 | Уровень: специалист | Доступ: свободно
Самостоятельная работа 8:

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

Листинг 12.10. содержит код класса Enemy. Именно здесь реализован алгоритм обхода препятствий.

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 Enemy : gBaseClass
    {
        Vector2 Direction;
        //Переменная, задающая первоначальное направление
        //движения
        int WhereToMove = 1;
        //Генератор случайных чисел
        Random r = new Random();
        //Переменная для исключения повторного
        //поворота объекта при движении вдоль
        //стены или границы экрана
        bool flag = true;
        //Переменная для исключения повторного поворота объекта
        //при движении в свободном пространстве
        bool flag1 = false;
        //Количество ходов в свободном пространстве перед
        //поворотом значение
        int FreeMoveCount = 150;

        public Enemy(Game game, ref Texture2D _sprTexture,
            Vector2 _sprPosition, Rectangle _sprRectangle)
            : base(game, ref _sprTexture, _sprPosition, _sprRectangle)
        {
            sprSpeed = 1;
            Direction = new Vector2(0, 0);
            // TODO: Construct any child components here
        }

        public override void Initialize()
        {
            // TODO: Add your initialization code here

            base.Initialize();
        }

        //Функция для реализации "взгляда" объекта. Если он видит
        //объект преследования - возвращает True, иначе - False
        bool simulateMove(Vector2 testPosition)
        {
            //Находится ли объект игрока так, что объект
            //управляемый компьютером может "видеть" его.
            //Предположим, что да
            bool IsCollide = true;
            bool CollisionDetected = false;
            Vector2 Dir;
            Vector2 OldPosition = this.sprPosition;
            if (testPosition.X == -1 & testPosition.Y == -1)
            {
                foreach (gBaseClass spr in Game.Components)
                {

                    if (spr.GetType() == (typeof(Me)))
                    {

                        while (CollisionDetected == false)
                        {
                            Dir = (this.sprPosition - new Vector2 (spr.sprPosition.X, spr.sprPosition .Y));
                            if (Dir.X > 0) MoveLeft(Math.Abs(Dir.X / 100));
                            if (Dir.X < 0) MoveRight(Math.Abs(Dir.X / 100));
                            if (Dir.Y > 0) MoveUp(Math.Abs(Dir.Y / 100));
                            if (Dir.Y < 0) MoveDown(Math.Abs(Dir.Y / 100));
                            if (IsCollideWithWall())
                            {
                                CollisionDetected = true;
                                IsCollide = false;

                            }
                            if (IsCollideWithObject(spr))
                            {
                                CollisionDetected = true;
                                IsCollide = true;


                            }
                            

                        }
                    }
                }

            }
            else //Проверяем на столкновение объекта и стены
                //эта часть функции реализует "зрение" объекта
            {
                while (CollisionDetected == false)
                {
                    Dir = (this.sprPosition - testPosition);
                    if (Dir.X > 0) MoveLeft(Math.Abs(Dir.X / 100));
                    if (Dir.X < 0) MoveRight(Math.Abs(Dir.X / 100));
                    if (Dir.Y > 0) MoveUp(Math.Abs(Dir.Y / 100));
                    if (Dir.Y < 0) MoveDown(Math.Abs(Dir.Y / 100));
                    if (IsCollideWithWall())
                    {
                        CollisionDetected = true;
                        IsCollide = true;

                    }
                    if (CheckBounds())
                    {
                        CollisionDetected = true;
                        IsCollide = true;
                    }
                    if (Math.Abs(Dir.X) < 0.1 & Math.Abs(Dir.Y) < 0.1)
                    {
                        CollisionDetected = true;
                        IsCollide = false;
                    }
                }

            }


            this.sprPosition = OldPosition;
            return IsCollide;

        }
        
        
        
        
        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));
                        }

                    }

                }
            }
        }
        //Движение вдоль стены или в свободном пространстве
        //Направление движения
        //1 - влево
        //2 - вправо
        //3 - вверх
        //4 - вниз
        void WallWalk(int Direction)
        {
            if (Direction == 1)
            {

                MoveLeft(sprSpeed);
          
            }

            if (Direction == 2)
            {

                MoveRight(sprSpeed);
             
            }
            if (Direction == 3)
            {
                MoveUp(sprSpeed);

            }
            if (Direction == 4)
            {
                MoveDown(sprSpeed);
            
            }

        }
        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            
            //Если на пути нет препятствий
            //Перемещение к объекту игрока
         

            if (simulateMove(new Vector2(-1, -1)))
            {
                SeekAndDestroy();
                
            }
            else 
            {
                //Перемещение вдоль стены или в 
                //свободном пространстве
                WallWalk(WhereToMove);

                //Разрешить повороты вдоль стены
                flag = true;


                if (IsCollideWithWall() | CheckBounds())
                {
                    //Разрешить повороты в пространстве
                    //Найдя "разрыв" в стене объект повернет в него
                    flag1 = true;

                    //
                    if (WhereToMove == 1 & flag)
                    {
                        WhereToMove = 3;
                        //Если сделан поворот
                        //Нельзя сразу же делать поворот в другую сторону
                        flag = false;
                    }
                    if (WhereToMove == 2 & flag)
                    {

                        WhereToMove = 4;
                        flag = false;
                    }
                    if (WhereToMove == 3 & flag)
                    {
                        WhereToMove = 2;
                        flag = false;
                    }
                    if (WhereToMove == 4 & flag)
                    {
                        WhereToMove = 1;
                        flag = false;
                    }
                }
                
                //Блок обработки перемещений в пространстве
                //и в "разрывах" стен

                //Если выполнено количество шагов, заданное
                //в переменной FreeMoveCount
                //Установить новое случайное значение для этой переменной
                //Разрешить повороты в свободном пространстве
                if (FreeMoveCount <0)
                {
                    FreeMoveCount = r.Next(20, 100);
                    flag1 = true;
                }

                //Если не было поворота вдоль стены
                //И разрешены повороты в пространстве
                //И выше объекта нет препятствия
                //То - сменить направление движения на "вверх"
                //После
                if (flag&flag1 & WhereToMove == 2 & simulateMove(new Vector2(this.sprPosition.X, this.sprPosition.Y - 5)) == false)
                {
                    WhereToMove = 3;
                    flag1 = false;
                    flag = false;
                }
                //Если при движении вверх обнаруживаем, что нет препятствия слева
                //поворачиваем влево

                if (flag&flag1 & WhereToMove == 3 & simulateMove(new Vector2(this.sprPosition.X - 5, this.sprPosition.Y)) == false)
                {
                    WhereToMove = 1;
                    flag1 = false;
                    flag = false;
                }
                
                //Если при движении влево обнаруживаем, что нет препятсвия внизу
                //двигаемся вниз
                if (flag&flag1 & WhereToMove == 1 & simulateMove(new Vector2(this.sprPosition.X, this.sprPosition.Y + 5)) == false)
                {
                    WhereToMove = 4;
                    flag1 = false;
                    flag = false;
                }
                
                //Если двигались вниз и обнаружили, что справа нет препятствия
                //Двигаемся вправо
                if (flag&flag1 & WhereToMove == 4 & simulateMove(new Vector2(this.sprPosition.X + 5, this.sprPosition.Y)) == false)
                {
                    WhereToMove = 2;
                    flag1 = false;
                    flag = false;
                }
               
            }
           
            //Проверка на столкновение с границами экрана
            Check();
            //Уменьшаем на 1 количество допустимых ходов в 
            //свободном пространстве
            FreeMoveCount--;
            base.Update(gameTime);
        }
    }
}
Листинг 12.10. Код класса Enemy

Комментарии к коду раскрывают особенности алгоритма.

Выводы

Задача реализации искусственного интеллекта в игровых программах сталкивается с двумя ограничениями. Во-первых – чем сложнее и продуманнее эта система, и чем лучше она, в результате, работает – тем естественнее ведут себя персонажи. Во-вторых – игровые программы требуют немало вычислительных ресурсов, поэтому любое усложнение – в том числе – усложнение системы ИИ – ведет к падению производительности. Поэтому программисты вынуждены идти на компромисс – максимально упрощать систему ИИ таким образом, чтобы ее работа не тормозила выполнение игры. Результаты такого упрощения обычно выражаются в некоторых странностях в поведении персонажей. Например, персонаж может "застрять" в двери или в каком-нибудь другом месте карты, при прохождении которого ИИ, встроенный в игру, не предусматривает однозначного решения. При разработке системы ИИ широко применяется предварительный просчет возможных действий персонажей, после чего полученные данные применяются в ходе игры. Например, предварительно может быть составлена карта обхода местности, которая предусматривает указание путей прохождения и точек, в которых требуются какие-то особые действия персонажа – прыжок для преодоления препятствия, поворот, выполнение определенной манипуляции с другими объектами игрового мира. Обычно игровые персонажи имеют комбинированный ИИ. Например, заранее могут быть просчитаны возможные пути прохождения карты, а если при прохождении персонаж сталкивается с какими-либо подвижными объектами, он ведет себя уже не в соответствии с общей картой прохождения, а в соответствии с правилами обхода локальных препятствий.

Задание

Разработайте игровой проект – клон игры "Battle City" - симулятор танкового боя на карте, подобной карте, применяемой в наших примерах. Создайте следующие объекты карты:

  1. Кирпичные стены – объекты не могут преодолевать их, выстрел из пушки уничтожает один сегмент стены.
  2. Бетонные стены – непроницаемы для объекта, не уничтожаются выстрелами.
  3. Лес – объект движется по лесу замедленно, лес частично скрывает объект, выстрел уничтожает сегмент леса
  4. Вода – объект не может преодолеть водную преграду, однако снаряд беспрепятственно преодолевает воду
  5. База игрока – несколько выстрелов врага уничтожают базу
  6. Два вида танков врага
    1. Танк первого вида перемещается по карте случайным образом, случайным же образом стреляя
    2. Танк второго вида перемещается по карте вдоль стен, прицельно стреляя по игроку и, при обнаружении базы, стреляя по ней. При обнаружении игрока танк второго вида пытается преследовать его.
  7. Система бонусных объектов
    1. Бонус, при подборе которого вражеские танки, находящиеся на карте, уничтожаются
    2. Бонус, добавляющий одну "жизнь" игроку
  8. Снаряд

Пример реализации этой учебной игры вы можете найти в одном из следующих занятий.

Alina Lasskaja
Alina Lasskaja

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