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

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

Из этой статьи вы узнаете всё о том, как сделать свою компьютерную игру. В супер сжатом виде здесь изложены все аспекты создания игр и данны сслыки на другие мои статьи. По этой статье вы научитесь делать любые игры с 2-D и 3-D графикой, даже если не знакомы с программированием. Здесь я даю основы в очень сжатом виде, но этого достаточно.

1. Достаёте какой-нибудь язык программирования: например, это может быть Delphi. Или любой другой. Посмотрите простые примеры. В основном важно понять, что такое переменные, и смысл выражения x:=x+1. Здесь x - это переменная. Например, x=5. Тогда x+1=6. Но ведь 5 не равно шести! В этом и состоит смысл работы с переменными. Слева - значение в будущем. Справа - значение в прошлом. По середине находится то самое ":=", что в настоящем производит действие +1. Если записать в числах, то всё будет правильно: 6=5+1. В прошлом было 5, в будущем будет 6, в настоящем мы прибавляем 1 к прошлому, получая будущее.

Просто найдите тоненькую книжку по паскалю. Страниц 40-50, не больше. Лучше даже 30 страниц. Найдите такую простенькую книжечку по какому-нибудь языку. Просто нужно понять как задавать переменные, как заносить в них числа, как их менять, как сравнивать. И ещё маленький элемент - как делать циклы. То есть вы должны понять как в программе задать выполнение какого-нибудь действия определённое число раз.

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

Смысл создания игр и состоит в том, что все объекты в них существуют отдельно друг от друга. Например, вы делаете стратегию (статья "Создание стратегических игр"). Если вы посмотрите на то, как я сделал самодельный WarCraft, то заметите следующее: у каждого объекта в игре есть свои координаты, свой уровень энергии, свои мысли, вообщем всё своё. Но наряду с этим введены взаимодействия между объектами типа: "если координаты объекта A близки к координатам объекта B и они являются врагами друг другу, то сделать каждому объекту мысль атаковать другого".

На самом деле всё ещё проще. Вот есть у нас шарик, и мы хотим, чтобы он летал по экрану и отражался от краёв экрана. Итак, выше я говорил, что все объекты существуют АБСОЛЮТНО отдельно. Именно поэтому всё так просто. Шарик существует отдельно, у него есть координаты X,Y. Это координаты шарика, его личные координаты. Никакой объект не трогает эти координаты. Есть стены. Они тоже никак не связаны с шариком. То есть шарик никак не влияет на стены. Пусть у нас шарик летит вправо, т.е. в каждом кадре x:=x+1. С правой стороны экрана у нас находится стена. Если мы находимся перед стеной, то x<800. Если за стеной, то x>800. В данном случае 800 - это разрешение экрана по оси X (горизонтальная ось экрана при разрешении 800x600). Как я уже говорил выше, шарик летит себе и летит. x:=x+1. Поэтому из кадра в кадр x сначала равен 0, потом 1, потом 2, потом 3, потом 4... и так кадры сменяются, x=15, x=16, ... x=100... и т.д.. Шарик приближается к правой стороне экрана, где находится стена: x=790, x=791, x=792, x=793, x=794, x=795, x=796, x=797, x=798, x=799, x=800, x=801, x=802, x=803, x=804, x=805 и т.д.. То есть шарик пролетает насквозь стены и не обращает на неё НИКАКОГО влияния. Опять говорю вам о том, что объекты существуют абстолютно отдельно друг от друга, т.е. шарик и стена НИКАК не связаны. Шарик - это координаты x,y (на экране). Стена - это выражение x<800,x>800 (перед стеной и за стеной). То есть шарик подлетает к стене x=795, 796, 797, 798, 799, 800, 799, 798, 797 и отлетает. Так как же сделать так, чтобы шарик не пролетал стену насквозь, а отразился от неё? Для этого нужно в момент столкновения поменять x:=x+1 (полёт вправо) на x:=x-1 (полёт влево). Но как это сделать? Очень просто! Для реализации этого мы делаем следующее: до запуска главного цикла игры вводим переменную naprx, равную 1:

