Как
устроены 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
(раздел "насчёт написания игры") =)
|