Карта мира в 2.5D на примитивах

Карта на земле (X/Z): замок, города, лагеря, ресурсные точки, отряды и метки разведки -- примитивы,
подписи смотрят в камеру; наклонная перспективная камера с тем же поведением (перетаскивание, щипок, тап);
туман -- в текстуре земли; солнце и 3D-рендерер URP для камеры (Map3D). Логика и окна не менялись.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-04 14:55:41 +01:00
co-authored by Claude Opus 5.5
parent a3c4a99a79
commit 9a6e5a2e7b
10 changed files with 268 additions and 135 deletions
+46 -22
View File
@@ -1,15 +1,22 @@
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 minOrthoSize = 4f;
public float maxOrthoSize = 16f;
public float minDistance = 7f;
public float maxDistance = 42f;
public float mouseZoomSpeed = 2f;
[Header("Тап")]
@@ -20,6 +27,7 @@ public class MapCameraController : MonoBehaviour
[HideInInspector] public Vector2 mapHalfSize = new Vector2(20f, 14f);
private Camera cam;
private Vector3 target; // точка на земле, на которую смотрит камера
private Vector3 lastPointerPosition;
private Vector3 pressPosition;
private bool isPressed;
@@ -31,9 +39,15 @@ public class MapCameraController : MonoBehaviour
void Awake()
{
cam = GetComponent<Camera>();
cam.orthographic = false;
cam.fieldOfView = 40f;
cam.nearClipPlane = 0.3f;
cam.farClipPlane = 300f;
Map3D.UseRenderer3D(cam);
Apply();
}
// Было ли за этот кадр нажатие по карте (не по UI и не перетаскивание)
// Было ли за этот кадр нажатие по карте (не по UI и не перетаскивание). Точка -- на земле (x, 0, z)
public bool TryGetTap(out Vector3 worldPosition)
{
worldPosition = tapWorldPosition;
@@ -42,8 +56,9 @@ public class MapCameraController : MonoBehaviour
public void CenterOn(Vector3 worldPosition)
{
transform.position = new Vector3(worldPosition.x, worldPosition.y, transform.position.z);
target = new Vector3(worldPosition.x, 0f, worldPosition.z);
ClampToMap();
Apply();
}
void Update()
@@ -54,12 +69,14 @@ public class MapCameraController : MonoBehaviour
{
HandlePinch();
isPressed = false; // после щипка не засчитываем отпускание как тап
Apply();
return;
}
HandlePointer();
HandleMouseWheel();
ClampToMap();
Apply();
}
void HandlePointer()
@@ -86,10 +103,10 @@ public class MapCameraController : MonoBehaviour
if (isDragging)
{
// Сдвигаем камеру ровно на столько, на сколько сдвинулся палец в мире -- карта "прилипает" к пальцу
Vector3 previous = cam.ScreenToWorldPoint(lastPointerPosition);
Vector3 current = cam.ScreenToWorldPoint(Input.mousePosition);
transform.position += previous - current;
// Сдвигаем камеру ровно на столько, на сколько сдвинулся палец по земле -- карта "прилипает" к пальцу
target += GroundPoint(lastPointerPosition) - GroundPoint(Input.mousePosition);
ClampToMap();
Apply();
}
lastPointerPosition = Input.mousePosition;
}
@@ -99,8 +116,7 @@ public class MapCameraController : MonoBehaviour
if (!isDragging)
{
hasTap = true;
tapWorldPosition = cam.ScreenToWorldPoint(Input.mousePosition);
tapWorldPosition.z = 0f;
tapWorldPosition = GroundPoint(Input.mousePosition);
}
isPressed = false;
isDragging = false;
@@ -116,7 +132,7 @@ public class MapCameraController : MonoBehaviour
float currentDistance = (a.position - b.position).magnitude;
if (currentDistance <= 0.01f) return;
cam.orthographicSize = Mathf.Clamp(cam.orthographicSize * previousDistance / currentDistance, minOrthoSize, maxOrthoSize);
distance = Mathf.Clamp(distance * previousDistance / currentDistance, minDistance, maxDistance);
ClampToMap();
}
@@ -124,22 +140,30 @@ public class MapCameraController : MonoBehaviour
{
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (scroll == 0f || IsPointerOverUI()) return;
cam.orthographicSize = Mathf.Clamp(cam.orthographicSize - scroll * mouseZoomSpeed * 5f, minOrthoSize, maxOrthoSize);
distance = Mathf.Clamp(distance - scroll * mouseZoomSpeed * 10f, minDistance, maxDistance);
}
// Камера не уезжает за край карты (если карта меньше экрана -- центрируем)
// Точка, на которую смотрит камера, не уходит за край карты
void ClampToMap()
{
float halfViewHeight = cam.orthographicSize;
float halfViewWidth = halfViewHeight * cam.aspect;
target.x = Mathf.Clamp(target.x, -mapHalfSize.x, mapHalfSize.x);
target.z = Mathf.Clamp(target.z, -mapHalfSize.y, mapHalfSize.y);
target.y = 0f;
}
float maxX = Mathf.Max(0f, mapHalfSize.x - halfViewWidth);
float maxY = Mathf.Max(0f, mapHalfSize.y - halfViewHeight);
void Apply()
{
Quaternion rot = Quaternion.Euler(pitch, yaw, 0);
transform.rotation = rot;
transform.position = target - rot * Vector3.forward * distance;
}
Vector3 p = transform.position;
p.x = Mathf.Clamp(p.x, -maxX, maxX);
p.y = Mathf.Clamp(p.y, -maxY, maxY);
transform.position = p;
// Точка на земле (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()