Sokopango Game (продолжение 3) - разное в разработке + приз

Привет. Я, если можно так выразиться, инди-разработчик, выпустивший игру Sokopango.
Сегодня я расскажу о некоторых технических деталях при разработке игры, вернусь к iOS, а также упомяну про новый игровой режим - кампанию, пройдя 1-м которую, можно выиграть настоящие $100.

Sokopango Game (продолжение 3) - разное в разработке + приз

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

1. Немного про версию для iOS.

Здесь дополнения про разработку приложения для iOS, которых нет в предыдущей статье. Ниже мои заметки, которые я записывал для себя на будущее, некоторые из них могут быть не к месту, но были оставлены - вдруг кому пригодится в решении проблем.
1.1 Подтверждение платежных данных.
Если приложение будет приносить прибыль (оно платное, или есть встроенные покупки), необходимо указать платежные данные в своем профиле.
Идем:

  • App Store Connect
  • Соглашения, налоги и банковские операции
  • Заходим в "Платные приложения".
  • Заполняем необходимы данные. Это будет контактная информация, налоговая форма и указание валютного банковского счета. В налоговой форме в "Type of Beneficial Owner" я указал как "Individual/Sole proprietor", поэтому везде указывал свои личные данные (ФИО и номер телефона везде повторялись), также указал валютный счет, который я открыл в личном кабинете банка за минуту.
  • Отправляем на проверку, ждем подтверждения и заветного статуса "Активно".

Также об этом можно почитать в Workflow for configuring in-app purchases.
1.2 Опция "bUseStoreV2" в файле конфигурации и метод "RestoreInAppPurchase".
По умолчанию для проекта включена опция "bUseStoreV2=true", см. "[EngineFolder]/Config/IOS/IOSEngine.ini". С ней у меня не работали внутриигровые покупки. И такая проблема не у меня одного, исходя из постов на форуме и AnswerHub-е.
Решение: прописал строку "bUseStoreV2=false" в файле "[UnrealProject]/Config/IOS/IOSEngine.ini". Если файла нет, создаем - смотрим примеры проектов, либо в интернете ищем.
Конечно, при каждой покупке придется вводить логин/пароль от "Apple ID", но для меня это лучше, чем ничего, а времени разбираться не было.
Еще у меня не работало восстановление данных по покупкам (метод "RestoreInAppPurchase"), а главное - не отображались цены по покупкам (метод "ReadInAppPurchaseInformation), хотя на Android тот же код прекрасно возвращал цены. После некоторого времени я убрал использование "RestoreInAppPurchase" (у меня была реклама, отключение которой можно было купить, но потом рекламу убрал, и соответственно данную покупку тоже, и отпала необходимость восстанавливать данные по приобретенным покупкам), и, о чудо! - после этого стали загружаться цены в игре.
1.2 Сертификаты.
а) Сертификаты хранятся в Личном хранилище Текущего Пользователя.
Хранилище можно открыть через IE, либо через консоль оснастки, см. статью "Как просматривать сертификаты с помощью оснастки консоли MMC".
б) Разработчик одновременно может иметь только 2 сертификата 1 вида, и генерировать 2/3 в сутки, иначе будет ошибка "Maximum number of certificates generated". Чтобы можно было сгенерировать новые, нужно удалить неиспользуемые, либо подождать.
в) Ошибка "Ключ не может быть использован в указанном состоянии" при сборке проекта.
Удалил сертификаты из настроек проекта, попробовал заново их добавить, при добавлении потребовался ввод пароля - ввел. Опять сборка с ошибкой, не помню какая.
В общем удалил сертификаты (из хранилища сертификатов, см. выше) и профайлы ("C:\Users\%UserName%\AppData\Local\Apple Computer\MobileDevice\Provisioning Profiles"). Потом заново все их сгенерировал.
В настройках проекта заново их добавил - при добавлении сертификатов уже пароль не требовался, а требовалось указать файл "*KeyPair.key". Проект успешно собрался.
Возможно, следовало не генерировать заново все, а удалить сертификаты и профайлы, и заново их добавить.
1.4 Загрузка в App Store Connect.
После обновления UE до 4.24, необходимо обновить Xcode до 11 версии.
Для загрузки приложения в App Store Connect вместо Application Loader использует Transporter.
1.5 Видео для App Store.
В Google Play для отображения видео на странице приложения достаточно указать ссылку на видео с YouTube, в App Store Connect нужно это видео заливать, притом с определенным разрешением сторон для каждой группы устройств.
Здесь можно посмотреть спецификацию к видеопревью приложения.
У меня уже был готовое видео, но оно не подходило по размеру сторон.
Краткая спецификация по видео (тексты ошибок в App Store Connect при неудачной загрузке видео):

  • iPhone 6,5-дюймовый дисплей - "App preview dimensions should be: 1920x886, 886x1920.".
  • iPhone 5,5‑дюймовый дисплей - "App preview dimensions should be: 1920x1080, 1080x1920.".
  • iPad Pro (3-го поколения) 12,9-дюймовый дисплей - "App preview dimensions should be: 1600x1200, 1200x1600.".
  • iPad Pro (2-го поколения) 12,9‑дюймовый дисплей - "App preview dimensions should be: 1200x900, 900x1200, 1600x1200, 1200x1600.".

Чтобы изменить разрешение видео, я использовал следующий сервис (не ради рекламы, а для уменьшения времени поиска тем, кому это нужно, само собой можно использовать другие сервисы). Разрешение меняется путем добавления черных полос к картинке. Для меня это было приемлемо.
Еще у меня ролик был длиннее 30 секунд (это максимум для превью), поэтому его пришлось чуть обрезать.
Пользовался этим онлайн-редактором, который первый нашел в поисковике.
1.6 Разное.
Не помню зачем я себе это сохранил, статья про настройку удаленной сборки проекта.

2. Редактор уровней.

А теперь кое-что поинтереснее.
2.1 Введение.
Одна из особенностей игры - все элементы в уровне могут быть интерактивными/динамическими. По этой причине мне не подошел TileMap - нет плавного перехода спрайта из одной клетки в другую, и нет возможности использовать анимацию. Был идея реализовать свой TileMap с дробными адресами и флипбуками, но эту идею я отложил до лучших времен.
Без TileMap-а было бы не удобно создавать уровни в редакторе UE - замучился бы с точной расстановкой элементов уровня, поэтому решил создавать уровни в отдельном редакторе, а сами уровни хранить в файлах.
Так как лучше всего на тот момент из инструментов я знал 1С, редактор уровней делал на ней.
2.2 Небольшое отступление.
В следующем разделе будут картинки из редактора уровней, в которых некоторые моменты могут быть не понятны, поэтому расскажу немного про реализацию некоторых элементов уровня в игре.
У меня уровень поделён на условные ячейки - места нахождения объектов. Точнее, используется центр ячейки - адрес нахождения блока, либо адрес, от которого или к которому объект двигается. Нельзя остановиться или повернуть где-нибудь между ячейками (центрами ячеек).
Сделано это было по следующим причинам:

  • более удобное управление и предсказуемое поведение героя. Что это значит. Например, нам нужно повернуть, а сзади нас догоняет враг. Т.к. размеры всех объектов одинаковые (герой/враг/блок/проход), игроку пришлось бы ждать до последнего, чтобы нажать кнопку и повернуть. Нажали поздно - врезались в стену, съели; нажали рано - задели стену, замедлились, съели. При моей реализации достаточно заранее нажать кнопку поворота, и как только герой дойдет до следующего центра ячейки - повернет. Пока герой идет, можно несколько раз сменить направление - применено будет последнее на момент пересечения центра ячейки. Получается, что все двигаются как бы по невидимым дорожкам - понятно, предсказуемо.
  • иногда неправильно срабатывала коллизия между героем/блоком/врагом. У меня размер спрайта в уровне равен 16 пикселям, размер капсулы пришлось делать 15.9, но все равно были проблемы.
  • было бы сложнее реализовать AI врагов в плане движения. Насколько я помню, были проблемы с NavMesh-ем, вроде не хотел нормально работать с маленькими объектами, а возможно и со спрайтами тоже не хотел взаимодействовать (не помню уже).
  • прочие проблемы - какой блок пнуть, если находишься между ними, узкие проходы, пр.

Чтобы реализовать движение по ячейкам, сделал свой MovemenComponent.
AI врагов тоже свой - в коде реализован 1 декоратор, 2 сервиса, 1 задача, 1 AI контроллер; в блюпринте блэкборд и дерево, которые на выходе дают всего лишь направление (вверх, вниз, влево, вправо, стоп). Это направление поступает на вход к привязанному к врагу MovemenComponent-у, которые двигает врага в нужном направлении.
2.3 Презентация.
Ну и собственно, как это выглядит (создание 1-го уровня игры):
(Длина гифки 12 секунд, время ускорено в 5 раз, размер 1.11 МБ.)

На просторах интернета нашел сайт по генерации разного рода штук - карты, имена, прочее. Доработал редактор уровней для загрузки сгенерированного лабиринта одним из генераторов. Теперь создавать уровни стало гораздо легче - сгенерировал лабиринт, сохранил в файл, загрузил в редактор, допилил, готово:
(Длина гифки 19 секунд, время ускорено в 5 раз, размер 3.15 МБ.)

Хотя все уровни технически прямоугольные, визуально они могут иметь сложную форму:

Sokopango Game (продолжение 3) - разное в разработке + приз

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

Sokopango Game (продолжение 3) - разное в разработке + приз

Адрес ячейки и мировые координаты в игре. Пользовался этим в начале разработки, сейчас эти данные для меня не актуальны, т.к. все функции получения/преобразования адресов/координат работают исправно.
Так выглядит информация по блоку:

Sokopango Game (продолжение 3) - разное в разработке + приз

Здесь видна ссылка на ассет спрайта, которая используется при загрузке уровня - по этой ссылке идет загрузка ассета (в UE 4 используется метод "LoadObject<>()"). То же самое и для анимации.
А здесь данные по врагу:

Sokopango Game (продолжение 3) - разное в разработке + приз

Просто список анимаций. Как выглядят данные по анимации видно на предыдущей картинке.
Что касается непосредственно рисования уровня, то я сделал все максимально удобно - приблизил редактор, если можно так сказать, к обычным графическим редакторам:

Sokopango Game (продолжение 3) - разное в разработке + приз

Что есть на панели инструментов:

- карандаш - рисует выбранный спрайт (блок, враг, герой) там, куда ткнул мышкой, рисует пиксель/линию/прямоугольник:

- пипетка - тут все понятно - кликнули в ячейку со спрайтом, теперь этим спрайтом можно рисовать:

- отдельная кнопка для рисования запретных ячеек (ячейки, куда нельзя пойти, нельзя спавниться, пр.):

- ластик - работает так же как и карандаш, только стирает:

- возможность в выделенной области заменить один спрайт на другой. Удобно при загрузке карт с сайта по генерации лабиринтов:

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

2.4 Хранение уровней.
После того, как уровень нарисован в редакторе и сохранен в базу, он выгружается в json-файл в специальный каталог проекта UE.
Далее все эти файлы уровней пакуются в игру. Когда игра запускается, открывается сначала 1-я карта (файл *.umap проекта UE) — это главное меню. Затем открывается 2-я карта - сама игра. Таким образом у меня всего 1 карта для режима игры.
Загрузка очередного уровня — это всего лишь удаление всех существующих акторов блоков, врагов и героя (+ еще некоторые объекты); чтение json-файла, а затем создание необходимых объектов в игре. А после игрок может наслаждаться прохождением очередного уровня.
Блюпринты не поддерживают работу с json (в стандартном варианте, без плагинов из маркетплейса), поэтому чтобы работать с json, нужно это делать в коде, а для этого необходимо подключить модуль "Json" в "*.Build.cs" файле в PublicDependencyModuleNames.
Выгрузка файлов уровней из редактора возможна как поштучно, так и группой, а также для тестов можно выгружать уровни без врагов.
Что касаемо размера файлов уровней, сейчас общий размер файлов всех 63 уровней составляет 483 КБ (494830 байта) в несжатом виде. Можно еще ужать, используя короткие json-ключи, но я это не стал делать, ибо "и так сойдет".
Для сравнения: 1-й уровень весит 6057 байт, а тестовый на 4000 объектов (для тестирования производительности) уже 105350 байт, в то время как абсолютно пустая карта *.umap в UE весит почти 18 килобайт. Думаю, используя загрузку уровней из файлов, я хоть что-то да выиграл в размере готового файла игры.
2.5 Разное
Ребенку нравиться рисовать уровни, а потом в них сразу же играть.

3. Скрипты сборки проекта.