naprx:=1;

В главном цикле программы будет:

x:=x+naprx;
if x>=800 then naprx:=-1;

Вот теперь мы ввели СВЯЗЬ. Но объекты-то всё равно существуют отдельно друг от друга! Вообщем смысл такой, что строчка if x>=800 then naprx:=-1; - это внешний наблюдатель. Это всё равно, что если бы вы наблюдали за приближением шарика к стене и силой мысли изменили содержимое памяти компьютера с единиц (1) на минус единицу (-1). Более подробно об этом вопросе можно прочитать в статье "Как сделать 2d игру". Ещё раз обращаю ваше внимание на то, что ВСЕ ОБЪЕКТЫ В ИГРАХ ПОЛНОСТЬЮ ОТДЕЛЬНЫ ДРУГ ОТ ДРУГА! Но есть как бы внешний наблюдатель, реализованный в виде IF-ов. То есть вы описиываете идею из своих мозгов, связанную с наблюдением. Если игра в вашем воображении, то этот внешний наблюдатель - это вы. Вы представляете, как шарик движется к стене, а потом отлетает от неё. А чтобы сделать игру достаточно прописать это в IF-ах и циклах, чтобы изменять параметры героев игры.

2. Найдите какую-нибудь программу "основу" для вывода графики на экран с помощью OpenGL. Например, можете скачать nehegl_delphi.zip или одну из моих программ (рекомендую вот эту: kursor.zip). Основу ещё иногда называют фреймворком (framework) или OpenGL framework.

3.Найдите в этой программе процедуру рендеринга (rendering, отрисовка). Рассмотрим исходник nehegl_delphi.zip файл NeHeGL.pas. Вот эта процедура:

procedure Draw;
begin
...
...
...
end;

А в моей программе kursor.zip файла Unit1.pas такой процедурой является

procedure TForm1.glDraw();
begin
...
...
...
end;

Смысл этих процедур состоит в следующем: Windows периодически даёт возможность всем запущенным программам сделать какие-нибудь действия. В играх на OpenGL эти действия в основном как раз и умещаются в показанной выше процедуре. Это из области вечных циклов и т.п..

Например, если вы в игре видите 100 FPS, то это означает, что за одну секунду игра успела запустить свою главную процедуру (процедуру Draw) 100 раз. А теперь более понятное объяснение: многозадачность в Windows обеспечивается тем, что он поочерёдно вызывает главную процедуру программы.

Например, главная процедура программы следующая:

procedure Draw;
begin
x:=x+1;
end;

Это означает, что Windows сделает так:

вызов процедуры Draw Word-а
вызов процедуры Draw WinAMP-а
вызов процедуры Draw Вашей игры (x:=x+1)
вызов процедуры Draw Рабочего стола
вызов процедуры Draw Word-а
вызов процедуры Draw WinAMP-а
вызов процедуры Draw Вашей игры (x:=x+1)
вызов процедуры Draw Рабочего стола
вызов процедуры Draw Word-а
вызов процедуры Draw WinAMP-а
вызов процедуры Draw Вашей игры (x:=x+1)
вызов процедуры Draw Рабочего стола
и т.д.

То есть у самого Windows-а есть что-то вроде бесконечного цикла, из которого он вызывает главные процедуры всех запущенных программ.

Разумеется знатоки программирования мне возразят, что я описал процесс неправильно. Но здесь всё дело заключается в том, что статей на тему обработки сообщений Windows и устройства программ предостаточно. Но вот информации о том, как начать делать игры, не так уж и много. Поэтому будем считать, что Windows работает именно так, как я описал.

4. Ну а теперь всё понятно: вы нашли главную процедуру отрисовки. А это значит, что вы можете уже сейчас двигать точку в окне программы:

procedure TForm1.glDraw();
begin
ОчиститьЭкран;
x:=x+1;
y:=y+1;
ЗакраситьТочкуБелымЦветом(x,y);
...
end;

Вот. Как только вы запустите свою программу, наискосок экрана будет двигаться белая точка. Чем быстрее компьютер, тем быстрее будет двигаться точка. Чтобы точка двигалась на всех компьютерах одинаково, следует учитывать время отрисовки кадра:

procedure TForm1.glDraw();
begin
t1:=t2;
t2:=getTickCount;
ОчиститьЭкран;
ВремяОтрисовкиКадра:=(t2-t1)/1000;
x:=x+5*ВремяОтрисовкиКадра;
y:=y+5*ВремяОтрисовкиКадра;
ЗакраситьТочкуБелымЦветом(x,y);
...
end;

Здесь всё просто: функция getTickCount возвращает значение общего таймера Windows в миллисекундах. Мы знаем текущее время t2 и прошлое время t1. Значит прошлый кадр отрисовался за (t2-t1) миллисекунд. Находим время отрисовки как: dt=t2-t1. И смотрим аналогию:

S=V*t ПУТЬ=СКОРОСТЬ*время
x=X*dt КООРДИНАТА=СКОРОСТЬ*времяотрисовкикадра

Дополнительную информацию по движениям, поворотам и перемещениям вы можете прочитать в статье "Как сделать 2d игру".

5. Всё. Теперь вы можете сделать любую игру. Смысл создания игр состоит в том, что все объекты существуют в них отдельно, но между ними описаны взаимодействия. Например, вы выводите две точки. Координатой первой точки управляете вы. Координатой второй точки управляет другой человек. То есть связи между координатами никакой нет. Но теперь вы берёте формулу из книжки: расстояние между двумя точками. И делаете в своей программе взаимосвязь: если расстояние между точкой1 и точкой 2 меньше 30 пикселей, то выводится сообщение "монстр Немезида догнал прелестную Джилл" (по сути Game Over) и обнуляете значения, чтобы начать игру заново. Объяснение реализации перемещения игрока с использованием тригонометрических функций SIN и COS даётся в статье "Создание летающих пулек". Также советую почитать мои размышления на тему написания игры.

Что ещё важно: ведь по сути игра разделяется на два пласта: числовая часть и отображение. Есть алгоритмы, переменные и т.д.. А есть картинка на экране. Именно по этой причине любую 2d игру можно переделать в 3d. Например, игру PACMAN. Например, посмотрите статью "Как устроены 2d бродилки и создание искуственного интелекта (AI)." там ведь хоть в 3D, хоть в 2D эту игру делать.

Итак, теперь вы знаете, что сделать игру - это очень просто. Для этого нужно сделать три простых вещи:

1) Задать массивы с игроками: у каждого игрока СВОИ ЛИЧНЫЕ координаты x,y; уровень здоровья power, мысли и т.д.
2) Сделать процедуры, которые движут героем на основе его ЛИЧНЫХ параметров. Например у 5-го игрока есть мысль идти к 15-тому (igrok[5] misl:="idti 15"). С помощью процедур, которые движут героем, мы должны изменять координаты igrok[5].x,igrok[5].y до тех пор, пока он не окажется рядом с igrok[15].x, igrok[15].x.
3) Сделать процедуры, которые меняют ЛИЧНЫЕ параметры одних объектов в зависимости от ЛИЧНЫХ параметров других объектов. Например, у вас есть массивы объектов: игроки array[0..100] of Tigrok, пульки pulki[0..300] of Tpulka и т.д.. Есть массив треугольников, из которых состоит уровень array[0..50000] of TTriangle. Теперь делаете так: если ЛИЧНЫЕ координаты игрока находятся близко к ЛИЧНЫМ координатам пули, то убираем пулю (делаем у пули личный параметр letit:=false) и уменьшаем личную энергию игрока (power:=false). Все проверки вы делаете в циклах, перебирая различные комбинации. Если пуля пролетает далеко от игрока, то ничего и не делаете (не сработает процедура проверки).

Потом всё это отобразить на экране. Для начала без особо крутой графики: просто выводите точки и линии. Потом нарисуете спрайты, или возьмите готовые, и замените процедуры вывода точке на процедуры вывода спрайтов.