2026-10-12 23:54:03 +03:00
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.EventSystems;
|
|
|
|
|
|
2026-11-04 14:55:41 +01:00
|
|
|
// Камера карты мира, 2.5D (переход на 3D, 04.11.2026): наклонный вид под фиксированным углом, без вращения.
|
|
|
|
|
// Перетаскивание (палец/мышь) двигает карту, два пальца/колёсико -- приближение, границы карты.
|
2026-10-12 23:54:03 +03:00
|
|
|
// Отличает "тап" от "перетаскивания": тап -- это касание, при котором палец почти не сдвинулся.
|
2026-11-04 14:55:41 +01:00
|
|
|
// Карта лежит на земле: координаты карты (x, y) -> мир (x, 0, y).
|
2026-10-12 23:54:03 +03:00
|
|
|
[DefaultExecutionOrder(-10)] // считать тап раньше, чем его прочитает WorldMapController
|
|
|
|
|
[RequireComponent(typeof(Camera))]
|
|
|
|
|
public class MapCameraController : MonoBehaviour
|
|
|
|
|
{
|
2026-11-04 14:55:41 +01:00
|
|
|
[Header("Вид")]
|
|
|
|
|
public float pitch = 55f; // наклон, градусы от горизонта
|
|
|
|
|
public float yaw = 0f; // север карты -- вверху экрана, как было в 2D
|
|
|
|
|
public float distance = 18f;
|
|
|
|
|
|
2026-10-12 23:54:03 +03:00
|
|
|
[Header("Зум")]
|
2026-11-04 14:55:41 +01:00
|
|
|
public float minDistance = 7f;
|
|
|
|
|
public float maxDistance = 42f;
|
2026-10-12 23:54:03 +03:00
|
|
|
public float mouseZoomSpeed = 2f;
|
|
|
|
|
|
|
|
|
|
[Header("Тап")]
|
|
|
|
|
[Tooltip("Если палец сдвинулся меньше, чем на столько пикселей, -- это нажатие, а не перетаскивание")]
|
|
|
|
|
public float tapMaxMovePixels = 15f;
|
|
|
|
|
|
|
|
|
|
// Границы карты в клетках Unity (задаёт WorldMapController)
|
|
|
|
|
[HideInInspector] public Vector2 mapHalfSize = new Vector2(20f, 14f);
|
|
|
|
|
|
|
|
|
|
private Camera cam;
|
2026-11-04 14:55:41 +01:00
|
|
|
private Vector3 target; // точка на земле, на которую смотрит камера
|
2026-10-12 23:54:03 +03:00
|
|
|
private Vector3 lastPointerPosition;
|
|
|
|
|
private Vector3 pressPosition;
|
|
|
|
|
private bool isPressed;
|
|
|
|
|
private bool isDragging;
|
|
|
|
|
private bool pressStartedOverUI;
|
|
|
|
|
private bool hasTap;
|
|
|
|
|
private Vector3 tapWorldPosition;
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
{
|
|
|
|
|
cam = GetComponent<Camera>();
|
2026-11-04 14:55:41 +01:00
|
|
|
cam.orthographic = false;
|
|
|
|
|
cam.fieldOfView = 40f;
|
|
|
|
|
cam.nearClipPlane = 0.3f;
|
|
|
|
|
cam.farClipPlane = 300f;
|
|
|
|
|
Map3D.UseRenderer3D(cam);
|
|
|
|
|
Apply();
|
2026-10-12 23:54:03 +03:00
|
|
|
}
|
|
|
|
|
|
2026-11-04 14:55:41 +01:00
|
|
|
// Было ли за этот кадр нажатие по карте (не по UI и не перетаскивание). Точка -- на земле (x, 0, z)
|
2026-10-12 23:54:03 +03:00
|
|
|
public bool TryGetTap(out Vector3 worldPosition)
|
|
|
|
|
{
|
|
|
|
|
worldPosition = tapWorldPosition;
|
|
|
|
|
return hasTap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CenterOn(Vector3 worldPosition)
|
|
|
|
|
{
|
2026-11-04 14:55:41 +01:00
|
|
|
target = new Vector3(worldPosition.x, 0f, worldPosition.z);
|
2026-10-12 23:54:03 +03:00
|
|
|
ClampToMap();
|
2026-11-04 14:55:41 +01:00
|
|
|
Apply();
|
2026-10-12 23:54:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Update()
|
|
|
|
|
{
|
|
|
|
|
hasTap = false;
|
|
|
|
|
|
|
|
|
|
if (Input.touchCount >= 2)
|
|
|
|
|
{
|
|
|
|
|
HandlePinch();
|
|
|
|
|
isPressed = false; // после щипка не засчитываем отпускание как тап
|
2026-11-04 14:55:41 +01:00
|
|
|
Apply();
|
2026-10-12 23:54:03 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HandlePointer();
|
|
|
|
|
HandleMouseWheel();
|
|
|
|
|
ClampToMap();
|
2026-11-04 14:55:41 +01:00
|
|
|
Apply();
|
2026-10-12 23:54:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HandlePointer()
|
|
|
|
|
{
|
|
|
|
|
if (Input.GetMouseButtonDown(0))
|
|
|
|
|
{
|
|
|
|
|
isPressed = true;
|
|
|
|
|
isDragging = false;
|
|
|
|
|
pressStartedOverUI = IsPointerOverUI();
|
|
|
|
|
pressPosition = Input.mousePosition;
|
|
|
|
|
lastPointerPosition = Input.mousePosition;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isPressed || pressStartedOverUI)
|
|
|
|
|
{
|
|
|
|
|
if (Input.GetMouseButtonUp(0)) isPressed = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Input.GetMouseButton(0))
|
|
|
|
|
{
|
|
|
|
|
if (!isDragging && (Input.mousePosition - pressPosition).magnitude > tapMaxMovePixels)
|
|
|
|
|
isDragging = true;
|
|
|
|
|
|
|
|
|
|
if (isDragging)
|
|
|
|
|
{
|
2026-11-04 14:55:41 +01:00
|
|
|
// Сдвигаем камеру ровно на столько, на сколько сдвинулся палец по земле -- карта "прилипает" к пальцу
|
|
|
|
|
target += GroundPoint(lastPointerPosition) - GroundPoint(Input.mousePosition);
|
|
|
|
|
ClampToMap();
|
|
|
|
|
Apply();
|
2026-10-12 23:54:03 +03:00
|
|
|
}
|
|
|
|
|
lastPointerPosition = Input.mousePosition;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Input.GetMouseButtonUp(0))
|
|
|
|
|
{
|
|
|
|
|
if (!isDragging)
|
|
|
|
|
{
|
|
|
|
|
hasTap = true;
|
2026-11-04 14:55:41 +01:00
|
|
|
tapWorldPosition = GroundPoint(Input.mousePosition);
|
2026-10-12 23:54:03 +03:00
|
|
|
}
|
|
|
|
|
isPressed = false;
|
|
|
|
|
isDragging = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HandlePinch()
|
|
|
|
|
{
|
|
|
|
|
Touch a = Input.GetTouch(0);
|
|
|
|
|
Touch b = Input.GetTouch(1);
|
|
|
|
|
|
|
|
|
|
float previousDistance = ((a.position - a.deltaPosition) - (b.position - b.deltaPosition)).magnitude;
|
|
|
|
|
float currentDistance = (a.position - b.position).magnitude;
|
|
|
|
|
if (currentDistance <= 0.01f) return;
|
|
|
|
|
|
2026-11-04 14:55:41 +01:00
|
|
|
distance = Mathf.Clamp(distance * previousDistance / currentDistance, minDistance, maxDistance);
|
2026-10-12 23:54:03 +03:00
|
|
|
ClampToMap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HandleMouseWheel()
|
|
|
|
|
{
|
|
|
|
|
float scroll = Input.GetAxis("Mouse ScrollWheel");
|
|
|
|
|
if (scroll == 0f || IsPointerOverUI()) return;
|
2026-11-04 14:55:41 +01:00
|
|
|
distance = Mathf.Clamp(distance - scroll * mouseZoomSpeed * 10f, minDistance, maxDistance);
|
2026-10-12 23:54:03 +03:00
|
|
|
}
|
|
|
|
|
|
2026-11-04 14:55:41 +01:00
|
|
|
// Точка, на которую смотрит камера, не уходит за край карты
|
2026-10-12 23:54:03 +03:00
|
|
|
void ClampToMap()
|
|
|
|
|
{
|
2026-11-04 14:55:41 +01:00
|
|
|
target.x = Mathf.Clamp(target.x, -mapHalfSize.x, mapHalfSize.x);
|
|
|
|
|
target.z = Mathf.Clamp(target.z, -mapHalfSize.y, mapHalfSize.y);
|
|
|
|
|
target.y = 0f;
|
|
|
|
|
}
|
2026-10-12 23:54:03 +03:00
|
|
|
|
2026-11-04 14:55:41 +01:00
|
|
|
void Apply()
|
|
|
|
|
{
|
|
|
|
|
Quaternion rot = Quaternion.Euler(pitch, yaw, 0);
|
|
|
|
|
transform.rotation = rot;
|
|
|
|
|
transform.position = target - rot * Vector3.forward * distance;
|
|
|
|
|
}
|
2026-10-12 23:54:03 +03:00
|
|
|
|
2026-11-04 14:55:41 +01:00
|
|
|
// Точка на земле (y = 0) под экранной точкой
|
|
|
|
|
Vector3 GroundPoint(Vector3 screen)
|
|
|
|
|
{
|
|
|
|
|
Ray ray = cam.ScreenPointToRay(screen);
|
|
|
|
|
var plane = new Plane(Vector3.up, Vector3.zero);
|
|
|
|
|
return plane.Raycast(ray, out float t) ? ray.GetPoint(t) : target;
|
2026-10-12 23:54:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool IsPointerOverUI()
|
|
|
|
|
{
|
|
|
|
|
if (EventSystem.current == null) return false;
|
|
|
|
|
if (Input.touchCount > 0) return EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId);
|
|
|
|
|
return EventSystem.current.IsPointerOverGameObject();
|
|
|
|
|
}
|
|
|
|
|
}
|