Главная » Хабрахабр » Моя история создания мотивационного приложения (iOS и Android) для дочери с дочерью на Unity и C#

Моя история создания мотивационного приложения (iOS и Android) для дочери с дочерью на Unity и C#

История создания приложения, позволяющего детям зарабатывать деньги своим умом

В итоге, получилось приложение, позволяющее ребёнку зарабатывать деньги своим умом. Это первая часть истории (вперемешку с рассказом о моих ошибках и их решениях) о том, как я (где-то два года в свободное время) разрабатывал мобильное приложение под iOS и Android, которое бы мотивировало мою дочь решать примеры по математике, чтобы она достигла автоматизма в основах арифметики (склад числа 10 или табличка умножения).

Использовал я движок Unity и язык C#, а также дополнительный набор софта вроде обязательного Photohsop или Audacity (для создания звуков).

План рассказа (часть первая)

  • Предыстория
  • О монетизации
  • Почему Unity
  • О Scriptable Objects
  • О плагине Anima2D
  • О Lean Localization
  • Об iTween
  • О Unity Analitics
  • О Visual Studio
  • Ссылки

Предыстория и колорадские жуки

Хотя, с программированием знаком не понаслышке (колупал Basic в школе и баловался с C/C++ в универе). Мой предыдущий опыт — это несколько лет в 3D и, с недавних пор, разработка нескольких инди-игр на пару с программистом, где я выступал в основном только в роли дизайнера и художника (очень редко пописывая какие-то элементарные скрипты на C#).

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

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

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

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

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

Монетизация и удовольствие

Я даже не думал делать приложение доступным на всех iOS устройствах или публиковать его для всех и, тем более, не думал выпускать его для платформы Android (наслушался страшных историй от iOS разработчиков плюс, мне не на чём тестировать Андроид версию). Изначально я делал это приложение (рабочее название было Math4Ami) исключительно под iPod touch 5 дочери.

Какое-то время назад я загорелся желанием опубликовать его в AppStore (меня очень манила мысль, что у меня будет своё приложение в Apple магазине и все это смогут увидеть).

Ведь весь функционал был готов, оставалось дело за малым — сделать его рабочим для всех соотношений сторон экрана и понятным не только для меня, но и для других родителей. Подумал, что за месяц управлюсь. И вот, спустя полгода, я опубликовал его в AppStore и Google Play.

Причин для этого решения несколько. Я изначально решил сделать Math4Ami полностью бесплатным без намёка на монетизацию.

Как вы уже поняли, я изначально делал его бесплатным для дочери и не хотел ничего прикручивать в «конце» разработки. Первая.

Я решил, что это будет разработка для собственного удовольствия. Вторая. Моя логика была следующей: если я плачу деньги за посещение аквапарка, покупку книги или мороженного, чтобы получить удовольствие, то почему я не могу себе позволить платить деньги за хостинг, доменное имя или членство в Apple Developer Program, если это тоже приносит мне удовольствие. У меня уже есть подобный опыт — я делаю блог для удовольствия (который изначально только кушал средства: деньги на хостинг и на доменное имя, время на написание статей и их продвижение).

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

К тому же, для участия в программах «Made for kids» у Apple и «Designed for Families» у Google, нужно строго фильтровать показываемую детям рекламу. Внутриигровую рекламу я на дух не переношу — не люблю когда дизайн приложения уродуют рекламные сообщения (разве что кроме рекламы в виде просмотров видео по желанию, а не когда видео выскакивает из-за угла).

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

Почему Unity и как

Также у меня был хороший друг программист на C# и я надеялся, что он мне поможет с программированием, если что. Выбрал я Unity поскольку работал в нём раньше и мне понравилось. А ещё у Unity шикарное сообщество и очень легко найти ответы в Гугле почти на все вопросы реализации чего-то там на C# + Юнити.

С Unreal я тоже работал (как 3D художник), но так и не разобрался ни с C++, ни с 2D функционалом.

Все данные хранились на моём eVPS (Elastic Virtual Private Server) и я использовал FTP, чтобы передавать TXT-файлы с данными и настройками приложения (до использования базы данных руки так и не дошли, хотя первые шаги в написании своего сервера на node.js я предпринял). Изначально Math4Ami было «облачным», хотя это и громко сказано. Для работы с ftp я прикрутил к Unity лёгкий в использовании Simple C# FTP Class.

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

Часть 1: iOS, но тогда её ещё не было). С одной стороны, это было бы слишком заморочено: делать аутентификацию (этого пользователи ох как не любят) или сохранять в iCloud идентификатор сессии с помощью NSUbiquitousKeyValueStore (это позволило бы автоматически идентифицировать пользователя между удалением апликухи и повторной установкой), но я так и не разобрался с этим (возможно, мне бы помогла статья Пишем плагин для Unity правильно.

С другой стороны, данные в этом приложении не такие уж и важные, чтобы их нужно было хранить на сервере.

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

В итоге я сделал, чтобы всё хранилось локально (на устройстве), но уже не в txt, а JSON формате.

ScriptableObject и правильные ответы

Я использовал родные методы UnityEngine для сериализации объектов в json — JsonUtility (а потом сохранял текстовые файлы json локально на устройстве в папку Application.persistentDataPath). JSON формат в связке с ScriptableObject оказался шикарной находкой.

Даже не представляю, как я раньше жил без SO. ScriptableObject (SO) — это отдельная тема разговора, но я всё же её затрону.

Всё что я использую в работе, я почерпнул из этих двух мегаполезных видео о принципах работы с SO (и сопутствующего кода на GitHub и Bitbucket):

Лично я использовал SO для таких целей:

  • Для хранения данных (чтобы не нужно было каждый раз лезть в код для добавления нового функционала или данных):
    • разновидность примеров,
    • тип валюты,
    • стиль кнопок (у меня во многих местах одинаковые кнопки и я просто создаю skin-ы для них на основе SO),
    • величина награды и т.д.

  • В качестве глобальных переменных (которые видны во всех сценах):
    • количество верных ответов,
    • количество заработанных денег,
    • активные в данный момент настройки,
    • текущий тип примера,
    • таймер, рекорды и т.п.

  • Для хранения логики (к примеру, подписка на событие получения правильного ответа).

Поэтому логика работы у меня такова: Единственный минус SO для работы с данными — в нём нельзя хранить данные между сессиями приложения: ассет SO (после холодного запуска приложения) будет всегда содержать данные, которые вы записали туда в редакторе.

  1. После старта приложения я считываю json-файлы с диска и подгружаю данные из них в ассеты SO (метод FromJsonOverwrite).
  2. Пока приложение работает и мне нужна максимальная производительность — я работаю только с ассетами Scriptable Object-ов. Эти asset-ы хранят данные всё время, пока приложение запущено или находится в фоне.
  3. Когда нужно сохранить данные (к примеру, при завершении приложения или по ходу работы), то я сериализую SO в json (метод ToJson) и сохраняю на диск.

Есть (очевидный) недостаток такого подхода — нельзя сохранить на диск только один изменившийся параметр (если их несколько в SO), всё время приходится сохранять текстовый json-файл полностью.

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

На видео ниже я показываю пример моей реализации учёта верных и неправильных ответов с помощью UnityEvent (событие — изменилось ли количество правильных ответов) + Listener (слушатели делают какую-то работу, если услышали, что получен правильный ответ, а логика подписки слушателей на событие также реализована на SO) + SO (ведёт учёт количества правильных ответов):

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

Anima2D, персонажи и дёрганая улыбка

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

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

Этот скрипт по сути просто переключает спрайты для рта (улыбка, открытая улыбка, испуганный рот) с помощью ползунка Frame: Смена выражений лица реализована с помощью скрипта Sprite Mesh Animation, входящего в состав мощного плагина Anima2D (который Unity недавно выкупила и сделала бесплатным).

sprite mesh animation переключение выражений лица спрайтами

Поэтому я создал новый анимационный слой OpenSmile (стрелка 1 на рис. Вся засада в том, что значение ползунка Frame нельзя изменять напрямую из скриптов, а только через систему анимации. ниже) в режиме смешивания Additive с весом Weight=1 и добавил туда анимацию ужаса (Coin_scared) и широкой улыбки (SmillingWide).

Я всё ещё в процессе приведения имён к единому стилю. Кстати, вы заметили, какой плохой пример я подаю, с именами анимации? Правильно было бы изменить Coin_scared на A_CoinScared (почему именно так читайте в разделе «О чём я жалею»).

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

По своей сути, анимации SmillingWide и Coin_scared — это просто анимация ползунка Frame в позицию 1 и 2, соответственно.

настройка перехода между анимациями выражения лица

выше), в инспекторе открываются свойства этого перехода (стрелка 3 на рис. Вся проблема была в том, что переход из любого состояния в состояние ужаса (при клике на переход (стрелка 2 на рис. выше), которое было не нулевым по умолчанию. выше)) происходил не моментально, а плавно на протяжении отрезка времени Transition Duration (стрелка 4 на рис. Поэтому, чтобы избавиться от моргающего глюка, нужно было всего лишь обнулить величину Transition Duration. Таким образом, значение ползунка Frame не могло изменяться правильно, ведь там были только целые числа, а значит между 0 и 1 нету промежуточного значения.

выше). Ну а условием перехода в состояние ужаса служит trigger isScared (стрелка 5 на рис. Я активирую этот триггер в коде с помощью следующего обращения к объекту, на котором висит компонент Animator (с контролером, слои которого я показал выше):

...GetComponent<Animator>().SetTrigger("isScared");

Как я переводил приложение на разные языки

Где-то здесь же, на Хабре, я читал, что о локализации нужно задумываться ещё в начале создания приложения и я последовал этому совету… сразу же… спустя полтора года разработки (как только решил, что Math4Ami будет публичным).

Почему я выбрал именно Lean Localization (кроме той причины, что плагин бесплатный) я уже не помню, но помню, что выбирал долго и усердно.

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

Плагин переводит всё (от текста до звуков и картинок).

Ошибка в том, что я поместил не все фразы в текстовый файл (слева на рис. Но я всё равно допустил одну ошибку с локализацией (хотя я сделал её намеренно, поскольку хотел попробовать разные подходы). Некоторые фразы остались внутри компоненты Lean Localization (справа на рис. ниже). Поэтому теперь, когда я отдаю этот файл переводчику на японский, мне придётся поработать вручную (чтобы перенести ВСЁ в текстовый файл). ниже).

Хотя, некоторые вещи нельзя перевести текстовым файлом (типа пробела " ", который я использовал в качестве разделителя между тысячами) — придётся всё равно использовать компоненту.

Сочный iTween

А ещё до этого мне в душу запало другое видео — The art of screenshake, которое на самом деле не только и не столько о дрожании экрана. Когда-то давно я посмотрел шикарнющее видео Juice it or lose it о том, как всякие маленькие микродвижения и нюансы анимации помогают из скучной игры сделать захватывающее дух действие.

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

Я его укоротил как только мог, но он всё равно занимает чуть больше 4 секунд (исчезает клавиатура, появляется надпись победа, идёт подсчёт копеек, выезд таблицы рекордов, получение шильдика «Новый рекодр», отображение кнопки «Еще»). Только одно место меня смущает очень сильно — финальный подсчёт заработанных денег (вы можете видеть этот момент в конце моей видеодемонстрации выше).

Даже не представляю, как без него вообще можно что-то делать в Unity. Лучшим «источником сока» для меня является бесплатное дополнение iTween. Я его использую везде, где нужна хоть какая-то анимация (будь то анимация кнопки или появление пункта меню или анимация подсчёта копеек).

Lerp или Mathf. Я пробовал реализовывать что-то подобное самостоятельно на основе корутинов и Mathf. Поэтому сейчас я не стараюсь изобретать велосипед, а просто наслаждаюсь iTween. MoveTowards, но это было не гибко и не универсально (а порой и работало по разному в редакторе и на устройстве).

Есть и подводные камни у этой системы анимации, с которыми я неправильно боролся поначалу:

  • Если во время работы iTween спрятать объект (через SetActive(false), к примеру), а потом показать его снова, то iTween продолжит исполняться с прерванного места.
  • Если во время работы одного iTween запустить другой (который влияет на те же значения), то по окончанию исполнения обоих, объект может не вернуться в исходное положение.
  • Нужно следить за тем, какой именно GameObject запускает iTween, а на каком эта анимация работает.

Чтобы остановить iTween анимацию, нельзя просто запустить iTween. К примеру (по последнему пункту), объект А запускает iTween, чтобы он работал на объекте Б. Нужно запустить iTween. Stop() на объекте А. Stop(объект Б).

Изинг — это параметр, который смягчает движение (чтобы оно не начиналось рывком и не заканчивалось тупо). Сильной стороной iTween является возможность использования разных типов изинга (type of easing).

Обалденной находкой для меня стали типы изинга:

  • spring
  • easeOutBounce
  • easeInBack
  • easeOutElastic

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

Статистика Apple и Google хорошо, но Unity Analytics лучше

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

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

Я могу в одном месте сравнивать данные с разных платформ (iOS и Android).

Типа Remote Settings (изменение контента приложения без заливки обновления) или A/B Testing или Tutorial Manager. А сколько там всего интересно, что хотелось бы попробовать, да всё руки не доходят.

Visual Studio наподобие Sublime

В прошлом, когда мне нужно было править какой-то код (будь-то python, html или node.js) я использовал Notepad++ (полностью бесплатный, но только под Windows) и Sublime Text (платный под все ОС, но можно полноценно пробовать бесплатно).

В Unity я сидел на MonoDevelop, но он меня так задолбал своими глюками (типа невозможность переключаться между раскладками или вставить что-то, скопированное за пределами Mono), что я решил — пора бросать тонущий корабль и перелез на Visual Studio Community 2017 (благо, она бесплатная для одиноких, как я, разработчиков).

Но я хотел, чтобы моё приложение работало под iOS 7 (поскольку iPhone у дочери именно с этой iOS), поэтому нужно было использовать любую версию Unity старее 2018-ой. Для разработчиков на Unity 2018 сейчас это не актуально, так как в составе 2018-ой версии идёт мультиплатформенный Visual Studio Code.

Помогло мне с переходом на VS видео How to setup Visual Studio with Unity.

С коробки VS не имеет всех тех классных штук, к которым я привык в других редакторах, поэтому я упростил себе жизнь:

  • включил миникарту вместо простой вертикальной прокрутки:

ак включить миникарту в visual studio вместо простой прокрутки

  • добавил расширение SemanticColorizer, которое позволяет более гибко настраивать цвета кода. Конкретно мне оно понадобилось, чтобы отличать глобальные переменные от локальных по цвету.
  • установил расширение Match Margin, которое выделяет слово под кареткой и все его копии по тексту кода, а также делает это на миникарте. Это очень удобно для быстрой навигации по коду, чтобы найти все места, где используется какой-то метод или переменная:

оформление кода и улучшенная миникарта в visual studio

  • использую Strip'em для автоматического исправления line endings.

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

Во второй части я расскажу: Это конец первой части.

  • О написании кода
  • О контроле версий
  • Об озвучке
  • Об иконке
  • О сборке под Android
  • О сборке под iOS
  • О названии и продвижении
  • Статистика
  • О чём жалею
  • Что понял

Ссылки

Список ссылок из тела статьи в очерёдности их упоминания:

+ Simple C# FTP Class.
+ Идентификатор сессии для iOS.
+ Пишем плагин для Unity правильно. Часть 1: iOS.
+ Методы для сериализации объектов в JSON (official help).
+ ScriptableObject (official help).
+ Видеоурок Game Architecture with Scriptable Objects (код).
+ Мастер-класс Overthrowing the MonoBehaviour Tyranny in a Glorious Scriptable Object Revolution (код).
+ Видеодемонстрация работы моего приложения в редакторе.
+ Бесплатный плагин Anima2D для скелетной анимации 2D персонажей.
+ Бесплатная библиотека для локализации приложений — Lean Localization.
+ Видео об уловках, улучшающих восприятие игры Juice it or lose it.
+ Видео о действующих на подсознание приёмах анимации The art of screenshake.
+ Бесплатная, но мощная система анимации iTween.
+ Визуальная демонстрация типов изинга (нужен флеш).
+ iTween (official help).
+ Unity Analitics.
+ Текстовые редакторы Notepad++ и Sublime Text.
+ Visual Studio Community 2017 и Visual Studio Code.
+ Видеоурок How to setup Visual Studio with Unity.
+ Плагин SemanticColorizer (для настроек цвета кода).
+ Плагин Match Margin (выделяет слово под кареткой и все его копии).
+ Плагин Strip'em (автоисправление line endings).


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Как Яндекс применил компьютерное зрение для повышения качества видеотрансляций. Технология DeepHD

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

Security Week 36: Telnet должен быть закрыт

Telnet — это очень старый протокол. Википедия сообщает, что он был разработан в 1969 году, много лет активно использовался для удаленного доступа к компьютерам и серверам, причем как под управлением Unix/Linux, так и для систем под Windows (telnet можно было ...