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