Создание летающих пулек.
главная страница статьи файлы о сайте ссылки
Создание летающих пулек.

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

Как сделать крутые летающие пульки? Очень просто! Вообще-то тема создания летающих - одна из важнейших для программирования игр.

Файлы к статье: pulki.zip (10kb)

Итак, у нас есть пульки и есть игрок с пушкой. Нужно сделать так, чтобы игрок стрелял пульками, и они продолжали лететь в своих направлениях, даже если игрок уже пошёл в другую сторону.

Вначале опишем игрока: игрок находиться в точке X,Y и смотрит в какую-то сторону (угол PHI). В Delphi это выглядит так:

type Tigrok=record
x,y:single; // координаты
phi:single; // направление взгляда (поворот игрока)
end;

Пулька тоже имеет координаты X,Y и направление PHI:

type Tpulka=record
x,y:single; // координаты
phi:single; // направление полёта пули
letit:boolean; // true - летит, false - уже не летит (не существует)
life:integer; // сколько прошло времени после выстрела
end;

Но здесь есть дополнительные данные. Это связано с тем, что пуля может лететь только определённое время, потому что из-за трения воздуха она упадёт. Поэтому задан параметр letit. Чтобы вовремя "убрать" пулю, нам нужно знать как давно был сделан выстрел. Для этого я ввёл параметр life.

Когда игрок ходит, то меняются его координаты X и Y. Направление, в котором ходит игрок, определяется углом PHI. Например, игрок находится в точке X=0, Y=0:

X=0
Y=0

Если скорость передвижения игрока составляет 10 пикселей за одну перерисовку экрана, то в следующий момент координата игрока будет следующей:

X=0+10*COS(PHI)
Y=0+10*SIN(PHI)

Тут всё просто. Если игрок смотрит под углом 0 градусов, т.е. PHI=0, то он попадёт в точку с координатой:

X=0+10*COS(0)=0+10*1=10
Y=0+10*SIN(0)=0+10*0=0

То есть он окажеться в точке (10,0). То же относится и к пулькам: пульки могут перемещаться в определённом направлении с определённой скоростью.

Выше мы задавали два типа: игрока и пульку. Вот эти типы:

type Tigrok=record
x,y:single; // координаты
phi:single; // направление взгляда (поворот игрока)
end;

type Tpulka=record
x,y:single; // координаты
phi:single; // направление полёта пули
letit:boolean; // true - летит, false - уже не летит (не существует)
life:integer; // сколько прошло времени после выстрела
end;

Теперь зададим переменные этих типов. Сначала зададим игрока:

var igrok1:Tigrok;

А как задать пульки? Ведь одновременно могут лететь сотни и даже тысячи пуль! Одной переменной здесь не обойтись, поэтому нужен массив. Зададим массив из 101 пульки:

pulki:array[0..100] of Tpulka;

Почему 101 пулька? Вообще это значение вы выбираете сами. Всё зависит от ного, как много пуль будет одновременно летать в вашей игре. Просто делайте их такое количество, чтобы всегда хватало свободных пуль.

Что значит "свободных пуль"? Свободные пули - это такие пули, которые не летят:

letit=false

Если пуля не летит, значит она свободна, и мы можем запустить её в полёт! Рисовать на экране следует только те пули, которые летят:

letit=true

Например, если вы залпом выпустите все 101 пулю залпом, то какое-то время ни один игрок в уровне не сможет стрелять. Дело в том, что пока пули летят, у них запись letit:=true, и мы не можем задавать им другое направление полёта.

Итак, у нас есть пульки pulki:array[0..100] of Tpulka, есть игрок igrok1:Tigrok. Нужно наладить между ними взаимодействие.

Сначала надо как-нибудь подвигать игроком и выбрать направление стрельбы. Будем считать, что игрок может стрелять в ту сторону, куда сейчас смотрит. Сам процесс выстрела описывается следующим образом:

координата свободной пули = координата игрока + небольшое смещение

Данное смещение необходимо с той целью, чтобы игрок не попал сам в себя. Ведь если пуля будет выходить из самого игрока, то используемые в вашей игре алгоритмы проверки столкновений приведут к уменьшению энергии игрока.

Нарисуем игрока. Пусть это будет кружочек:

glBegin(GL_LINE_STRIP);
for i:=0 to 10 do
glVertex3f(x+5*cos((360/10)*i*3.14/180), y+5*sin((360/10)*i*3.14/180),0);
glEnd;

Курожочек нарисован с помощью GL_LINE_STRIP из линий. В точках окружности мы помещаем 10 точек, между которыми проводим линии.

Далее нужно нарисовать пушку игрока. Для этого нарисуем линию, которая как бы убежала от игрока в направлении его движения:

glBegin(GL_LINES);
glVertex3f(x+6*cos(phi*3.14/180), y+6*sin(phi*3.14/180),0);
glVertex3f(x+14*cos(phi*3.14/180), y+14*sin(phi*3.14/180),0);
glEnd;

То есть мы берём угол PHI, наклоняем линию под этим углом и рисуем.

Пульку нарисовать тоже просто. Причём пулька рисуется абсолютно так же, как и пушка. Но только линию мы сделаем покороче:

glBegin(GL_LINES);
glVertex3f(x+cos(phi*3.14/180), y+sin(phi*3.14/180),0);
glVertex3f(x+3*cos(phi*3.14/180), y+3*sin(phi*3.14/180),0);
glEnd;

Длина определяется разницей чисел, на которые умножается SIN и COS, то есть:

glVertex3f(x+cos(phi*3.14/180), y+sin(phi*3.14/180),0)
glVertex3f(x+3*cos(phi*3.14/180),y+3*sin(phi*3.14/180),0)

будет короче, чем

glVertex3f(x+6*cos(phi*3.14/180), y+6*sin(phi*3.14/180),0);
glVertex3f(x+14*cos(phi*3.14/180),y+14*sin(phi*3.14/180),0);

Но дело тут не в том, что 3<6. Ведь:

glVertex3f(x+13*cos(phi*3.14/180), y+13*sin(phi*3.14/180),0);
glVertex3f(x+14*cos(phi*3.14/180),y+14*sin(phi*3.14/180),0);

будет самой короткой из этих линий. Вообщем мы рисуем обычный вектор.

В примере, который вы можете скачать с этой страницы, идёт отрисовка игрока:

with igrok1 do
DrawIgrok(x,y,phi);

Потом для 101 пульки мы делаем цикл:

for i:=0 to 100 do
begin
...
end

В этом цикле мы проверяем как давно у нас летит пулька:

if (pulki[i].letit=true) and (pulki[i].life>200) then

Если пулька летит слишком долго, то

then pulki[i].letit:=false;

Потом смотрим: если время полёта пули (life) пока что в заданных нами пределах (<=200), то рисуем эту пульку:

if (pulki[i].letit=true) then
begin
DrawPulka(pulki[i].x,pulki[i].y,pulki[i].phi); // рисуем
...
end;

Если мы будем много стрелять, то в скоро не останеться свободных пуль, так как life всегда равняется нулю. Чтобы время, которое прошло после начала полёта данной пули увеличивалось, мы делаем простое увеличение в каждом кадре отрисовки:

pulki[i].life:=pulki[i].life+1; // увеличиваем время полёта

Само перемещение пульки делается очень просто: у нас есть угол, есть текущие координаты. Нужно изменить текущие координаты, прибавив к ним вектор направления. Вектор направления описывается очень просто:

VECTOR=(cos(phi),sin(phi))

Поэтому перемещение пули будет выглядеть так:

with pulki[i] do x:=x+3*cos(phi*3.14/180);
with pulki[i] do y:=y+3*sin(phi*3.14/180);

То есть каждый кадр мы будем перемещать пульку по направлению PHI на три пикселя.

Потом реализовано движение игрока. Более подробно этот вопрос описан в других моих статьях.

Теперь одна важная вещь:

temp:=false; - если нашли пулю, то будет true
if klavishi.probel=true then - нажат пробел
for i:=0 to 100 do - пройдёмся по всем пулям
begin
// если пулька не летит - значит свободна
if pulki[i].letit=false then
begin
задаём пульке направление,
совпадающее с координатами X,Y и направлением игрока (PHI):

pulki[i].x:=igrok1.x+3*cos(phi*3.14/180);
pulki[i].y:=igrok1.y+3*sin(phi*3.14/180);
pulki[i].phi:=igrok1.phi;
pulki[i].letit:=true; - пуля летит
pulki[i].life:=0; - пуля выпущена ТОЛЬКО ЧТО, то есть время равно НУЛЮ
temp:=true - пуля найдена, поиск можно прекратить
end;
if temp=true then break; // нашли пульку - можно выходить из цикла
end;

Это самая важная часть в программе. Важно понять, что если одновременно летит много пуль, то выстрелить будет невозможно, так как у всех пуль будет LETIT=TRUE. То есть, например, если летит 101 пуля, то стрелять уже не получится. И в программе вы это увидите. Через определённое время пули исчезают, так как наращивается переменная LIFE каждой пули. При превышении определённого значения мы "вырубаем" пулю:

if (pulki[i].letit=true) and (pulki[i].life>200) thenthen pulki[i].letit:=false;

Другая важная часть в этой программе - это перемещение игрока. Как вы смогли заметить, перемещение игрока и пули выполняются одинаковым способом с применением тригонометрических функций: SIN и COS. Понять, как это происходит, очень просто: когда угол PHI = 0, то COS(PHI)=1 SIN(PHI)=0 и мы движемся вправо:

x:=x+1
y:=y+0

Когда PHI=90, то COS(90)=0 SIN(90)=1 и мы движемся вверх:

x:=x+0
y:=y+1

Когда PHI=45, то движемся наискосок. Это связано с тем что COS(45)=0,7 SIN(45)=0,7:

x:=x+0,7
y:=y+0,7

А если мы сместились на гипотенузу, то её длина равна:

корень из ( 0,7*0,7 + 0,7*0,7) = 0,99

То есть вообще-то, каким бы ни был угол PHI, перемещаемся на одинаковое расстояние. Это относится и к игроку и к пулям. Пример с пульками очень важен в плане понимания "многозадачности" в играх. Пульки куда-то летят. Куда лететь они сами знают (у них есть текущая координата и угол). Похожие приёмы используются в разных местах при создании игр.