Как сделать двери, с физикой и без?

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

Сборка двери

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

Как сделать двери, с физикой и без?

Обратите внимание, что модель двери должен быть правильно развернута в пространстве относительно осей. Чтобы открывалась она в противоположное направление, относительно направления оси Z (синего цвета) на скриншоте.

Создаем в Door еще один дочерний объект, назовем его Anchor и передвигаем этот якорь в то место, где должны быть дверная петля:


После, делаем модель двери дочерней к Anchor.

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

Без физики

Вешаем на модель двери скрипт Door:

using UnityEngine;
using System.Collections;

public class Door : MonoBehaviour {

	public Transform anchor; // дверная петля
	public float distance = 20; // если игрок уходит на большую дистанцию - скрипт отключается, для оптимизации
	public bool isOpen = false; // на старте сцены дверь открыта?
	public float openAngle = 120;
	public float closeAngle = 0;
	public float smooth = 2;

	private Transform target;

	void Awake () 
	{
		openAngle = Mathf.Abs(openAngle);
		closeAngle = Mathf.Abs(closeAngle);
		if(isOpen) anchor.localRotation = Quaternion.Euler(0, openAngle, 0);
		enabled = false;
	}

	void Update () 
	{
		if(isOpen)
		{
			Quaternion rotation = Quaternion.Euler(0, openAngle, 0);
			anchor.localRotation = Quaternion.Lerp(anchor.localRotation, rotation, smooth * Time.deltaTime);
		}
		else
		{
			Quaternion rotation = Quaternion.Euler(0, closeAngle, 0);
			anchor.localRotation = Quaternion.Lerp(anchor.localRotation, rotation, smooth * Time.deltaTime);
		}

		if(target)
		{
			float dis = Vector3.Distance(transform.position, target.position);
			if(dis > distance) enabled = false;
		}
	}

	public void Invert(Transform player)
	{
		target = player;
		isOpen = !isOpen;
	}
}

На камеру игрока вешаем скрипт DoorControl:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]

public class DoorControl : MonoBehaviour {

	public float distance = 5; // в приделах этой дистанции дверь будет доступна
	public string doorTag = "Door"; // тег двери
	public KeyCode key = KeyCode.F; // клавиша управления
	private Camera cam;

	void Awake () 
	{
		cam = GetComponent<Camera>();
	}

	void Update () 
	{
		if(Input.GetKeyDown(key))
		{
			RaycastHit hit;
			Ray ray = cam.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
			if (Physics.Raycast(ray, out hit, distance))
			{
				if(hit.collider.tag == doorTag)
				{
					hit.transform.GetComponent<Door>().enabled = true;
					hit.transform.GetComponent<Door>().Invert(transform);
				}
			}
		}
	}
}

С физикой

Делаем тоже-самое, но используем другие скрипты.

Вместо Door, вешаем PhysicsDoor:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(HingeJoint))]

public class PhysicsDoor : MonoBehaviour {

	public bool isOpen = false;
	public float force = 50; // добавление силы при открытии замка и для закрытия двери
	public float distance = 20;

	private Rigidbody body;
	private HingeJoint hinge;
	private Vector3 pos;
	private Quaternion rot;
	private Transform target;

	void Awake () 
	{
		pos = transform.localPosition;
		rot = transform.localRotation;

		hinge = GetComponent<HingeJoint>();
		body = GetComponent<Rigidbody>();

		if(!isOpen) body.isKinematic = true;

		JointMotor motor = hinge.motor;
		motor.force = force;
		motor.targetVelocity = force;
		motor.freeSpin = false;
		hinge.motor = motor;

		enabled = false;
	}
	
	public void Invert(Transform player)
	{
		target = player;

		if(!isOpen) 
		{
			body.isKinematic = false;
			body.AddForce((transform.position - Vector3.forward).normalized * force);
		}
		else
		{
			hinge.useMotor = true;
		}

		isOpen = !isOpen;
	}
	
	void Update ()
	{
		if(hinge.angle < 1 && hinge.useMotor)
		{
			hinge.useMotor = false;
			body.isKinematic = true;
			transform.localPosition = pos;
			transform.localRotation = rot;
			isOpen = false;
		}

		if(target)
		{
			float dis = Vector3.Distance(transform.position, target.position);
			if(dis > distance) enabled = false;
		}
	}
}

А вместо DoorControl, цепляем на камеру PhysicsDoorControl:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]

public class PhysicsDoorControl : MonoBehaviour {

	public float distance = 5;
	public string doorTag = "Door";
	public KeyCode key = KeyCode.F;
	private Camera cam;
	
	void Awake () 
	{
		cam = GetComponent<Camera>();
	}
	
	void Update () 
	{
		if(Input.GetKeyDown(key))
		{
			RaycastHit hit;
			Ray ray = cam.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
			if (Physics.Raycast(ray, out hit, distance))
			{
				if(hit.collider.tag == doorTag)
				{
					hit.transform.GetComponent<PhysicsDoor>().enabled = true;
					hit.transform.GetComponent<PhysicsDoor>().Invert(transform);
				}
			}
		}
	}
}

Чтобы физика работала, осталось сделать еще несколько мелочей. Переходим к двери и настраиваем компонент Hinge Joint:


Anchor - регулируем позицию и ставим его в тоже место, где наша дверная петля.
Axis - выбираем ось Y.
Включаем Use Limits и ставим нужные значения.

Теперь физика двери будет работать, если всё правильно настроено.

Скачать и посмотреть в работе:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.

Комментариев 10

Офлайн
DropDeadRed 5 июля 2016
Я еще совсем зеленый и был бы очень благодарен если бы мне помогли в создании двери. Хотелось бы, чтобы если игрок блокирует дверь она упиралась в игрока и продолжила свое движение когда игрок отойдет- Дверь Без физики.
Офлайн
Light 5 июля 2016
DropDeadRed, в этом случаи дверь должна "знать", что упирается в игрока. То бишь в скрипт Door, который весит на коллайдере двери. Если например, добавить переменную:

private bool stop;

Затем в самом начале функции Update, добавить:

if(stop) return;

Затем, где-нибудь в скрипт внести еще пару функций:

void OnCollisionStay(Collision coll)
{
	if(coll.collider.tag == "Player")
	{
		stop = true;
	}
}

void OnCollisionExit(Collision coll)
{
	if(coll.collider.tag == "Player")
	{
		stop = false;
	}
}

Получится, что когда дверь натыкается на коллайдер игрока, движение будет остановлено.
Офлайн
Light,
Анимация закрытия нету он просто перескакивает в начальную позицию, как исправить???
Офлайн
AndreyLunev 4 октября 2017
А что делать, если дверь поворачивается по центру?
Офлайн
Light 4 октября 2017
AndreyLunev, и в чем проблема? Подробнее.
Офлайн
AndreyLunev 4 октября 2017
Light,
Жаль скриншот не могу закинуть.
Суть в том, что поставил дверь, дал двери тэг, навешал анчор, навешал оба скрипта. Всё работает (она открывается), но ось поворота двери по центру самой двери.
Офлайн
Light 4 октября 2017
AndreyLunev, значит якорь не там, нужно делать смещение как в демо.
Офлайн
hotbit13 30 июня 2019
Light,
Что делать если дверь не открывается в билде,но в Юнити работает?
Офлайн
Light 1 июля 2019
hotbit13, может баг какой, попробуй обновить юнити.
Офлайн
hotbit13 1 июля 2019
Light,
Обновил до последней версии,все-равно не работает в билде.
Могу ли я скинуть файл вам?
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика