О программировании игр-2

Nov 22, 2008 17:50

Окончание. Начало здесь

Сейчас я раскрою тайну, доступную только шаолиньским монахам и ниндзям. Я расскажу эксклюзивный алгоритм, разработанный мной на первом курсе института, с помощью которого можно сделать трехмерную игру. Это жжжжутко сложный алгоритм, поэтому я не боюсь его выдавать - все равно ничего не поймете!

Все, что вам потребуется - базовые знания тригонометрии, теории комплексных чисел, тензоров, алгебры кватернионов, теории матриц и интегралов по контуру бумага в клеточку, карандаш, ластик и фломастеры. Фломастеры вам понадобятся красный, зеленый и синий. Если же вы решили заняться трехмерной графикой всерьез, то еще желтый и фиолетовый, без них по-настоящему красивую картинку не получить. Но мы обойдемся красным, синим и зеленым, чтобы было проще.

У кого нету фломастеров, но есть цветные мелки, не расстраивайтесь! Мелками тоже можно. Хотя не так аккуратно получится.



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



Вот стою я, такой нарядный, в лабиринте. Стою там, где нарисована стрелочка, и смотрю туда, куда она показывает.


Как же нарисовать лабиринт таким, каким я буду видеть его из своих глаз?

Возьмем кусок бумаги в клеточку, карандашик и нарисуем прямоугольник. Проведем у него диагонали. На диагоналях нарисуем прямоугольник вдвое меньше. Внутри него нарисуем прямоугольник вчетверо меньше. Внутри него нарисуем прямоугольник в восемь раз меньше. И так далее, пока карандаш не кончится.


А теперь посмотрим на тот коридор, который в нашем лабиринте уходит от меня вверх. По левой стенке у него зеленый квадратик, потом пропуск, потом синий квадратик. Прямо по коридору три пустых клетки, потом упираемся в синий квадратик. И, наконец, по правой стенке у него два красных квадратика и пропуск.
Берем фломастеры и закрашиваем нашу картиночку в соответствии с этим.


Теперь то же самое сделаем с коридором, который отходит от нас в правую сторону. Вот так он выглядит:


Поскольку смотрим мы на плане лабиринта вверх-вправо, под углом где-то 30 градусов, то мы видим оба коридора, причем верхний больше, чем правый. Возьмем ножницы, отрежем от картинки верхнего коридора правые две трети, а от картинки правого коридора - левую треть. А теперь состыкуем и аккуратно склеим эти кусочки. А лишние карандашные линии сотрем ластиком.


Получаем трехмерную картинку из игры "Алкаш". Проблемы могут возникнуть в двух случаях:

1. Что делать, если мы стоим не в центре клетки, а ближе к ее уголку?
2. Что делать, если нам встретится коридор шириной не в одну клетку, а сразу в две? Или даже целая комната размером пять на восемь клеток?

Обе проблемы я решил очень просто:

1. Мой игровой персонаж мог стоять только в центрах клеток, прыгая от клетки к клетке дискретно.
2. Лабиринт мог состоять только из коридоров шириной в одну клетку.

При желании можем не закрашивать стенки фломастерами, а только обводить их контуры. Получится не так красиво, все равно будет смотреться, как трехмерная картинка.


Закраска - довольно ресурсоемкая операция, а несколько разноцветных линий, координаты которых расчитываются примитивными операциями, тянул даже слабенький процессор ЕС1841.

Впрочем, хватит уже про алкашей, вернемся к другим играм. Как-то раз на первом или втором курсе института у меня вдруг случился день рождения, и родители подарили мне книжку "Секреты программирования игр" Андре Ла Мота, Д.Ратклиффа, Д.Тайлера и М.Семинаторе.

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

Обдумывая алгоритмы трехмерной графики, я всегда исходил из предположения, что для того, чтобы нарисовать трехмерный мир, надо перебрать все предметы этого мира, и каждый из них нарисовать на экране. При этом рассчитать, как эти предметы будут друг друга загораживать, и рисовать в таком порядке, чтобы более ближние перекрывали более дальние. Предметы, которые полностью заслонены другими предметами, можно не рисовать, их все равно не видно.

Концепция отсечения лучей переворачивает все с ног на голову.

Чтобы нарисовать трехмерную картинку, надо просто взять камень и отсечь все ненужное перебрать на мониторе все пиксели и каждый из них покрасить в нужный цвет. Не рисуй предметы, а крась пиксели.

Осталось научиться расчитывать, в какой цвет надо красить пиксели.

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


Если у нас 320 пикселей по ширине и 200 пикселей по высоте (а именно в таком разрешении когда-то работали большинство игр), то нам придется перебрать 64000 точек и для каждой расчитать, куда упрется луч. Это будет работать очень медленно. Поэтому буквально применять эту концепцию довольно накладно, нужен какой-то облегченный вариант.

Будем рисовать все тот же кубический лабиринт с бумажки в клеточку. Если мы расчитали какой-то луч и обнаружили, что он уперся в стенку, то все лучи выше или ниже него упрутся ровно в ту же самую стенку (или ни во что не упрутся). То есть, фактически, целая вертикальная полоска пикселей от верха до низа монитора будет относиться строго к одной стенке.

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

Вернемся опять к двумерной картинке. Есть лабиринт из клеточек. Мы стоим в какой-то клеточке и испускаем глазом желтые лучи. Нам нужно для каждого из этих лучей узнать, в какую закрашенную клеточку он упрется.


Это очень просто узнать. Оставим от клеточек только вертикальные линии. Расчитываем пересечения луча с этими вертикальными линиями. Нетрудно видеть, что каждое следующее пересечение выше предыдущего на одну и ту же величину. Если вы знаете за тригонометрию, то вы легко найдете эту величину.

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

Теперь останется выбрать то пересечение из двух, который ближе по расстоянию. В данном случае это результат 1.


Итак, мы определили, что луч уперся в синюю стенку. Теперь для этого луча нам нужно закрасить все пиксели по вертикали, от нижнего края монитора до верхнего. 200 штук. Причем серединный, сотый пиксель соответствует горизонтальному (параллельному полу) лучу - это тот луч, который мы протрассировали. Так что серединный пиксель определенно синий.

Если мы начнем подниматься от него выше, это значит, что мы начнем задирать луч вверх. Какое-то время луч будет упираться в ту же самую стенку, все выше и выше - пиксели будут такие же синие. Потом луч пойдет по потолку. А потолок у нас, например, серый. Так что с какого-то пикселя и до верху экрана будут идти серые пиксели.

Если же мы пойдем вниз, то точно так же пиксели докуда-то будут синие. Дальше до низа экрана будет пол, который у нас, например, тоже серенький.


Все, что нам осталось - найти величины x1 и x2, чтобы знать, откуда и докуда нарисовать синюю полоску.

Нам известно расстояние, которе прошел протрассированный луч, прежде, чем уперся в стенку - пусть это расстояние R. Известен также рост персонажа - метрвосимисят. Но поскольку глаза расположены ниже макушки, будем считать, что они на высоте 170 см. от пола. Высота потолков, допустим, 3 метра. То есть, от глаз до потолка 130 см. Расстояние от глаз до монитора берем какое-то условное, исходя из того, какой обзор мы хотим иметь. Например, 20 сантиметров.


Кто знает за пропорцию и за подобные треугольники, тот легко найдет, чему равны x1 и x2.

Протрассировали луч, нашли x1 и x2, нарисовали вертикальную полоску. Сдвинулись на пиксель вправо, протрассировали еще один луч, нарисовали вертикальную полоску. Когда сделаем так 320 раз, получим результат.


Но однотонные стенки - это скучно. Попробуем разрисовать их кирпичиками.

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


Получаем текстурированные стенки.


Для того, чтобы все стало совсем зашибись, неплохо нанести текстуры также на пол и потолок. А также помимо самого лабиринта добавить предметы, оружие и врагов.

Вот первая игра, которую я запрограммировал по этому алгоритму. Я как раз тогда начал изучать язык С, и решил написать игру на С, потому что С работал быстрее Паскаля. Заодно и язык изучил в процессе написания.
Наклепал для игры целых 18 уровней, 9 видов оружия, 6 видов врагов (+ 2 босса), взрывающиеся бочки, радиоактивные отходы, телепортаторы - все как полагается.


Игра Kombat. Играть надо вдвоем, друг против друга. Экран разделен на две половины, каждый смотрит на свою. Правда есть недостаток - у большинства клавиатур есть ограничение на максимальное количество одновременно нажатых клавиш. Поэтому если один игрок нажмет и будет держать много клавиш, он этим не дает играть другому.




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

Кстати, обе игры можно скачать здесь: http://panda-ppi.narod.ru/games/index.html
У меня они даже идут под XP. Со страшной скоростью, поскольку компьютеры нынче помощнее, чем тогда были. Но игры можно замедлять. В WKW есть файл delay.dat. В нем хранится число. Чем больше число, тем медленней идет игра. Для моего компьютера нормально где-то 150. В игре Kombat время ускоряется и замедляется клавишами + и -.

В предыдущих играх я трассировал лучи в двухмерном лабиринте, находя пересечение с квадратиком. Когда у меня появился компьютер помощнее (пентиум аш на целых 150 мегагерц), я подумал: почему бы не попробовать протрассировать аналогично все 64000 лучей в трехмерном пространстве, находя пересечение с кубиками? Игровой мир у меня состоял из мелких кубиков - уже не в виде плоского лабиринта, а в виде большого куба, заполненного мелкими кубиками. Получилось все очень медленно, кадров 5 в секунду. Поэтому игру на основе этого алгоритма я делать не стал, а смоделировал внутренности родительской дачи. Там было два этажа, лестница и состоящая из кубиков мебель.


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


Когда мы трассируем луч (здесь я опять трассирую только 320 лучей, по одному на каждую вертикальную полоску на экране), то находим его пересечение с сектором, в котором в данный момент находимся. Если эта сторона граничит с другим сектором, то продолжаем луч в другой сектор и ищем пересечение с ним. И так далее, пока не упремся в стенку, не граничащую с другими секторами. По сравнению с клеточным лабиринтом требуются более сложные расчеты. Зато время расчетов не зависит от размеров комнат. Можно сделать огромную комнату из одного сектора, и она будет считаться столько же, сколько маленькая. В клеточном же лабиринте чем больше клеточек помещаются в комнате, тем дольше расчитываются лучи.



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



Точно такую же секторную структуру имеет игровое пространство игры Doom. Так что моя мечта, которую я мечтал с первого курса, наконец-то, сбылась - я написал графический движок, по структуре трехмерного пространства аналогичный движку Дума. Кстати, эту игру я тогда принес на зачет по трехмерной графике. Был у нас такой курс, и надо было сделать что-то вроде курсовой работы - нарисовать какую-нибудь трехмерную сцену. Мне однокурсники сказали: "Ты чего, собираешься, как все, какой-нибудь вращающийся кубик программировать? У тебя же вон целая трехмерная игра. Тебе за нее должны сразу шестерку с плюсом поставить, как минимум."

Так что мне в результате единственному из группы поставили четверку - все остальные получили пятерки за свои вращающиеся кубики. Преподаватель не заценил то, что я всю графику просчитываю сам (в отличие от других работ, где использовался DirectX или OpenGL, который все нарисует за вас), и придрался к тому факту, что моя игра не поддерживает нормальных разрешений, а только 320*200.

Как раз в то время я параллельно с учебой начал работать. Учеба и работа в сумме отнималь слишком много времени, поэтому программирование игр пришлось забросить. Так что эту игру я так и не дописал: все, что в ней было можно - это походить по пустому уровню. Никаких врагов и т.д. не было.

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


креатив, игры

Previous post Next post
Up