Контроль камеры от третьего лица [TPS]

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

Скрипт для персонажа PlayerControlTPS:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Rigidbody))]

public class PlayerControlTPS : MonoBehaviour {

	public float speed = 1.5f; // макс. скорость
	public float acceleration = 100f; // ускорение

	public Transform rotate; // объект вращения (локальный)

	public KeyCode jumpButton = KeyCode.Space; // клавиша для прыжка
	public float jumpForce = 10; // сила прыжка
	public float jumpDistance = 1.2f; // расстояние от центра объекта, до поверхности

	private Vector3 direction;
	private float h, v;
	private int layerMask;
	private Rigidbody body;
	private float rotationY;
	private float rotationX;
	
	void Awake()
	{
		body = GetComponent<Rigidbody>();
		body.freezeRotation = true;
		gameObject.tag = "Player";

		// объекту должен быть присвоен отдельный слой, для работы прыжка
		layerMask = 1 << gameObject.layer | 1 << 2;
		layerMask = ~layerMask;
	}
	
	void FixedUpdate()
	{
		body.AddForce(direction.normalized * acceleration * body.mass * speed);
		
		// Ограничение скорости, иначе объект будет постоянно ускоряться
		if(Mathf.Abs(body.velocity.x) > speed)
		{
			body.velocity = new Vector3(Mathf.Sign(body.velocity.x) * speed, body.velocity.y, body.velocity.z);
		}
		if(Mathf.Abs(body.velocity.z) > speed)
		{
			body.velocity = new Vector3(body.velocity.x, body.velocity.y, Mathf.Sign(body.velocity.z) * speed);
		}
	}

	bool GetJump() // проверяем, есть ли коллайдер под ногами
	{
		bool result = false;

		RaycastHit hit;
		Ray ray = new Ray(transform.position, Vector3.down);
		if (Physics.Raycast(ray, out hit, jumpDistance, layerMask))
		{
			result = true;
		}
		
		return result;
	}

	void Update()
	{
		h = Input.GetAxis("Horizontal");
		v = Input.GetAxis("Vertical");

		// вектор направления движения
		direction = new Vector3(h, 0, v);
		direction = Camera.main.transform.TransformDirection(direction);
		direction = new Vector3(direction.x, 0, direction.z);

		if(Mathf.Abs(v) > 0 || Mathf.Abs(h) > 0) // разворот тела по вектору движения
		{
			rotate.rotation = Quaternion.Lerp(rotate.rotation, Quaternion.LookRotation(direction), 10 * Time.deltaTime);
		}

		Debug.DrawRay(transform.position, Vector3.down * jumpDistance, Color.red); // подсветка, для визуальной настройки jumpDistance
		
		if(Input.GetKeyDown(jumpButton) && GetJump())
		{
			body.velocity = new Vector2(0, jumpForce);
		}
	}
}

Это немного переделанный скрипт, который мы ранее использовали для шутера от первого лица. Здесь вектор движения рассчитывается относительно камеры, а не поворота головы, как было ранее. А разворот по вектору происходит, если нажата одна из клавиш движения. При этом разворачиваем мы не сам объект на котором весит Rigidbody, а дочерние объекты, вращение самого Rigidbody заблокировано. В случаи, когда мы вращаем дочерние объекты, у нас больше вариантов, можно выбрать, какие трансформы будут вращаться, а какие будут всегда в исходном виде.

Скрипт для камеры CameraControlTPS:

using UnityEngine;
using System.Collections;

public class CameraControlTPS : MonoBehaviour {

	public enum InversionX {Disabled = 0, Enabled = 1};
	public enum InversionY {Disabled = 0, Enabled = 1};
	public enum Smooth {Disabled = 0, Enabled = 1};

	[Header("General")]
	public float sensitivity = 2; // чувствительность мышки
	public float distance = 5; // расстояние между камерой и игроком
	public float height = 2.3f; // высота

	[Header("Over The Shoulder")]
	public float offsetPosition; // смешение камеры вправо или влево, 0 = центр

	[Header("Clamp Angle")]
	public float minY = 15f; // ограничение углов при наклоне
	public float maxY = 15f;

	[Header("Invert")] // инверсия осей
	public InversionX inversionX = InversionX.Disabled;
	public InversionY inversionY = InversionY.Disabled;

	[Header("Smooth Movement")]
	public Smooth smooth = Smooth.Enabled;
	public float speed = 8; // скорость сглаживания

	private float rotationY;
	private int inversY, inversX;
	private Transform player;

	void Start()
	{
		player = GameObject.FindGameObjectWithTag("Player").transform;
		gameObject.tag = "MainCamera";
	}

	// проверяем, если есть на пути луча, от игрока до камеры, какое-либо препятствие (коллайдер)
	Vector3 PositionCorrection(Vector3 target, Vector3 position)
	{
		RaycastHit hit;
		Debug.DrawLine(target, position, Color.blue);
		if(Physics.Linecast(target, position, out hit)) 
		{
			float tempDistance = Vector3.Distance(target, hit.point);
			Vector3 pos = target - (transform.rotation * Vector3.forward * tempDistance);
			position = new Vector3(pos.x, position.y, pos.z); // сдвиг позиции в точку контакта
		}
		return position;
	}

