Опубликован: 14.08.2012 | Уровень: специалист | Доступ: платный
Самостоятельная работа 12:

Искусственный интеллект в играх

Аннотация: Эта лабораторная работа посвящена использованию методов искусственного интеллекта при разработке игр.

Цель работы: Научиться применять искусственный интеллект в компьютерных играх

18.1. Обзор подходов к разработке системы ИИ

Тема искусственного интеллекта (artifical intelligence, AI, ИИ) будоражит умы многих начинающих программистов, разработчиков и любителей компьютерных игр. ИИ кажется многим чем-то удивительно сложным, интересным, таинственным. Действительно, ведь технологии ИИ заставляют персонажей игр действовать разумно. Однако в ИИ нет ничего таинственного. За десятилетия развития этой области знаний было разработано огромное количество алгоритмов, применимых в самых разных областях деятельности. И компьютерные игры – лишь сравнительно небольшое поле для технологий искусственного интеллекта.

Очень сложно найти компьютерную игру, которая обходится без ИИ. Классический пример "умных" игр – это программы для игры в шашки, шахматы и прочие настольные игры. Каждая игра, в которой компьютер играет против пользователя, оснащена ИИ.

Игровой ИИ, в первом приближении, можно разделить на два вида. Первый – наиболее очевидный – это интеллект отдельных игровых персонажей. Например, каждый танк в популярной некогда приставочной игре Battle City пытается добраться до базы игрока, уничтожить её и его танк. Танки в игре действуют неслаженно, они не отличаются особенным "умом", однако играть интересно – всё дело в том, что для данной игры такой вид ИИ вполне подходит. Он не делает игру скучной.

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

Если бы действия армии в StarCraft никак не контролировались, а ИИ присутствовал лишь на уровне отдельного юнита – игра превратилась бы в скучный поиск и уничтожение врагов. А StarCraft, несмотря на серьёзный возраст остаётся увлекательной игрой. Даже в однопользовательской кампании StarCraft способна очень сильно "затянуть" игрока, не говоря уже о сетевых баталиях.

Кстати, несложно заметить, что в том же StarCraft индивидуальный ИИ есть и у юнитов, которыми управляет пользователь. Например, та же команда "патрулировать", отданная пользователем, заставит существо из StarCraft послушно ходить по указанному пути. Но если на пути появится препятствие (например, игрок построит там здание, преграждающее путь) – юнит сам решит, что ему делать. Аналогично, он самостоятельно примет решение об атаке, если в поле его видимости появятся враги.

Системы ИИ, применяемые в компьютерных играх, можно разделить на два основных вида. Во-первых – это так называемые детерминированные системы. Они отличаются предсказуемостью действий персонажа. И во-вторых – это недетерминированные системы – персонаж, управляемый таким ИИ, может действовать непредсказуемо, принимать неожиданные решения.

Как мы уже сказали, индивидуальный ИИ юнитов играет подчинённую роль в сравнении с групповым ИИ. А может ли ИИ отдельного юнита повлиять на игру вцелом? Может – в том случае, если предусмотрено распространение успехов отдельного юнита на всех схожих. Например, какой-то юнит столкнулся с сильным противником и чудом вышел победителем в схватке. Этот юнит набрался опыта, который, благодаря групповому ИИ, может быть распространён на других юнитов. Т.е. если один юнит чему-то научился, другие, благодаря групповому ИИ, смогут перенять у него новые умения. Таким образом, индивидуальный и групповой ИИ взаимосвязаны, а в некоторых случаях и взаимозависимы.

Персонаж, оснащённый недетерминированным ИИ, отличается непредсказуемостью поведения, большей "живостью". Играть против таких персонажей обычно гораздо интереснее, чем против жёстко детерминированных. Популярным в последнее время способом реализации недетерминированного ИИ является технология нейронных сетей. Она позволяет создавать персонажи с очень сложным поведением. К тому же, нейросети обладают свойством обучаемости. То есть персонажи игр не только разумно ведут себя, но и учатся на своих ошибках.

На практике находят применение как детерминированные, так и недетерминированные виды ИИ. Обычно они действуют совместно. Например, для выполнения каких-то простых однозначных действий (скажем, при приближении к стене свернуть) могут применяться простые и быстрые детерминированные алгоритмы. В более сложных случаях (например – купить ли акции компании Х учитывая огромное количество параметров, напасть ли на врага, учитывая его возможности, свои возможности, наличие подкрепления и т.д.) – применяются более сложные недетерминированные алгоритмы. Частично детерминированные (например, при приближении к стене персонаж с вероятностью 50% повернёт налево, с вероятностью 30% - направо, и с 20% вероятностью развернётся и пойдёт обратно) так же находят широкое применение в играх.

18.2. Реализация алгоритма преследования

Реализуем игру, использующую алгоритм преследования. Сущность этого алгоритма заключается в следующем. Объект-преследователь сравнивает свои координаты в игровом мире с координатами объекта-жертвы и корректирует свои координаты таким образом, чтобы приблизиться к жертве. В простейшем случае преследование осуществляется на открытом пространстве.

Создадим новый игровой проект P12_1 на основе проекта P10_1. Будем использовать два объекта – преследователя и жертву. Преследователь будет перемещаться в сторону жертвы со скоростью, на 1 меньше, чем скорость жертвы. Если объекты столкнуться – жертва будет уничтожена.

На рис. 18.1 приведено окно Обозреватель решений игрового проекта P12_1.

Обозреватель решений, P12_1

Рис. 18.1. Обозреватель решений, P12_1

Мы используем базовый класс gBaseClass, класс для объекта-преследователя (Enemy), класс объекта-жертвы (Me) и класс для объекта-стены. Класс объекта-стены будет нужен нам для того, чтобы изучить поведение объекта, реализующего алгоритм преследования, при столкновении с непреодолимым препятствием на пути к жертве. Мы значительно модифицировали код классов в сравнении с исходным проектом P5_1, поэтому ниже вы найдете полный код классов игры. В листинге 18.1 вы можете найти код класса 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_1
{
    /// <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, 0, 0, 0 }, 
            { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, 
            { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, 
            { 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 0, 0 }, 
            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 
            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0 },
            { 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.1. Код класса Game1
Гулич Анна
Гулич Анна
Невозможно пройти тесты, в окне с вопросами пусто
Сашечка Огнев
Сашечка Огнев
Россия, Красноярский край
Андрей Корягин
Андрей Корягин
Россия, Пенза, Вазерская средняя школа, 2001