Как стать автором
Обновить

Unity3d: Как сделать дверь с полного нуля

Время на прочтение6 мин
Количество просмотров8K

Введение

Начинающий разработчик часто задается вопросом: а как сделана та или иная вещь в игре? Даже на первый взгляд простые вещи такие как дверь вызывают затруднения, поэтому сегодня разберем то, как можно создать дверь в Unity с полного нуля.

Процесс создания двери в Unity

С чего начать?

Для начала предлагаю определить задачи по результатам которых на выходе получим готовый продукт. И так давайте подумаем что нам нужно:

  • Модель двери.

  • Скрипт позволяющий управлять дверью.

  • Механизмы для взаимодействия с дверью.

Модель двери

Модель двери должна состоять как минимум из двух частей - это неподвижная часть дверная коробка и подвижная часть дверное полотно. Также можно добавить третий элемент - это дверная ручка.

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

После установки пакета открываем окно ProBuilder через верхнее меню редактора Tools->Probuilder->... Далее выбираем инструмент Door.

И создаем дверной проход:

Далее создаем дверное полотно с помощью инструмента Cube:

Выбираем одну из вершин и задаем опорную точку. Она необходима т.к. относительно ее будет вращаться дверное полотно.

Если у дверного полотна установить Rotation по оси Y то мы увидим как будет выглядеть открытая дверь:

С помощью все того же куба делаем ручку и помещаем ее в объект Leaf нашей иерархии объектов, чтобы она двигалась вместе с дверью:

Создадим материал и добавим текстуру двери. Далее наложим материал на наши детали.

 Для привязки текстуры будем использовать инструмент UV Editor в том же ProBuilder. Привяжем развертку к каждой детали:

В результате модель двери выглядит вот так:

Скрипт для управления дверью

Для управлению дверью есть два варианта. Первый создать анимацию открытия/закрытия и подключить ее в скрипте, но тогда мы теряем гибкость в кастомизации управления дверью. Второй вариант описать вращение с помощью кода и тогда у нас будет возможность гибко настраивать дверь (скорость вращения, кривая вращения, вращения из разного положения и т.п.). Пойдем по второму пути. Вращать дверное полотно будем по оси Y. Для вращения будем использовать начальный и конечный угол в градусах. По умолчанию угол ноль градусов. 

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

[SerializeField] private Transform _rotatingLeaf;
[SerializeField] private AnimationCurve _animationCurve;
[SerializeField] private float _duration = 1.0f;
private Coroutine _rotateCoroutine;

private IEnumerator Rotate(float start, float end)
{
   for (float i = 0; i < 1; i += Time.deltaTime / _duration)
   {
       _rotatingLeaf.transform.rotation = Quaternion.Lerp(
           Quaternion.Euler(0, start, 0),
           Quaternion.Euler(0, end, 0),
           _animationCurve.Evaluate(i));

       yield return null;
   }

   _rotatingLeaf.transform.rotation = Quaternion.Euler(0, end, 0);
   _rotateCoroutine = null;
}

Угол открытия зададим через сериализованное поле.

[Range(-180, 180)] [SerializeField] private float _openAngle = 90.0f;

Чтобы получить текущий угол вращения полотна напишем метод:

private float GetCurrentAngle()
{
   float currentAngle = Quaternion.Angle(Quaternion.identity, _rotatingLeaf.transform.rotation);
   currentAngle *= _openAngle > 0 ? 1 : -1;
   return currentAngle;
}

Так как дверь может находиться в трех положения: закрыта, открыта, приоткрыта, то добавим перечисление:

private enum DoorState
{
   Undefined,
   Open,
   Close,
}

И метод определяющий текущее состояние двери по углу открытия:

private DoorState GetDoorState(float angle)
{
   if (Mathf.Approximately(0, angle))
       return DoorState.Close;

   if (Mathf.Approximately(_openAngle, angle))
       return DoorState.Open;

   return DoorState.Undefined;
}

Осталось дело за малым напишем методы открытия/закрытия:

public void Open()
{
   var currentAngle = GetCurrentAngle();

   if (GetDoorState(currentAngle) == DoorState.Open)
       return;

   if (_rotateCoroutine != null)
       StopCoroutine(_rotateCoroutine);

   _rotateCoroutine = StartCoroutine(Rotate(currentAngle, _openAngle));
}

public void Close()
{
   var currentAngle = GetCurrentAngle();

   if (GetDoorState(currentAngle) == DoorState.Close)
       return;

   if (_rotateCoroutine != null)
       StopCoroutine(_rotateCoroutine);

   _rotateCoroutine = StartCoroutine(Rotate(currentAngle, 0));
}

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

Чтобы дверью можно было управлять только из крайних положений (полностью открыта или полностью закрыта) добавим еще один метод:

public void Toggle()
{
   var currentAngle = GetCurrentAngle();
   if (GetDoorState(currentAngle) == DoorState.Close)
       Open();
   else if (GetDoorState(currentAngle) == DoorState.Open)
       Close();
}