	void LateUpdate()
	{
		if(player)
		{
			if(inversionX == InversionX.Disabled) inversX = 1; else inversX = -1;
			if(inversionY == InversionY.Disabled) inversY = -1; else inversY = 1;

			// вращение камеры вокруг игрока
			transform.RotateAround(player.position, Vector3.up, Input.GetAxis("Mouse X") * sensitivity * inversX);

			// определяем точку на указанной дистанции от игрока
			Vector3 position = player.position - (transform.rotation * Vector3.forward * distance);
			position = position + (transform.rotation * Vector3.right * offsetPosition); // сдвиг по горизонтали
			position = new Vector3(position.x, player.position.y + height, position.z); // корректировка высоты
			position = PositionCorrection(player.position, position); // находим текущую позицию, относительно игрока

			// поворот камеры по оси Х
			rotationY += Input.GetAxis("Mouse Y") * sensitivity;
			rotationY = Mathf.Clamp(rotationY, -Mathf.Abs(minY), Mathf.Abs(maxY));
			transform.localEulerAngles = new Vector3(rotationY * inversY, transform.localEulerAngles.y, 0);

			if(smooth == Smooth.Disabled) transform.position = position;
			else transform.position = Vector3.Lerp(transform.position, position, speed * Time.deltaTime);
		}
	}
}

Камера не должна быть дочерним объектом! Основные комментарии есть в коде. Стоит отметить, что настроить ее можно как по центру, относительно игрока. Так и сдвинуть к левому или правому плечу, часто такой вид используется при прицеливании.

Скачать демо сцену:

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

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

Офлайн
maribara 3 января 2019
Подскажите как сделать так, что бы персонаж смотрел на определённый объект при зажатой кнопке. Т.е. добавить автоприцеливание
Офлайн
Light 26 сентября 2018
Dr.Jedy.Mr.God, надо внести изменения в скрипт PlayerControlTPS.

Найти:
if(Mathf.Abs(v) > 0 || Mathf.Abs(h) > 0) // разворот тела по вектору движения
{
	rotate.rotation = Quaternion.Lerp(rotate.rotation, Quaternion.LookRotation(direction), 10 * Time.deltaTime);
}

Заменить на:
if(Mathf.Abs(v) > 0 || Mathf.Abs(h) > 0) // разворот тела по вектору движения
{
	rotate.rotation = Quaternion.Lerp(rotate.rotation, Quaternion.LookRotation(direction), 10 * Time.deltaTime);
}
else
{
        Vector3 look = Camera.main.transform.TransformDirection(Vector3.forward);
        look.y = 0;
        rotate.rotation = Quaternion.Lerp(rotate.rotation, Quaternion.LookRotation(look), 10 * Time.deltaTime);
}
Офлайн
Dr.Jedy.Mr.God 25 сентября 2018
Лигхт,
Подскажите, пожалуйста, что нужно удалить и заменить, чтобы персонаж разворачивался вместе с камерой при отсутствии движения?
Я пробовал подключать к персу такой скрипт, работает, но тогда при движении перс жестко трясётся. Колдовство с риджитбоди не помогает.
Офлайн
Akilon 14 мая 2017
Если прыгать на стенку то он там и висит на ней, пока не отжать кнопку "вперёд"))
Офлайн
Light 24 апреля 2017
Light, хм, при включенном Interpolate вроде норм, без него "дрожит". Если возможно, запиши несколько секунд видео и пришли в ЛС ссылку, интересно посмотреть, что и как.
Офлайн
Liphanes 24 апреля 2017
Цитата: Light
Liphanes, а версия юнити какая?
5.6
Офлайн
Light 24 апреля 2017
Liphanes, а версия юнити какая?
Офлайн
Liphanes 24 апреля 2017
Хотя вообще возникает ощущение того что камера как бы не сразу догоняет модель персонажа
Офлайн
Liphanes 24 апреля 2017
Цитата: Light
Liphanes, дрожание есть и в демо (без изменений)?

Да и в демо тоже, я уже предпологаю что это может быть баг самого unity
Офлайн
Light 23 апреля 2017
Liphanes, дрожание есть и в демо (без изменений)?
Офлайн
Liphanes 23 апреля 2017
Light,
К сожалению, по непонятным мне причинам, interpolate не помогает. Дрожание остается, даже не представляю как это еще можно исправить
Офлайн
Light 23 апреля 2017
Liphanes, включить у Rigidbody режим Interpolate.
Офлайн
Liphanes 23 апреля 2017
Есть один вопрос, как вы решили проблему с "дрожанием" игрока. Я сделал подобный контроллер, но игрок (а точнее его модель) "дрожит" при движении.

Проверил ваш скрипт, дрожание осталось. Вообще существует ли возможность избавиться от этого дрожания
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика