Как сделать переход на следующий уровень в unity 3d

Обновлено: 08.07.2024

Вам когда-либо приходилось думать над тем, как сделать управление сценами в вашем проекте менее болезненным? Когда у вас достаточно простая игра, в которой всего несколько сцен идущих одна за другой, то, зачастую, всё проходит гладко. Но когда количество сцен растёт и усложняются переходы между ними — они могу загружаться в разном порядке и поведение некоторых из них должно зависеть от входящих параметров — задача становится менее тривиальной.

Ниже несколько подходов к ее решению, которые мне приходилось видеть чаще всего:

  • Файлы — при переходе из одной сцены в другую, все необходимые данные записываются в JSON/XML файл, а когда следующая сцена загрузилась, считывают их обратно. Как минимум, это медленно (говоря о чтении и записи в файл), да и процесс дебага становится менее удобным.
  • Огромный статический класс, который обрабатывает все возможные переходы между сценами. Они очень похожи на божественные объекты и довольно часто являются причиной утечек памяти, а также боли в нижней части спины, когда новый разработчик пытается понять, что вообще происходит в этой тысяче строк статического кода.
  • DontDestroyOnLoad GameObject — этот подход похож на предыдущий, но представлен GameObject'а в сцене с кучей ссылок в Инспекторе. По сути, это один из тех синглтонов, которые каждый из нас видел в большинстве проектов.

Я хочу показать вам подход, который использую уже не один год. Он помогает сделать переходы более прозрачными для разработчика, становится проще разобраться где и что происходит, а также дебажить.

В каждой сцене у меня есть SceneController . Он отвечает за проброс всех необходимых ссылок и инициализацию ключевых объектов. В некотором смысле, его можно считать точкой входа сцены. Для представления аргументов я использую класс SceneArgs и у каждой сцены есть свой класс, представляющий ее аргументы и являющийся наследником SceneArgs .

Как я уже написал выше, у каждой сцены есть свой контроллер, который наследуется от SceneController .

Я использую отдельный класс для представления аргументов по одной простой причине. Изначально, метод загрузки сцены принимал аргументы в виде массива объектов params object[] args . Это был унифицированный способ для загрузки любой сцены с возможностью передать аргументы. Когда контроллер сцены получал управление, он парсил этот массив объектов и получал все необходимые данные. Но, кроме банального боксинга, здесь была ещё одна проблема — ни для кого, кроме разработчика, который писал этот контроллер (а со временем и для него самого) не было очевидно, какого типа аргументы и в каком порядке нужно передать, чтобы потом не возникло ошибок кастинга. Когда мы создаём новый метод, то в его сигнатуре указываем порядок и типы аргументов и затем IDE может нам подсказать, если при его вызове мы допустили ошибку. Но с аргументом params object[] args мы видим лишь то, что нужно передать массив аргументов и каждый раз, чтобы понять их порядок и типы, разработчику нужно лезть в код контроллера и смотреть как же они парсятся. Мне хотелось сохранить метод запуска таким же унифицированным (один метод для запуска любой сцены), но при этом дать возможность жестко ограничить типы аргументов для каждой из сцен. И для этого нужны ограничения where , которые есть в SceneController .

Как известно, мы должны каждый раз передавать name или buildIndex сцены, чтобы загрузить её через метод LoadScene() или LoadSceneAsync() в Unity API. Этого хотелось бы избежать, потому я использую кастомный атрибут SceneControllerAttribute , чтобы привязать конкретный контроллере к конкретной сцене. Здесь используется имя сцены, а не её buildIndex лишь по той причине, что, на моём опыте, оно реже подвергается изменениям.

Допустим, у нас есть сцена MainMenu . В таком случае, классы её аргументов и контроллера будут иметь следующий вид:

Собственно, это всё (из того, что касается контроллера сцены и её аргументов). Осталось лишь понять, как происходит переход от одной сцены к другой. Этим занимается внезапно статический класс SceneManager . Очень важно, чтобы он был как можно меньше, проще и понятнее. Чтобы он не превратился в один из тех ненавистных божественных объектов с тоннами зависимостей. У него всего лишь одна простая задача — передать управление от контроллера одной сцены к контроллеру следующей. За все последующие инициализации и прочее отвечает уже сам контроллер.

Позвольте мне немного объяснить этот код. При вызове OpenSceneWithArgs() вы передаёте тип контроллера ( TController ) сцены, которую нужно загрузить, тип параметров ( TArgs ) и, собственно, сами параметры ( sceneArgs ). В первую очередь, SceneManager проверяет, есть ли у TController атрибут SceneControllerAttribute . Он должен быть, потому именно он определяет, к какой сцен привязан контроллер TController . Дальше мы просто добавляем аргументы sceneArgs в словарь. Если не было передано каких-либо аргументов, мы создаём экземпляр типа TArgs и присваиваем его свойству IsNull значение true . Если всё прошло гладко, то будет вызван метод из Unity API LoadSceneAsynс() и ему будет передано имя сцены, которое берётся из атрибута SceneControllerAttribute .

Загружается следующая сцена и у её контроллера вызывается метод Awake() . Дальше, как видим в SceneController , TController вызывает SceneManager.GetArgs() , чтобы получить аргументы, которые были переданы и записаны в словарь, а затем производит все необходимые инициализации.

В результате у нас каждая сцена сама отвечает сама за себя, а переходы происходят через небольшой класс SceneManager , отвечающий исключительно за передачу управления между ними. Просто попробуйте применить этот подход в одном из ваших пет проектов и вы заметите, на сколько прозрачнее и понятнее для вас и всех кто работает с вами станут переходы между сценами и их инициализации. Буду рад вашим комментариям. Успехов в разработке!

Как сделать многоуровневую игру в Unity? Или игру, в которой игрок может перемещаться (телепортировать) между локациями?

Один из наиболее частых вариантов реализации заключается в создании для каждого уровня игры отдельной сцены. Итак, алгоритм создания такой игры следующий.

Шаг 1. Создать сцены для каждого уровня и пр. составляющих игры. Продумать понятную и гибкую систему идентификации файлов сцен

Например, сцены могут иметь следующие имена:

  • StartScene – стартовая сцена игры. Это может быть сцена, в которой игрок знакомится с правилами игры, может изменить некоторые начальные настройки, выбрать персонажа и т.д.
  • Layer1,Layer2,Layer3, … — сцены, соответствующие 1-му, 2-му, 3-му и т.д. уровням игры.
  • EndGame – конечная сцена, которая загружается в финале игры.

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

Например, следующий уровень может загружаться при следующих возможных условиях:

а) игрок набрал определённое количество баллов. Это условие имеет смысл проверять каждый кадр в методе Update();

б) игрок добрался до определенного места в игре. Например, пришёл к заданной двери. Соответственно на объекте двери необходимо поставить коллайдер, и при входе в этот коллайдер должна будет выполняться загрузка сцены;

Family Island - меню выбора островов для путешествий и приключений

Family Island — меню выбора островов для путешествий и приключений

Когда речь идёт не столько об уровнях игры, сколько о разных локациях, между которыми игрок имеет возможность перемещаться. Пример такой игры – Family Island – там игрок может путешествовать на различные острова.

Candy Crush Saga — карта игры с выбором уровней

Второй вариант геймплея с аналогичным функционалом – наличие в реализации отдельной сцены с картой игры. Кликая по определённым зонам-кнопкам этой игры, игрок загружает соответствующий уровень (при условии, что этот уровень для него открыт). Наиболее популярный пример такой игры – Candy Crush Saga и ей подобные.

Шаг 3. Написание функции (метода) загрузки соответствующей сцены.

Для загрузки сцен в движке Unity предусмотрен метод LoadScene (…) из класса SceneManager пространства имён UnityEngine.SceneManagement.

Это значит, что в классе (скрипте), в котором будет выполняться загрузка сцены с помощью указанного метода, должна быть прописана директива:

Далее, при вызове метода следует указывать и имя класса:

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

Наиболее простые способы вызова метода следующие.

Вариант 1 – с явным указанием имени загружаемой сцены в качестве параметра. Например:

Вариант 2 – с указанием индекса загружаемой сцены в качестве параметра. Например:

Индекс – это число-порядковый номер сцены, который автоматически присваивается в окне настроек Build Settings:

Окно настроек Build Settings Unity

Окно настроек Build Settings

Один из возможных вариантов скрипта следующий:

Этот скрипт должен быть на объектах-дверях или телепортах. На них же должен стоять коллайдер-триггер.

Шаг 4. Заполнить раздел Scenes In Build в окне настроек Build Settings, внеся туда все сцены, которые необходимо будет загружать

Пример этого окна представлен выше. Чтобы его вызвать, используется главное меню, пункты File -> Build Settings...


Меню File -> Build Settings… Unity

Разберемся, что и как. Переменная sceneEnd используется для перехода на следующую сцену, в любом другом скрипте достаточно написать:

nextLevel - здесь нужно указать следующую сцену, конкретно id. Если хотите указывать по имени, тогда надо изменить переменную:

Далее, Cursor.visible это показывать или не показывать курсор, если Вам нужно чтобы курсор был виден всегда, то удалите соответствующие строки.

Настроим саму сцену. Создаем GameObject -> UI -> Image. Нажмите на переключатель 2D в окне редактора, это нужно чтобы удобней было редактировать элементы UI, после, переключите обратно. И еще надо перекрасить сразу наше изображение в черный цвет.


Не забудьте повесить скрипт на изображение.

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


Осталось убрать галочку с Image иначе будет мешать работать дальше.

Плавный переход между сценами Fade InOut

Всё, можно пробовать!

Осталось уточнить некоторые детали. Чтобы всё работало как полагается, на сцене либо не должно быть других элементов UI, либо наша картинка должна быть поверх всех остальных элементов, например:

Дима Пивоваров


Дима Пивоваров

UNITY ASSETS

К сожалению не когда спрашивать у сообщества умеет или нет, нужно просто репостнуть что бы не потерять когда он понадобится именно мне :)

CreeX

🤣

Действительно, так и надо делать.

Анатолий Малков


Анатолий Малков

Вариант только для очень простеньких игр. Как только появляются неуничтожимые объекты или меняются статичные переменные все накрывается медным тазом. Чтобы сделать рестарт уровня, нужно сбросить все переменные отвечающие за сам игровой процесс

Читайте также: