В этой статье попробуем разобрать метод “свободного” перетаскивания 2D камеры на сцене с помощью мыши либо касаний, после прочтения которой вы узнаете как преобразовывать экранные координаты в координаты мира на сцене, как создавать проекции плоскостей и как управлять движением камеры. Примерный результат работы можно увидеть по анимации ниже.
Проекция
Структура Plane представляет собой проекцию поверхности в пространстве. Этот мнимый объект представляет из себя плоскость бесконечного размера, который находится в определенной точке и повернут в определенную сторону. Зачем нужны какие то проекции вообще? Дело в том, что если мы хотим пускать луч из точки нажатия необходимо иметь поверхность с которой этот луч столкнется, чаще всего такие поверхности делают коллайдерами, но так как на сцене у нас кроме камеры и простого спрайта больше ничего нет, нам необходимо иметь какой то другой объект в который можно пускать луч. Этим объектом и является плоскость Plane.
Камера
Все действия камеры будем обрабатывать в скрипте CameraControl.
- public sealed class CameraControl : MonoBehaviour {
- private void Start() {}
- private void Update() {}
- }
В методе Start будем брать компонент камеры на объекте, а в методе Update обрабатывать действия мыши и перемещения. Для этого добавим еще пару методов: UpdateInput, где будем обрабатывать действия мыши либо касаний, и метод UpdatePosition, где будем совершать само перемещение камеры.
- public sealed class CameraControl : MonoBehaviour {
- private void Start() {}
- private void Update() {
- UpdateInput();
- UpdatePosition();
- }
- private void UpdateInput() {}
- private void UpdatePosition() {}
- }
Для обработки действий мыши добавим еще несколько отдельных методов: OnPointDown который будет вызываться при нажатии мышкой либо касании экрана, OnPointMove метод, где будем обрабатывать перемещении мыши по экрану и метод OnPointUp который будет срабатывать при завершении перетаскивания мыши. Также каждый этот метод будет принимать параметры позиции мыши в виде Vector2.
- public sealed class CameraControl : MonoBehaviour {
- private void Start() {}
- private void Update() {
- UpdateInput();
- UpdatePosition();
- }
- private void UpdateInput() {}
- private void UpdatePosition() {}
- private void OnPointDown(Vector2 mousePosition) {}
- private void OnPointMove(Vector2 mousePosition) {}
- private void OnPointUp(Vector2 mousePosition) {}
- }
Теперь необходимо в методе UpdateInput обработать события мыши.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private void UpdateInput() {
- Vector2 mousePosition = Input.mousePosition;
- if (Input.GetMouseButtonDown(0)) OnPointDown(mousePosition);
- else if (Input.GetMouseButtonUp(0)) OnPointUp(mousePosition);
- else if (Input.GetMouseButton(0)) OnPointMove(mousePosition);
- }
- private void OnPointDown(Vector2 mousePosition) {}
- private void OnPointMove(Vector2 mousePosition) {}
- private void OnPointUp(Vector2 mousePosition) {}
- }
Тут все достаточно просто – в зависимости от того какое действие сейчас выполняет мышь, нажатие, движение и тд, вызывается соответствующий метод обработки в который передается позиция мыши на экране. На заметку: действия кнопки мыши под индексом 0 также будут выполняться при обработки касаний.
Добавим несколько переменных для работы скрипта. Первая переменная moveSpeed будет отвечать за скорость перемещения камеры в точку, sensitivity будет отвечать за чувствительность к движениям мыши по экрану, а boolean переменная isDragging будет показывать, что камера перемещается с помощью перетаскивания мыши.
- public sealed class CameraControl : MonoBehaviour {
- [Range(0, 10f)]
- public float moveSpeed = 10f;
- [Range(0f, 5f)]
- public float sensitivity = 3;
- public bool isDragging { get; private set; }
- /*…остальной код…*/
- }
Еще добавим переменную камеры, в которую мы будем помещать сам компонент Camera в методе Start.
- public sealed class CameraControl : MonoBehaviour {
- [Range(0, 10f)]
- public float moveSpeed = 10f;
- [Range(0f, 5f)]
- public float sensitivity = 3;
- public bool isDragging { get; private set; }
- public new Camera camera { get; private set; }
- private void Start() {
- this.camera = GetComponent<Camera>();
- }
- /*…остальной код…*/
- }
И так задача скрипта заключается в следующем: необходимо перемещать камеру в том направлении в котором мы двигаем мышкой либо касанием по экрану. Так как мы уже обрабатываем действия мыши в трех методах OnPointDown, OnPointMove и OnPointUp, необходимо экранные координаты перевести в координаты сцены, то есть мировые, для чего нам и понадобятся рейкасты и проекция Plane. Новый метод GetWorldPoint будет принимать экранные координаты мыши и переводить их в мировые.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private Vector2 GetWorldPoint(Vector2 mousePosition) {
- Vector2 point = Vector2.zero;
- Ray ray = this.camera.ScreenPointToRay(mousePosition);
- return point;
- }
- }
Для начала инициируем новую переменную point и переменную ray в которую поместим рейкаст, вызвав метод ScreenPointToRay у компонента камеры. Для создания проекции необходимо указать ее место расположения на сцене – position, и направлении в которую она смотрит normal.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private Vector2 GetWorldPoint(Vector2 mousePosition) {
- Vector2 point = Vector2.zero;
- Ray ray = this.camera.ScreenPointToRay(mousePosition);
- Vector3 normal = Vector3.forward;
- Vector3 position = Vector3.zero;
- Plane plane = new Plane(normal, position);
- return point;
- }
- }
При работе в орфографическом режиме на камере направление чаще всего будет forward, то есть туда же куда смотрит камера. Проекция готова, теперь необходимо пустить в нее луч с помощью метода Raycast и получить мировые координаты точки.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private Vector2 GetWorldPoint(Vector2 mousePosition) {
- Vector2 point = Vector2.zero;
- Ray ray = this.camera.ScreenPointToRay(mousePosition);
- Vector3 normal = Vector3.forward;
- Vector3 position = Vector3.zero;
- Plane plane = new Plane(normal, position);
- float distance;
- plane.Raycast(ray, out distance);
- point = ray.GetPoint(distance);
- return point;
- }
- }
Готово, теперь переходим к обработке действий мыши. В методе OnPointDown будем указывать точку отсчета координат движения мыши.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private Vector2 tempCenter, targetDirection, tempMousePos;
- private void OnPointDown(Vector2 mousePosition) {
- this.tempCenter = GetWorldPoint(mousePosition);
- this.targetDirection = Vector2.zero;
- this.tempMousePos = mousePosition;
- this.isDragging = true;
- }
- }
Во время нажатия левой кнопки мыши по экрану, получаем мировые координаты в в переменной tempCenter, это и станет точкой отсчета перемещения мыши. Теперь опишем действия при окончании перемещения мыши в методе OnPointUp.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private void OnPointUp(Vector2 mousePosition) {
- this.isDragging = false;
- }
- }
Здесь мы просто задаем переменно isDragging значение false, и переходим в метод OnPointMove, где будем обрабатывать движения мыши по экрану.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private void OnPointMove(Vector2 mousePosition) {
- if (this.isDragging) {
- }
- }
- }
Для начала проверим условие, что мы сейчас совершаем перетаскивание камеры мышкой используя переменную isDragging. Далее определим текущую мировую координаты позиции мышки с помощью метода GetWorldPoint.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private void OnPointMove(Vector2 mousePosition) {
- if (this.isDragging) {
- Vector2 point = GetWorldPoint(mousePosition);
- }
- }
- }
Для того чтобы камера не двигалась при минимальном движении мышки, добавим новое условие, в котором укажем минимальное расстояние для совершения действия перемещения.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private void OnPointMove(Vector2 mousePosition) {
- if (this.isDragging) {
- Vector2 point = GetWorldPoint(mousePosition);
- float sqrDist = (this.tempCenter – point).sqrMagnitude;
- if (sqrDist > 0.1f) {
- }
- }
- }
- }
После чего можно определить направление в котором необходимо будет переместить камеру с помощью метода normalized.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private void OnPointMove(Vector2 mousePosition) {
- if (this.isDragging) {
- Vector2 point = GetWorldPoint(mousePosition);
- float sqrDist = (this.tempCenter – point).sqrMagnitude;
- if (sqrDist > 0.1f) {
- this.targetDirection = (this.tempMousePos – mousePosition).normalized;
- this.tempMousePos = mousePosition;
- }
- }
- }
- }
Перемещать камеру будем в методе UpdatePosition используя текущую позицию камеры, направление движения и чувствительность.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private void UpdatePosition() {
- float speed = Time.deltaTime * this.moveSpeed;
- if (this.isDragging) this.tempSens = this.sensitivity;
- else this.tempSens = Mathf.Lerp(this.tempSens, 0f, speed);
- }
- }
В переменной speed укажем скорость перемещения используя дельту задержки Time.deltaTime и переменную moveSpeed. Дальше с помощью условия определяем текущую чувствительность мыши в переменной tempSens. После чего можно наконец задать новую точку куда следует переместить камеру.
- public sealed class CameraControl : MonoBehaviour {
- /*…остальной код…*/
- private void UpdatePosition() {
- float speed = Time.deltaTime * this.moveSpeed;
- if (this.isDragging) this.tempSens = this.sensitivity;
- else this.tempSens = Mathf.Lerp(this.tempSens, 0f, speed);
- Vector2 newPosition = this.transform.position + this.targetDirection * this.tempSens;
- this.transform.position = Vector2.Lerp(this.position, newPosition, speed);
- }
- }
Ребят, если кто-то искал, как я, простое решение, то вот оно: public class CameraMove : MonoBehaviour { Vector3 touch; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if(Input.GetMouseButtonDown(0)) { touch = Camera.main.ScreenToWorldPoint(Input.mousePosition); } if (Input.GetMouseButton(0)) { Vector3 direction = touch – Camera.main.ScreenToWorldPoint(Input.mousePosition); Camera.main.transform.position += direction; } } } перетаскиваете на камеру и всё! а с границами и с зумом дальше сами, там всё тоже просто!
я пытался вникнуть, как новичок – не вышло…
у меня проект 2D, думал тут будет банально три метода, как бы вроде это всё, что нужно, грубо говоря
private void OnPointDown(Vector2 mousePosition) {}
private void OnPointMove(Vector2 mousePosition) {}
private void OnPointUp(Vector2 mousePosition) {}
плюнул вникать, решил скопировать – юнити выдаёт 18 ошибок…
поищу что-то по проще и доступнее для понимания…
нельзя весь код сразу написать?
М-да уж. Вы наверное даже не переписали уже готовый скрипт и просто нажали ctrl + c, ctrl + v. Вы хоть пытались вникнуть в то как работает этот скрипт?