|
Быть может кто-то из Вас знает игру 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
