Files
MVP1/Assets/Scripts/WorldMap/MapCameraController.cs
T
AlexanderandClaude Opus 5.5 9a6e5a2e7b Карта мира в 2.5D на примитивах
Карта на земле (X/Z): замок, города, лагеря, ресурсные точки, отряды и метки разведки -- примитивы,
подписи смотрят в камеру; наклонная перспективная камера с тем же поведением (перетаскивание, щипок, тап);
туман -- в текстуре земли; солнце и 3D-рендерер URP для камеры (Map3D). Логика и окна не менялись.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:55:41 +01:00

176 lines
6.2 KiB
C#

using UnityEngine;
using UnityEngine.EventSystems;
// Камера карты мира, 2.5D (переход на 3D, 04.11.2026): наклонный вид под фиксированным углом, без вращения.
// Перетаскивание (палец/мышь) двигает карту, два пальца/колёсико -- приближение, границы карты.
// Отличает "тап" от "перетаскивания": тап -- это касание, при котором палец почти не сдвинулся.
// Карта лежит на земле: координаты карты (x, y) -> мир (x, 0, y).
[DefaultExecutionOrder(-10)] // считать тап раньше, чем его прочитает WorldMapController
[RequireComponent(typeof(Camera))]
public class MapCameraController : MonoBehaviour
{
[Header("Вид")]
public float pitch = 55f; // наклон, градусы от горизонта
public float yaw = 0f; // север карты -- вверху экрана, как было в 2D
public float distance = 18f;
[Header("Зум")]
public float minDistance = 7f;
public float maxDistance = 42f;
public float mouseZoomSpeed = 2f;
[Header("Тап")]
[Tooltip("Если палец сдвинулся меньше, чем на столько пикселей, -- это нажатие, а не перетаскивание")]
public float tapMaxMovePixels = 15f;
// Границы карты в клетках Unity (задаёт WorldMapController)
[HideInInspector] public Vector2 mapHalfSize = new Vector2(20f, 14f);
private Camera cam;
private Vector3 target; // точка на земле, на которую смотрит камера
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>();
cam.orthographic = false;
cam.fieldOfView = 40f;
cam.nearClipPlane = 0.3f;
cam.farClipPlane = 300f;
Map3D.UseRenderer3D(cam);
Apply();
}
// Было ли за этот кадр нажатие по карте (не по UI и не перетаскивание). Точка -- на земле (x, 0, z)
public bool TryGetTap(out Vector3 worldPosition)
{
worldPosition = tapWorldPosition;
return hasTap;
}
public void CenterOn(Vector3 worldPosition)
{
target = new Vector3(worldPosition.x, 0f, worldPosition.z);
ClampToMap();
Apply();
}
void Update()
{
hasTap = false;
if (Input.touchCount >= 2)
{
HandlePinch();
isPressed = false; // после щипка не засчитываем отпускание как тап
Apply();
return;
}
HandlePointer();
HandleMouseWheel();
ClampToMap();
Apply();
}
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)
{
// Сдвигаем камеру ровно на столько, на сколько сдвинулся палец по земле -- карта "прилипает" к пальцу
target += GroundPoint(lastPointerPosition) - GroundPoint(Input.mousePosition);
ClampToMap();
Apply();
}
lastPointerPosition = Input.mousePosition;
}
if (Input.GetMouseButtonUp(0))
{
if (!isDragging)
{
hasTap = true;
tapWorldPosition = GroundPoint(Input.mousePosition);
}
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;
distance = Mathf.Clamp(distance * previousDistance / currentDistance, minDistance, maxDistance);
ClampToMap();
}
void HandleMouseWheel()
{
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (scroll == 0f || IsPointerOverUI()) return;
distance = Mathf.Clamp(distance - scroll * mouseZoomSpeed * 10f, minDistance, maxDistance);
}
// Точка, на которую смотрит камера, не уходит за край карты
void ClampToMap()
{
target.x = Mathf.Clamp(target.x, -mapHalfSize.x, mapHalfSize.x);
target.z = Mathf.Clamp(target.z, -mapHalfSize.y, mapHalfSize.y);
target.y = 0f;
}
void Apply()
{
Quaternion rot = Quaternion.Euler(pitch, yaw, 0);
transform.rotation = rot;
transform.position = target - rot * Vector3.forward * distance;
}
// Точка на земле (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;
}
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();
}
}