0,0
рейтинг
29 января 2015 в 13:56

Разработка → Unity 2D: работа со спрайтами в разных разрешениях дисплея из песочницы


Начиная с версии 4.3 в Unity появилась возможность работы с 2D графикой, большая часть новых стандартных решений мне пришлись по душе, потому что я как раз незадолго до этого обновления перешел с Corona SDK.
Но что меня не порадовало, так это отсутствие стандартных инструментов для оптимизации спрайтов под разные разрешения экранов, что имеет довольно таки существенное влияние на производительность на маломощных устройствах.

Конечно, можно использовать что-то похожее на 2D Toolkit для решения этой проблемы, но зачем платить 75$ если можно сделать все самому?

Cо слов пользователей официального форума Unity, разработчики в скором времени не планируют расширять 2D функционал, по крайней мере до релиза 5 версии Unity, и пока что пользователи должны самостоятельно решать данную проблему. Бороздя просторы интернета в надежде найти ответ, я набрел на интересный доклад одного из разработчиков Unity на летней Nordic Game Conference 2014, название говорит само за себя «2D issues and how to solve them». Пользуясь материалами этого доклада, я сделал свое решение проблемы поддержки дисплеев разного разрешения.

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

Подготовка спрайтов

Итак, на первом этапе мы должны организовать атласы спрайтов для разных разрешений: SD, HD, ultra-HD, у нас же будут использованы суффиксы 1x, 2x, 4x.

Берем атлас спрайтов, в нашем случае это ’spritesheet1@4x.png', в инспекторе выбираем нужные параметры, режем атлас в Sprite Editor, если требуется. Создаем еще две копии атласа в Project Browser (cmd+D, ctrl+D) и переименуем их так, чтобы суффиксы в названии были '@2x', '@1x’, меняем свойство Max Size на значение в 2 и в 4 раза меньше соответственно.

Спрайты должны находится в папке Resources, если таковой не имеется — создайте. Это позволяет загружать файлы с этой папки во время выполнения программы.





Обращу Ваше внимание на поля Pixels Per Unit и Format, первое поможет подобрать размер спрайтов под размеры сцены без изменения scale, а второе является очень важным для правильной передачи цвета, размера билда и использования ресурсов графического процессора. На эту тему есть замечательный мануал

Подготовка префаба

Тут все просто, мы собираем игровой объект на основе атласа спрайтов с суффиксом ‘@2x’, добавляем анимацию и любые другие фишки, которые могут вам понадобится. Сохраняем объект как префаб.
Суффикс ‘@2x’ был выбран, потому что большая часть устройств имеют hd разрешение, нам не придется делать лишнюю работу в большинстве случаев.

Скрипт

Скрипт будет работать с любым количеством компонентов SpriteRenderer. Он не будет влиять ни на анимацию, ни на что другое, главное чтобы имена спрайтов в атласе и SpriteRenderer`е были одинаковыми. Данную особенность можно применять не только для смены разрешения спрайтов, но и для замены их на полностью другие, например при создании другого скина персонажа.

Основной принцип работы скрипта таков: у нас есть публичная переменная spriteSheet, в которой мы передаем имя атласа, в котором находятся спрайты нашего объекта.



С помощью метода GetQuality узнаем с каким дисплеем мы имеем дело (для моих целей было достаточно ориентироваться на высоту экрана).

Потом в методе ManageQuality, имея данные о разрешении экрана, загружаем в массив sprites все спрайты нужного нам атласа с правильным суффиксом. В массив renderers загружаем все компоненты SpriteRenderer, которые находятся в объекте. Ищем в массиве sprites спрайт по имени и присваиваем его спрайту компонента SpriteRenderer, если такой существует. Завершает все Resources.UnloadUnusedAssets (), этот метод выгружает из памяти неиспользуемые ассеты.

Тут находится скрипт
using UnityEngine;
using System.Collections;
using System;

public class ConstObjectsQuality : MonoBehaviour {

	public string spriteSheet;

	private string qSuffix;
	
	void Awake(){
		qSuffix = GetQuality ();
		ManageQuality ();
	}
	
	private string GetQuality(){
		int screenH = Screen.height;
		print (screenH);
		if (screenH > 1440)
			return "4x";
		else if (screenH < 720)
			return "1x";
		else
			return "2x";
	}


	private void ManageQuality(){

		if (qSuffix == "1x" || qSuffix ==  "4x") {

			Sprite[] sprites = Resources.LoadAll<Sprite>(spriteSheet + "@" + qSuffix);

			if (sprites != null) {
				SpriteRenderer[] renderers = GetComponentsInChildren<SpriteRenderer>(true);
				
				if (renderers.Length > 0) {

					foreach (SpriteRenderer r in renderers)
					{
						if (r.name != null){
							string spriteName = r.sprite.name;
							Sprite newSprite = Array.Find(sprites, item => item.name == spriteName);
							
							if (newSprite)
								r.sprite = newSprite;
						}
					}
				}
			}
		}

		Resources.UnloadUnusedAssets ();

	}

}


Также этот скрипт можно использовать для изменения всех спрайтов в сцене. Для этого создаем новый объект, например SpriteManager, и добавляем к нему данный скрипт, но с измененным определением массива renderers:

SpriteRenderer[] renderers = GameObject.FindObjectsOfType<SpriteRenderer>();

Спасибо за внимание, надеюсь статья была вам полезна.
Александр Петров @deepnavy
карма
12,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (10)

  • 0
    Читал, что спрайты из Resources не попадают в автоматически генерируемые текстурные атласы — что вызывает создание нескольких текстур, а каждая новая текстура — это дополнительный Draw Call. Просветите, кто в курсе, так ли это?
    • 0
      Протестировал у себя в игре. После загрузки спрайтов из Resources количество Draw Calls не меняется.
      Вот статистика с 2х(hd) текстурами, которые не загружаются из Resources:



      А вот 4х(ultra-hd) которые подгружались из Resources



      Разное количество сохраненных тестур батчингом показывает, потому что некоторые текстуры и объекты work in progress.
      • 0
        Спасибо!
  • 0
    Вопрос?
    А если таких атласов будет ну скажем 5, учитывая HD SD MD все это хранить в билде немного увеличит размер.(загрузку с интернета откинем)
    Как вариант хранить одни лист HD, потом его при первом запуске ужимать, пересчитывать текстуры и использовать (ну и сохранить, что бы не повторять)
    (в unityне профессионал но интересно ваше мнение ?)
  • 0
    Спасибо за статью. Я некоторое время назад делал ассет для Unity, который грузит разные сцены в зависимости от разрешения экрана.

    Для этого тоже используются суффиксы (у файлов сцены), но вида ".1024x768", плюс еще генерируется xml со списком сцен (так как в рантайме список получить проблематично). Алгоритм выбора у меня посложнее, если интересно, то можно посмотреть на github (много кода).

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

    В общем, Вы меня заставили задуматься о добавлении нового функционала к моему проекту :-)
    • 0
      Интересный подход, я бы о таком даже и не подумал. К сожалению, приходится делать много ненужной механической работы.
      Но вот я думал, что замена спрайтов в рантайме это довольно таки дорогая для производительности операция, оказылось, что она проходит без заметных глазу фризов даже на маломощных устройствах, если конечно количество объектов в сцене в пределах разумного.

      Мне интересно как реализуется данная фича в 2d Toolkit.
      А если брать простые 2д фреймфорки типо CoronaSDK или Cocos, то там спрайты же для разных разрешений грузятся сразу, без поиска и замены, потому что у них все пишется с кода. Такой подход тоже можно реализовать в unity, и я видел решения где-то на форуме официальном, но это не очень удобно для привычного рабочего процесса, т.к. нужно будет все спрайты удалять с объектов перед запуском или вообще создавать объекты с кода в рантайме. Вообщем мне от этого становится страшно:)
      • +1
        Просто до того, как писать этот ассет, я работал на проекте (довольно успешная мобильная игра, но не на Unity), где было пять версий сцен для разных разрешений. Именно версий, так как сцены различались кардинально: для телефонов это был портрет, для планшетов — ландшафт, разные элементы UI, + сам игровой процесс отличался, даже количество сцен было разным и т.п. Отсюда и растут уши.

        К сожалению, приходится делать много ненужной механической работы.
        А вот тут есть вариант перенести Ваш подход в скрипт редактора и совместить с моим. То есть, в редакторе по нажатию одной кнопочки будут заменяться текстуры в конкретной сцене (а то и сразу во всех, если их можно грузить из скриптов редактора), что ускорит создание версий. Можно было бы это сделать вообще автоматически, но, к сожалению, Unity не позволяет вызвать скрипт перед началом сборки (в Pro, вроде бы, можно).

        … если конечно количество объектов в сцене в пределах разумного.
        Если делать только для себя, то, я согласен, что такой подход более чем оправдан. Если же делать ассет, которым будут пользоваться другие люди, которые, предположительно, могут вообще быть не программистами, то уже нехорошо. Правда, можно оставлять в сцене всегда минимальные текстуры, а в рантайме менять их на нужные в корутине. Например, по десятку за кадр. Тогда и для больших сцен все может быть вполне окей.

        Мне интересно как реализуется данная фича в 2d Toolkit.
        С 2d toolkit я не работал, но, насколько я понимаю, у них сделан собственный компонент спрайта. Атласы текстур хранятся в ресурсах, также как у Вас, но при этом грузится всегда только нужный, как в кокосе. Правда, я могу и ошибаться. Подобная идея мне не очень нравится, так как в Unity уже есть вполне приличный спрайт, которым люди пользуются, в том числе и я.
        • 0
          … где было пять версий сцен для разных разрешений...

          Спасибо, нужно взять себе на заметку Ваше решение. В купе с хорошей автоматизацией будет эффективно для поддержки разных устройств.

          Unity не позволяет вызвать скрипт перед началом сборки (в Pro, вроде бы, можно).

          Насколько я знаю, в Unity API ограниченные возможности по пре/после-процессингу билда. Не очень компетентен я в этой сфере, но что-то можно сделать через BuildPipeline, где можно управлять процессом упаковки.

          А еще можно запускать скрипты прямо в редакторе Running Editor Script Code on Launch, ExecuteInEditMode

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

          Неплохое решение.

          Кстати, есть еще решение для свапинга атласа спрайтов с помощью NGUI, нашел я его на lynda.com, но я думаю в интернете это где-то гуляет.
          • +1
            Насколько я знаю, в Unity API ограниченные возможности по пре/после-процессингу билда.
            Я разбирался как раз для этого ассета, там ситуация такая: пост-процессинг есть и работает во free версии. Есть два его варианта, один (PostProcessSceneAttribute) срабатывает при запуске сцены в редакторе (почему-то после Awake), и я его использую в своем ассете, чтобы пересобирать xml со списком сцен. А второй вариант — после билда (PostProcessBuildAttribute). И его у меня использовать не получается, так как собранный билд уже проблематично переделывать, особенно, если это apk или ipa. Поэтому пришлось добавить пункт меню, чтобы вручную пересоздавать xml.

            В BuildPipeline метод BuildPlayer, к сожалению, pro-only. Кроме того, как я понимаю, это обходной путь, чтобы создавать собственную последовательность сборки и для нее нужно делать отдельную кнопку или пункт меню. А стандартный билд по-прежнему будет собираться, как и раньше. Думал, может у них что-то поприличнее сделано, но вроде нет.

            Выполнение скриптов в редакторе тоже не спасет, так как они выполняются иначе, чем в рантайме (например, Update вызывается только при изменении сцены).

            А вот зато можно написать скрипт редактора для SpriteRenderer. Например, вот такой скрипт не дает изменить спрайт:
            Код
            using UnityEngine;
            using UnityEditor;
            
            [CustomEditor(typeof(SpriteRenderer))]
            public class SpriteRendererEditor : Editor
            {
            	Sprite _oldSprite;
            
            	void OnEnable()
            	{
            		var sr = (SpriteRenderer)target;
            		_oldSprite = sr.sprite;
            	}
            
            	public override void OnInspectorGUI ()
            	{
            		var sr = (SpriteRenderer)target;
            		DrawDefaultInspector ();
            		if (_oldSprite != null && sr.sprite != _oldSprite)
            			sr.sprite = _oldSprite;
            	}
            }
            

            Таким макаром мы можем отлавливать момент установки пользователем текстуры. А зная, в каком разрешении сцена, можно сразу текстуру поменять. К сожалению, это не покрывает use case'ы, когда пользователь изменяет разрешение сцены или когда он сначала сделал сцену, а потом добавил текстуры для других разрешений. То есть, все равно придется делать кнопку, которую нужно нажать перед билдом.

            А еще, кстати, есть префабы, и это вообще отдельный разговор…

            Кстати, есть еще решение для свапинга атласа спрайтов с помощью NGUI, нашел я его на lynda.com, но я думаю в интернете это где-то гуляет.
            Я так думаю, Вы об этом. Идея вроде бы та же, что и у Вас, меняются атласы в рантайме, но только средствами NGUI.
  • 0
    Спасибо, недавно только начал осваиваться с Unity и думал, что все это Unity делает автоматически. Думал, что просто кидаешь в самом большом разрешение а Unity сам подготавливает в зависимости от размера сцены

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