Постмортем разработки игры Zombotron

Буквально вчера я наконец-то закончил полугодовой марафон разработки игры Zombotron. Так что теперь можно немного подытожить этап разработки и рассказать о новых открытиях и фэйлах.

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

А пока мы ожидаем релиза, я расскажу немного о разработке. Началось все 8 января. Как раз где-то на новогодних праздниках я решил, что хватит заниматься всякими мелкими играми и нужно что-то сделать такое немного более масштабное, например стрелялочку с физикой. Хотя изначально мне казалось, что ничего тут особо сложного нет и разработка пойдет достаточно шустро, тем более опыт и наработки физического платформера у меня уже были на основе MiningTruck 1-2 части. Ну сделать вместо машинки с двумя колесиками человечка с одним колесиком и дать ему пушку, что может быть проще?! Какой же я был наивный :)

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

Один из первых билдов игры

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

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

Скролл игрового мира

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

Вертикальные полоски

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

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

Visible Culling

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

Физика

Все, кто работал с Box2D, прекрасно знают, что он начинает тормозить, когда количество динамических объектов в физическом мире приблежается к отметке в 100 единиц. Впрочем, этой проблемой грешат почти все физические движки на Flash. В прошлых играх на этапе создания уровня я создавал сразу весь физический мир и это было большой проблемой, на слабых компьютерах игра конечно выдавала меньше FPS, чем хотелось бы. А тут по мимо простых ящиков и бочек должны быть не только всякие конструкции с джоинтами, но и враги с искуственным интеллектом! И как же быть?

Решение естественно только одно: создавать уровень динамически по мере продвижения персонажа по карте. Звучит сложно, но на деле все не так страшно.

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

Правда все это хорошо было только до тех пор, пока мне не понадобилось создавать динамические конструкции, связанные джоинтами. Если все объекты разделены как бы на клеточки и объекты в каждой клеточке создаются исключительно тогда, когда клеточка становится видной игроку (то есть с произвольным временным интервалом), то каким же образом сцепить-то объекты, находящиеся в разных клеточках, например джоинтом!?

Решение этой задачи в целом достаточно простое: я придумал такую вещь, как CreatorArea. Мм.. Даже не знаю, как кратко и понятно описать :) Но если в двух словах, то это некая произвольная область (прямоугольник), которая создает по команде объекты во всех клетках, которые покрывает и выполняет дополнительные методы, которые устанавливают соеденения и связи. Более того, позже я добавил и DestructorArea — это альтернативный вариант создателя, который удаляет все физические объекты из покрываемой области. Уничтожитель используется только в тех частях карты, которые игрок уже прошел и куда уже не может вернуться. Таким образом по факту получается так, что физическая модель мира существует в полном объеме исключительно вокруг игрока :)

Триггеры

Но Креаторы и Деструкторы — это только верхушка айсберга. Думаю, что каждый, кто пытался написать платформер, в котором при нажатии на кнопку открывалась бы дверь — сталкивался с вопросом: «Как же красиво связать дверь с кнопкой?». Знаю по своему опыту, что многие стараются делать прямые указатели, например с кнопки на двери и с двери на кнопку. Но мне нужен был универсальный вариант, чтобы у меня были какие-то объекты, например лифты, двери, а так же переключатели и триггеры-невидимки, которые бы их активировали. И чтобы я мог по ходу создания уровня использовать разные их комбинации, например связать дверь с лифтом, или при открытии двери разрушать какие-то важные джоинты, чтобы игроку что-нибудь падало на голову — то есть прямыми указателями тут уже никак не обойтись.

А решение еще проще, чем с указателями: я написал класс контроллер, который все это безобразие контролировал. Самое смешное, что сам контроллер состоит из нескольких простых методов add(object), remove(object), callAction(), callDistanceAction(). А все объекты, которые могут быть добавлены в этот контроллер должны имплементировать интерфейс IBasicObject, а интерфейс в свою очередь обязывал все эти классы иметь alias, action(), switchState(). Догадываетесь в чем сила ООП? :)

А сила в том, что контроллер — это фактически хранилище всех объектов, с которыми могут быть произведены действия. Согласно интерфейсу каждый объект должен иметь уникальный alias (имя) и зная только имя этого объекта можно выполнить его действие, сказав контроллеру object.callAction("door1"); - контроллер перебирает список всех добавленных в него объектов и вызывает метод action() у объектов с именем "door1".

Соотвественно все алиасы прописываются в редакторе уровней, причем по мимо базового алиаса у объекта может быть callbackAlias — это как бы ссылка на другой объект, который вызывается, после того, как действие текущего объекта выполнено. Таким образом можно создавать всякие интересные цепочки событий. А у кнопок и триггеров невидимок есть targetAlias — это алиас того объекта, который должен выполнить свое действие, вызывается когда игрок активирует кнопку, ну или попадает в область триггера :) В общем получилось все очень универсально и все объекты можно связывать друг с другом как угодно, главное только чтобы алиасы были указаны верно.

Все упичканно триггерами

Обратите внимание на обилие триггеров и других непонятных вещей — весь уровень заскриптован без кода.

Кстати, СreatorArea и DestructorArea для запланированного создания/удаления объектов тоже активируются триггерами невидимками. И вообще большая часть игровых уровней как раз в итоге держится на всех этих триггерах.

Искуственный интеллект

Впрочем поведение врагов сложно назвать настоящим искуственным интеллектом, но в итоге поведение врагов в игре получилось весьма интересным и порой непредсказуемым. В игре Mushroomer я уже имел опыт написания ИИ, но это была адская работа для меня, так как тот код по большей степени состоял из портянки if () { ... } else if () {...} и соотвественно пачки всяких флагов, на основе которых выбирались какие-то решения. А если вы играли и если вы помните, то там не все враги друг к другу равнодушны и их ИИ был достаточно запутанным в итоге с точки зрения кода.

Благо перед тем, как начать разработку игры, я прочитал пост RaymondGames с условным примером о том, на чем базируется ИИ в Half-Life и посмотрел TaskManager от Хитри.

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

Впрочем на деле TaskManager от Хитри тоже оказался очень полезной штукой, для выполнения каких-то простых списков задач — показать там кнопочки с заданным интервалом или запрограммировать эффект горения, или гибель монстра в несколько этапов. Но, к сожалению, в том виде в каком он есть его активно использовать чревато. Основной его недостаток — это перехват события Event.ENTER_FRAME — это конечно делает менеджер задач универсальной штукой, но если вдруг у вас в игре окажется 5-10-20 одновременно работающих менеджеров, то это уже беда. Вообще события очень тормозные, поэтому более одного перехватчика события Event.ENTER_FRAME лучше не использовать. Во всяком случае когда я переписал TaskManager таким образом, чтобы он не использовал перехват событий, то в дебажном плеере я заметил прирост производительности. Еще при использовании TaskManager я заметил мелкие скачки потребления/освобождения памяти и это при том, что у меня практически все кэшированное. Посмотрев еще раз внимательно на TaskManager я стал подозревать, что проблема заключается в методах shift(), unshift() класса Array, использующиеся для удаления и перемещения задач в очереди выполнения. Точно не знаю как эти методы устроены внутри, но когда я отказался от Array и сделал свой связный список то проблема исчезла.

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

Я наверное дня 3 бился с вопросом, как грамотно организовать эту цепочку, чтобы все бежали к игроку по тревоге, и потом еще 2 месяца время от времени балансировал эту систему. Путь тут искать сложно, враги сквозь друг друга не проходят и не видят т.п. Первое что пришло в голову — это например включать тревогу и пусть все бегут к тому, кто включил тревогу, при этом чтобы и сами врубали тревогу. Только тут случался парадокс (если можно так выразиться) — они как мухи на котлету друг к другу слетались и стояли большущей кучей, не обращая на игрока никакого внимания :) Вот тут-то мне и пришлось поломать голову.

Зрение врагов

Цифры над головами отображают их alarmId.

В итоге я придумал систему приоритета. То есть, по мимо флага alarm у врагов появился еще и флаг alarmId — формулу вычисления которого я рожал в долгих муках. Но идея такова, что тот, кто ближе к игроку, тот имеет высший приоритет, а тот кто дальше — более низкий приоритет, и враги бегут к тем своим соратникам на помощь, которые имеют высший приоритет и таким образом они выстраиваются в колонну и идут стройным рядом к игроку на смерть! :)

Расовые группы

Вообще получилось так, что все игровые объекты, включая и новые типы врагов, добавлялись в игру постепенно и в рядовых проверках ИИ на свой/чужой становилось все больше условий по типам объектов. Например, какие объекты можно ломать, а какие нет и таким образом добавляя в игру какой-нибудь новый игровой объект: ящик там или бочку — приходилось лезть в ИИ каждого врага, добавлять новые условия и потом все это тестировать, конечно в итоге мне все это надоело и я решил разделить объекты на группы. Даже не знаю почему сразу мне этого в голову не пришло, но на деле оказалось очень здорово! Например, в игре всего несколько групп GROUP_BREAKABLE, GROUP_PLAYER, GROUP_ZOMBIE, GROUP_ROBOT и т.п. И когда нужно добавить нового врага или объект, просто его включаешь в какую-нибудь группу, и согласно этой группе другие объекты, обладающие ИИ и не очень, уже знают можно ли его сломать или убить. И после добавления этих групп мне пришла неожиданная идея разделения врагов по расовому признаку. То есть грубо говоря все вражеские группы друг с другом воюют, как с игроком. И в итоге все проверки в ИИ свелись до банального:

if (isFriend(seeObj.group))
{
  // Привет! Как дела?
  if (врагов видел?)
  {
    // Аа! Аларм, аларм!
    // Бегу на помощь!
  }
}
else if (!isFriend(seeObj.group))
{
  // Ааа!!1 За родину!
}

Я вообще не знаю с какого перепуга в играх враги друг с другом дружат, но при этом люто ненавидят игрока!? :) Поэтому в Зомботроне я сделал все по честному — все друг друга не любят! Зомби для зомби друг, зомби роботу враг, робот для робота друг и т.п. За счет чего порой кажется, что игрок так мимо проходил и в передрягу вдруг попал :) Зато на мой взгляд появилась некая атмосфера и присуствие жизни, которая течет в игровом мире как бы без игрока. В общем не знаю как вам, а мне в кайф накрыть из базуки группировку в 5-6 врагов, пока те выясняют отношения друг с другом :D Хотя есть предчувствие, что будут комментарии типа: «wtf!!?! Как это они тут перегрызлись без меня-то??».

Так же хочется отметить ключевую особенность игры — это рэгдольчики трупов врагов. А ключевая особенность она именно потому, что когда враги погибают — получаешь какое-то невероятное эстетическое удовольствие от процесса их убиения :) Конечно рэгдоллы — это далеко не новаторский подход и во многих стрелялках уже давно встречаются, но чаще всего разработчики рэгдолы отдают честной физике, что порой выглядит не по «голливудски» и скучно, ну упал реалистично и ладно. Я же постарался этому моменту уделить больше внимания и добавил кучу твиков для воздействия сил на бездыханную тушку, поэтому тела порой выполняют кульбиты, полеты и разрывы, не объяснимые с точки зрения законов физики, но зато на мой взгляд красочно и фаново :)

Рефакторинг

Рефакторинг — это отличная штука, но порой рефакторинг может создать рекурсию программиста и тот в свою очередь повиснит и никогда не закончит проект. В Зомботроне было много рефакторинга, причем меня порой переклинивало так, что если я придумал как что-то улучшить — я потом 2-3 дня копашил весь код, частично меняя архитектуру игры и тестировал все по новой. В большинстве случаев это с играло хорошую роль — код получился более менее понятным и доступным. Но где-то остались и «лохматые» кусочки :) Полгода приличный период времени и за это время некоторые мои подходы к разработке изменились, поэтому было забавно порой открывать какие-то старые классы, которые были написанны в числе первых и не подвергались дальнейшим переработкам — и вот смотришь на них и думаешь «какой я тупой... был». Но, к сожалению, или к счастью, некоторые «лохматые» кусочки, так и остались нерасчесанными.

А самый большой и возможно самый сложный рефакторинг мог случится это когда мне вдруг шарахнуло в голову переработать всю игру под ручной рендер. Мой мозг мучал мою совесть и обратно, наверное недели полторы — то еще противостояние было. Представляете, работаешь над игрой 3 месяца, много уже что готово и отлажено, а потом бац и ВНЕЗАПНО осознаешь, что стандартный рендер тебе не подходит, и пофиг, что предыдущие игры на нем работают, вот надо здесь и сейчас переделывать все под новый! Ууу... Я считаю себя большим героем, что справился с этим желанием, а то глазом моргнуть бы не успел, как месяц пролетел бы! :)

Музыка и звуки

Музыкой и звуками для этой игры, как и для прошлых игр вновь занимался Александр Ахура. На этот раз я в большей степени доверился Ахуре и не навязывал каких-то своих мыслей о музыкальном сопровождении. За счет чего получилось много экспериментов и в итоге для игры было написанно более 10 трэков в разных направлениях. Пробовали активную, немного попсовую и просто эмбиент, в итоге Ахура решил сделать музыку, как эмбиент, чтобы она не мешала действию и была просто достойным фоном, который должен дополнять происходящее и при этом не мешать. И я думаю, что это у нас удалось. В игру вошло аж целых 7 музыкальных трэков!

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

А вот со звуками больших эксперементов не было. Но звуков получилось в итоге очень много: 76 шт. Хотя казалось бы, чего там столько озвучивать!? Но и тут без хитростей не обошлось. Для разных вещей и событий порой используются одни и те же звуки, но подвоха на самом деле не заметно. То есть звуков могло бы быть и больше.

Но в итоге я озвучкой очень доволен, все получилось на высшем уровне! :)

Итоги

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

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

Вообще, самый большой и толстый вывод, который я из всего этого сделал — Flash это не та платформа, которой нужны большие и сложные проекты, тем более если их делать в одиночку. Количество потраченного времени и объем вложенных затрат не влияет на успешность игры в целом. Наверное поэтому делать маленькие и очень успешные игры — это искусство :)

Но что сделано, то сделано. Релиз в среду, а там игроки оценят или нет. А я доволен проделанной работой как слон :P

P.S.: Итого в игре только 152 игровых класса не считая библиотеки Box2D и утилитных классов которые я таскаю в отдельном пакете из проекта в проект. Даже и не знаю чтобы со мной стало если бы я пытался ограничивать себя количеством классов в 5-10 шт. O_O


Индикаторы: Разное
Постоянная ссылка

 

 

Круто! Ну, ждём среды поиграться.

InspiritGames
10 Июля 2011
— 00:59
#

Уже собирался спать ложиться и тут на тебе - статья) Очень интересно почитать про масштабный проект. Я и сам вчера написал клас резки и скролла игрового мира, тоже квадратики.

Отдельный респект за группы врагов, во мноих играх этого не хватает.

Согласен с выводом, вчера же и пришёл к нему)) Flash - тормознутая вещь. Согласись, как бы было просто жить даже без половины твоих оптимизаций. Надеюсь с Molehill что-то изменится.

Komizart
10 Июля 2011
— 01:57
#

Ай молодца! :). Завидую твоей Силе Воли. (пора менять надпись на "Я не зомби!").

WeslomPo
10 Июля 2011
— 02:34
#

о май гот! ,)

oleh
10 Июля 2011
— 02:57
#

Черт, сколько труда... Надеюсь все окупится, как в финансовом так и в фановом плане.

Роланд
10 Июля 2011
— 03:42
#

Круто! А где будет релиз?

frPix
10 Июля 2011
— 11:36
#

Помимо клевой игры ты капитально вложился в ресёрч. Это всегда дорого и не всегда понятно к чему приведет. Зато теперь на такой базе можно и вполне быстро проекты делать. Главное использовать силу воли и не уходить в рекурсию разработчика :)

Ждем среды! )

Dragosha
10 Июля 2011
— 11:47
#

Отличный постмортем. Сиквел планируешь? Удачного запуска!

Eugene
10 Июля 2011
— 11:55
#

Захватывающая статья! Очень интересно читать о процессе разработки такого большого игроваого продукта! Желаю чтобы все твои усилия многократно окупились в финансовом плане!

P.S.: тоже интересно, на каком сайте будет релиз. Если не секрет, конечно...

jarofed
10 Июля 2011
— 13:00
#

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

Alex
10 Июля 2011
— 13:09
#

@frPix, jarofed, к релизу будет отдельная запись в которой будут все ссылки, пароли и явки ;)

Ant.Karlov
10 Июля 2011
— 15:36
#

@Eugene, на возможный сиквел безусловно есть расчет, но скорее всего не сразу — если игра будет достойна продолжения, то разработку продолжения начну не раньше чем осенью-зимой этого года. А пока следующая моя игра которую я задумал не будет стрелялкой :) Устал я от шутера немножко, надо разнообразить свой рабочий процесс разработкой новых игровых механик ;)

Ant.Karlov
10 Июля 2011
— 15:39
#

Очень интересно! Удачного релиза.

ps_coder
10 Июля 2011
— 17:05
#

ты уже и сайтец зарезервировал под него.

Zarkua
10 Июля 2011
— 18:04
#

А сколько весит игра?

Pavel
10 Июля 2011
— 18:25
#

Поздравляю с релизом, понимаю как же всетаки это сложно "Делать игры" :) Желаю чтобы все ожидания оправдались :)

Marik
10 Июля 2011
— 19:36
#

Ждём! :)

WizVio
10 Июля 2011
— 19:37
#

если боты валят друг друга, то круто: кто играл в КР 1-2, поймет. А там, по-моему, логика через простые автоматы реализована.

Слава
10 Июля 2011
— 23:46
#

Спасибо за пост Anton. Поздравляю с завершением!
Конечно же пара вопросов =) :
1) Как проверяется столкновение игрока с тригерами? Стандартными методами flash(hitTestObject)?
2) "свой связный список" - это какой?
3) Враги дерутся между собой это очень хорошо!А они расставлены по миру так, чтобы при входе игрока они не начали драться пока игрок не пришел или произвольно получаются события при действиях игрока( игрок привел зомби к роботу и др.)?

Вот это разойдется на цитаты(!): "Делать маленькие и очень успешные игры — это искусство"

С Уважением Антон.

Vacsa
11 Июля 2011
— 10:42
#

@Ant.Karlov Здравствуйте. Спасибо за ссылки на ИИ персонажей, было очень познавательно узнать о других видах, о которых я даже не подозревал :) Но мне хотелось бы задать вопрос именно по ИИ, просто чтобы удостовериться прав я или нет :)
Так вот, почему все пишут про State машину и говорят про if {} else {} ? Я только начинаю делать игру, и при выборе ИИ не знал как по другому сделать и сделал её на событиях, т.е. на Listeners и Handlers, а для очередей использовал обычный стек, в котором переключение тоже сделано на событиях. Так вот у меня вопрос: чем плоха реализация ИИ на событиях в отличие от того же Schedule-based AI? Тем более что насколько мне известно в Java и соответственно в ActionScript это можно сказать одно из приоритетных способов вообще интерактива. Или до этого никто не придумывал такого способа?

Заранее спасибо за ответы :)

pSi_X
11 Июля 2011
— 16:24
#

Опобликуй на Хабре, когда выйдет, - народу интересно будет.

Ситуация с врагами друг друга у ИИ очерняется тем что после некоторого времени игры встает вопрос какого хрена они не перегрызли глоткки до меня... И тут же вырисовывается ситуация когда видно что они специально делаются для игрока как только он встречается с ними.... То есть палка о двух концах как говорят. И вся "жизнь" пропадает. Подобное можно заметить в gta4 где в конце концов такие сценки с жизнью вокруг не особо часто встречаешь.

starready
11 Июля 2011
— 18:20
#

@pSi_X:
у вас события работают по схеме:
addEventListener(foo);

function foo()
{
removeEventListener(foo); //опаньки, вот это место!
}

?

