Опыт разработки 2D шутера/платформера на cocos2d + box2d

в 17:39, , рубрики: box2d, cocos2d, game development, iOS, ipad, iphone, objective-c, разработка под iOS, метки: , , , , , ,

Приветствую, уважаемые читатели.

Вступление

Хочу поделиться с сообществом опытом создания игры для iOS на базе cocos2d+box2d и задачами, с которыми мне пришлось столкнуться, а также методами их решенеия. Опыт программирования у меня значительный, в game development я новичок, опыт использования Objective-C около года. Поэтому вполне допускаю, что для встреченных мной «задач» давно уже существуют решения в виде готовых библиотек/классов/методик, а я просто плохо искал и не смог найти их в интернете. А может быть в открытом доступе их нет и каждый решает их как может, с другими не делится. Я больше склоняюсь ко второму варианту, потому решил поделиться — вдруг кому-то пригодится.

Вся статья разделена на Общую часть, которая может быть интересна всем, зашедшим под кат, и Техническую часть, которая будет интересна, скорее всего, лишь разработчикам, людям, которые делали, делают или собираются делать игру на cocos2d и/или box2d. В конце статьи конечно же есть Заключение.

Общая часть

Что за игра?

Технически игра представляет собой одностиковый 2D шутер/платформер. Все декорации и персонажи нарисованы от руки нашим доморощенным художником. Все персонажи исключительно анимированные (то есть ragdoll нигде не использовались). Не смотря на то, что технически игра выполена как 2D, за счёт использования эффекта параллакса и использования pixel-perfect декораций, нарисованных с эффектом проекции, создается ощущение объемной картинки.

Игрок управляет главным героем при помощи экранных сенсорных элементов управления. Слева расположен джойстик, справа три кнопки — прыгать, стрелять или поставить чекпоинт (вообщето respawn point — точка, куда герой восстанавливается после смерти). Джойстиком мы задаем направление вдижения — вправо либо влево. Когда нажата кнопка стрельбы, то джойстиком можно задать одно из десяти (!) направлений стрельбы — пять влева и пять вправо (правые являются зеркальным отражением левых, само собой). Стрелять мы можем только когда стоим на земле, когда герой стреляет, он останавливается и идти не может.

Кроме того, на экране присутствует индикатор здоровья (всего три жизни), индикатор наличия возможности поставить чекпоинт и индикатор количества собранных бонусов. Бонусы позволяют заработать возможность поставить чекпоинт, если такая возможность уже израсходована. Кроме того, иногда попадаются бонусы, позволяющие на время сменить оружие.

Сроки

Разработка игры заняла около 4-х месяцев от момента, когда я присоединился к молодой game-dev студии из г. Санкт-Петербург до момента, когда мы отправили игру на approve в App Store. Писал игру по сути с нуля, ориентируясь лишь на крайне кривой код, сделанный какими-то аутсорсерами-без-опыта. В этом кривом коде был реализован лишь один из шести уровней, и то не до конца, игра постоянно вылетала (как я позже выяснил — ошибка доступа к памяти).

Структура

Игра состоит всего из нескольких экранов. С главного меню можно попасть внастройки либо на карту. На карте выбираем уровень и понеслась. Самое интересное на уровнях.

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

Сюжет

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

Уникальные уровни

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

Техническая часть

Задача 1 — динамическая подгрузка декораций

Одной из значительных особенностей данной игры, делающей её не похожей ни на одну другую игру для iOS, являются размеры уровней — от 14-ти до 30-ти экранов. С учётом использования pixel-perfect графики в формате PNG в режиме RGBA8888 и количества слоёв на уровне, очевидно, что ни на одном существующем iOS устройстве не хватит памяти, выделяемой приложению, для того чтобы разом загрузить весь такой уровень. Соответственно очевидное решение — подгружать декорации динамически в зависимости от позиции камеры на уровне.

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

Пришлось пилить свою реализацию решения данного вопроса. Каждый слой декораций на уровне было решено разрезать на куски размером с экран устройства (в пикселях) и затем просто подгружать в память и отображать на уровне только те куски, которые попадают в данный момент в «активну зону». Активная зона — это прямоугольная область на уровне, как правило это область, отображаемая сейчас в камере плюс рамка вокруг камеры. От уровня к уровню в зависимости от специфики уровня эта область корректировалась для оптимизации процесса подргузки/выгрузки декораций. На некоторых уровнях активная зона и вовсе высчитывается динамически в зависимости от позиции главного героя на уровне.

Задача 2 — артефакты на стыках фрагментов декораций

Когда фоновая картинка одна (цельная) — все просто. Создаем спрайт, позиционируем его на слое и всё прекрасно, у нас есть фон/декорация уровня. Так как при решении Задачи 1 декорации уровня пришлось разрезать на куски, очевидно что теперь декорацию уровня на каждом из слоев нужно складывать как мазаику. Нужные фрагменты одной большой картинки нужно выложить на слое в нужном порядке «встык» один к другому, плиточкой (или «тайлами»).

В cocos2d существует известная проблема, заключающаяся в том, что, когда вы выкладываете два спрайта вплотную друг к другу, теоретически между ними не должно быть зазора, но на практике при движении камеры наблюдается артефакт в виде полосок между двумя такими спрайтами. Решение было найдено не то чтобы быстро, но найдено. Оказалось этот артефакт можно устранить, положив фрагменты в «атлас» (CCBatchNode, те самые, которые принято использовать для создания анимации), а каждый из фрагментов сделать не самостоятельной картинкой (НЕ self-rendered спрайтом), а фреймом из атласа (batch-rendered спрайтом).

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

Задача 3 — сценарии возникновения врагов

По задумке нашего геймдиза, злодеи НЕ должны сразу все быть на уровне, а должны появляться по достижении главного героя определённой позиции на уровне. Готовых решений я не обнаружил. Было решено расположить на уровне специальные сенсоры, при касании которых главным героем срабатывает определённое событие, которое по определённому сценарию создает злодеев (в нужном количестве, в нужном месте, возможно с задержкой, а не сразу после касания сенсора). В итоге может получиться, что главный герой прошел пустой участок, запрыгнул или спрыгнул куда-нибудь, и тут на него повалили: спереди, сзади, сверху…

Задача 4 — передвижение героя по произвольной формы земле

«Землей» считаетются все элементы окружения, на которых герой может стоять. Земля на уровне была задана геймдизайнером произвольной кривой линией на фоне декораций первого плана. Эта линия была оттрассирована мной (при помощи специальных инструментов, конечно же) в шейп типа полигон (poligon). Конечно, линию земли было бы гораздо логичнее задать в виде ChainShape, но инструмент, который мы использовали для подготовки шейпов физических тел позволял создавать только шейпы типа Circle и Poligon. Перспектива создавать вручную в коде или как то ещё огромные ChainShape из тысяч точек (учитывая что уровни просто огромны, а степень детализации линии земли должна быть достаточно высокой) меня не особо прельщала, а производительность игры при реализации земли через PoligonShape и через ChainShape не меняется (как показали предварительные тесты). Потом земля загружалась в игру в виде самостоятельного физичекого статического тела, которое располагается на уровне нужным образом (позиция тела земли синхронизирована с позицией декораций первого плана). В итоге создается иллюзия, что главный герой «идёт» по извилистой трапинке, огибает препятствия, запрыгиват на коробки и бочки и т.п.

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

Задача 6 — реализация юнитов

Юниты — это главный герой и все злодеи (враги).

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

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

Для изображения юнита на уровне используется спрайт с картинкой, на которой нарисован юнит. Как правило, статические картинки не используются для изображения юнитов. Вместо этого на спрайте проигрывается анимация, соответствующая текущему состоянию юнита. Все юниты могут быть направлены по оси Х вправо или влево. Для создания анимаций состояний юнита используется один набор кадров анимации юнита, ориентированных в одном направлении. При переключении направления юнита по оси Х меняется лишь параметры спрайта, изображающего юнит — парамерт scaleX умнажается на -1, что позволяет зеркально отразить спрайт и все отображающиеся на нём кадры анимации по оси Х.

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

В игре присутствуют разные категории юнитов-врагов. Летающие и наземные. Наземные делятся на статических (не меняют своей позиции на уровне, атакуют только когда главный герой подходит к ним на расстояние атаки), и подвижных. Подвижные враги включают в себя ходячих (или ползающих) и прыгающих. На каждом уровне свой уникальный набор врагов. Даже враги, относящиеся к одной и той же категории обладают разным поведением. У разных врагов разное количество жизней, сценарии поведения. В момент смерти враги «выкидывают» бонусы (некоторые каждый раз, некоторые с определённой вероятностью).

Задача 7 — стрельба главного героя

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

Рикошеты. На уровен расположено множество мелких сенсоров, которые обозначают точки, на которых может проигрываться анимация рикошета (как от стрельбы очередью из автомата). Когда главный герой стреляет, из числа сенсоров рикошетов, находящихся сейчас в активной зоне поражения, каждые 1/60 секунды (каждый цикл обработки игры, main loop), выбирается один и на нём проигрывается анимация рикошета. Пока на одном из сенсоров играется анимация рикошета, повторно на этом сенсоре такая анимация проиграна быть не может. Таким образом, в целом создается впечатление «живости» сцены.

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

Так как стрельба используется часто, а направления стрельбы меняются ОЧЕНЬ быстро, для лучшей производительности было решено поступить следующим образом. Для каждой из основных и промежуточных зон поражения стрельбы было создано отдельное тело, состоящее из одной фикстуры-сенсора. Форма такой фикстуры четко соответствует форме соответствующей зоны поражения стрельбы главного героя. По-умолчанию все эти тела выключены (IsActive() == NO), то есть пока данная зона поражения не активна, соответствующее ей тело выключено и не учавствует в обсчёте симуляции мира, соответственно не влияет на производительность. Все эти тела с сенсорами стрельбы расположены должным образом поверх тела главного героя, а их позиции синхронизированы с позицией тела главного героя. То есть сенсоры стрельбы расположены так, что чётко покрывают соответствующие зоны поражения стрельбы главного героя.

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

Заметка по оптимизации 1 — PTM_RATIO

Никогда и ни за что не трогайте параметр PTM_RATIO! Как выяснилось опытным путем, значение, отличающиеся от дефолтного (32.0) влияет на производительность в худшую сторону. Однажды я (без особой на то надобности, признаться) поставил значение 100, после чего с неделю искал причину резкого падения производительности уже при 2-3 врагах на уровне, что было ужасно.

Заметка по оптимизации 2 — «Телепортация в масло»

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

На деле оказалось все не так просто. Главный герой натыкался на припятствие, частично проходил сквозь препятствие, а затем препятствие «выталкивало» его обратно рывком. Если продолжать «идти на препятствие», то ситуация циклически повторялась, причем герой заходил с каждым разом всё «глубже» в препятствие, пока вовсе в одну из «попыток» не проходил препятствие на сквозь. Такое нас конечно не устраивало.

Причина кроется в способе реализации ходьбы через микро «телепартацию» героя (метод body->SetTransform()). Этот метод (в отличии от например методов ApplyLinearImpulse(...) или ApplyForce(...) и т.п.) по сути производим телепортацию, то есть немедленное и атомарное позиционирование тела в указанные координаты и с указанным углом поворота. Причем обработка возможных коллизий происходит уже после перемещения, даже если у перемещаемого тела активирован признак bullet (SetBullet(YES)). То есть по сути это что-то сродни телепортации.

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

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

Заметка по оптимизации 3 — движение наземных юнитов по земле

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

Так вот, чтобы юниты корректно двигались по неровной поверхности, нижняя часть тела юнита должна быть плоской (круглые или полукруглые поверхности работают как прямоугольное тело без трения, даже если трение задать не нулевое), но очень узкой. Выглядит это так — за основу для тела юнита можно взять прямоугльник. Затем прямугольному шейпу снизу добавляем точек и из нижней плоскости как бы вытягиваем «ножку». Причем края у этой ножки не должны быть вертикальными — должны быть наклонными, примерно под 45 градусов. Иначе юнит по прежнему будет застревать. Высота такой «ножки» зависит от высоты неровностей, которые юнит должен преодалевать и подбирается индивидуально.

Кроме того, для при реализации анимированных персонажей/юнитов нужно не забыть запретить им вращение (body->SetFixedRotation(YES)), иначе они будут падать, что нам не нужно. При реализации больших наземных юнитов, у которых точка опоры не одна, как, например, у двуногих человекоподобных юнитов, а две, как, например, у четвероногих юнитов, для изображения их ходьбы по наклонным поверхностям и мелким неровностям мы использовали следующую хитрость.

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

Инструменты подготовки ресурсов

Для подготовки ресурсов использовали TexturePacker для создания аталосов, PhysicsEditor — для создания шейпов физических тел. Это инструменты от одного и то того же разработчика. Довольно удобные и стабильно работающие инструменты, рекомендую. Не хватает им конечно определённых возможностей, но зато работают стабильно, не делая ничего лишнего.

Смотрели изначально связку инструментов создания атласов и уровней SpriteHelper + LevelHelper. LevelHelper не поддерживает динамической загрузки/выгрузки декораций уровня, а загружает весь уровень целиком, потому, соответственно, нам не подошел.

Кроме того, мне не очень нравится подход разработчика LevelHelper, который предлагает для использования уровней, сделанных в LevelHelper, использовать набор классов-обёрток, написанных им специально для загрузки и использования уровней. Эти классы весьма объемны и сложны, ориентированы на поддержку как кода с ARC, так и без него, как на поддержку iOS, так и на поддержку MacOS. Вобщем там много всего, залезть и поправить тот код я вряд ли смогу и захочу, а без этого я должен полностью полагаться корректность и производительность чужого универсального решения? Когда встает вопрос производительности — совершенно не понятно где искать узкие места, в моем коде или это проблема в коде LevelHelper.

По результатам попыток (!) его использования в начале проекта у меня сложились подозрения, что код LevelHelper'а не очень то производителен и не стабилен, но доказывать я это не пытался да и не хочу. Стоит отметить, что сейчас у него вышло мажорное обновление, не совместимое с проектами, сделанными в предыдущей версии приложения. Как в новой версии обстоят дела, пока не знаю.

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

Заключение

Игра вышла в AppStore сегодня, 2 ноября по всему миру. Ссылку на игру в AppStore пока не выкладываю, так как на «Я пиарюсь» хаб у меня кармы не достаточно (насколько я знаю), для меня самое важно было поделиться опытом. Кому интересно получить ссылочку или узнать название — пишите в личку (надеюсь правила Хабра это не запрещают?).

Как обычно, вопросы и комментарии приветствуются.

Автор: MaxKhatskevich

Источник

  1. Irix:

    Спасибо за статью! Как раз пытаюсь разобраться с box2d. Получается, что определить точку касания для юнита(верх/низ/право/лево) можно только используюя сенсоры? Может это как-то можно вычислить через контакты? Не хотелось бы добавлять кучу сенсоров. Буду рада услышать Ваше мнение по этому вопросу.
    ЗЫ ну и ссылочку на игру приложете, интересно будет взглянуть!

  2. Станислав:

    Такая же проблема с налазанием динамических объектов на статические , только способ автора по решению этой проблемы не помог (( Разочарован кокосом из-за этого

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js