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_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;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/65875.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Bootstrap 前端 UI 框架

Bootstrap官网&#xff1a;Bootstrap中文网 铂特优选 Bootstrap 下载 点击进入中文文档 点击下载 生产文件是开发响应式网页应用&#xff0c;源码是底层逻辑代码&#xff0c;因为是要制作响应式网页&#xff0c;所以下载开发文件 引入 css 文件&#xff0c; bootstrap.css 和 …

error: linker `link.exe` not found

开始学习rust&#xff0c;安装好rust的环境&#xff0c;开始从hello world开始&#xff0c;结果用在win10环境下&#xff0c;使用vs code或cmd窗口编译rust报错&#xff1a; PS E:\study_codes\rust-demo\chart01> rustc hello.rs error: linker link.exe not found| note:…

django基于Python的校园个人闲置物品换购平台

Django 基于 Python 的校园个人闲置物品换购平台 一、平台概述 Django 基于 Python 的校园个人闲置物品换购平台是专为校园师生打造的一个便捷、环保且充满活力的线上交易场所。它借助 Django 这一强大的 Python Web 开发框架&#xff0c;整合了校园内丰富的闲置物品资源&…

【Vim Masterclass 笔记10】S06L23:Vim 核心操作训练之 —— 文本的搜索、查找与替换操作(第二部分)

文章目录 S06L23 Search, Find, and Replace - Part Two1 文本替换命令 :s/old/new/2 指定范围的文本替换3 特例&#xff1a;路径的替换4 文件行号的配置5 要点总结&#xff08;1&#xff09;搜索当前行&#xff08;Same Line Searching&#xff09;&#xff08;2&#xff09;跨…

【计算机网络】课程 实验五 静态路由配置

实验五 静态路由配置 一、实验目的 理解静态路由的工作原理&#xff0c;掌握如何配置静态路由。 二、实验分析与设计 【背景描述】 假设校园网分为 2 个区域&#xff0c;每个区域内使用 1 台路由器连接 2 个子网&#xff0c; 现要在路由器上 做适当配置&#xff0c;实现校…

Linux下部署Redis(本地部署超详细)

非docker 1、下载Redis 历史版本&#xff1a; http://download.redis.io/releases 我的&#xff1a; http://download.redis.io/releases/redis-7.0.5.tar.gz 2.安装教程 1.Redis是基于c语言编写的需要安装依赖&#xff0c;需要安装gcc yum install gcc-c 2.查看gcc版…

Spring——几个常用注解

环境配置 1.在配置文件中导入约束(context — 共三个)并添加一项配置( context:annotation-config/) 才能支持注解的使用 context 约束&#xff1a; xmlns:context“http://www.springframework.org/schema/context” 2.xsi:schemaLocation下的&#xff1a;" http://ww…

Oopsie【hack the box】

Oopsie 解题流程 文件上传 首先开启机器后&#xff0c;我们先使用 nmap -sC -SV来扫描一下IP地址&#xff1a; -sC&#xff1a;使用 Nmap 的默认脚本扫描&#xff08;通常是 NSE 脚本&#xff0c;Nmap Scripting Engine&#xff09;。这个选项会自动执行一系列常见的脚本&am…

单片机-定时器中断

1、相关知识 振荡周期1/12us; //振荡周期又称 S周期或时钟周期&#xff08;晶振周期或外加振荡周期&#xff09;。 状态周期1/6us; 机器周期1us; 指令周期1~4us; ①51单片机有两组定时器/计数器&#xff0c;因为既可以定时&#xff0c;又可以计数&#xff0c;故称之为定时器…

【蓝牙】win11 笔记本电脑连接 hc-06

文章目录 前言步骤 前言 使用电脑通过蓝牙添加串口 步骤 设置 -> 蓝牙和其他设备 点击 显示更多设备 更多蓝牙设置 COM 端口 -> 添加 有可能出现卡顿&#xff0c;等待一会 传出 -> 浏览 点击添加 hc-06&#xff0c;如果没有则点击 再次搜索 确定 添加成…

2 XDMA IP中断

三种中断 1. Legacy 定义&#xff1a;Legacy 中断是传统的中断处理方式&#xff0c;使用物理中断线&#xff08;例如 IRQ&#xff09;来传递中断信号。缺点&#xff1a; 中断线数量有限&#xff0c;通常为 16 条&#xff0c;限制了可连接设备的数量。中断处理可能会导致中断风…

【算法】时间复杂度以及O(N^2)的排序

目录 1.常数时间的操作 2.时间复杂度 2.1.以选择排序为例 2.2.O(n^2)从何而来 2.3.冒泡排序 2.3.1.抑或运算 2.4.插入排序 3.二分法 3.1.局部最小 4.递归 4.1.递归行为时间复杂度的估计 1.常数时间的操作 一个操作如果和样本的数据量无关&#xff0c;每次都是固定时…

C# 或 .NetCore 如何使用 NPOI 导出图片到 Excel 文件

今天在本文中&#xff0c;我们将尝试使用NPOI库将图像插入到 Excel 文件的特定位置。请将以下逻辑添加到您的写作方法中&#xff0c;在 Excel 文件中添加图像&#xff08;JPEG、PNG&#xff09;,我已经有一个示例 jpeg 文件 - Read-write-excel-npoi.jpg &#xff0c;我们将尝试…

Observability:将 OpenTelemetry 添加到你的 Flask 应用程序

作者&#xff1a;来自 Elastic jessgarson 待办事项列表可以帮助管理与假期计划相关的所有购物和任务。使用 Flask&#xff0c;你可以轻松创建待办事项列表应用程序&#xff0c;并使用 Elastic 作为遥测后端&#xff0c;通过 OpenTelemetry 对其进行监控。 Flask 是一个轻量级…

使用Matplotlib显示中文的方法

1 问题提出 使用图1所示的代码进行matplotlib绘图时&#xff0c;因为其默认不支持中文&#xff0c;此时无法显示正确内容&#xff0c;如图2所示。 图1 matplotlib绘图绘图代码 图2 matplotlib无法显示中文 2 问题解决 2.1 设置全局字体 在图1所示的代码中&#xff0c;第13…

详解opencv resize之INTER_LINEAR和INTER_AREA

一。先简单介绍一下resize的用法 src&#xff1a;输入图&#xff0c; dst&#xff1a;输出图 dsize&#xff1a;输出图的宽高&#xff0c;如果dsize不为空&#xff08;即宽高都不是0&#xff09;&#xff0c;则以dsize为准进行resize。 fx, fy是放大缩小的比例&#xff0c;是…

UnityDemo-TheBrave-制作笔记

这是我跟着b站up主MStudio的视频学习制作的&#xff0c;大体上没有去做一些更新的东西&#xff0c;这里只是一个总的总结。在文章的最后&#xff0c;我会放上可以游玩该游戏的链接和exe可执行文件&#xff0c;不过没有对游戏内容进行什么加工&#xff0c;只有基本的功能实现罢了…

使用LSTM预测股票收盘价

在金融数据预测中&#xff0c;LSTM&#xff08;长短期记忆网络&#xff09;凭借其在时间序列数据建模中的优势&#xff0c;成为了分析股票价格趋势的热门选择。本篇博客将以完整的代码实现为例&#xff0c;展示如何利用LSTM网络对股票收盘价进行预测&#xff0c;并从数据处理到…

模拟SpringIOCAOP

一、IOC容器 Ioc负责创建&#xff0c;管理实例&#xff0c;向使用者提供实例&#xff0c;ioc就像一个工厂一样&#xff0c;称之为Bean工厂 1.1 Bean工厂的作用 先分析一下Bean工厂应具备的行为 1、需要一个获取实例的方法&#xff0c;根据一个参数获取对应的实例 getBean(…

预编译SQL

预编译SQL 预编译SQL是指在数据库应用程序中&#xff0c;SQL语句在执行之前已经通过某种机制&#xff08;如预编译器&#xff09;进行了解析、优化和准备&#xff0c;使得实际执行时可以直接使用优化后的执行计划&#xff0c;而不需要每次都重新解析和编译。这么说可能有一些抽…