Я уже писал про скрипты сборки в 1-й статье. Если кратко - вместо использования меню "File -> Package Project" или Project Launcher я написал свои скрипты для сборки проекта с нужными параметрами.
В общем, делюсь скриптами - репо.
В bat-файлах я не силен, поэтому могут быть недочеты.
В репозитории находятся bat-файлы с уже готовыми настройками. После копирования и донастроек скриптов (путь к движку/проекту и прочее - всего 5 мест надо откорректировать в файлах) достаточно просто запустить батник для сборки проекта. Информация по текущему состоянию сборки будет видна в окне консоли. После завершения консоль закроется, будут сформированы лог файлы, и, если не будет ошибок, в указанном каталоге будет находится готовая игра.
Параметры сборки и сами скрипты можно менять. В скриптах есть комментарии + в файле "README.md" немного информации по параметрам, а также даны ссылки, где можно посмотреть параметры UAT (Unreal Automation Tool - софтина, которая и собирает проект).
Проект можно собирать под любые платформы (Android, IOS, Win64, Win32, Mac, Linux, ...) и любые конфигурации (Development, Shipping, Debug, DebugGame, Test), также можно собирать с/без флага "For Distribution".
Для сборки под Android дополнительно (не обязательно) можно указать формат упаковки текстур (Android Texture Formats).
Тестировалось и используется на Windows. Для сборки под iOS/macOS придется вручную менять сертификаты и профайлы в настройках перед сборкой нужной конфигурации (Development/Shipping), так как эти данные прописаны в конфиге, и UAT берет их оттуда - их нет в параметрах командой строки.
По умолчанию проект собирается со следующими настройками:

  • полная перекомпиляция;
  • сжатие;
  • без контента редактора UE;
  • пакуется (без множества файлов — это видно на примере сборки под Windows содержимого папки "Content");
  • без краш-репортера;
  • без файлов отладки (под Windows, про другие платформы не в курсе, может быть актуально наличие отладочных файлов символов "*.pdb", по крайнем мере я пользовался ими при отлове багов после перехода на UE 4.24, когда ошибки были при запуске игры не из редактора).

Плюсы использования скриптов сборки (учитывая мой скромный опыт):

  • их можно использовать в планировщике задач, например, если практикуются ночные сборки;
  • не надо запускать редактор UE;

Используется лицензия Creative Commons CC0, это значит, что никаких ограничений на использования скриптов нет.

4. Аналитика.

Долгое время в приложении не было аналитики, но решил добавить, чтобы отслеживать данные по кампании (см. ниже) + получить опыт с внедрение и работой этой штуки.
В документации перечислены провайдеры, с которыми умеет работать UE 4, но не один из них не подошел:

  • Apsalar из коробки не работает в Android + платный;
  • Flurry также не работает в Android, но уже бесплатный;
  • Adjust работает на обоих платформах (не собирал, не проверял, но видно из подключенных модулей в плагине), вроде платно (на сайте про бесплатность ни слова, цены скрыты, нужно делать запрос);

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

5. Новый игровой режим $100.

Игру я начал делать 31.07.18. Спустя 10 месяцев - 30.05.19 выпустил на Android, а еще через 2 месяца - 26.07.19 игра попала в App Store. Хотя игра и была выпущена, в ней оставались косяки и были реализованы не все фичи, которые хотел. Только после исправлений и доделок я планировал заняться продвижением. Неспешно все исправляя и доделывая (работа и семья накладывают ограничения на количество свободного времени), мне пришла идея о новом режиме игры, как о способе привлечь игроков.
Идея в следующем:
- есть обычный режим, где игрок проходит уровни, и если надо покупает жизни, чтобы играть дальше;
- и новый режим - все тоже самое, только нельзя делать покупки, а 1-й кто пройдет, получит приз 100 долларов США. Рабочее название "Кампания $100". Таким образом, проходя кампанию, у игрока число жизней ограничено - 5 запасных жизней на игру + 1 текущая + после прохождения каждого 10-го уровня дается 1 дополнительная жизнь. Проиграл - начинай сначала, чтобы получить приз.
На что сделана ставка? Уровни и там и там одинаковые. Предполагается, что игрок будет тренироваться в обычном режиме игры - пытаться пройти уровень, и при необходимости покупать жизни, чтобы получить навыки прохождения уровня. Потом эти навыки будут применены при прохождении этого же уровня кампании.
Чтобы привлечь как можно больше игроков, перевел игру (благо там текста мало) на 10 языков, не включая русский: английский, 2 китайских, японский, корейский, испанский, итальянский, немецкий, португальский, французский. Ну как перевел - использовал машинный перевод, ибо зажал деньги на перевод - ипотека и все такое.
Теперь про получение приза. После успешного прохождения кампании игроку нужно будет ввести свой e-mail, я его получу и отправлю инструкцию с дальнейшими действиями по получению приза. Этот этап у меня не сильно продуман. Пока остановился на том, что в переписке решим, как будут получены деньги игроком - возможно это будет простой перевод на карту либо счет.
Что из этого выйдет - пока не знаю. Доработку игры уже заморозил, т.к. сделал все, что запланировал для релиза. Начинаю этап продвижения. По сути, эта статья - первый шаг в продвижении. Только сейчас понимаю, что надо было раньше начинать работать с аудиторией - статьи, записи в соц. сетях, анонсы и прочее, чтобы про игру знало как можно больше человек. Но что есть то есть.
И вдогонку. В Google Play, чтобы соответствовать правилам, пришлось изменить возрастные ограничения, пройдя опрос, в котором пришлось указать "Да" в пункте "ИМИТАЦИЯ АЗАРТНЫХ ИГР, РЕАЛЬНЫЕ АЗАРТНЫЕ ИГРЫ, РОЗЫГРЫШИ ДЕНЕЖНЫХ ПРИЗОВ". Из-за этого игра перестала быть доступной в Южной Корее. Как-нибудь потом разберусь.
Так что прошу всех желающих причаститься и попробовать урвать приз в $100.
Очень буду рад конструктивной критике и найденным багам.

6. Прочее.

Убрал рекламу полностью, из монетизации остались только внутриигровые покупки.

7. Ссылки.

1010
18 комментариев

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

Тут несколько вариантов:
- спавнить его всегда в одном месте, как в оригинале, тогда надо учитывать, что то место может быть занято блоком (тоже несколько вариантов);
- либо в радиусе от героя;
- либо как я сделал - в случайном месте;
Не стал заморачиваться на этим в общем.
Но да, может быть очень неприятно, когда огородился от врагом, одного убил, а потом другой появляется в твоем "домике". 

Без рекламы хорошо, респект.

поиграл совсем не много, из того что сразу хотелось бы сказать:

1) По умолчанию крестовина у тебя справа, это дико не привычно. Иконки смены стороны которую занимает крестовина не информативны, нашёл наугад. Гуд за локализацию, идея сделать всё картинками хорошая.

2) Ящик после пинка меняет свойства, т е первым пинком можно убить, вторым удалить с карты, очень советую менять цвет после смены состояния.

3) После поражения приходится долго ждать пока человечек улетит, потом антисмайлик . Я бы сделал что бы уровень начинался практически мгновенно. Просто по середине всплывала бы картинка провала (антисмайлик и уходила в прозрачность).

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

1) Уже понял этот косяк, исправлю.
Идея с картинками была как раз в том, чтобы не локализовывать игру. Частичная локализация появилась только с приходом идеи нового режима - эту часть я и перевел.
2) Блок уничтожается, если его пинаешь, а он стоит впритык. Если за ним ничего нет, он будет двигаться. Т.е. его можно сколь угодно долго пинать, пока он не присланён ни к чему.
3) Возможно. Но пока оставлю как есть, поскольку на геймплее это не сказывается.
4) Это не плющаенье, а изменение прозрачности. Хотел сделать, как будто душа улетает. А эффект полета - это какое-то визуально изменение спрайта души. Исправлю.

Всего один приз на $100? Мало для пиар-акции.
Советую приглянуться к опыту Highly Likely которые не так давно тут пиарили розыгрыш гифт-карт на 190000 рублей. Правда, я не уверен что они сами действительно заплатили 190000 за эти карты, но тем не менее толпа игроков желающих принять участие в розыгрыше у них была. 

25 уровень вообще возможно пройти?) Какой-то он чересчур сложный.
Нашел несколько багов:
1) После смерти иногда номер уровня меняется на 1
2) У меня пару раз случались перелёты через уровень, появлялась надпись 8=>9 и потом сразу же 9=>10
3) Игра иногда внезапно вылетает