Меню настроек графики

В данном примере, мы рассмотрим возможности регулировки различных настроек графики в Unity. Однако, отметим сразу, что речь пойдет о настройках движка, без дополнительных пост-эффектов [Image Effects], для них лучше писать отдельный класс управления. В этот раз, будем заниматься регулировкой настроек, которые можно найти в меню редактора (Edit > Project Settings > Quality). Менять будем следующие параметры: стандартные пресеты графики, разрешение экрана, полноэкранный режим, вертикальная синхронизация, качество теней, сглаживание, разрешение теней, анизотропная фильтрация, разрешение текстур. Дополнительно, предусмотрено сохранение и загрузка настроек. Плюс, есть возможность восстановить настройки по умолчанию, для каждого из пресетов.


Важная деталь! Скрипт написан из расчета на стандартные пресеты:

Меню настроек графики

Количество и имена пресетов, должны быть стандартными. Если вы изменили это условие, то необходимо соответствующим образом переписать код скрипта настроек.

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

Итак, собственно сам класс, работает с UI элементами:

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;

public class QualitySettingsMenu : MonoBehaviour {

	[SerializeField] private Dropdown resolutions; // разрешение экрана
	[SerializeField] private Dropdown qualityPreset; // пресеты качества (настраиваются в редакторе)
	[SerializeField] private Dropdown antiAliasing; // сглаживание
	[SerializeField] private Dropdown shadowQuality; // качество теней
	[SerializeField] private Dropdown shadowRes; // разрешение теней
	[SerializeField] private Dropdown textureRes; // разрешение текстур
	[SerializeField] private Dropdown anisotropic; // анизотропная фильтрация
	[SerializeField] private Button applyButton; // кнопка применения настроек и их сохранения
	[SerializeField] private Button revertButton; // кнопка возврата настроек, для текущего пресета
	[SerializeField] private Toggle fullscreenToggle; // вкл/выкл полноэкранный режим
	[SerializeField] private Toggle vSyncToggle; // вкл/выкл вертикальная синхронизация
	private int[] aliasing = new int[]{0, 2, 4, 8};
	private int[] texRes = new int[]{0, 1, 2, 3};
	private int[] af = new int[]{0, 2, 4, 8, 16};
	private string[] shadowQualityList, shadowResolutionList, qualityNamesList;
	private Resolution[] resolutionsList;
	private int aniso;

	void Awake()
	{
		resolutionsList = Screen.resolutions;
		qualityNamesList = QualitySettings.names;
		shadowQualityList = System.Enum.GetNames(typeof(ShadowQuality));
		shadowResolutionList = System.Enum.GetNames(typeof(ShadowResolution));
		Load();
		BuildMenu();
	}

	void Load()
	{
		QualitySettings.shadows = (ShadowQuality) System.Enum.Parse(typeof(ShadowQuality), PlayerPrefs.GetString("shadowQuality", QualitySettings.shadows.ToString()));
		QualitySettings.vSyncCount = PlayerPrefs.GetInt("vSync", QualitySettings.vSyncCount);
		vSyncToggle.isOn = (QualitySettings.vSyncCount > 0) ? true : false;
		QualitySettings.antiAliasing = PlayerPrefs.GetInt("aliasing", QualitySettings.antiAliasing);
		QualitySettings.masterTextureLimit = PlayerPrefs.GetInt("texRes", QualitySettings.masterTextureLimit);
		QualitySettings.shadowResolution = (ShadowResolution) System.Enum.Parse(typeof(ShadowResolution), PlayerPrefs.GetString("shadowRes", QualitySettings.shadowResolution.ToString()));
		aniso = PlayerPrefs.GetInt("aniso", 16);
		AnisoFiltering(aniso);
		fullscreenToggle.isOn = Screen.fullScreen;
	}

