Как устроены 2d бродилки и создание искуственного интелекта (AI).
главная страница статьи файлы о сайте ссылки
Как устроены 2d бродилки и создание искуственного интелекта (AI).

Георгий Мошкин
tmtlib@narod.ru

Игра про монетки. Так монетки будут лежать в ячейках сетки.
А монстры будут иметь AI, и перемещаться, как и главный герой, по сетке.

Задаём уровень:
---
var level:array[0..100,0..100] of integer; // 0 - пусто, 1 - стена, 2 - вода, 3 - монетка

Задаём размер ячейки:
const dx=25;

Задаём тип монстра/игрока:
---
type tIgrok=record
x,y:single; //координата
intx,inty:integer; // положение в ячейке
phi:single; //угол поворота
power:single; // энергия
end;

Задаём всех монстров:
---
var monsters:array[0..49] of tIgrok;

Задаём главного игрока:
---
var glavn:tIgrok;


Загружаешь уровень:
---
load;

В ГЛАВНОМ ЦИКЛЕ ИГРЫ:

проверяешь скорость отрисовки
elapsed:=функция_замера_времени_между_кадрами/1000;
if elapsed>0.05 then elapsed:=0.05; //чтоб не пролетал сквозь стены на медленных компах


Проверяешь клавиатуру и поворачиваешь игрока:
---
if LEFT then glavn.phi:=glavn.phi-250*elapsed;
if RIGHT then glavn.phi:=glavn.phi+250*elapsed;

250 - скорость поворота

Проверяешь клавиатуру и двигаешь игрока в направлении phi:
---
if UP then
begin
glavn.x:=glavn.x+100*cos(phi*3.14/180);
glavn.z:=glavn.z+100*sin(phi*3.14/180);
end;

if DOWN then
begin
glavn.x:=glavn.x-100*cos(phi*3.14/180);
glavn.z:=glavn.z-100*sin(phi*3.14/180);
end;

Определяешь ячейку, в которую попал главный игрок:
---
with glavn do
begin
intx:=round((x-dx/2)/dx);
inty:=round((z-dx/2)/dx);
end;

Если ячейка - стена, то выталкиваешь обратно:
---
if (level[glavn.intx,glavn.inty]=1) then
begin
...
glavn.x:=...
glavn.y:=...
...
end;

Если ячейка - монетка (цифра 3), то забираешь её:
---
if (level[glavn.intx,glavn.inty]=3) then
begin
level[glavn.intx,glavn.inty]=0; // - теперь здесь пустота
score:=score+1; // - увеличиваешь "очки"
end;

Двигаешь монстрами по их AI:
---
for i:=0 to 49 do
begin
...
...
if ... then AI_LEFT:=true else AI_LEFT:=false;
if ... then AI_RIGHT:=true else AI_RIGHT:=false;
if ... then AI_UP:=true else AI_UP:=false;
if ... then AI_DOWN:=true else AI_DOWN:=false;
...
...
здесь проверяешь что выдаёт "виртуальная" клавиатура AI;
if AI_LEFT then monsters[i].phi:=monsters[i].phi-250*elapsed;
if AI RIGHT then monsters[i].phi:=monsters[i].phi+250*elapsed;
if AI_UP then
begin
  monsters[i].x:=monsters[i].x+100*cos(phi*3.14/180);
  monsters[i].z:=monsters[i].z+100*sin(phi*3.14/180);
end;
if AI_DOWN then
begin
  monsters[i].x:=monsters[i].x-100*cos(phi*3.14/180);
  monsters[i].z:=monsters[i].z-100*sin(phi*3.14/180);
end;

end;

Если ячейка - стена, то выталкиваешь монстра обратно:
---
for i:=0 to 49 do
if (level[monsters[i].intx,monsters[i].inty]=1) then
begin
...
monsters[i].x:=...
monsters[i].y:=...
...
end;


Если чем-нибудь стреляешь, то создаёшь тип пули (всё аналогично игроку):
задаёшь пуле phi такой же, как и phi игрока. Если стреляют монстры, то в их AI делаешь генератор стрельбы. При стрельбе как бы нажимаешь пробел: AI_SPACE:=true и тогда делаешь if AI_SPACE then ...

Если пуля попала в игрока, то делаешь glavn.power:=glavn.power-1;
Если пуля попала в монстра, то делаешь monsters[i].power:=monsters[i].power-1;

Определить попадание пули просто:
1) определяешь ячейку, в которой летит пуля
2) смотришь, есть ли кто-нибудь в этой ячейке
3) если есть, то уничтожаешь пулю и уменьшаешь power у соответствующего монстра

Чтобы не деалать лишних проверок, можешь везде добавить
к for i:=0 to 49 do
вот это:
for i:=0 to 49 do if mosters[i].power>0 then (если энергия больше нуля, то...)

Если нужны указатели - можешь просто запихнуть все описанные выше переменные
в тип Tworld
---
type Tworld=record
...
...
...
end;

А потом сделать переменную с указателем
var world:^Tworld;

в программе выделить память
getmem(world,sizeOf(world^);

И обращаться ко всему через world:
например, вместо monsters[i] будет world^.monsters[i]
вместо level[,] будет world^.level[,] и тд.

Что-то в этом роде у меня работало в 3D, есть видеоролик
http://www.tmtlib.narod.ru/17sep-high.wmv
Был реализовано определение столкновений и возможность "толкать" монстров: если побежишь на монстра, то он будет сдвигаться. Хотя и монстры могут сдвигать главного игрока. Очень удобно использовать одинаковый тип для игрока и монстров. Так как нажатия клавишь игрока я брал из DirectInput, а нажатия клавишь монстров (зомби) - из AI.

 

То есть, условно было так:
UP:=IsKeyDirectInput(KEY_UP);
DOWN:=IsKeyDirectInput(KEY_DOWN);
LEFT:=IsKeyDirectInput(KEY_LEFT);
RIGHT:=IsKeyDirectInput(KEY_RIGHT);
а у AI:
AI_UP:=true или false по алгоритму AI;
AI_DOWN:=true или false по алгоритму AI;
AI_LEFT:=true или false по алгоритму AI;
AI_RIGHT:=true или false по алгоритму AI;

Главного игрока и монстров вообще желательно запихнуть в один массив:
---
type tIgrok=record
x,y:single; //координата
intx,inty:integer; // положение в ячейке
phi:single; //угол поворота
power:single; // энергия
ai:integer; // ТИП ИСКУССТВЕННОГО ИНТЕЛЛЕКТА; 0 - клавиатура, 1 - зомби, 2 - летающий зомби, 3 - медведь
end;

Задаём всех монстров:
---
var monsters:array[0..50] of tIgrok;

Какому-нибудь из монстров делаешь тип ai КЛАВИАТУРА:

monsters[15].ai:=0;

а всем остальным присваиваешь реальный AI:

monsters[...].ai:=1,2 или 3

тогда в цикле для монстров ты можешь в зависимости от типа AI делать так:
for i:=0 to 50 do
case monsters[i]AI of
0:begin
// здесь читаешь нажатия клавишь из DirectInput
end;
1:begin
// здесь сам задаёшь КАК БЫ нажатия клавишь в соответствии с AI зомби
end;
2:begin
// здесь сам задаёшь КАК БЫ нажатия клавишь в соответствии с AI летающего зомби
end;
3:begin
// здесь сам задаёшь КАК БЫ нажатия клавишь в соответствии с AI медведя
end;

Так как у меня был самопальный AI, который делался абсолютно по НИКАКОМУ известному методу, то я делал так:

type tIgrok=record
x,y:single; //координата
intx,inty:integer; // положение в ячейке
phi:single; //угол поворота
power:single; // энергия
ai:integer; // ТИП ИСКУССТВЕННОГО ИНТЕЛЛЕКТА; 0 - клавиатура, 1 - зомби, 2 - летающий зомби, 3 - медведь
AI_UP:boolean; // НАЖАТА ЛИ КЛАВИША ВВЕРХ
AI_DOWN:boolean; // НАЖАТА ЛИ КЛАВИША ВНИЗ
AI_LEFT:boolean; // НАЖАТА ЛИ КЛАВИША ВЛЕВО
AI_RIGHT:boolean; // НАЖАТА ЛИ КЛАВИША ВПРАВО
end;

У игрока с ai = 0 переменные
AI_UP,AI_DOWN,AI_LEFT,AI_RIGHT брались по DirectInput или сообщению OnKeyDown

У игрока с ai > 0 переменные
AI_UP,AI_DOWN,AI_LEFT,AI_RIGHT задавались по всяким глючным AI, проверяющим "что видит" монстр и т.д.

Например, тупое AI: пока монстр ничего не увидел, то он крутится вокруг себя, т.е.
AI_LEFT:=true;

Если в его поле зрения попал mosters[i] с ai=0 (т.е. ГЛАВНЫЙ ГЕРОЙ), то
AI_LEFT:=false
и врубается ходьба по направлению к главному герою: AI_UP:true;


Тип оружия
---
TWeapon = record
power:integer; //разрушительная мощность
end;

Хранение оружия/объектов игрока
---
type tIgrok=record
...
guns:array[0..10] of TWeapon; // ОДИННАЦАТЬ ЯЧЕЕК ДЛЯ ОРУЖИЯ
current:integer; // текущее оружие
end;

guns[current]:=... // 0 - ничего нет, 1 - дубинка, 2 - огнемёт, 3 - пулемёт

Другой способ хранения
---
var level:array[0..100,0..100] of integer; // 0 - пусто, 1 - стена, 2 - вода (МОНЕТКИ ЗДЕСЬ НЕТ)
тогда задаёшь объекты:
type tobekt=record
x,y:single; //координаты в 2d пространстве
intx,inty:integer; //координаты в ячейке
end;

var obekti:array[0..10000] of tobekt;
Посмотри мои бредни на эту тему http://www.tmtlib.narod.ru/docs.htm
(раздел "насчёт написания игры") =)