box-, cocos- и пицца- 2d

  • Tutorial
В этой статье я хочу поделиться с вами историей создания первой iOS игры в нашей компании и рассказать про опыт использования 2d графического движка — cocos2d. В рассказе мы пройдемся по некоторым техническим проблемам, с которыми нам пришлось столкнуться во время разработки игры, и расскажем про эволюцию геймплея от начала и до конца.

image

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

Изначально идея, которую мы выбрали, была такой: перед игроком лежал сочный торт, который нужно было быстро разрезать на мелкие кусочки за небольшой промежуток времени. Для придания игре динамичности отрезанные куски должны были анимироваться с помощью какого-либо физического движка. После недолгого поиска мы решили, что наиболее продуктивно будет делать игру на движке cocos2d (так как я и Арсений iOS разработчики) и box2d (так как он бесплатный и отлично работает с cocos2d), и мы ограничили себя только одной платформой iOS.

Ядро для проекта было найдено в отличном туториале от Allen Tan, тем самым нам не пришлось погружаться в реализацию алгоритмов разрезания и триангуляции. Туториал основывался на библиотеке PRKit, которая умеет отрисовывать выпуклые текстурированные полигоны. Также в ней находился удобный класс PRFilledPolygon, сабкласс которого связывал физическое представление полигона с его отображением. Мы решили, что будем использовать этот сабкласс в качестве основы для наших будущих кусочков торта.

Воодушевленные тем, что наиболее трудоемкая часть была написана за нас, мы вздохнули с облегчением, но ненадолго — первые сложности появились достаточно быстро. После запуска первого прототипа мы вспомнили об известном ограничении box2d — не более 8 вершин на фигуру. Основываясь на туториале и вышеупомянутой библиотеке, нам необходимо было сделать так, чтобы фигура разрезаемого торта представляла собой полигон, так как box2d не умеет работать с сегментами круга (которые получались бы при разрезания круглого торта). Итак, с учетом ограничения box2d и того, что наш торт должен был быть приближен к его реальной форме, мы решили, что он будет состоять из массива восьмиугольников, которые в итоге образуют приближенную к округлой фигуру. Это решение привело к проблемам с текстурированием, так как в туториале речь шла о телах, которые состоят ровно из одной фигуры. Проблема была решена передачей конструктору PRFilledPolygon массива вершин, образующих только внешнюю границу фигуры. Результат:

image

Изначальный алгоритм разрезания также нужно было модифицировать под тела, состоящие из множества фигур. После недолгого рассуждения было решено просто увеличить максимальное количество вершин у фигуры с 8 до 24 (делается путем редактирования соответствующего параметра в box2d settings) и вернуться к телам, состоящим ровно из одной фигуры (для некоторых проектов это решение было бы неприемлемо, но для наших целей оно вполне подходило). Профайлинг показал, что никаких серьезных различий в скорости работы при восьми вершинах и 24-х нет. Тем не менее, после увеличения количества кусочков на экране до двухсот и более, FPS резко падал до 10, из-за чего в игру было практически невозможно играть. Около 20% процессорного времени уходило на просчитывание столкновений, остальное — на отрисовку кусочков и их анимацию.

Решение не заставило себя ждать. Как только кусочек становился достаточно маленьким, мы просто отключали для него просчет столкновений. Но все же скорость игры оставляла желать лучшего, что подтолкнуло нас к решению немного изменить геймплей: маленькие кусочки нужно удалять с экрана и добавлять в прогресс-бар игрока. Площадь уничтоженной поверхности определяла качество прохождения уровня. К кусочкам применялись linear/angular damping, что не позволяло им хаотически разлетаться по экрану.

image

К этому времени Валентин создал модель торта:

image

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

image

Однако с нашим упрощенным геймплеем пицца тоже выглядела не очень гармонично, отчего в качестве графической основы решено было использовать абстрактные геометрические примитивы. Так как требуемый для этого редизайн был довольно прост, а идея отлично «ложилась» на технологию, использующуюся в PRFilledPolygon, мы очень быстро реализовали задуманное. Каждому кусочку была добавлена обводка, которую мы реализовали с помощью CCDrawNode, передав ему в качестве параметров массив вершин полигона и цвет обводки. Это немного замедлило игру, но FPS все еще был достаточно высок. В то же время полученные очертания фигуры выглядели намного лучше, чем отрисованные с помощью обычных методов ccDraw.

image

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

image

Итак, враги и основной геймплей были готовы. После этого мы решили реализовать систему миров, каждый из которых подразделялся на несколько уровней. Все уровни хранились в отдельных .plist файлах, которые содержали в себе описание изначальной фигуры, позиции врагов, длительность уровня, и другие параметры. Дерево игровых объектов строилось с помощью стандартного для Objective-C KVC. Например:

//......
- (void)setValue:(id)value forKey:(NSString *)key{
   if([key isEqualToString:@"position"] && [value isKindOfClass:[NSString class]]){
       CGPoint pos = CGPointFromString(value);
       self.position = pos;
   }
   else if([key isEqualToString:@"laserConditions"]){
       
       NSMutableArray *conditions = [NSMutableArray array];
       for(NSDictionary *conditionDescription in value){
           LaserObstacleCondition *condition = [[[LaserObstacleCondition alloc] init] autorelease];
           [condition  setValuesForKeysWithDictionary:conditionDescription];
           [conditions addObject:condition];
       }
       [super setValue:conditions forKey:key];
       
   }
   else{
       [super setValue:value forKey:key];
   }
}
//......

//Afterawrds the values got set with the dictionary read from the plist file:
[self setValuesForKeysWithDictionary: dictionary];



Чтобы показать меню выбора миров и уровней, мы использовали CCMenu с некоторыми расширениями: CCMenu+Layout (позволяет расположить элементы меню на сетке) и CCMenuAdvanced (добавляет прокрутку). Валентин занялся проектировкой уровней, а мы с Арсением приступили к реализации эффектов.

Для визуальных эффектов мы использовали CCBlade — библиотеку, которая анимирует касания пользователя. Кроме того, каждое разрезание фигуры озвучивалось эффектами, наподобие звука лазерного меча из Star Wars. Другой эффект, который мы добавили, — исчезание маленьких кусочков. Разрезание без какого либо эффекта выглядело довольно-таки скучно, поэтому мы решили удалять кусочки с плавным изменением прозрачности и добавлять “+” над исчезающей фигурой.

Часть с изменением прозрачности была реализована с помощью добавления протокола CCLayerRGBA к PRFilledPolygon. Для этого мы поменяли стандартную шейдер-программу, используемую в PRFilledPolygon на kCCShader_PositionTexture_uColor:

-(id) initWithPoints:(NSArray *)polygonPoints andTexture:(CCTexture2D *)fillTexture usingTriangulator: (id<PRTriangulator>) polygonTriangulator{
if( (self=[super init])) {
       //Changing the default shader program to kCCShader_PositionTexture_uColor
       self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture_uColor];
}	
	return self;
}


и передали ей color uniform

//first we configure the color in the color setter:
colors[4] = {_displayedColor.r/255.,
                        _displayedColor.b/255.,
                        _displayedColor.g/255.,
                        _displayedOpacity/255.};

//then we pass this color as a uniform to the shader program, where colorLocation = glGetUniformLocation( _shaderProgram.program, "u_color")
-(void) draw {
   //...
	[_shaderProgram setUniformLocation:colorLocation with4fv:colors count:1];
   //...
}


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

image

Для звуковых эффектов мы написали небольшую обертку над Simple audio engine. Однако и тут не обошлось без проблем: мы столкнулись с неподдерживаемым форматов .wav файлов, но, к счастью, она решилась простой конвертацией файлов в поддерживаемый формат 8 или 16 bit PCM. В дургом случае эффект либо вообще не проигрывался, либо слышалось потрескивание из динамика.

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

image

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

image

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