	void Save()
	{
		PlayerPrefs.SetInt("vSync", QualitySettings.vSyncCount);
		PlayerPrefs.SetInt("aliasing", aliasing[antiAliasing.value]);
		PlayerPrefs.SetInt("texRes", QualitySettings.masterTextureLimit);
		PlayerPrefs.SetString("shadowRes", QualitySettings.shadowResolution.ToString());
		PlayerPrefs.SetString("shadowQuality", QualitySettings.shadows.ToString());
		PlayerPrefs.SetInt("aniso", af[anisotropic.value]);
		PlayerPrefs.Save();
	}

	string ResToString(Resolution res)
	{
		return res.width + " x " + res.height;
	}

	void RefreshDropdown()
	{
		resolutions.RefreshShownValue();
		qualityPreset.RefreshShownValue();
		antiAliasing.RefreshShownValue();
		shadowRes.RefreshShownValue();
		textureRes.RefreshShownValue();
		anisotropic.RefreshShownValue();
		shadowQuality.RefreshShownValue();
	}

	void BuildMenu()
	{
		resolutions.options = new List<Dropdown.OptionData>();
		qualityPreset.options = new List<Dropdown.OptionData>();
		antiAliasing.options = new List<Dropdown.OptionData>();
		shadowRes.options = new List<Dropdown.OptionData>();
		textureRes.options = new List<Dropdown.OptionData>();
		anisotropic.options = new List<Dropdown.OptionData>();
		shadowQuality.options = new List<Dropdown.OptionData>();

		for(int i = 0; i < resolutionsList.Length; i++)
		{
			Dropdown.OptionData option = new Dropdown.OptionData();
			option.text = ResToString(resolutionsList[i]);
			resolutions.options.Add(option);
			if(resolutionsList[i].height == Screen.height && resolutionsList[i].width == Screen.width) resolutions.value = i;
		}

		for(int i = 0; i < qualityNamesList.Length; i++)
		{
			Dropdown.OptionData option = new Dropdown.OptionData();
			option.text = QualityNames(qualityNamesList[i]);
			qualityPreset.options.Add(option);
		}

		for(int i = 0; i < aliasing.Length; i++)
		{
			Dropdown.OptionData option = new Dropdown.OptionData();
			option.text = (aliasing[i] == 0) ? "Выключено" : aliasing[i] + "x Multi Sampling";
			antiAliasing.options.Add(option);
			if(aliasing[i] == QualitySettings.antiAliasing) antiAliasing.value = i;
		}

		for(int i = 0; i < texRes.Length; i++)
		{
			Dropdown.OptionData option = new Dropdown.OptionData();
			option.text = TextureResolutionNames(texRes[i]);
			textureRes.options.Add(option);
		}

		for(int i = 0; i < af.Length; i++)
		{
			Dropdown.OptionData option = new Dropdown.OptionData();
			option.text = (af[i] == 0) ? "Выключено" : af[i] + "x";
			anisotropic.options.Add(option);
			if(af[i] == aniso) anisotropic.value = i;
		}

		for(int i = 0; i < shadowQualityList.Length; i++)
		{
			Dropdown.OptionData option = new Dropdown.OptionData();
			option.text = ShadowQualityNames(shadowQualityList[i]);
			shadowQuality.options.Add(option);
		}

		for(int i = 0; i < shadowResolutionList.Length; i++)
		{
			Dropdown.OptionData option = new Dropdown.OptionData();
			option.text = ShadowResolutionNames(shadowResolutionList[i]);
			shadowRes.options.Add(option);
			if(shadowResolutionList[i].CompareTo(QualitySettings.shadowResolution.ToString()) == 0) shadowRes.value = i;
		}

		shadowQuality.value = (int)QualitySettings.shadows;
		textureRes.value = QualitySettings.masterTextureLimit;
		qualityPreset.value = QualitySettings.GetQualityLevel();
		qualityPreset.onValueChanged.AddListener(delegate{ApplyPresets();});
		applyButton.onClick.AddListener(()=>{ApplySettings();});
		revertButton.onClick.AddListener(()=>{PresetsSettings();});

		RefreshDropdown();
	}

