不使用第二个摄像机实现类似开放世界的大地图功能。
功能如下:
-
按下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;}
}