HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#Руководства</a></p>
1 <p><a>#Руководства</a></p>
2 <ul><li>17 июл 2019</li>
2 <ul><li>17 июл 2019</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Без меню игроку не изменить настройки, не сохраниться или не загрузить прошлый прогресс. Объясняем, как сделать меню в Unity.</p>
4 </ul><p>Без меню игроку не изменить настройки, не сохраниться или не загрузить прошлый прогресс. Объясняем, как сделать меню в Unity.</p>
5 <p> vlada_maestro / shutterstock</p>
5 <p> vlada_maestro / shutterstock</p>
6 <p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
6 <p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
7 <p>Если вы уже немного понимаете, как работать в Unity, и попробовали что-то создать, пора научиться верстать игровые меню. Если нет, прочтите для начала<a>статью</a>о создании игры.</p>
7 <p>Если вы уже немного понимаете, как работать в Unity, и попробовали что-то создать, пора научиться верстать игровые меню. Если нет, прочтите для начала<a>статью</a>о создании игры.</p>
8 <p>Здесь будут описаны общие моменты, с полной версией проекта можно ознакомиться, скачав его из <a>репозитория</a>на GitHub.</p>
8 <p>Здесь будут описаны общие моменты, с полной версией проекта можно ознакомиться, скачав его из <a>репозитория</a>на GitHub.</p>
9 <p>Для создания интерфейсов, в том числе и меню, в Unity используются UI-объекты. К ним относятся:</p>
9 <p>Для создания интерфейсов, в том числе и меню, в Unity используются UI-объекты. К ним относятся:</p>
10 <ul><li>кнопки;</li>
10 <ul><li>кнопки;</li>
11 <li>изображения;</li>
11 <li>изображения;</li>
12 <li>списки;</li>
12 <li>списки;</li>
13 <li>слайдеры;</li>
13 <li>слайдеры;</li>
14 <li>чекбоксы;</li>
14 <li>чекбоксы;</li>
15 <li>выпадающие списки и другие элементы.</li>
15 <li>выпадающие списки и другие элементы.</li>
16 </ul><p>Чтобы работать с ними, нужно создать объект<em>Canvas</em>и дать ему понятное название. Например<em>MenuCanvas</em>. Добавьте в него объект<em>Panel</em>и задайте какое-нибудь фоновое изображение или цвет.</p>
16 </ul><p>Чтобы работать с ними, нужно создать объект<em>Canvas</em>и дать ему понятное название. Например<em>MenuCanvas</em>. Добавьте в него объект<em>Panel</em>и задайте какое-нибудь фоновое изображение или цвет.</p>
17 <p>После этого можно начинать верстать меню. Создайте внутри<em>MenuCanvas</em>объект типа<em>Empty</em>и назовите его<em>MainMenu</em>. Внутрь него можно добавить элементы типа<em>Text</em>и <em>Button</em>.</p>
17 <p>После этого можно начинать верстать меню. Создайте внутри<em>MenuCanvas</em>объект типа<em>Empty</em>и назовите его<em>MainMenu</em>. Внутрь него можно добавить элементы типа<em>Text</em>и <em>Button</em>.</p>
18 <p>Менять надпись на кнопке можно с помощью вложенного объекта<em>Text</em>. Добавьте их столько, сколько вам необходимо. Затем разместите их на холсте так, чтобы получить что-то вроде этого:</p>
18 <p>Менять надпись на кнопке можно с помощью вложенного объекта<em>Text</em>. Добавьте их столько, сколько вам необходимо. Затем разместите их на холсте так, чтобы получить что-то вроде этого:</p>
19 <p>Ваше главное меню готово. Нужно ещё добавить отдельные подменю для настроек, сохранения и загрузки.</p>
19 <p>Ваше главное меню готово. Нужно ещё добавить отдельные подменю для настроек, сохранения и загрузки.</p>
20 <p>Чтобы создать несколько экранов меню, добавьте ещё несколько объектов типа<em>Empty</em>и поместите новые элементы в них. Например, в этом проекте будут созданы<em>SaveMenu</em>,<em>LoadMenu</em>и <em>SettingsMenu</em>.</p>
20 <p>Чтобы создать несколько экранов меню, добавьте ещё несколько объектов типа<em>Empty</em>и поместите новые элементы в них. Например, в этом проекте будут созданы<em>SaveMenu</em>,<em>LoadMenu</em>и <em>SettingsMenu</em>.</p>
21 <p>При добавлении объекта он становится активным, поэтому все кнопки и слайды будут просто налезать друг на друга. Чтобы отключить какое-нибудь меню, нажмите на его объект и в <em>Inspector</em>возле его названия уберите галочку.</p>
21 <p>При добавлении объекта он становится активным, поэтому все кнопки и слайды будут просто налезать друг на друга. Чтобы отключить какое-нибудь меню, нажмите на его объект и в <em>Inspector</em>возле его названия уберите галочку.</p>
22 <p>Этому действию соответствует метод<em>SetActive()</em>, который можно использовать, чтобы переключать меню при нажатии кнопки. Для этого выберите кнопку и в окне <em>Inspector</em>найдите поле<em>On Click ().</em></p>
22 <p>Этому действию соответствует метод<em>SetActive()</em>, который можно использовать, чтобы переключать меню при нажатии кнопки. Для этого выберите кнопку и в окне <em>Inspector</em>найдите поле<em>On Click ().</em></p>
23 <p>В нём описаны действия, которые будут выполняться при клике на кнопку.</p>
23 <p>В нём описаны действия, которые будут выполняться при клике на кнопку.</p>
24 <p>На скриншоте показано отключение основного меню и включение меню с сохранениями при нажатии кнопки<em>SaveButton</em>. Сделано всё это без использования кода.</p>
24 <p>На скриншоте показано отключение основного меню и включение меню с сохранениями при нажатии кнопки<em>SaveButton</em>. Сделано всё это без использования кода.</p>
25 <p>Отдельно рассмотрим создание настроек игры. Чтобы реализовать их, нужно сначала сверстать меню с помощью объектов<em>Toggle, Dropbox</em>и <em>Slider</em>:</p>
25 <p>Отдельно рассмотрим создание настроек игры. Чтобы реализовать их, нужно сначала сверстать меню с помощью объектов<em>Toggle, Dropbox</em>и <em>Slider</em>:</p>
26 <p>Дальше создайте скрипт<em>Menu.cs</em>и прикрепите его к <em>MenuCanvas</em>: он будет отвечать за работу со всеми настройками.</p>
26 <p>Дальше создайте скрипт<em>Menu.cs</em>и прикрепите его к <em>MenuCanvas</em>: он будет отвечать за работу со всеми настройками.</p>
27 <p>В него нужно добавить следующие библиотеки:</p>
27 <p>В него нужно добавить следующие библиотеки:</p>
28 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; //Работа с интерфейсами using UnityEngine.SceneManagement; //Работа со сценами using UnityEngine.Audio; //Работа с аудио<p>Затем добавьте эти поля:</p>
28 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; //Работа с интерфейсами using UnityEngine.SceneManagement; //Работа со сценами using UnityEngine.Audio; //Работа с аудио<p>Затем добавьте эти поля:</p>
29 public bool isOpened = false; //Открыто ли меню public float volume = 0; //Громкость public int quality = 0; //Качество public bool isFullscreen = false; //Полноэкранный режим public AudioMixer audioMixer; //Регулятор громкости public Dropdown resolutionDropdown; //Список с разрешениями для игры private Resolution[] resolutions; //Список доступных разрешений private int currResolutionIndex = 0; //Текущее разрешение<p>Не забудьте добавить в скрипт выпадающий список и регулятор громкости:</p>
29 public bool isOpened = false; //Открыто ли меню public float volume = 0; //Громкость public int quality = 0; //Качество public bool isFullscreen = false; //Полноэкранный режим public AudioMixer audioMixer; //Регулятор громкости public Dropdown resolutionDropdown; //Список с разрешениями для игры private Resolution[] resolutions; //Список доступных разрешений private int currResolutionIndex = 0; //Текущее разрешение<p>Не забудьте добавить в скрипт выпадающий список и регулятор громкости:</p>
30 <p>Теперь можно заняться основным функционалом. В первую очередь нужно создать метод, который будет открывать меню в игре.</p>
30 <p>Теперь можно заняться основным функционалом. В первую очередь нужно создать метод, который будет открывать меню в игре.</p>
31 public void ShowHideMenu() { isOpened = !isOpened; GetComponent&lt;Canvas&gt; ().enabled = isOpened; //Включение или отключение Canvas. Ещё тут можно использовать метод SetActive() }<p>Вызываться этот метод будет при нажатии на кнопку<em>Esc</em>:</p>
31 public void ShowHideMenu() { isOpened = !isOpened; GetComponent&lt;Canvas&gt; ().enabled = isOpened; //Включение или отключение Canvas. Ещё тут можно использовать метод SetActive() }<p>Вызываться этот метод будет при нажатии на кнопку<em>Esc</em>:</p>
32 void Update() { if(Input.GetKey(KeyCode.Escape)) { ShowHideMenu(); } }<p>Дальше нужно создать методы, которые будут вызываться при изменении настроек в меню:</p>
32 void Update() { if(Input.GetKey(KeyCode.Escape)) { ShowHideMenu(); } }<p>Дальше нужно создать методы, которые будут вызываться при изменении настроек в меню:</p>
33 public void ChangeVolume(float val) //Изменение звука { volume = val; } public void ChangeResolution(int index) //Изменение разрешения { currResolutionIndex = index; } public void ChangeFullscreenMode(bool val) //Включение или отключение полноэкранного режима { isFullscreen = val; } public void ChangeQuality(int index) //Изменение качества { quality = index; }<p>Выберите необходимый объект, например, список с уровнями качества, и добавьте обработчик события<em>On Value Changed</em>.</p>
33 public void ChangeVolume(float val) //Изменение звука { volume = val; } public void ChangeResolution(int index) //Изменение разрешения { currResolutionIndex = index; } public void ChangeFullscreenMode(bool val) //Включение или отключение полноэкранного режима { isFullscreen = val; } public void ChangeQuality(int index) //Изменение качества { quality = index; }<p>Выберите необходимый объект, например, список с уровнями качества, и добавьте обработчик события<em>On Value Changed</em>.</p>
34 <p>Для этого перетащите объект<em>MenuCanvas</em>и выберите<em>Menu.ChangeQuality</em>. Обратите внимание, что метод указан без скобок. В таком случае значение будет передано ему автоматически. Здесь это индекс выбранного пункта.</p>
34 <p>Для этого перетащите объект<em>MenuCanvas</em>и выберите<em>Menu.ChangeQuality</em>. Обратите внимание, что метод указан без скобок. В таком случае значение будет передано ему автоматически. Здесь это индекс выбранного пункта.</p>
35 <p>Чтобы настройки вступили в силу, нужен ещё один метод:</p>
35 <p>Чтобы настройки вступили в силу, нужен ещё один метод:</p>
36 public void SaveSettings() { audioMixer.SetFloat("MasterVolume", volume); //Изменение уровня громкости QualitySettings.SetQualityLevel(quality); //Изменение качества Screen.fullScreen = isFullscreen; //Включение или отключение полноэкранного режима Screen.SetResolution(Screen.resolutions[currResolutionIndex].width, Screen.resolutions[currResolutionIndex].height, isFullscreen); //Изменения разрешения }<p>Добавьте вызов этого метода при нажатии на кнопку сохранения настроек.</p>
36 public void SaveSettings() { audioMixer.SetFloat("MasterVolume", volume); //Изменение уровня громкости QualitySettings.SetQualityLevel(quality); //Изменение качества Screen.fullScreen = isFullscreen; //Включение или отключение полноэкранного режима Screen.SetResolution(Screen.resolutions[currResolutionIndex].width, Screen.resolutions[currResolutionIndex].height, isFullscreen); //Изменения разрешения }<p>Добавьте вызов этого метода при нажатии на кнопку сохранения настроек.</p>
37 <p>Меню можно улучшить, если сделать автоматическое добавление всех доступных разрешений:</p>
37 <p>Меню можно улучшить, если сделать автоматическое добавление всех доступных разрешений:</p>
38 resolutionDropdown.ClearOptions(); //Удаление старых пунктов resolutions = Screen.resolutions; //Получение доступных разрешений List&lt;string&gt; options = new List&lt;string&gt; (); //Создание списка со строковыми значениями for(int i = 0; i &lt; resolutions.Length; i++) //Поочерёдная работа с каждым разрешением { string option = resolutions [i].width + " x " + resolutions [i].height; //Создание строки для списка options.Add(option); //Добавление строки в список if(resolutions[i].Equals(Screen.currentResolution)) //Если текущее разрешение равно проверяемому { currResolutionIndex = i; //То получается его индекс } } resolutionDropdown.AddOptions(options); //Добавление элементов в выпадающий список resolutionDropdown.value = currResolutionIndex; //Выделение пункта с текущим разрешением resolutionDropdown.RefreshShownValue(); //Обновление отображаемого значения<p>Также сюда стоит добавить возможность выхода из игры:</p>
38 resolutionDropdown.ClearOptions(); //Удаление старых пунктов resolutions = Screen.resolutions; //Получение доступных разрешений List&lt;string&gt; options = new List&lt;string&gt; (); //Создание списка со строковыми значениями for(int i = 0; i &lt; resolutions.Length; i++) //Поочерёдная работа с каждым разрешением { string option = resolutions [i].width + " x " + resolutions [i].height; //Создание строки для списка options.Add(option); //Добавление строки в список if(resolutions[i].Equals(Screen.currentResolution)) //Если текущее разрешение равно проверяемому { currResolutionIndex = i; //То получается его индекс } } resolutionDropdown.AddOptions(options); //Добавление элементов в выпадающий список resolutionDropdown.value = currResolutionIndex; //Выделение пункта с текущим разрешением resolutionDropdown.RefreshShownValue(); //Обновление отображаемого значения<p>Также сюда стоит добавить возможность выхода из игры:</p>
39 public void QuitGame() { Application.Quit(); //Закрытие игры. В редакторе, кончено, она закрыта не будет, поэтому для проверки можно использовать Debug.Log(); }<p>Или перехода на другую сцену:</p>
39 public void QuitGame() { Application.Quit(); //Закрытие игры. В редакторе, кончено, она закрыта не будет, поэтому для проверки можно использовать Debug.Log(); }<p>Или перехода на другую сцену:</p>
40 public void GoToMain() { SceneManager.LoadScene("Menu"); //Переход на сцену с названием Menu }<p>Теперь остается только функция сохранения.</p>
40 public void GoToMain() { SceneManager.LoadScene("Menu"); //Переход на сцену с названием Menu }<p>Теперь остается только функция сохранения.</p>
41 <p>Сохранения представляют собой файлы, в которых хранится информация о текущем состоянии игровых объектов:</p>
41 <p>Сохранения представляют собой файлы, в которых хранится информация о текущем состоянии игровых объектов:</p>
42 <ul><li>позиции игрока;</li>
42 <ul><li>позиции игрока;</li>
43 <li>уровне;</li>
43 <li>уровне;</li>
44 <li>мане;</li>
44 <li>мане;</li>
45 <li>здоровье;</li>
45 <li>здоровье;</li>
46 <li>опыте и так далее.</li>
46 <li>опыте и так далее.</li>
47 </ul><p>Чтобы можно было всё это удобно преобразовать в файл, используется сериализация - специальный инструмент, который позволяет сохранить объект в формате JSON или XML. Лучше сохранять всё в виде бинарных файлов, потому что так игроки не смогут изменить характеристики своего персонажа.</p>
47 </ul><p>Чтобы можно было всё это удобно преобразовать в файл, используется сериализация - специальный инструмент, который позволяет сохранить объект в формате JSON или XML. Лучше сохранять всё в виде бинарных файлов, потому что так игроки не смогут изменить характеристики своего персонажа.</p>
48 <p>Чтобы сохранить данные (координаты, наличие предметов в инвентаре, здоровье), создается класс<em>SaveData</em>:</p>
48 <p>Чтобы сохранить данные (координаты, наличие предметов в инвентаре, здоровье), создается класс<em>SaveData</em>:</p>
49 [System.Serializable] //Обязательно нужно указать, что класс должен сериализоваться public class SaveData { //Создание полей с игровыми параметрами public float currHP; public float HP; public float currMP; public float MP; public float currXP; public float XP; public int level; public float[] position; //В Unity позиция игрока записана с помощью класса Vector3, но его нельзя сериализовать. Чтобы обойти эту проблему, данные о позиции будут помещены в массив типа float. public SaveData(Character character) //Конструктор класса { //Получение данных, которые нужно сохранить HP = character.HP; currHP = character.currHP; MP = character.MP; currMP = character.currMP; XP = character.XP; currXP = character.currXP; level = character.level; position = new float[3] //Получение позиции { character.transform.position.x, character.transform.position.y, character.transform.position.z }; } }<p>Теперь понадобится дополнительный класс, который будет отвечать за сохранение и загрузку данных:</p>
49 [System.Serializable] //Обязательно нужно указать, что класс должен сериализоваться public class SaveData { //Создание полей с игровыми параметрами public float currHP; public float HP; public float currMP; public float MP; public float currXP; public float XP; public int level; public float[] position; //В Unity позиция игрока записана с помощью класса Vector3, но его нельзя сериализовать. Чтобы обойти эту проблему, данные о позиции будут помещены в массив типа float. public SaveData(Character character) //Конструктор класса { //Получение данных, которые нужно сохранить HP = character.HP; currHP = character.currHP; MP = character.MP; currMP = character.currMP; XP = character.XP; currXP = character.currXP; level = character.level; position = new float[3] //Получение позиции { character.transform.position.x, character.transform.position.y, character.transform.position.z }; } }<p>Теперь понадобится дополнительный класс, который будет отвечать за сохранение и загрузку данных:</p>
50 using UnityEngine; using System.IO; //Библиотек для работы с файлами using System.Runtime.Serialization.Formatters.Binary; //Библиотека для работы бинарной сериализацией public static class SaveLoad //Создание статичного класса позволит использовать методы без объявления его экземпляров { private static string path = Application.persistentDataPath + "/gamesave.skillbox"; //Путь к сохранению. Вы можете использовать любое расширение private static BinaryFormatter formatter = new BinaryFormatter(); //Создание сериализатора public static void SaveGame(Character character) //Метод для сохранения { FileStream fs = new FileStream (path, FileMode.Create); //Создание файлового потока SaveData data = new SaveData(character); //Получение данных formatter.Serialize(fs, data); //Сериализация данных fs.Close(); //Закрытие потока } public static SaveData LoadGame() //Метод загрузки { if(File.Exists(path)) { //Проверка существования файла сохранения FileStream fs = new FileStream(path, FileMode.Open); //Открытие потока SaveData data = formatter.Deserialize(fs) as SaveData; //Получение данных fs.Close(); //Закрытие потока return data; //Возвращение данных } else { return null; //Если файл не существует, будет возвращено null } } }<p>Созданные методы будут вызываться из класса<em>Character</em>:</p>
50 using UnityEngine; using System.IO; //Библиотек для работы с файлами using System.Runtime.Serialization.Formatters.Binary; //Библиотека для работы бинарной сериализацией public static class SaveLoad //Создание статичного класса позволит использовать методы без объявления его экземпляров { private static string path = Application.persistentDataPath + "/gamesave.skillbox"; //Путь к сохранению. Вы можете использовать любое расширение private static BinaryFormatter formatter = new BinaryFormatter(); //Создание сериализатора public static void SaveGame(Character character) //Метод для сохранения { FileStream fs = new FileStream (path, FileMode.Create); //Создание файлового потока SaveData data = new SaveData(character); //Получение данных formatter.Serialize(fs, data); //Сериализация данных fs.Close(); //Закрытие потока } public static SaveData LoadGame() //Метод загрузки { if(File.Exists(path)) { //Проверка существования файла сохранения FileStream fs = new FileStream(path, FileMode.Open); //Открытие потока SaveData data = formatter.Deserialize(fs) as SaveData; //Получение данных fs.Close(); //Закрытие потока return data; //Возвращение данных } else { return null; //Если файл не существует, будет возвращено null } } }<p>Созданные методы будут вызываться из класса<em>Character</em>:</p>
51 public void LoadCharacter() { SaveData data = SaveLoad.LoadGame(); //Получение данных if(!data.Equals(null)) //Если данные есть { HP = data.HP; currHP = data.currHP; MP = data.MP; currMP = data.currMP; XP = data.XP; currXP = data.currXP; level = data.level; currHP = data.currHP; transform.position = new Vector3(data.position[0], data.position[1], data.position[2]); } }<p>Можно проверять:</p>
51 public void LoadCharacter() { SaveData data = SaveLoad.LoadGame(); //Получение данных if(!data.Equals(null)) //Если данные есть { HP = data.HP; currHP = data.currHP; MP = data.MP; currMP = data.currMP; XP = data.XP; currXP = data.currXP; level = data.level; currHP = data.currHP; transform.position = new Vector3(data.position[0], data.position[1], data.position[2]); } }<p>Можно проверять:</p>
52 <p>Получился достаточно компактный код. Но чем больше нужно сохранить данных, тем объемнее он будет. Например, информацию о пользователе, настройках и других объектах можно добавить в один большой файл, а можно создать несколько отдельных файлов для них.</p>
52 <p>Получился достаточно компактный код. Но чем больше нужно сохранить данных, тем объемнее он будет. Например, информацию о пользователе, настройках и других объектах можно добавить в один большой файл, а можно создать несколько отдельных файлов для них.</p>
53 <p>Полученных из статьи знаний хватит, чтобы создавать другие интерфейсы в игре:</p>
53 <p>Полученных из статьи знаний хватит, чтобы создавать другие интерфейсы в игре:</p>
54 <ul><li>полосы здоровья;</li>
54 <ul><li>полосы здоровья;</li>
55 <li>уровень персонажа;</li>
55 <li>уровень персонажа;</li>
56 <li>меню паузы.</li>
56 <li>меню паузы.</li>
57 </ul><p>Также вы сможете использовать другие механизмы сохранения, в том числе и чекпоинты. Если же вы хотите подробнее узнать обо всём этом, записывайтесь на курс "<a>Разработчик игр на Unity</a>". Вы будете работать над своими проектами, для которых будете создавать интерфейсы, графику, скрипты, генерацию мира и многое другое.</p>
57 </ul><p>Также вы сможете использовать другие механизмы сохранения, в том числе и чекпоинты. Если же вы хотите подробнее узнать обо всём этом, записывайтесь на курс "<a>Разработчик игр на Unity</a>". Вы будете работать над своими проектами, для которых будете создавать интерфейсы, графику, скрипты, генерацию мира и многое другое.</p>
58 <a>Научитесь: Профессия Разработчик игр на Unity с нуля Узнать больше</a>
58 <a>Научитесь: Профессия Разработчик игр на Unity с нуля Узнать больше</a>