Освоение 2D-камер в Unity: руководство для разработчиков игр

Опубликовано: 2022-03-11

Для разработчика камера — один из краеугольных камней процесса разработки игры. От простого показа вашей игры в шахматном приложении до мастерского управления движением камеры в 3D-игре AAA для получения кинематографических эффектов камеры в основном используются в любой видеоигре, когда-либо созданной, даже до того, как они стали называться «камерами».

В этой статье я объясню, как спроектировать систему камер для 2D-игр, а также объясню некоторые моменты, как реализовать ее в одном из самых популярных игровых движков — Unity.

От 2D к 2.5D: расширяемая система камер

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

Система камер, которую мы здесь создаем, предназначена для 2D-платформеров, но может быть легко расширена для других типов 2D-игр, 2,5D-игр или даже 3D-игр.

Освоение 2D-камеры в Unity: руководство для разработчиков игр

Освоение 2D-камеры в Unity: руководство для разработчиков игр
Твитнуть

Я собираюсь разделить функциональность камеры на две основные группы: отслеживание камеры и эффекты камеры.

Отслеживание

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

Эффекты

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

Начиная

Создайте новый 2D-проект в Unity и импортируйте стандартные ресурсы, особенно персонажа RobotBoy. Затем создайте наземный блок и добавьте экземпляр персонажа. Вы должны иметь возможность ходить и прыгать со своим персонажем в текущей сцене. Убедитесь, что камера настроена на ортогональный режим (по умолчанию она установлена ​​на перспективу).

Отслеживание цели

Следующий скрипт добавит основное поведение отслеживания нашей основной камере. Скрипт должен быть прикреплен как компонент к основной камере в вашей сцене, и он предоставляет поле для назначения целевого объекта для отслеживания. Затем скрипт гарантирует, что координаты x и y камеры совпадают с отслеживаемым объектом. Вся эта обработка выполняется на этапе обновления.

 [SerializeField] protected Transform trackingTarget; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x, trackingTarget.position.y, transform.position.z); }

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

Добавление смещения

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

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

 [SerializeField] float xOffset; [SerializeField] float yOffset; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x + xOffset, trackingTarget.position.y + yOffset, transform.position.z); }

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

Сглаживание вещей

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

 [SerializeField] protected float followSpeed; // ... protected override void Update() { float xTarget = trackingTarget.position.x + xOffset; float yTarget = trackingTarget.position.y + yOffset; float xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); float yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); transform.position = new Vector3(xNew, yNew, transform.position.z); } 

Остановите головокружение: блокировка оси

Так как вашему мозгу неприятно смотреть, как камера все время движется вверх-вниз вместе с персонажем, мы вводим блокировку оси. Это означает, что мы можем ограничить отслеживание только одной осью. Затем мы разделим наш код отслеживания на независимое от оси отслеживание и примем во внимание новые флаги блокировки.

 [SerializeField] protected bool isXLocked = false; [SerializeField] protected bool isYLocked = false; // ... float xNew = transform.position.x; if (!isXLocked) { xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); } float yNew = transform.position.y; if (!isYLocked) { yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); } 

Система дорожек

Теперь, когда камера отслеживает игрока только по горизонтали, мы ограничены высотой одного экрана. Если персонаж поднимается по какой-то лестнице или прыгает выше этой, мы должны следовать за ним. Мы делаем это с помощью системы дорожек.

Представьте себе следующий сценарий:

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

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

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

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

Реализация

Возможная реализация состоит в том, чтобы добавить дорожки как простые объекты в сцену. Мы будем использовать их координату Y в паре со смещением Y в приведенном выше скрипте отслеживания для реализации системы. Поэтому их расположение по координатам X и Z не имеет значения.

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

А класс LaneSystem позаботится о перемещении камеры между полосами движения в зависимости от эталонного положения. FollowSpeed ​​снова используется здесь для интерполяции положения, чтобы предотвратить слишком резкое переключение полосы движения:

 [SerializeField] Transform reference; [SerializeField] List<Transform> lanes; [SerializeField] float followSpeed = 5f; // ... void Update() { float targetYCoord = transform.position.y; if (lanes.Count > 1) { int i = 0; for (i = 0; i < lanes.Count - 1; ++i) { if ((reference.position.y > lanes[i].position.y) && (reference.position.y <= lanes[i + 1].position.y)) { targetYCoord = lanes[i].position.y; break; } } if (i == lanes.Count - 1) targetYCoord = lanes[lanes.Count - 1].position.y; } else { targetYCoord = lanes[0].position.y; } float yCoord = Mathf.Lerp(transform.position.y, targetYCoord, Time.deltaTime * followSpeed); transform.position = new Vector3(transform.position.x, yCoord, transform.position.z); }

Эта реализация не является WYSIWYG и оставлена ​​как таковая в качестве упражнения для читателя.

Система узлов блокировки

Движение камеры по дорожкам — это здорово, но иногда нам нужно, чтобы камера была зафиксирована на чем-то, например, на точке интереса (POI) в игровой сцене.

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

Переключение слежения камеры на узел блокировки и обратно может быть выполнено либо простым переключателем, либо системой стека, в которой режимы слежения нажимаются и выталкиваются.

Реализация

Чтобы настроить узел блокировки, просто создайте объект (может быть пустым или, как на скриншоте ниже, спрайт) и прикрепите к нему большой 2D-компонент Circle Collider, чтобы он отмечал область, в которой будет находиться игрок, когда камера сосредоточить узел. Вы можете выбрать любой тип коллайдера, в качестве примера я выбираю Circle. Также создайте тег, который вы можете легко проверить, например «CameraNode», и назначьте его этому объекту.

Добавьте следующее свойство в скрипт отслеживания на вашей камере:

 public Transform TrackingTarget { get { return trackingTarget; } set { trackingTarget = value; } }

Затем прикрепите к проигрывателю следующий скрипт, который позволит ему временно переключить цель камеры на установленный вами узел блокировки. Скрипт также запомнит свою предыдущую цель, чтобы вернуться к ней, когда игрок выйдет за пределы зоны срабатывания. Вы можете пойти дальше и преобразовать это в полный стек, если вам это нужно, но для нашей цели, поскольку мы не перекрываем несколько узлов блокировки, это подойдет. Также имейте в виду, что вы можете настроить положение Circle Collider 2D или снова добавить любой другой вид коллайдера, чтобы активировать блокировку камеры, это всего лишь простой пример.

 public class LockBehavior : MonoBehaviour { #region Public Fields [SerializeField] Camera camera; [SerializeField] string tag; #endregion #region Private private Transform previousTarget; private TrackingBehavior trackingBehavior; private bool isLocked = false; #endregion // Use this for initialization void Start() { trackingBehavior = camera.GetComponent<TrackingBehavior>(); } void OnTriggerEnter2D(Collider2D other) { if (other.tag == tag && !isLocked) { isLocked = true; PushTarget(other.transform); } } void OnTriggerExit2D(Collider2D other) { if (other.tag == tag && isLocked) { isLocked = false; PopTarget(); } } private void PushTarget(Transform newTarget) { previousTarget = trackingBehavior.TrackingTarget; trackingBehavior.TrackingTarget = newTarget; } private void PopTarget() { trackingBehavior.TrackingTarget = previousTarget; } } 

Масштаб камеры

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

2D-зум камеры в Unity 3D можно получить, манипулируя орфографическим размером камеры. Присоединение следующего сценария в качестве компонента к камере и использование метода SetZoom для изменения коэффициента масштабирования приведет к желаемому эффекту. 1,0 означает отсутствие увеличения, 0,5 означает двукратное увеличение, 2 означает двукратное уменьшение и так далее.

 [SerializeField] float zoomFactor = 1.0f; [SerializeField] float zoomSpeed = 5.0f; private float originalSize = 0f; private Camera thisCamera; // Use this for initialization void Start() { thisCamera = GetComponent<Camera>(); originalSize = thisCamera.orthographicSize; } // Update is called once per frame void Update() { float targetSize = originalSize * zoomFactor; if (targetSize != thisCamera.orthographicSize) { thisCamera.orthographicSize = Mathf.Lerp(thisCamera.orthographicSize, targetSize, Time.deltaTime * zoomSpeed); } } void SetZoom(float zoomFactor) { this.zoomFactor = zoomFactor; }

Дрожание экрана

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

Пример реализации того, как это сделать, доступен на GitHub: gist.github.com/ftvs/5822103. Реализация довольно проста. В отличие от других эффектов, которые мы рассмотрели до сих пор, он зависит от некоторой случайности.

Исчезновение и наложение

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

Вот пример этой конфигурации. Обратите внимание, что объект панели пользовательского интерфейса назначается дочернему элементу «Camera Overlay» основного объекта камеры. Camera Overlay предоставляет скрипт под названием Overlay, который включает следующее:

 [SerializeField] Image overlay; // ... public void SetOverlayColor(Color color) { overlay.color = color; } 

Чтобы получить эффект затухания, измените свой сценарий наложения, добавив интерполяцию к целевому цвету, который вы установили с помощью SetOverlayColor, как в следующем сценарии, и установите начальный цвет нашей панели на черный (или белый) и целевой цвет. до окончательного цвета вашего наложения. Вы можете изменить fadeSpeed ​​на то, что соответствует вашим потребностям, я думаю, что 0,8 будет хорошим для начала. Значение fadeSpeed ​​работает как модификатор времени. 1.0 означает, что это произойдет в течение нескольких кадров, но в течение 1 секунды. 0,8 означает, что на самом деле потребуется 1/0,8 = 1,25 секунды.

 public class Overlay : MonoBehaviour { #region Fields [SerializeField] Image overlay; [SerializeField] float fadeSpeed = 5f; [SerializeField] Color targetColor; #endregion void Update() { if (overlay.color != targetColor) { overlay.color = Color.Lerp(overlay.color, targetColor, Time.deltaTime * fadeSpeed); } } #region Public public void SetOverlayColor(Color color) { targetColor = color; } #endregion }

Заворачивать

В этой статье я попытался продемонстрировать основные компоненты, необходимые для создания модульной системы 2D-камеры для вашей игры, а также то, что требуется для ее разработки. Естественно, у всех игр есть свои особые потребности, но с описанными здесь базовым отслеживанием и простыми эффектами вы можете продвинуться далеко вперед, а также иметь план реализации собственных эффектов. Затем вы можете пойти еще дальше и упаковать все в повторно используемый пакет Unity 3D, который вы также можете перенести в другие проекты.

Системы камер очень важны для передачи нужной атмосферы вашим игрокам. Я люблю использовать хорошее сравнение, когда вы думаете о разнице между классическим театром и кино. Камеры и пленка сами по себе привнесли в сцену так много возможностей, что со временем она превратилась в самостоятельный вид искусства, поэтому, если вы не планируете реализовывать еще одну игру «Понг», продвинутые камеры должны быть вашим предпочтительным инструментом в любом игровом проекте, в котором вы работаете. возьмусь отныне.

Связанный: Unity с MVC: как повысить уровень разработки игр