【Unity3D】实现2D小地图效果

目录

一、玩家脚本Player

二、Canvas组件设置

三、小地图相关

四、GameLogicMap脚本修改


  

基于:【Unity3D】Tilemap俯视角像素游戏案例-CSDN博客

2D玩家添加Dotween移动DOPath效果,移动完成后进行刷新小地图(小地图会顺便刷新大地图)

一、玩家脚本Player

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class Player : MonoBehaviour
{void Update(){if (Input.GetMouseButtonDown(0)){Vector2 pos = Input.mousePosition;Ray ray = Camera.main.ScreenPointToRay(pos);RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction);if (hit.collider != null){Vector2 hitPos = hit.point;Vector3Int v3Int = new Vector3Int(Mathf.FloorToInt(hitPos.x), Mathf.FloorToInt(hitPos.y), 0);List<Vector3Int> pathPointList = GameLogicMap.Instance.PlayAstar(v3Int);Vector3[] pathArray = new Vector3[pathPointList.Count];int offset = pathPointList.Count - 1;for (int i = pathPointList.Count - 1; i >= 0; i--){                    Vector3Int pointPos = pathPointList[i];Vector3 worldPos = GameLogicMap.Instance.GetWorldPos(pointPos);pathArray[offset - i] = worldPos;}transform.DOPath(pathArray, 1f).OnComplete(() =>{Debug.Log("移动完成 更新小地图");GameLogicMap.Instance.DrawSmallMap();}).SetAutoKill(true);}}}public Vector3Int GetPos(){Vector3 pos = transform.position;return new Vector3Int(Mathf.FloorToInt(pos.x - 0.5f), Mathf.FloorToInt(pos.y - 0.5f), 0);}
}

二、Canvas组件设置

三、小地图相关

原理:利用2D游戏为了实现寻路而创建的二维数组去生成一张Texture2D纹理图,大地图是直接用int[,]map二维数组去创建,map[x,y]等于1是空地,不等于1是障碍物或不可穿越地形;
大地图上的玩家绿点是直接拿到玩家在map的坐标点直接绘制。

小地图是以玩家为中心的[-smallSize/2, smallSize/2]范围内进行绘制;小地图上的玩家绿点是直接绘制到中心点(smallSize.x/2, smallSize.y/2);

注意:绘制到Texture2D的像素点坐标是[0, size]范围的,则小地图的绘制是SetPixel(i, j, color),传递i, j是[0,size]范围的,而不要传递x, y,这个x,y的取值是以玩家点为中心的[-size/2, size/2]范围坐标值,如下取法:

x = 玩家点.x - size.x/2 + i;
y = 玩家点.y - size.y/2 + j;

用2层for遍历来看的话就是从(玩家点.x - size.x/2, 玩家点.y - size.y/2)坐标点,从下往上,从左往右依次遍历每个map[x,y]点生成对应颜色的像素点,构成一张Texture2D图片。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class SmallMapFor2D : MonoBehaviour
{public Image smallImage;public Vector2Int smallSize;private Vector2Int lastSmallSize;Texture2D smallImageTexture2D;public Image bigImage;Vector2Int bigSize;Texture2D bigImageTexture2D;private Vector3Int playerPoint;//环境二维数组地图[静态] 若是动态需改为自定义类数组,此时是int值类型数组private int[,] map;//初始化大地图整体public void InitBigMap(int[,] map, Vector2Int size){//已初始化则直接退出if (bigImageTexture2D != null)return;this.map = map;this.bigSize = size;bigImageTexture2D = new Texture2D(bigSize.x, bigSize.y);bigImageTexture2D.filterMode = FilterMode.Point;DrawBigMap();}public void DrawBigMap(){int mapWidth = map.GetLength(0);int mapHeight = map.GetLength(1);for (int i = 0; i < bigSize.x; i++){for (int j = 0; j < bigSize.y; j++){//判断是否在map范围内if (i < mapWidth && j < mapHeight){if (map[i, j] == 1) //空地{bigImageTexture2D.SetPixel(i, j, new Color(0.6f, 0.6f, 0.6f, 0.5f));}else //障碍物{bigImageTexture2D.SetPixel(i, j, new Color(0.3f, 0.3f, 0.3f, 0.5f));}}else{//非map范围内bigImageTexture2D.SetPixel(i, j, new Color(0.1f, 0.1f, 0.1f, 0.8f));}}}if (playerPoint != null){//更新大地图玩家点bigImageTexture2D.SetPixel(playerPoint.x, playerPoint.y, Color.green);}bigImageTexture2D.Apply();//bigImage.material.SetTexture("_MainTex", bigImageTexture2D);bigImage.material.mainTexture = bigImageTexture2D;bigImage.SetMaterialDirty();}//动态绘制小地图 时机:玩家移动完成后public void DrawSmallMap(Vector3Int playerPoint){//中途可换小地图大小if (this.lastSmallSize != null && this.lastSmallSize.x != smallSize.x && this.lastSmallSize.y != smallSize.y){if (smallImageTexture2D != null){Destroy(smallImageTexture2D);smallImageTexture2D = null;}smallImageTexture2D = new Texture2D(smallSize.x, smallSize.y);smallImageTexture2D.filterMode = FilterMode.Point;}this.lastSmallSize = smallSize;this.playerPoint = playerPoint;int mapWidth = map.GetLength(0);int mapHeight = map.GetLength(1);        for (int i = 0; i < smallSize.x; i++){for (int j = 0; j < smallSize.y; j++){//中心点是人物点 故绘制点为如下int x = playerPoint.x - smallSize.x / 2 + i;int y = playerPoint.y - smallSize.y / 2 + j;//判断是否在map范围内if (x >= 0 && x < mapWidth && y >= 0 && y < mapHeight){if (map[x, y] == 1) //空地{smallImageTexture2D.SetPixel(i, j, new Color(0.6f, 0.6f, 0.6f, 0.5f));}else //障碍物{smallImageTexture2D.SetPixel(i, j, new Color(0.3f, 0.3f, 0.3f, 0.5f));}}else{//非map范围内smallImageTexture2D.SetPixel(i, j, new Color(0.8f, 0f, 0f, 0.8f));}}}smallImageTexture2D.SetPixel(smallSize.x / 2, smallSize.y / 2, Color.green);        smallImageTexture2D.Apply();//smallImage.material.SetTexture("_MainTex", smallImageTexture2D);smallImage.material.mainTexture = smallImageTexture2D;smallImage.SetMaterialDirty();//更新大地图 因为玩家位置变化//(可优化 不需要重新整张地图再绘制 只更新上一个玩家点和新玩家点,只是要保存的大地图原始数据、旧玩家点更新即可)DrawBigMap();}
}

 小地图2张Image图片的材质球要使用不同的材质球实例

四、GameLogicMap脚本修改

主要变化新增和修改:

初始化绘制大地图和小地图
smallMapFor2D.InitBigMap(map, new Vector2Int(map.GetLength(0), map.GetLength(1)));
DrawSmallMap();

A*寻路方法返回路径数组,这是一个从终点到起点的数组,所以使用时是倒序遍历的。
public List<Vector3Int> PlayAstar(Vector3Int endPos)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Tilemaps;
using UnityEngine.UI;public class GameLogicMap : MonoBehaviour
{public static GameLogicMap _instance;public static GameLogicMap Instance{get { return _instance; }}public Grid terrainGrid;private Tilemap terrainTilemap;public Grid buildGrid;private Tilemap buildTilemap;public Player player;public int[,] map;private Vector3Int mapOffset;public SmallMapFor2D smallMapFor2D;private const int ConstZ = 0;public class Point{public Vector3Int pos;public Point parent;public float F { get { return G + H; } } //F = G + Hpublic float G; //G = parent.G + Distance(parent,self)public float H; //H = Distance(self, end)public string GetString(){return "pos:" + pos + ",F:" + F + ",G:" + G + ",H:" + H + "\n";}}private List<Point> openList = new List<Point>();private List<Point> closeList = new List<Point>();public LineRenderer lineRenderer;private void Awake(){_instance = this;}void Start(){terrainTilemap = terrainGrid.transform.Find("Tilemap").GetComponent<Tilemap>();buildTilemap = buildGrid.transform.Find("Tilemap").GetComponent<Tilemap>();BoundsInt terrainBound = terrainTilemap.cellBounds;BoundsInt buildBound = buildTilemap.cellBounds;map = new int[terrainBound.size.x, terrainBound.size.y];mapOffset = new Vector3Int(-terrainBound.xMin, -terrainBound.yMin, 0);Debug.Log("mapOffset:" + mapOffset);foreach (var pos in terrainBound.allPositionsWithin){var sprite = terrainTilemap.GetSprite(pos);if (sprite != null){SetMapValue(pos.x, pos.y, 1); //空地1}}foreach (var pos in buildBound.allPositionsWithin){var sprite = buildTilemap.GetSprite(pos);if (sprite != null){SetMapValue(pos.x, pos.y, 2); //障碍2}}smallMapFor2D.InitBigMap(map, new Vector2Int(map.GetLength(0), map.GetLength(1)));DrawSmallMap();}//绘制小地图public void DrawSmallMap(){//传递玩家在Map的坐标smallMapFor2D.DrawSmallMap(ToMapPos(player.GetPos()));}private void SetMapValue(int x, int y, int value){map[x + mapOffset.x, y + mapOffset.y] = value;}private Vector3Int ToMapPos(Vector3Int pos){return pos + mapOffset;}public List<Vector3Int> PlayAstar(Vector3Int endPos){endPos = ToMapPos(endPos);Debug.Log(endPos);openList.Clear();closeList.Clear();Vector3Int playerPos = player.GetPos();playerPos = ToMapPos(playerPos);openList.Add(new Point(){G = 0f,H = GetC(playerPos, endPos),parent = null,pos = playerPos,});List<Vector3Int> resultList = CalculateAstar(endPos);if (resultList != null){lineRenderer.positionCount = resultList.Count;for (int i = 0; i < resultList.Count; i++){Vector3Int pos = resultList[i];lineRenderer.SetPosition(i, GetWorldPos(pos));}}else{Debug.LogError("寻路失败;");}return resultList;}public Vector3 GetWorldPos(Vector3Int pos){pos.x = pos.x - mapOffset.x;pos.y = pos.y - mapOffset.y;return terrainTilemap.GetCellCenterWorld(pos);}private List<Vector3Int> CalculateAstar(Vector3Int endPos){int cnt = 0;while (true){//存在父节点说明已经结束            if (openList.Exists(x => x.pos.Equals(endPos))){Debug.Log("找到父节点~" + endPos + ",迭代次数:" + cnt);List<Vector3Int> resultList = new List<Vector3Int>();Point endPoint = openList.Find(x => x.pos.Equals(endPos));resultList.Add(endPoint.pos);Point parent = endPoint.parent;while (parent != null){resultList.Add(parent.pos);parent = parent.parent;}return resultList;}cnt++;if (cnt > 100 * map.GetLength(0) * map.GetLength(1)){Debug.LogError(cnt);return null;}//从列表取最小F值的Point开始遍历Point currentPoint = openList.OrderBy(x => x.F).FirstOrDefault();string str = "";foreach (var v in openList){str += v.GetString();}Debug.Log("最小F:" + currentPoint.GetString() + "\n" + str);Vector3Int pos = currentPoint.pos;for (int i = -1; i <= 1; i++){for (int j = -1; j <= 1; j++){if (i == 0 && j == 0){continue;}//过滤越界、墙体(map[x,y]不等于1)、已处理节点(存在闭合列表的节点)Vector3Int tempPos = new Vector3Int(i + pos.x, j + pos.y, ConstZ);if (tempPos.x < 0 || tempPos.x >= map.GetLength(0) || tempPos.y < 0 || tempPos.y >= map.GetLength(1)|| map[tempPos.x, tempPos.y] != 1|| closeList.Exists(x => x.pos.Equals(tempPos))){continue;}//判断tempPos该节点是否已经计算,  在openList的就是已经计算的Point tempPoint = openList.Find(x => x.pos.Equals(tempPos));float newG = currentPoint.G + Vector3.Distance(currentPoint.pos, tempPos);if (tempPoint != null){//H固定不变,因此判断旧的G值和当前计算出的G值,如果当前G值更小,需要改变节点数据的父节点和G值为当前的,否则保持原样float oldG = tempPoint.G;if (newG < oldG){tempPoint.G = newG;tempPoint.parent = currentPoint;Debug.Log("更新节点:" + tempPoint.pos + ", newG:" + newG + ", oldG:" + oldG + ",parent:" + tempPoint.parent.pos);}}else{tempPoint = new Point(){G = newG,H = GetC(tempPos, endPos),pos = tempPos,parent = currentPoint};Debug.Log("新加入节点:" + tempPoint.pos + ", newG:" + newG + ", parent:" + currentPoint.pos);openList.Add(tempPoint);}}}//已处理过的当前节点从开启列表移除,并放入关闭列表openList.Remove(currentPoint);closeList.Add(currentPoint);}}private float GetC(Vector3Int a, Vector3Int b){return Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);}
}

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

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

相关文章

四.3 Redis 五大数据类型/结构的详细说明/详细使用( hash 哈希表数据类型详解和使用)

四.3 Redis 五大数据类型/结构的详细说明/详细使用&#xff08; hash 哈希表数据类型详解和使用&#xff09; 文章目录 四.3 Redis 五大数据类型/结构的详细说明/详细使用&#xff08; hash 哈希表数据类型详解和使用&#xff09;2.hash 哈希表常用指令(详细讲解说明)2.1 hset …

C#通过3E帧SLMP/MC协议读写三菱FX5U/Q系列PLC数据案例

C#通过3E帧SLMP/MC协议读写三菱FX5U/Q系列PLC数据案例&#xff0c;仅做数据读写报文测试。附带自己整理的SLMP/MC通讯协议表。 SLMP以太网读写PLC数据20191206/.vs/WindowsFormsApp7/v15/.suo , 73216 SLMP以太网读写PLC数据20191206/SLMP与MC协议3E帧通讯协议表.xlsx , 10382…

MySQL知识点总结(十四)

mysqldump和mysqlpump实用程序在功能上有哪些相同和不同的地方&#xff1f; 二者都能用来执行逻辑备份&#xff0c;将所有数据库&#xff0c;特定数据库或特定表转储到文本文件&#xff0c;可移植&#xff0c;独立于存储引擎&#xff0c;是很好的复制/移动策略&#xff0c;适合…

006 LocalStorage和SessionStorage

JWT存储在LocalStorage与SessionStorage里的区别和共同点如下&#xff1a; 区别 数据有效期&#xff1a; • LocalStorage&#xff1a;始终有效&#xff0c;存储的数据会一直保留在浏览器中&#xff0c;即使窗口或浏览器关闭也一直保存&#xff0c;因此常用作持久数据。 • Se…

【算法】经典博弈论问题——威佐夫博弈 python

目录 威佐夫博弈(Wythoff Game)【模板】 威佐夫博弈(Wythoff Game) 有两堆石子&#xff0c;数量任意&#xff0c;可以不同&#xff0c;游戏开始由两个人轮流取石子 游戏规定&#xff0c;每次有两种不同的取法 1)在任意的一堆中取走任意多的石子 2)可以在两堆中同时取走相同数量…

具身智能研究报告

参考&#xff1a; &#xff08;1&#xff09;GTC大会&Figure&#xff1a;“具身智能”奇点已至 &#xff08;2&#xff09;2024中国具身智能创投报告 &#xff08;3&#xff09;2024年具身智能产业发展研究报告 &#xff08;4&#xff09;具身智能行业深度&#xff1a;发展…

把本地搭建的hexo博客部署到自己的服务器上

配置远程服务器的git 安装git 安装依赖工具包 yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel安装编译工具 yum install -y gcc perl-ExtUtils-MakeMaker package下载git&#xff0c;也可以去官网下载了传到服务器上 wget https://www.ke…

STM32 旋转编码器

旋转编码器简介 旋转编码器&#xff1a;用来测量位置、速度或旋转方向的装置&#xff0c;当其旋转轴旋转时&#xff0c;其输出端可以输出与旋转速度和方向对应的方波信号&#xff0c;读取方波信号的频率和相位信息即可得知旋转轴的速度和方向 类型&#xff1a;机械触点式/霍尔传…

安卓逆向之脱壳-认识一下动态加载 双亲委派(二)

一&#xff1a;动态加载与双亲委派模型 在 Java 和 Android 中&#xff0c;ClassLoader 是一个非常重要的组件&#xff0c;负责将 .class 文件或 .dex 文件的字节码加载到内存中&#xff0c;供程序使用。在这其中&#xff0c;有两种关键的概念需要深入理解&#xff1a;动态加载…

网络模型简介:OSI七层模型与TCP/IP模型

计算机网络是现代信息社会的基石&#xff0c;而网络通信的基础在于理解网络模型。网络模型是对通信过程的抽象&#xff0c;它帮助我们理解数据从源到目的地的传输过程。常见的网络模型有 OSI 七层模型 和 TCP/IP 模型&#xff0c;这两种模型在理论和实践中都起着重要作用。 一、…

基于Cipher的Java加密工具类

一、什么是Cipher Cipher是一种用于加密和解密信息的算法或方法。它将原始的明文转化为密文&#xff0c;从而保护信息的安全性和机密性。使用密码学中的数学原理和技术&#xff0c;Cipher可以对数据进行加密处理&#xff0c;使其在传输或存储过程中对未授权的访问者不可读。在必…

[MySQL]事务的理论、属性与常见操作

目录 一、事物的理论 1.什么是事务 2.事务的属性&#xff08;ACID&#xff09; 3.再谈事务的本质 4.为什么要有事务 二、事务的操作 1.事务的支持版本 2.事务的提交模式 介绍 自动提交模式 手动提交模式 3.事务的操作 4.事务的操作演示 验证事务的回滚 事务异常…

后盾人JS--闭包明明白白

延伸函数环境生命周期 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> <…

Python爬虫之——Cookie存储器

目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码总结 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍&#x1f308; 博客主页&#xff1a;请点击——> 一晌小贪欢的博客主页求关注 &…

MFC开发,给对话框添加垂直滚动条并解决鼠标滚动响应的问题

无论在使用QT或者MFC进行界面开发时&#xff0c;都会出现在一个对话框里面存在好多的选项&#xff0c;导致对话框变得非常长或者非常大&#xff0c;就会显现的不美观&#xff0c;在这种情况下通常是添加一个页面的滚动条来解决这个问题&#xff0c;下面我们就来介绍给MFC的对话…

(undone) MIT6.S081 2023 学习笔记 (Day6: LAB5 COW Fork)

网页&#xff1a;https://pdos.csail.mit.edu/6.S081/2023/labs/cow.html 任务1&#xff1a;Implement copy-on-write fork(hard) (doing) 现实中的问题如下&#xff1a; xv6中的fork()系统调用会将父进程的用户空间内存全部复制到子进程中。如果父进程很大&#xff0c;复制过…

分享| RL-GPT 框架通过慢agent和快agent结合提高AI解决复杂任务的能力-Arxiv

结论 “RL-GPT: Integrating Reinforcement Learning and Code-as-policy” RL-GPT 框架为解决大语言模型在复杂任务处理中的难题提供了创新有效的途径&#xff0c; 旨在将强化学习&#xff08;RL&#xff09;和代码即策略相结合&#xff0c; 以解决大语言模型&#xff08…

PPT自动化 python-pptx -7: 占位符(placeholder)

占位符&#xff08;placeholder&#xff09;是演示文稿中用于容纳内容的预格式化容器。它们通过让模板设计者定义格式选项&#xff0c;简化了创建视觉一致幻灯片的过程&#xff0c;同时让最终用户专注于添加内容。这加快了演示文稿的开发速度&#xff0c;并确保幻灯片之间的外观…

【Linux权限】—— 于虚拟殿堂,轻拨密钥启华章

欢迎来到ZyyOvO的博客✨&#xff0c;一个关于探索技术的角落&#xff0c;记录学习的点滴&#x1f4d6;&#xff0c;分享实用的技巧&#x1f6e0;️&#xff0c;偶尔还有一些奇思妙想&#x1f4a1; 本文由ZyyOvO原创✍️&#xff0c;感谢支持❤️&#xff01;请尊重原创&#x1…

一个简单的自适应html5导航模板

一个简单的 HTML 导航模板示例&#xff0c;它包含基本的导航栏结构&#xff0c;同时使用了 CSS 进行样式美化&#xff0c;让导航栏看起来更美观。另外&#xff0c;还添加了一些 JavaScript 代码&#xff0c;用于在移动端实现导航菜单的展开和收起功能。 PHP <!DOCTYPE htm…