Комментарии 24

  • НЛО прилетело и опубликовало эту надпись здесь
      +3
      Лучше она или нет, решать, скорее, игрокам.
      За неделю сделать полноценную качественную игру с нуля, не имея даже идеи, — сложно, ощутили на своей шкуре) И мы тут, как мне кажется, не справились на 100%. Но опыта и эмоций — хоть отбавляй. Если получим хороший фидбек, то обязательно сделаем более качественный ремейк, который уже, возможно, сможет полноценно соперничать с тем же Friut Ninja.
      • НЛО прилетело и опубликовало эту надпись здесь
          +1
          Я думаю, что игра, в первую очередь, должна быть веселой. Уникальным, как раз, должно быть, скорее, приложение — оно выполняет функцию.
          Функция игры, чаще всего — развлечение.
          Анализ мы, конечно, не проводили. Уникальность — не была самоцелью. Целью было получить удовольствие от процесса и выиграть хакатон)
          Но я считаю, что геймплей у нас более-менее уникален, хотя, конечно, и сильно похож на iSlash и FruitNinja.
            0
            О еще один сноб нарисовался. Игры надо делать больше, а не анализировать существующие. Тебе надо в лоловар клепать очередной говно-клон, там они любят поанализировать существующие игры, жаль, что кроме этого, больше ничего не умеют.
            • НЛО прилетело и опубликовало эту надпись здесь
                –2
                Тебе рассказать сколько лет онолитики хоронили рынок PC гейминга? Почему онолитики? Потому что в геймдеве нормальные аналитики отсутствуют, слишком маленькие ставки.
          0
          Всегда вымораживала подобная постановка вопроса.

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

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

            ИМХО. Я бы назвал это главной идеей всех таких игр. Да разорвет меня хабр-сообщество на части за мой банальный комментарий…
              +1
              Монетизация — неотъемлемая часть бизнеса. И это тоже надо учится делать.
              Победитель хакатона будет оцениваться в нескольких показателях, и один из них — количество кеша, которое удалось собрать приложению.
              Полагать, что на этой игре мы хотим заработать без паблишера и с маркетингом в $300 (еще одно условие соревнования) — смешно для любого, кто хоть что-то пытался сделать на этом рынке.
              Ну и чтобы дополнить картинку, скажу, что эта игра, по хорошему, должна быть платной. Такая монетизация на этом геймплее сработала был лучше. Но делать платным недопродукт рука не поднялась)
                +1
                На аппстор сейчас сложно продаватся, без паблишера да. 300 у.е. заработать будет сложно за месяць без адекватной раскрутки и игры для этого. Конкретно у вас жанр плохо подходит для такой монетизации, хотя игра хорошая.
                0
                ну и чтобы не быть голословным — статистика на вчера:
                установок — 1900
                покупок — $11.85
                потрачено на рекламу (в основном вк) — около 4000 руб
                0
                Приятный дизайн. Удручает большое количество американизмов и опечаток. Хакатон закончился, куда торопишься? Левл, пойнты, мицгола на Вас нету)))
                  0
                  Спасибо, за отзыв и за замечание — немного подредактировали.
                  0
                  Рекомендации по приложению или «то, что вызвало у меня дискомфорт»
                  — добавьте небольшую справку в начале первого уровня, так как при первом запуске не ясна цель игры. Читал я статью бегло, а обычный пользователь ее вообще не увидит. Я сразу подумал, что нужно как-то «правильно» порезать этот треугольник, а там оказывается просто игра на количество
                  -добавьте кнопку переиграть уровень (или она там есть, но ее не видно?). Иногда не хватает звездочек на новый уровень и приходиться возвращаться в главное меню…
                  — я бы все таки в меню покупок звездочек заменил знак «X» на "+", так как при их начальном количесве 0 у пользователя 0x10 все равно бы дало 0:) И судя по всему у Вас там именно добавление, а не примножение звездочек. Аналогично и в меню шаринга, а то возникает чувство, что какая-то операция в соц сетях стоит 5 звездочек.
                    0
                    Спасибо!
                    Есть еще довольно серьезный косяк — разлочить за звезду можно любой уровень и любой мир. По хорошему, конечно, нужно делать возможными для разблокирования только ближайшие к текущему уровни/миры.
                    0
                    Отлично! Особенное спасибо за идею выдачи пользователю звездочек в обмен на публикацию в соц. сети.
                    Где бы можно посмотреть видео? У меня нет iPhone.
                      0
                      Пожалуйста, но не обольщайтесь. На нашем примере могу сказать, что на шеринг в конце уровня нажимают в 56 (!) раз чаще.
                      Около 100 шеров против 2х из магазина.
                      Похожая ситуация была и в другом проекте.

                      Небольшое корявенькое видео геймплея — https://www.youtube.com/watch?v=Y1rgrEqJaNw
                      0
                      Хорошая идея. Мне кажется, было бы интересно сделать чтобы точки (или другие препятствия) перемещались, как в классике жанра: www.youtube.com/watch?v=NEq-mxFDuHM
                        0
                        Если часто резать на мелкие ломтики, то начинает тормозить и перестает реагировать на тач, плюс выкидывает иногда с деформацией геометрии. У меня iPad 1

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

                        Самое читаемое