если да, то посмотрите как часто скачет график использования памяти, каждый спуск на графике - это очень нагрузный для процессора цикл очистки garbage collector`ом. Вот вам и причина, почему обычные события лучше таким образом не использовать.

BuxomBerry
11 Июля 2011
— 18:26
#

@BuxomBerry а зачем удалять события?))например создали спрайт в классе, повесили на него слушателей и при определённой ситуации на этот спрайт отправляете события из мира и пока живет этот класс не нужно удалять события, потому что изначально они не enterframe, чтобы грузить процессор, и могут произойти в любое время, что дает волю фантазии :) И в графике памяти и фпс особых перебоев нет...

pSi_X
11 Июля 2011
— 20:19
#

Очень жду релиза.

@BuxomBerry Ваших статей я тоже жду.

Yukisj
11 Июля 2011
— 20:54
#

@Vacsa,

1. После того как я нахлебался проблем в MiningTruck 2 с использованием hitTest() — я вообще решил от него отказаться. Столкновения с триггерами проверяются как точка внутри квадрата. Если координаты точки больше верхней левой точки прямоугольника и меньше правой нижней точки прямоугольника — значит точка внутри квадрата.

2. Подробно о связных списках можно почитать здесь...

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

Ant.Karlov
11 Июля 2011
— 21:38
#

@pSi_X, события достаточно универсальная штука но при этом очень тормозная. И если честно я с трудом себе представляю как можно сделать сложный ИИ на событиях. Так как при активном использовании событий, особенно в таких вещах как ИИ, в итоге все получится очень запутанным и потом внести какие-либо изменения будет достаточно сложно. В случае Schedule-based AI все намного проще, есть условия (conditions), есть наборый действий (schedules), есть состояния (states) и в зависимости от условий и текущего состояния очень легко выбирать новые наборы действий, более того такой ИИ удобно обучать новым умениям и совершенно не страшно поломать уже существующий функционал.

Ant.Karlov
11 Июля 2011
— 21:48
#

@starready, я не хабро человег :) У меня нет там аккаунта да и стремления туда попасть тоже нету.

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

Ant.Karlov
11 Июля 2011
— 21:54
#

Антон, а вы уже начали новую игру?

Скромный разработчик игр
11 Июля 2011
— 22:16
#

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

то, что событие не является EnterFrame не значит что оно не заставляет обработчик событий перебирать всех слушателей, чтобы определить кому-какое событие отправить. отсюда кстати и "тормоза".

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

@Yukisj:
Постараюсь поскорее выложить что-нибудь новое ;) Самому уже надоел словно замерший на одном месте блог.

BuxomBerry
11 Июля 2011
— 23:18
#

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

Vacsa
11 Июля 2011
— 23:18
#

По ссылке есть код по связным спискам, а также тесты производительности: http://jacksondunstan.com/articles/1288

Flasher
11 Июля 2011
— 23:58
#

@Flasher, да, результаты жесть. Но скорее всего это еще зависит от кривости рук программиста. Я протащился от кода в методе elementAt() — может быть я плохой программист, но такое мне тупо лень бы было писать :D К тому же, наверное стоит сделать скидку на то, что автор теста писал полную альтернативу Array пытаясь сделать его таким же универсальным. Думаю, что если делать связный список узко-специализированным, то работать он будет намного быстрее.

Впрочем чего говорить, я попробую сделать тест своего связного списка, а там видно будет :) Box2D полностью построен на связных списках, так как в C++ это считается наиболее оптимальным видом списков. Может быть в AS3 другая ситуация... Посмотрим.

Ant.Karlov
12 Июля 2011
— 00:48
#

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

Flasher
12 Июля 2011
— 02:24
#

@Pavel, релизная версия игры весит 8мб, но если музыку и звуки сильнее ужать, то возможно можно было бы и в 7мб вписаться. Но как-то не хотелось жертвовать качеством звуков и музыки ради 1мб.

Ant.Karlov
12 Июля 2011
— 02:32
#

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

Ant.Karlov
12 Июля 2011
— 02:33
#

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

Спасибо.

Rigo
13 Июля 2011
— 18:13
#

ИМХО странно вообще было использовать hitTest в MiningTruck, если в Box2D есть Collision, Sensor, Query?

А связь между триггерами и последствиями тоже визуально задается? Интересно как? У меня в играх такие моменты не обходятся без xml-конфигов.

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

PS : AI в твоей игре, это действительно круто. Хорошо настроенный AI завораживает как бегущая вода, пламя, или работающий человек =)

Hyzhak
15 Июля 2011
— 01:22
#

Немного проектов, ИМХО, могут похвастаться такой проработкой. Это вобще лучшее из платформерных зомби-шутеров, что я видел, серьезно.

Азат
19 Июля 2011
— 02:09
#

Антон, статья просто великолепная, я ее прочитал после того как прошел всю игру. Увидел на сайте gamin.ru, что вышла игра про зомби и вдруг увидел знакомый стиль, побежал смотреть и пока не выиграл не успокоился, в игре очень понравилось: что зомби не любят роботов, взрывы и придавливание зомби.
В статье особенно понравилось про ИИ, мне как начинающему инди и казуальному разработчику, очень понравилась. Я в свои ИИ добавляю горы If(), и все давно думаю как же это безобразие превратить во что-то логичное и удобо читаемое.

Саша Маковский
19 Июля 2011
— 05:37
#

Вопрос, касающийся класса Контроллер. Если я правильно понял, то объекты, которые будут задействованы в цепочке событий имеют функцию action(), и/или callAction(). Тогда объекты у которых есть эти функции являются наследниками классов у которых этих функций нет, то есть расширяют базовый класс этим самым action(), который у всех свой? Например, сундук, который просто открывается и сундук который открывается, а вслед за ним появляется куча противников - это разные объекты одного класса или разных?

Quisit
20 Июля 2011
— 00:38
#

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

Quisit
20 Июля 2011
— 01:10
#

@Hyzhak, в MT2 hitTest() используется для определения пересечения точек с клипами чтобы на основе положения разных клипов создавать b2RevoluteJoint, то есть связывать разные объекты джоинтами. В самом же игровом процессе hitTest не используется, только при создании игрового уровня.

Связь между триггерами задается не совсем визуально. Для задания связей я использую свои компоненты. То есть конвертирую любой клип в компонент и задаю ему дополнительные свойства которые используются при парсе клипа с уровнем и создании игровых объектов. Свойства для клипа-компонента задаются через Component Inspector.

На этапе загрузки уровня считывается сразу весь клип уровня (физика и графика) и объекты сортируются согласно своим координатам в разные списки. Коордианты переводятся в тайлы, а списки привязаны к тайлам. Далее по мере продвижения героя в коде определяются ближайшие еще не созданные ячейки и при необходимости объекты находящиеся в них быстро создаются. То есть проще говоря вся информация из клипа уровня об игровых объектах записывается в массивы, а оригинальный клип удаляется.

Здорово, рад что AI понравился :)

Ant.Karlov
22 Июля 2011
— 00:45
#

@Саша Маковский, спасибо за отзыв! :)

Ant.Karlov
22 Июля 2011
— 00:45
#

@Quisit, для реализации контроллера обрабатывающего связь объектов, впрочем как и для всех остальных объектов я использую Интерфейсы. То есть имплементируя в какой-нибудь класс интерфейс IInteractiveObject, этот класс обязан иметь описание методов типа callAction() и т.п. :) Таким образом у родительских классов которые не являются интерактивными объектами или триггерами нет методов callAction() и они не могут быть добавлены в контроллер, что исключает путаницу и позволяет строго типизировать объекты :)

Обычный сундук из которого что-то выпадает и суднук который может призывать врагов — это один и тот же класс. Просто в первом случае у сундука поле callbackAction будет равно "null", а во втором случае, для вызова врагов будет ссылка на объект для которого будет выполнено действие callAction(). То есть, в моем случае chest.callbackAction == "backdoor1" — это двери на задней стенки из которых может выбегать подмога :)

Ant.Karlov
22 Июля 2011
— 00:52
#

@Quisit, думаю что на событиях такой контроллер можно написать, но в итоге он может получится запутанным и не удобным, к тому же события сами по себе не достаточно быстры. Я постараюсь написать пост в ближайшее время о подобных контроллерах и вы удивитесь как на самом деле все просто! И никакие события тут вовсе не нужны :)

Ant.Karlov
22 Июля 2011
— 00:54
#

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

Как вы разбиваете куклу персонажа, например при падении? Это же не рэгдол-анимация, слишком прожорливо.
Буду благодарен, если разъясните :)

пс. давно читаю вас, ждал игрушку - хороший вышел продукт.

пс.пс.: почему кстати решили делать на флэше? Могли бы с теми же усилиями (+- время на изучение новой среды) собрать игру для мобильных платформ. ИМХО пошло бы на ура.

Vorren
29 Июля 2011
— 17:31
#

@Vorren, да, все верно поняли. Графический спрайт персонажа привязан к физическому телу и его анимация включается в зависимости от текущего состояния персонажа. Для физических тел существ заблокирована возможность вращаться вокруг своей оси. Поиск пути в Зомботроне отсутствует — там есть только зрение и оно никак не привязано к тайлам :)

Трупы врагов — это самые настоящие рэгдоллы, никакой анимации смрети дополнительно не реализовано :) Безусловно прожорливо, зато какой фан! :) Для оптимизации есть ограничение на количество физических тел для рэгдоллов, таким образом если превышен максимум, то самые старые тела сразу удаляются и создаются новые, таким образом исключается риск слишком большого количества объектов на сцене :)

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

Ant.Karlov
31 Июля 2011
— 16:17
#

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

Monro
4 Августа 2011
— 19:41
#

@Monro, в Зомботроне физическое тело персонажа и врагов состоит из двух частей: прямоугольник и колесо. Во время ходьбы к телу колеса применяется угловая скорость:

wheelBody.SetAngularVelocity(20);

Ant.Karlov
5 Августа 2011
— 15:23
#

Вопрос насчет такой системы, когда физический мир только вокруг персонажа:
А если ящик был в одной клеточке(из нарезки мире), а потом сместился в другое место или вообще в другой слайд переехал, то это тоже отслеживается?

cot45
9 Августа 2011
— 14:42
#

@cot45, нет смещение физических объектов не отслеживается, поскольку удаление физического мира происходит исключительно по триггерам и удаляется только там куда игрок уже не может вернуться :) Можно было бы и отслеживать, но в моем случае это было уже не рационально делать. Может быть в следующей части игры сделаю продуманнее этот момент чтобы уровни можно было делать еще более насыщенными на физические объекты.

Ant.Karlov
12 Августа 2011
— 13:24
#

респект тебе за скурпулезную работу

Николай
26 Августа 2011
— 00:03
#

фы вфывфывфыв

фы вфы вфыв
4 Октября 2011
— 20:40
#

Спасибо, очень дельный постмортем. Много полезного подчерпнул

peregrimm
24 Октября 2011
— 22:09
#

Thanks for the postmertem!! What would have been a better environment to develop this game in??(From what i understand Flash/as3 would not be your choice)

rob macy
27 Ноября 2011
— 23:07
#

всем привет

НИКИТА
29 Мая 2012
— 14:53
#

артем
19 Сентября 2012
— 19:50
#

Хорошая игра. У меня есть один важный вопрос, как разработчика игр. Сказано, что персоонаж реализован как бокс с колесом, которму придается скорость для движения. Вопрос: почему он не скатывается с наклоной поверхности ? Я тоже сделел такую конструкция. Изменял angularDumping, но тело продолжало скатыватся. В чем секрет ?

Anton
1 Октября 2012
— 01:23
#

@Anton,

> Я тоже сделел такую конструкция. Изменял angularDumping, но тело продолжало скатыватся. В чем секрет ?

Когда игрок не удерживает клавишу влево или вправо, нужно занулять скорость вращения колеса. Еще, как вариант, можно устанавливать лимит вращения для джоинта колеса в 0. Ну и свойство скольжения должно быть минимально, чтобы трение было максимальным и колесо не соскальзовало как по льду.

Ant.Karlov
1 Октября 2012
— 19:37
#

как ты связываешь колесо и бокс ? revolutejoint? пытаюсь сделать также, но при движении конструкция валится на бок

mmm213
5 Мая 2013
— 17:54
#

@mmm213,

> как ты связываешь колесо и бокс ? revolutejoint?

Да. Чтобы конструкция не валилась, нужно настроить расположение колес и вес тел. Если хочется нестандартное какое-то расположение колес сделать, то можно воспользоваться твиком который в реальной жизни невозможен: сделать корпус легче чем колеса ;)

Ant.Karlov
6 Мая 2013
— 21:39
#

Игра супер!

Жека
7 Ноября 2013
— 11:22
#

я прошёл всю игру и 1,2,3 часть! жду игру 4 часть!

Жека
7 Ноября 2013
— 11:22
#

ты создатель?! ахринеть я твой поклонник!пиздец я с создателем базарю огооо

Жека
7 Ноября 2013
— 11:24
#

Жека слушай а что за 3 часть? это не зомботрон: машина времени? а то я её прошёл а полазил по инету и не могу найти подскажи

Wolverine
22 Марта 2014
— 06:56
#

@Wolverine, все части зомботронов можно найти на zombotron.com на главной странице.

Ant.Karlov
22 Марта 2014
— 22:46
#

Большая просьба-создать ещё одну часть зомботрона.

игрок
27 Августа 2015
— 19:54
#