不使用第二個攝像機實現類似開放世界的大地圖功能。
功能如下:
-
按下M鍵打開/關閉大地圖功能
-
打開大地圖時,默認玩家位置居中
-
大地圖支持拖拽,可調節拖拽速度,支持XY軸翻轉
-
支持大地圖設置邊緣偏移量
-
可設置是否啟動拖拽邊界
-
可調節縮放系數
?UGUI結構:
Canvas
? ? ? ? ---Root? (大地圖范圍)
? ? ? ? ? ? ? ? ---MapTiles (瓦片貼圖,可以像離線瓦片地圖那樣分層展示,尺寸:全局鋪滿)
? ? ? ? ? ? ? ? ---playerArrow? (玩家指示器,我用了個箭頭表示,設置大小,居中即可)
1.利用UI輔助地編標記范圍
?如果比例正確,大小不同可以微調縮放系數。
2.自動回正,在拖拽地圖后,再次進入會自動回正,讓玩家的位置處于屏幕中央
3.設置偏移,可以設計地圖邊緣樣式等
稍加修改可以改成小地圖,代碼里就不展示了。原理相同
?代碼如下:
using UnityEngine;
using Color = UnityEngine.Color;public class MapController : MonoBehaviour
{[Header("場景對象")]public Transform player; // 玩家對象public Transform plane; // 場景中的比例尺平面[Header("地圖UI對象")]public RectTransform mapRoot; // 地圖UI的根節點public RectTransform arrowP1; // 地圖上的箭頭[Header("縮放設置")][Tooltip("用于調整Plane大小的縮放系數")]public float scaleFactor = 10f;[Header("快捷鍵")]public KeyCode toggleMapKey = KeyCode.M; // 切換地圖的快捷鍵private MapDragHandler mapDragHandler; // 用來訪問拖拽處理器void Start(){AdjustPlaneScale();mapDragHandler = mapRoot.GetComponent<MapDragHandler>(); // 獲取拖拽處理器組件}void Update(){UpdateArrow();ToggleMap();}/// <summary>/// 根據縮放系數調整Plane的Scale,使其覆蓋場景范圍并匹配MapRoot的比例/// </summary>private void AdjustPlaneScale(){float mapWidth = mapRoot.sizeDelta.x;float mapHeight = mapRoot.sizeDelta.y;float aspectRatio = mapWidth / mapHeight;plane.localScale = new Vector3(scaleFactor * aspectRatio, 1, scaleFactor);}/// <summary>/// 更新地圖上箭頭的位置和旋轉,以反映玩家的當前位置和朝向/// </summary>private void UpdateArrow(){Vector3 playerPos = player.position;float planeWidth = plane.localScale.x * 10f;float planeHeight = plane.localScale.z * 10f;float mapWidth = mapRoot.sizeDelta.x;float mapHeight = mapRoot.sizeDelta.y;float normalizedX = playerPos.x / planeWidth;float normalizedY = playerPos.z / planeHeight;float mapX = normalizedX * mapWidth;float mapY = normalizedY * mapHeight;arrowP1.anchoredPosition = new Vector2(mapX, mapY);float rotationZ = -player.eulerAngles.y;arrowP1.rotation = Quaternion.Euler(0, 0, rotationZ);}private void ToggleMap(){if (Input.GetKeyDown(toggleMapKey) && mapRoot.transform.parent != null){GameObject mapParent = mapRoot.transform.parent.gameObject;bool isActive = !mapParent.activeSelf;mapParent.SetActive(isActive);if (isActive)CenterPlayerOnMap(player);}}/// <summary>/// 調整地圖偏移量以盡量將玩家置于屏幕中心/// </summary>private void CenterPlayerOnMap(Transform player){// 獲取玩家在場景中的位置Vector3 playerPos = player.position;// 計算 Plane 的實際寬度(單位:場景單位)float planeWidth = plane.localScale.x * 10f;float planeHeight = plane.localScale.z * 10f;// 獲取 MapRoot 的實際尺寸(單位:像素)float mapWidth = mapRoot.sizeDelta.x;float mapHeight = mapRoot.sizeDelta.y;// 將場景坐標歸一化到 [-0.5, 0.5] 的比例范圍內float normalizedX = playerPos.x / planeWidth;float normalizedY = playerPos.z / planeHeight;// 將歸一化坐標轉換為 MapRoot 的像素坐標float mapX = normalizedX * mapWidth;float mapY = normalizedY * mapHeight;// 計算偏移量,讓玩家位置對齊到屏幕中心float centerX = 0f; // 中心點的 X 坐標float centerY = 0f; // 中心點的 Y 坐標float offsetX = centerX - mapX;float offsetY = centerY - mapY;if (mapDragHandler.useBoundary){offsetX = Mathf.Clamp(offsetX, mapDragHandler.minBoundary.x, mapDragHandler.maxBoundary.x);offsetY = Mathf.Clamp(offsetY, mapDragHandler.minBoundary.y, mapDragHandler.maxBoundary.y);}// 應用偏移量到地圖根節點mapRoot.anchoredPosition = new Vector2(offsetX, offsetY);}#if UNITY_EDITORprivate void OnValidate(){if (plane != null){AdjustPlaneScale();}}Vector3 center; // 平面的中心點Vector2 size; // 平面的寬度和高度float heightSize = 2.0f; // 高void OnDrawGizmos(){if (plane != null){center = plane.position;size = new Vector2(plane.localScale.x * 10, plane.localScale.z * 10);}// Gizmos是繪制調試輔助圖形的簡單方法Gizmos.color = Color.blue;Vector3 halfSize = new Vector3(size.x / 2, 0, size.y / 2);Vector3 p1 = center + new Vector3(-halfSize.x, 0, -halfSize.z);Vector3 p2 = center + new Vector3(halfSize.x, 0, -halfSize.z);Vector3 p3 = center + new Vector3(halfSize.x, 0, halfSize.z);Vector3 p4 = center + new Vector3(-halfSize.x, 0, halfSize.z);Vector3 p5 = center + new Vector3(-halfSize.x, heightSize, -halfSize.z);Vector3 p6 = center + new Vector3(halfSize.x, heightSize, -halfSize.z);Vector3 p7 = center + new Vector3(halfSize.x, heightSize, halfSize.z);Vector3 p8 = center + new Vector3(-halfSize.x, heightSize, halfSize.z);Gizmos.DrawLine(p1, p2);Gizmos.DrawLine(p2, p3);Gizmos.DrawLine(p3, p4);Gizmos.DrawLine(p4, p1);Gizmos.DrawLine(p5, p6);Gizmos.DrawLine(p6, p7);Gizmos.DrawLine(p7, p8);Gizmos.DrawLine(p8, p5);Gizmos.DrawLine(p1, p5);Gizmos.DrawLine(p2, p6);Gizmos.DrawLine(p3, p7);Gizmos.DrawLine(p4, p8);}
#endif
}
using UnityEngine;
using UnityEngine.EventSystems;public class MapDragHandler : MonoBehaviour, IPointerDownHandler, IDragHandler
{[Header("方向取反設置")]public bool invertX = false; // X方向取反public bool invertY = false; // Y方向取反[Header("拖拽速度")]public float dragSpeed = 1f; // 拖拽速度[Header("拖拽邊界")]public bool useBoundary = false; // 是否使用邊界限制public Vector2 offsetBoundary;// 拖拽的邊界偏移量public Vector2 minBoundary; // 拖拽的最小邊界(左下角)public Vector2 maxBoundary; // 拖拽的最大邊界(右上角)private RectTransform mapRoot; // MapRoot 的 RectTransformprivate Vector2 previousPointerPosition; // 記錄上一次指針位置private void Awake(){// 獲取 RectTransform 組件mapRoot = GetComponent<RectTransform>();if (mapRoot == null){Debug.LogError("MapDragHandler 需要綁定一個具有 RectTransform 的對象!");}useBoundary = mapRoot.rect.width > Screen.width && mapRoot.rect.height > Screen.height;// 設置默認的邊界值,根據你的實際需求調整這些值minBoundary = new Vector2(-(mapRoot.rect.width / 2) + (Screen.width / 2) - offsetBoundary.x, -(mapRoot.rect.height / 2) + (Screen.height / 2) - offsetBoundary.y);maxBoundary = new Vector2((mapRoot.rect.width / 2) - (Screen.width / 2) + offsetBoundary.x, (mapRoot.rect.height / 2) - (Screen.height / 2) + offsetBoundary.y);}// 當指針按下時記錄初始位置public void OnPointerDown(PointerEventData eventData){RectTransformUtility.ScreenPointToLocalPointInRectangle(mapRoot.parent as RectTransform,eventData.position,eventData.pressEventCamera,out previousPointerPosition);}// 拖拽時計算位移public void OnDrag(PointerEventData eventData){Vector2 currentPointerPosition;RectTransformUtility.ScreenPointToLocalPointInRectangle(mapRoot.parent as RectTransform,eventData.position,eventData.pressEventCamera,out currentPointerPosition);// 計算拖拽的偏移量Vector2 delta = currentPointerPosition - previousPointerPosition;// 應用方向取反設置if (invertX) delta.x = -delta.x;if (invertY) delta.y = -delta.y;// 更新 MapRoot 的位置Vector2 newPosition = mapRoot.anchoredPosition + delta * dragSpeed;// 限制位置到指定邊界范圍內if (useBoundary){newPosition.x = Mathf.Clamp(newPosition.x, minBoundary.x, maxBoundary.x);newPosition.y = Mathf.Clamp(newPosition.y, minBoundary.y, maxBoundary.y);}// 設置新的位置mapRoot.anchoredPosition = newPosition;// 更新記錄的指針位置previousPointerPosition = currentPointerPosition;}
}