Save the Penguin — опыт разработки Android-игры на Unity
Пожалуй, многие разработчики когда-нибудь хотели создать свою игру — кто-то из-за денег, кто-то по причине получения интересного опыта. Создатели следующей игры относятся ко второй категории, поэтому игра не пересыщена рекламой, а встроенные покупки в ней отсутствуют. Давайте посмотрим на их опыт.
Идея для первой игры возникла быстро — было принято решение создать таймкиллер в портретной ориентации, причём таким образом, чтобы можно было играть одним пальцем. Собственно говоря, это разумно, т. к. не стоит выбирать для первой игры слишком сложный проект — велика вероятность, что вы отвлечётесь, потеряете мотивацию и не доведёте дело до конца. В нашем случае главным героем стал пингвин. Почему? Потому что пингвины классные))
Разработка игры осуществлялась на Unity и велась в свободное от работы время. На всё про всё ушло около месяца. Выбирать движок долго не пришлось, так как создатели знали C#, поэтому, сами понимаете, движок Unity стал отличным решением. В процессе разработки возникали проблемы, но о них ниже. Кстати, информация о решении этих проблем может быть полезна и вам, особенно, если вы начинающий разработчик.
Вопрос № 1: адаптация под разные разрешения
Создатели игры пошли по наиболее простому пути — он заключался в использовании текстуры большего размера, чем это было нужно. Кроме этого, осуществлялась привязка к соотношению сторон, а не к конкретному разрешению. Из-за характера текстур и возможностью пожертвовать какой-нибудь частью изображения всё получилось относительно неплохо.
Вопрос № 2: оптимизация
Первую версию игры разработчики написали быстро, после чего попросили друзей-тестировщиков проверить результат. Оказалось, что игра вылетала на старых устройствах, что было связано с большим потреблением оперативной памяти. Неожиданно. Для решения проблемы пришлось реализовать выбор размера ассета с учётом разрешения экрана:
public class QualityManager : MonoBehaviour { public string FolderInResources; private string _qSuffix; void Start() { _qSuffix = GetQuality(); ManageQuality(); } private string GetQuality() { var screenH = Screen.height; if (screenH > 2000) return "4x"; if (screenH < 960) return "1x"; return "2x"; } private void ManageQuality() { if (_qSuffix != "2x") { var spriteName = GetComponent<SpriteRenderer>().sprite.name.Split('@')[0]; var sprite = Resources.Load<Sprite>(String.Format("{0}/{1}@{2}", FolderInResources, spriteName, _qSuffix)); GetComponent<SpriteRenderer>().sprite = sprite; } Resources.UnloadUnusedAssets(); } }
Кроме этого, игра предусматривает покупку вещей для Пингвина, т. е. смену скина. В первой версии задействовался алгоритм, по которому скины хранились в оперативной памяти без учёта того, используются ли они в данный момент. В итоге, в целях оптимизации, логику смены скинов полностью переделали на более правильную.
public class ReSkinner : MonoBehaviour { public string SkinName; void LateUpdate() { var subSprites = Resources.LoadAll<Sprite>(SkinName); foreach (var render in GetComponentsInChildren<SpriteRenderer>()) { var newSprite = Array.Find(subSprites, item => item.name == render.sprite.name); if (newSprite) { render.sprite = newSprite; } } } }
Следующий момент — при разработке нередко применяли метод GetComponent в Update. На самом деле, делать это не рекомендуется, ведь GetComponent считается ресурсозатратной операцией. В принципе, во многих случаях эту операцию лучше вынести в метод Start, что и было сделано.
Итог всех вышеописанных танцев с бубном — увеличение производительности.
Вопрос № 3: сохранение данных
В Unity есть несколько способов для сохранения данных. Самый простой, по которому пошли разработчики, — хранение данных посредством встроенного класса PlayerPrefs. В этом случае хранимые данные не должны быть более 1 МБ, чего в целом было достаточно для решения поставленных задач. Но нужно ведь и хранить информацию о скинах, точнее, о том, доступен ли тот либо иной скин пользователю. По большому счету, речь идёт о булевой переменной, а хранить несколько десятков таких переменных довольно затратно. Решение — применение битовых масок.
static class FlagCollection { public static int SetFlag(this int collection, int numberFlag, bool valueFlag) { if (numberFlag < 0 || numberFlag >= 32) { throw new ArgumentException("Number of flag isn't correct"); } if (valueFlag) { return collection | (1 << numberFlag); } else { return collection & ~(1 << numberFlag); } } public static bool GetFlag(this int collection, int numberFlag) { if (numberFlag < 0 || numberFlag >= 32) { throw new ArgumentException("Number of flag isn't correct"); } return (collection & (1 << numberFlag)) != 0; } }
Выводы
Если вы хотите сделать свою игру, но что-то вас останавливает — отбросьте все сомнения и действуйте! Главное заключается в том, что вы получите бесценный опыт. И помните, что успех складывается из мелочей, поэтому будьте внимательны к деталям. Старайтесь делать всё качественно, ведь плохо реализованный продукт является заведомо провальным.
Вот и всё, итоговый результат можете посмотреть по этому адресу: https://www.apkmonk.com/app/com.pacetap.savethepenguin/.