当前位置: 首页 > article >正文

Unity 大地图功能 离线瓦片地图

不使用第二个摄像机实现类似开放世界的大地图功能。

功能如下:

  • 按下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_EDITOR

    private 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 的 RectTransform
    private 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;
    }
}


http://www.kler.cn/a/501686.html

相关文章:

  • 探索图像编辑的无限可能——Adobe Photoshop全解析
  • 【深度学习】Pytorch:调度器与学习率衰减
  • mermaid大全(语法、流程图、时序图、甘特图、饼图、用户旅行图、类图)
  • 【机器学习】农业 4.0 背后的智慧引擎:机器学习助力精准农事决策
  • Linux 系统中两个不同的目录/proc/device-tree 和 /dev
  • Vue的生命周期方法
  • python-leetcode-三数之和
  • h5使用better scroll实现左右列表联动
  • c++ haru生成pdf输出文本实例
  • Java 如何传参xml调用接口获取数据
  • 后端开发 Springboot整合Redis Spring Data Redis 模板
  • 【大数据】数据科学导论---数据科学的概念
  • 状态模式详解与应用
  • 人工智能之基于阿里云快速搭建语音合成
  • Seata的部署与微服务集成
  • pytorch张量的new_zeros方法介绍
  • python-leetcode-有效的数独
  • Java 将RTF文档转换为Word、PDF、HTML、图片
  • uniapp使用scss mixin抽离css常用的公共样式
  • PyTorch reshape函数介绍
  • 使用Cilium/eBPF实现大规模云原生网络和安全
  • MongoDB 删除集合
  • nginx增加新模块
  • Python orjson ujson有什么区别?
  • 【DevOps】Jenkins使用Pipeline构建java代码
  • AIGC是什么?怎么用?简单三步ToDesk云电脑快速用