Контроль камеры от третьего лица [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

Офлайн
Liphanes 23 апреля 2017
Есть один вопрос, как вы решили проблему с "дрожанием" игрока. Я сделал подобный контроллер, но игрок (а точнее его модель) "дрожит" при движении.

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

Да и в демо тоже, я уже предпологаю что это может быть баг самого unity
Офлайн
Liphanes 24 апреля 2017
Хотя вообще возникает ощущение того что камера как бы не сразу догоняет модель персонажа
Офлайн
Light 24 апреля 2017
Liphanes, а версия юнити какая?
Офлайн
Liphanes 24 апреля 2017
Цитата: Light
Liphanes, а версия юнити какая?
5.6
Офлайн
Light 24 апреля 2017
Light, хм, при включенном Interpolate вроде норм, без него "дрожит". Если возможно, запиши несколько секунд видео и пришли в ЛС ссылку, интересно посмотреть, что и как.
Офлайн
Akilon 14 мая 2017
Если прыгать на стенку то он там и висит на ней, пока не отжать кнопку "вперёд"))
Офлайн
Dr.Jedy.Mr.God 25 сентября 2018
Лигхт,
Подскажите, пожалуйста, что нужно удалить и заменить, чтобы персонаж разворачивался вместе с камерой при отсутствии движения?
Я пробовал подключать к персу такой скрипт, работает, но тогда при движении перс жестко трясётся. Колдовство с риджитбоди не помогает.
Офлайн
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);
}
Офлайн
maribara 3 января 2019
Подскажите как сделать так, что бы персонаж смотрел на определённый объект при зажатой кнопке. Т.е. добавить автоприцеливание
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика