| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Блог о создании игры "D2"
08.01.2011 Часть физического движка, отвечающая за передвижения, готова. Теперь нужно заняться другими делами: реализовать стрельбу, полет пули, уничтожение объектов. Для этого создать простейших врагов, пока не двигающихся, но уже имеющих здоровье.
В моей игре стрелять можно будет в восьми направлениях (четыре основных и четыре по диагонали). Для начала нужно создать систему выбора направления полета пули. Реализация стрельбы: выбор диагонального направления выстрела Создадим у героя ещё две переменные: «Hero.push» и «Hero.look» (Кажется, что уже много переменных? Это ещё начало). В переменной «Hero.push» будет хранится значение нажатых направлений в геометрических градусах (вправо = 0, вверх = 90, влево = 180, вниз = 270). В переменной «Hero.look» будет находится итоговое направление взгляда героя (или одно из обычных направлений, или диагональное направление, сложенное из двух соседних). Вычисление итогового направления из объектов-строчек получится громоздким и неудобным, лучше напишу кусок программного кода в объекте «Pease of Cod»: При нажатий направления «вправо»:Hero. look = 0; // по умолчанию направление «вправо» if Hero.push = 90 // если было нажато «вверх» then Hero.look = 45; // то направление «вверх вправо» if Hero.push = 270 // если было нажато «вниз» then Hero.look = 315; // то направление «вниз вправо» Hero.push = 0; // запоминаем нажатие «вправо» для других направлений Принцип понятен? Этот код нужно скопировать на нажатия всех четырех направлений (а по том ещё и на дублирующие клавиши), при этом исправлять цифровые значения для каждого случая. Теперь, с помощью переменной «Hero.look», у нас чётко вычисляется, куда нужно лететь пуле. Реализация стрельбы: выстрел, создание пуль Создаем объект «пуля» («Bullet»). Внутри создаём постоянный (бесконечно повторяющийся) таймер, по истечении которого постоянно происходит перемещение пули (действие «Move in Direction (Hero.look, 15)»). С этим пока всё.
Теперь у героя создаем событие, выполняемое по нажатию клавиши «G». Событие такое: на месте героя создается объект «пуля» (Bullet.Create; Bullet.x = Hero.x; Bullet.y = Hero.y + 20), в этот самый момент запускается внутренний таймер пули, задается её направление и скорость. Если пули будут постоянно создаваться, то нужно их и уничтожать. В объекте «Bullet» создадим событие, выполняющееся при соприкосновении с платформой (событие Collizion — Block), при этом пуля будет уничтожаться, а на её месте создаваться новый объект-спецэффект «попадание» («Bang»). В процессе создания игры мы создадим много различных объектов-спецэффетов, от обычных объектов они отличаются тем, что живут строго определенное время. При появлении такого объекта (событие «Create») сразу же запускается таймер (действие «Run Timer0»), по истечении которого произойдет уничтожение (действие «Bullet.Destroy»). За это строго отведенное время (параметр таймера) в спрайте объекта проиграется необходимая анимация (количество кадров анимации должно быть равно времени срабатывания таймера). Реализация стрельбы: попадание пули Создадим неподвижного врага («Enemy1») — объект наподобие героя, только без событий по нажатиям кнопок. Сделаем ему характеристику здоровье (Enemy1.health = 2) и постоянный таймер, проверяющий условие смерти врага (if Enemy1.health = 0), при выполнений которого запустится функция уничтожения (Enemy1.destroy). Теперь для объекта «пуля», создаём новое событие при столкновении с врагом: при попадании (событие «Collision — Enemy1»)здоровье врага отбавляется (Enemy1.health = Enemy1.health-1), пуля уничтожается (Bullet.destroy). Это у нас получилась реализация самой обыкновенной пули. Так как планируются несколько видов оружия, то нужно будет создавать для них другие пули, с другими спрайтами, с другой поражающей силой, с возможностью уничтожать особые объекты. А пока запустим игру для тестирования стрельбы. О, ужас! Герой из Braid начал стрелять. Нужно будет срочно поменять спрайт. Или, всё таки, потерпеть до конца создания движка? Ладно. А пока, рассмотрим ошибки. Реализация стрельбы: задержка между выстрелами, перезарядка При нажатии на кнопку, пули начинают создаваться нескончаемым потоком. Нужно это исправить. Создадим герою переменную «Hero.bang», которая будет регулировать возможность стрельбы. Изначально присвоим переменной значение «1». При выстреле переменная обнулится (Hero.bang = 0), а потом через определенное время восстановит значение «1». Настраивая таймер восстановления «Bang» можно задавать различную скорострельность оружия. Время восстановления возможности обычно называют «кулдаун» (от английского слова cooldown), именно кулдаун выстрелов мы сейчас и создали. Можно создать ещё одну переменную (reload), просчитывающую количество патронов. При окончании патронов, запускается более долгий таймер и звук перезарядки, а по окончании снова появляется возможность стрельбы. Теперь у героя созданы все основные функции. Попробую упорядочить всю информацию по объекту «герой».
Сейчас вам понятно, насколько просты механизмы, используемые в большинстве игр. Можно скачать, посмотреть исходный код своими глазами, запустить игру, проверить её возможности.
12.01.2011 Обычный враг был сделан для тестирования геройского оружия. Далее нужно приделать к врагу систему передвижения и стрельбы. Всё это не так просто, оставлю дело на потом. Для начала создам до конца более простого по реализации врага. Реализация статичного врага Создадим объект «стационарная пушка» (Turel). Передвигаться такая штука не будет, только стрелять и разрушаться от попадания пуль героя. Событие при попадании пули скопируем с обычного врага, а стрелять пушка будет прямо в героя – это очень легко сделать.
Вычисляем азимут героя относительно пушки (сейчас, наверно, большая часть читателей смотрит большими круглыми глазами на слово «азимут»), проще говоря: угол или направление куда нужно повернуться пушке, чтобы смотреть прямо на героя. Вычисляется это через равенство прямоугольного треугольника, вот так: target == arccos(Turel.x – Hero.x / Turel.y – Hero.y). Создаем особый снаряд для пушки по аналогии с другими (хорошо, что в GameMaker’е реализовано наследование свойств объектов). Запускаем игру в тестовом режиме. Пушка постоянно стреляет в направлении героя, даже если на пути между ними поставить стенку, даже если между ними выстроить целый, наполненный объектами уровень. Получилось легко и просто, но как-то неправдоподобно и глупо. Пушка не должна видеть нас издалека. Нужно сделать так, чтобы у пушки было два режима: первый – режим ожидания, второй – активный режим, когда она начинает стрелять в героя. И самое сложное – задать условия, когда один режим будет переключаться на другой. Для этого нужно создать ещё один объект – зону видимости пушки (Turel_zone). А вот как его реализовать? Есть два способа: 1) расставлять эти зоны вручную в редакторе уровня (очень трудоемкий процесс), 2) высчитывать их, и тогда они будут создаваться автоматически в процессе игры (очень сложно реализовать). Первый способ устаревший, но его всё ещё можно использовать в небольших играх. Но, так как я программист, я выбираю второй вариант (всё что можно автоматизировать – должно быть автоматизировано). Для начала нарисую несколько возможных зон обзора пушки на бумаге. Зона всегда представляет собой многоугольник (если максимально упростить – треугольник, выходящий из дула пушки, но из-за этого упрощения пушка будет видеть сквозь стены – меня это не устраивает). Теперь хотя бы известно, что нужно вычислить. Вычисления будут большие и долгие, но не стоит боятся добавлять их в игру, ведь они будут производится всего один раз для каждой пушки, и от них игра не будет тормозить. Берем центр пушки (turel.x + turel.whith/2 ; turel.y + turel.height/2) в качестве первой точки многоугольника. Проводим из неё геометрические лучи во все стороны, чтобы найти где есть препятствия. Так, так, лучи… Мне тут пришла в голову одна гениальная идея: вовсе не нужно реализовывать многоугольник, можно просчитывать всю зону с помощью лучей, не геометрических, а самых натуральных, наподобие солнечного света. Забываем несколько предыдущих абзацев и начинаем заново. Реализация сканирования местности
Создаем новый объект – волна сканирования (Scan). Он будет небольшим по размеру, а спрайт у него будет прозрачный – то есть на экране эти «сканы» никак отображаться не будут (только на время тестирования, нужно будет сделать их видимыми). Отдалено эти объекты будут напоминать пули: они будут выпускаться из пушки, лететь по изначально заданной траектории, и уничтожаться при соприкосновении со стенами. А если такой «скан» попадет на героя, то у пушки включится боевой режим. По сути это будут лазерные датчики, срабатывающие только на объекте «герой». Начнем реализацию: - выставляем первоначальное направление скана (Turel.scan = 0) – строго вправо,- начинаем цикл: - в центральной точке пушки создаем скан (Scan.create), - задаем ему определенное направление движения в градусах (Move in Direction (Scan) = (Turel.scan ; 15), - изменяем направление движения для последующих сканов (Turel.scan = Turel.scan + 30) – шаг изменения будет 30 градусов, итого будет 12 направлений (по-моему, вполне хватит такой точности), - ждем следующий такт, - а дальше полностью повторяем цикл. Теперь получается такой механизм: пушка постоянно сканирует всё вокруг, по времени скан создается через промежуток 1 секунду (это не должно сильно грузить движок игры), получается за каждые 12 секунды проверяется всё вокруг – это не позволит герою проскочить мимо незамеченным. Сканирование будет постоянным. Чем дальше будет герой от пушки, тем меньше будет вероятность попадания в него скана, то есть если герой будет в простреливаемой зоне, но на другом конце экрана, то ещё не факт, что пушка заметит его (выходит вполне правдоподобно)(а если нужна дальнозоркая пушка, то стоит уменьшить угол между соседними сканами). Вблизи же, пушка сработает даже быстрее чем за секунду, ведь в героя, скорее всего, будут попадать сканы сразу с нескольких направлений. При обнаружении героя, включится пушка, выпустит заряд в нужном направлении, успокоится и снова прейдет в режим ожидания. Если за это время герой останется в просматриваемой зоне, то опять сработает активизация пушки, и так пока герой не скроется из вида. Удачная находка эти лучи. С их помощью можно реализовать и динамические источники света. Последние достижения компьютерных технологий в обычном платформере! Для этого нужно уменьшить градус поворота невидимых сканов — фотонов. Сами фотоны так и останутся прозрачными (прозрачными на спрайте, а не невидимыми вообще. Спрайт у объекта должен быть обязательно, без него не будут срабатывать события соприкосновения с другими объектами), но при этом каждую секунду на их месте будут создаваться световые вспышки. Весь путь фотона, от начала и до конца, будет усеян этими вспышками, чем дольше будет лететь фотон, тем всё больше по размерам будут вспышки (для имитации законов распространения света), так же будут накладываться вспышки от фотонов с соседних направлений. Получится освещенная область. Когда в область войдет посторонний объект, фотоны начнут врезаться в него, и не будут доходить до конца своего прежнего пути. За объектом появится темная область – тень. Всё это хорошо, но при неправильных настройках даже самый мощный компьютер не справится с прорисовкой света. Подсчитайте навскидку, сколько будет появляться изображений световых вспышек, если фотоны будут каждую секунду разлетаться в 360 разных направлениях, и на каждом направлении в десятые доли секунды будут появляться новые объекты типа «вспышка»? (А если ещё не сделать непробиваемые границы уровня, и фотоны будут разлетаться во все стороны за пределы экрана далеко в бесконечность, то даже самый мощный компьютер зависнет – это только вопрос времени). Лучше сделать появление вспышек не таким частым и плотным, а их небольшое количество компенсировать продолжительностью и размером, и главное не забыть чтобы эти спецэффекты самоуничтожались в обязательном порядке через какое-то время. Ещё лучше сделать так, чтобы игрок мог настраивать наличие и качество динамического освещения в настройках, в самом начале игры. Вообще это самый простейший и очень плохой пример реализации динамического освещения (опытные игроделы, увидев использование такого способа, наверное, умрут со смеху). Сейчас существуют намного более эффективные технологии прорисовки света. А этот пример – просто информация для общего развития. Мне понравилась легкость, с которой я реализовал, реалистичную систему сканирования местности. Хочу добавить ещё немного реалистичности. На уровне уже есть физические объекты – стены. Надо бы на их основе сделать перемещаемые объекты. Для начала – самые обычные ящики (объект «Box»). Реализация интерактивных подвижных объектов Простой вариант Напомню, при соприкосновении со стеной, скорость героя обнуляется (Hero.dx = 0). А если сделать так, что скорость не обнуляется, а лишь частично снижается (Hero.dx == 1/3*Hero.dx), при этом полученная скорость героя передается задетому объекту (Box.dx == Hero.dx). Уже это даст возможность толкать ящики и прочие объекты строго по горизонтали. Но ещё можно сделать так, чтобы ящики могли заваливаться набок, переворачиваться и отлетать от взрывов. Простой передачей скорости в этом случае не обойтись. Нужно добавить ящику вес, включить для него действие силы тяжести, и реализовать действие силы трения, ещё нигде не используемой в игре. Сложный вариант
Итак, вернемся к передвижению ящика. Он будет вести себя по старому (просто сдвигаться в сторону), только при большом весе. Если же он будет весить мало, то ему легче будет перевернуться на бок, чем тащится по полу. Рассмотрим пример из реального мира, и вспомним при этом физику (по-моему, мы вспоминаем её с самого начала создания игры). Допустим, кто-то толкает ящик слева направо. Его сила направлена вправо по центру ящика и это самая большая сила. Ей противодействует сила трения, направленная влево по нижней границе ящика, она немного уменьшает результат от силы толкающего. Сила тяжести прижимает ящик сверху, а ей противостоит сила реакции опоры. Сила тяжести и сила реакции опоры приложены к одной точке, а вот внешняя сила человека и сила трения — в разных точках. Разница невелика, и пока не чувствуется. Но постепенно уменьшая силу тяжести до значения силы трения, можно добиться того, что ящик будет сдвигаться вправо не целиком, а только своей верхней частью (уходить под углом вверх), нижняя же будет стоять на месте благодаря силе трения. Получается заваливание ящика набок. Хм-м, если бы мне так интересно (пусть даже и не на компьютерных играх) преподавали физику в школе, я бы просто обожал учиться.
Теперь думаем как всё это реализовать в компьютерной игре. Силы тяжести и реакции опоры уже реализованы, а как же реализовать оставшиеся две? Ведь в них нужно учитывать точку приложения. А потом, после сложения сил, ещё и преобразовывать полученные данные в угол поворота ящика. Помню, что F = m*a*cos(ф). В формуле учитывается угол, но как преобразовать точку приложения в этот самый угол? Что нужно делать? Не знаю. Пойду читать учебник по физике. Нет, не сейчас, сначала рассмотрю ещё один случай с точками приложения сил. Например, стоит ящик, на нём сверху ещё один ящик. Если нижняя часть одного полностью соприкасается с верхней другого, то всё нормально. Но если верхний ящик сдвинут немного в сторону, то конструкция становится ненадежной и может развалиться. Это потому, что у верхнего ящика сила тяжести всё так же находится по центру, а вот сила реакции опоры сдвинулась, она не по центру ящика, а по центру области, соприкасающейся с нижним ящиком. Это тоже нужно будет просчитывать. Можно скачать, посмотреть исходный код своими глазами, запустить игру, проверить её возможности.
|
Прохождение игрПартнерские ссылкиКупить казуальные игры Футболки с надписями из игр Как сделать уникальный подарок | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|