	void PresetsSettings() // настройки пресетов по умолчанию
	{
		switch(qualityPreset.value)
		{
		case 0:
			QualitySettings.vSyncCount = 0;
			QualitySettings.antiAliasing = 0;
			QualitySettings.masterTextureLimit = 3;
			QualitySettings.shadows = ShadowQuality.Disable;
			QualitySettings.shadowResolution = ShadowResolution.Low;
			anisotropic.value = 0;
			AnisoFiltering(af[0]);
			break;

		case 1:
			QualitySettings.vSyncCount = 0;
			QualitySettings.antiAliasing = 0;
			QualitySettings.masterTextureLimit = 2;
			QualitySettings.shadows = ShadowQuality.HardOnly;
			QualitySettings.shadowResolution = ShadowResolution.Low;
			anisotropic.value = 0;
			AnisoFiltering(af[0]);
			break;

		case 2:
			QualitySettings.vSyncCount = 0;
			QualitySettings.antiAliasing = 0;
			QualitySettings.masterTextureLimit = 1;
			QualitySettings.shadows = ShadowQuality.All;
			QualitySettings.shadowResolution = ShadowResolution.Medium;
			anisotropic.value = 1;
			AnisoFiltering(af[1]);
			break;

		case 3:
			QualitySettings.vSyncCount = 0;
			QualitySettings.antiAliasing = 0;
			QualitySettings.masterTextureLimit = 0;
			QualitySettings.shadows = ShadowQuality.All;
			QualitySettings.shadowResolution = ShadowResolution.Medium;
			anisotropic.value = 2;
			AnisoFiltering(af[2]);
			break;

		case 4:
			QualitySettings.vSyncCount = 1;
			QualitySettings.antiAliasing = 2;
			QualitySettings.masterTextureLimit = 0;
			QualitySettings.shadows = ShadowQuality.All;
			QualitySettings.shadowResolution = ShadowResolution.High;
			anisotropic.value = 3;
			AnisoFiltering(af[3]);
			break;

		case 5:
			QualitySettings.vSyncCount = 1;
			QualitySettings.antiAliasing = 4;
			QualitySettings.masterTextureLimit = 0;
			QualitySettings.shadows = ShadowQuality.All;
			QualitySettings.shadowResolution = ShadowResolution.VeryHigh;
			anisotropic.value = 4;
			AnisoFiltering(af[4]);
			break;
		}

		int j = 0;
		foreach (string res in System.Enum.GetNames(typeof(ShadowResolution))) 
		{
			if(res.CompareTo(QualitySettings.shadowResolution.ToString()) == 0) shadowRes.value = j;
			j++;
		}

		vSyncToggle.isOn = (QualitySettings.vSyncCount > 0) ? true : false;
		textureRes.value = QualitySettings.masterTextureLimit;
		shadowQuality.value = (int)QualitySettings.shadows;

		for(int i = 0; i < aliasing.Length; i++)
		{
			if(aliasing[i] == QualitySettings.antiAliasing) antiAliasing.value = i;
		}

		RefreshDropdown();
	}

	void ApplyPresets() // применение пресетов
	{
		QualitySettings.SetQualityLevel(qualityPreset.value, true);
		PresetsSettings();
	}

	void AnisoFiltering(int value)
	{
		if(value > 0)
		{
			QualitySettings.anisotropicFiltering = AnisotropicFiltering.ForceEnable;
			Texture.SetGlobalAnisotropicFilteringLimits(value, 16);
		}
		else
		{
			QualitySettings.anisotropicFiltering = AnisotropicFiltering.Disable;
		}
	}

	void ApplySettings()
	{
		AnisoFiltering(af[anisotropic.value]);
		QualitySettings.vSyncCount = (vSyncToggle.isOn) ? 1 : 0;
		QualitySettings.masterTextureLimit = texRes[textureRes.value];
		QualitySettings.shadows = (ShadowQuality) System.Enum.Parse(typeof(ShadowQuality), shadowQualityList[shadowQuality.value]);
		QualitySettings.shadowResolution = (ShadowResolution) System.Enum.Parse(typeof(ShadowResolution), shadowResolutionList[shadowRes.value]);
		QualitySettings.antiAliasing = aliasing[antiAliasing.value];
		Screen.SetResolution(resolutionsList[resolutions.value].width, resolutionsList[resolutions.value].height, fullscreenToggle.isOn);
		Save();
	}