Все, накидываем скрипт на дверь и задаем необходимые параметры, указав в Rotating Leaf наше дверное полотно.

Механизм управления дверью

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


public class DoorTrigger : MonoBehaviour
{
   [SerializeField] private Door _door;

   private void OnTriggerEnter(Collider other)
   {
       if (other.tag == "Player")
       {
           _door.Open();
       }
   }

   private void OnTriggerExit(Collider other)
   {
       if (other.tag == "Player")
       {
           _door.Close();
       }
   }
}

Создаем пустой объект назовем его также DoorTrigger, добавляем на него этот скрипт и добавим компонент BoxCollider. Коллайдеру задаем размеры и устанавливаем флаг isTrigger.

Теперь когда в зону коллайдера переместиться объект с тегом “Player” то дверь автоматически откроется, а если объект покинет зону коллайдера то дверь автоматически закроется.

Заключение

Вот такой краткий гайд получился. Мы с полного нуля создали дверь, начали с модели и закончили автоматизаций. В заключении привожу полный код скрипта + бонусом добавил возможность задавать звуки открытия/закрытия, пользуйтесь на здоровье)

Hidden text
public class DoorController : MonoBehaviour
{
   [Header("Target object")] [Tooltip("Automatically uses an object from LeafRoot")] [SerializeField]
   private Transform _rotatingLeaf;

   [Header("Main")] [SerializeField] private DoorState _state = DoorState.Close;
   [SerializeField] private AnimationCurve _animationCurve;
   [SerializeField] private float _duration = 1.0f;
   [Range(-180, 180)] [SerializeField] private float _openAngle = 90.0f;
   [Header("Audio")] [SerializeField] private AudioClip _openingClip;
   [SerializeField] private AudioClip _closingClip;
   [Header("Optional")] [SerializeField] private AudioSource _audioSource;
   private Coroutine _rotateCoroutine;

   private void Awake()
   {
       AssignLeaf();

       if (!_audioSource)
           _audioSource = gameObject.AddComponent<AudioSource>();
   }

   public void Toggle()
   {
       var currentAngle = GetCurrentAngle();
       if (GetDoorState(currentAngle) == DoorState.Close)
           Open();
       else if (GetDoorState(currentAngle) == DoorState.Open)
           Close();
   }

   public void Open()
   {
       var currentAngle = GetCurrentAngle();

       if (GetDoorState(currentAngle) == DoorState.Open)
           return;

       if (_rotateCoroutine != null)
           StopCoroutine(_rotateCoroutine);

       PlaySound(_closingClip);
       _rotateCoroutine = StartCoroutine(Rotate(currentAngle, _openAngle));
   }

   public void Close()
   {
       var currentAngle = GetCurrentAngle();

       if (GetDoorState(currentAngle) == DoorState.Close)
           return;

       if (_rotateCoroutine != null)
           StopCoroutine(_rotateCoroutine);

       PlaySound(_openingClip);
       _rotateCoroutine = StartCoroutine(Rotate(currentAngle, 0));
   }

   private void OnValidate()
   {
       AssignLeaf();

       switch (_state)
       {
           case DoorState.Open:
               _rotatingLeaf.transform.rotation = Quaternion.Euler(0, _openAngle, 0);
               break;
           case DoorState.Close:
               _rotatingLeaf.transform.rotation = Quaternion.identity;
               break;
       }
   }

   private IEnumerator Rotate(float start, float end)
   {
       for (float i = 0; i < 1; i += Time.deltaTime / _duration)
       {
           _rotatingLeaf.transform.rotation = Quaternion.Lerp(
               Quaternion.Euler(0, start, 0),
               Quaternion.Euler(0, end, 0),
               _animationCurve.Evaluate(i));

           yield return null;
       }

       _rotatingLeaf.transform.rotation = Quaternion.Euler(0, end, 0);
       _rotateCoroutine = null;
   }

   private float GetCurrentAngle()
   {
       float currentAngle = Quaternion.Angle(Quaternion.identity, _rotatingLeaf.transform.rotation);
       currentAngle *= _openAngle > 0 ? 1 : -1;
       return currentAngle;
   }

   private void AssignLeaf()
   {
       if (!_rotatingLeaf)
           _rotatingLeaf = transform;
   }

   private void PlaySound(AudioClip clip)
   {
       _audioSource.clip = clip;
       _audioSource.Play();
   }

   private DoorState GetDoorState(float currentAngle)
   {
       if (Mathf.Approximately(0, currentAngle))
           return DoorState.Close;

       if (Mathf.Approximately(_openAngle, currentAngle))
           return DoorState.Open;

       return DoorState.Undefined;
   }

   private enum DoorState
   {
       Undefined,
       Open,
       Close,
   }
}


Присоединяйтесь к моим соц сетям:

YouTube: https://www.youtube.com/channel/UC8Pm1hZfQMKE8nfSdYqKugg

VK: https://vk.com/stupenkovanton

GitHub: https://github.com/stupenkov

Linkedin: https://www.linkedin.com/in/stupenkov/

Теги:
Хабы:
+2
Комментарии5

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн