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

от автора

Введение

Начинающий разработчик часто задается вопросом: а как сделана та или иная вещь в игре? Даже на первый взгляд простые вещи такие как дверь вызывают затруднения, поэтому сегодня разберем то, как можно создать дверь в 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/


ссылка на оригинал статьи https://habr.com/ru/post/686294/


Комментарии

Добавить комментарий

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