	string TextureResolutionNames(int value)
	{
		if(value == 0)
		{
			return "Высокое";
		}
		else if(value == 1)
		{
			return "Среднее";
		}
		else if(value == 2)
		{
			return "Низкое";
		}
		else if(value == 3)
		{
			return "Минимальное";
		}

		return "Error";
	}

	string ShadowQualityNames(string value)
	{
		if(value.CompareTo("HardOnly") == 0)
		{
			return "Низкое";
		}
		else if(value.CompareTo("Disable") == 0)
		{
			return "Выключено";
		}
		else if(value.CompareTo("All") == 0)
		{
			return "Высокое";
		}

		return "Error";
	}

	string ShadowResolutionNames(string value)
	{
		if(value.CompareTo("High") == 0)
		{
			return "Высокое";
		}
		else if(value.CompareTo("Low") == 0)
		{
			return "Низкое";
		}
		else if(value.CompareTo("Medium") == 0)
		{
			return "Среднее";
		}
		else if(value.CompareTo("VeryHigh") == 0)
		{
			return "Максимальное";
		}

		return "Error";
	}

	string QualityNames(string value)
	{
		if(value.CompareTo("Fastest") == 0)
		{
			return "Минимум";
		}
		else if(value.CompareTo("Fast") == 0)
		{
			return "Низко";
		}
		else if(value.CompareTo("Simple") == 0)
		{
			return "Средне";
		}
		else if(value.CompareTo("Good") == 0)
		{
			return "Высоко";
		}
		else if(value.CompareTo("Beautiful") == 0)
		{
			return "Очень высоко";
		}
		else if(value.CompareTo("Fantastic") == 0)
		{
			return "Максимально";
		}

		return "Error";
	}
}

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

Еще, стоит обратить внимание, чтобы у основной камеры были включены HDR и MSAA:


Скачать скрипт:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.
Тестировалось на: Unity 5.6.0

Комментариев 22

Офлайн
Alex2D 11 апреля 2017
Очень полезный и нужный пример, Спасибо! :)
Подскажите, что можно сделать, чтобы избежать ситуации когда после деактивации и последующего включения canvas перестают отображаться все выпадающие списки? Попытался исправить сам, но что-то пока никак... :)
PS Видимо тут нельзя приложить картинку, продублирую в группе ВК.
Офлайн
Light 11 апреля 2017
Alex2D, баг это, поправят со временем. А пока что, делай по другому, не трогай сам Canvas, а сделай в нем родителя для выпадающих, объедини их в группу в общем. Если включать/выключать группы внутри Canvas, то всё работает норм.
Офлайн
SleepyAsh 14 апреля 2017
Здравствуйте, вопрос не по теме. Есть ли в планах сделать управление 2d персонажем как в игре Sheltered?(Не сочтите за рекламу) Очень интересно.
Офлайн
Light 14 апреля 2017
SleepyAsh, что-то похожее было в планах...
Офлайн
SleepyAsh 14 апреля 2017
Light,
С нетерпением буду ждать blush
Офлайн
ABredin 14 мая 2017
Подскажите, у меня у одного такое или как: Все разрешения экрана в выпадающем списке дублируются, причем второе разрешение - 2 раза, второе - 3 раза, третье - 4 раза и т.д., когда доходит до 1920х1080 их там оч много
Офлайн
ABredin 14 мая 2017
В билде всё норм (сорян за дезинформацию) я всё скомпилил и всё работает
Офлайн
Sidiusz 25 августа 2017
При масштабировании приближается картинка вместо того, чтобы сужаться
Офлайн
Light 9 ноября 2017
В версии Unity 2017.2.0 изменены имена пресетов.

Поэтому строки:

string QualityNames(string value)
{
	if(value.CompareTo("Fastest") == 0)
	{
		return "Минимум";
	}
	else if(value.CompareTo("Fast") == 0)
	{
		return "Низко";
	}
	else if(value.CompareTo("Simple") == 0)
	{
		return "Средне";
	}
	else if(value.CompareTo("Good") == 0)
	{
		return "Высоко";
	}
	else if(value.CompareTo("Beautiful") == 0)
	{
		return "Очень высоко";
	}
	else if(value.CompareTo("Fantastic") == 0)
	{
		return "Максимально";
	}

	return "Error";
}

Меняем на:

string QualityNames(string value)
{
	if(value.CompareTo("Very Low") == 0)
	{
		return "Минимум";
	}
	else if(value.CompareTo("Low") == 0)
	{
		return "Низко";
	}
	else if(value.CompareTo("Medium") == 0)
	{
		return "Средне";
	}
	else if(value.CompareTo("High") == 0)
	{
		return "Высоко";
	}
	else if(value.CompareTo("Very High") == 0)
	{
		return "Очень высоко";
	}
	else if(value.CompareTo("Ultra") == 0)
	{
		return "Максимально";
	}

	return "Error";
}
Офлайн
p1kha 7 января 2018
Light,
Я думаю, что здесь не просто так else if вместо switch case? Если заменю на switch case, то ничего не измениться?
Офлайн
Light 7 января 2018
p1kha, конечно можно switch.
Офлайн
Falcion 25 декабря 2018
Здравствуйте! Встретилась проблема очень похожая на ситуацию как у ABredin'a. Но отличия тоже присутствуют, кол-во разрешение может быть хаотическим, даже без порядка (то есть 6 разрешений формата 640x480, потом 4 разрешения 720x480 и т.п). Что же делать в данной ситуации?
Офлайн
Light 26 декабря 2018
Falcion, можно отфильтровать список по частоте обновления экрана:

        resolutionsList = Screen.resolutions;

        List<Resolution> res = new List<Resolution>();

        int hz = resolutionsList[resolutionsList.Length - 1].refreshRate;

        for (int i = 0; i < resolutionsList.Length; i++)
        {
            if (resolutionsList[i].refreshRate == hz)
            {
                res.Add(resolutionsList[i]);
            }
        }

        resolutionsList = res.ToArray();
Офлайн
Falcion 27 декабря 2018
Light,
Спасибо большое, проблема со списками разрешений решена, но как говорится, беда не приходит одна и теперь при нажатии кнопки, грубо говоря "Принять" - экран скукоживается, скрины прилагаются http://prntscr.com/m0a5pt

Falcion,
И вдобавок резко и неожиданно кнопка "Revert" грубо говоря, также перестала работать
Офлайн
Light 28 декабря 2018
Falcion, версия Unity какая?
Офлайн
Falcion 28 декабря 2018
Light,
Unity 2018.2.18f1 (64-bit)
Офлайн
Light 28 декабря 2018
Falcion, проверял на версии 2018.3.0 нормально вроде всё.
Офлайн
Falcion 29 декабря 2018
Цитата: Light
Falcion, проверял на версии 2018.3.0 нормально вроде всё.

Решил попробовать удалить скрипт и переустановить его, в итоге всё работает чисто, я так и не понял как работает Unity и на какой магии ¯\_(ツ)_/¯
Но спасибо вам за помощь!
Офлайн
BOGZOERT 17 июня 2019
Light,
В пресетах качества: Error, Error, Error. Unity 2019.1.1
Офлайн
BOGZOERT 17 июня 2019
BOGZOERT, всё разобрался, изначально было 3 граф настройки, отредактировал QualityNames и всё встало на места)

BOGZOERT,
Вот как теперь сделать самостоятельными данные прессеты. Хочется использовать в проекте только пару функций(Разрешение экрана и текстуры)
Офлайн
alenasexbXX 26 июня 2019
Братан, надеюсь ты поможешь. Почему на андроидах не работает "ApplyButton"? Я даун, помоги мне.
Офлайн
Light 26 июня 2019
alenasexbXX, это только для ПК.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика