【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 + H
        public 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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Langchain+文心一言调用

import osfrom langchain_community.llms import QianfanLLMEndpointos.environ["QIANFAN_AK"] "" os.environ["QIANFAN_SK"] ""llm_wenxin QianfanLLMEndpoint()res llm_wenxin.invoke("中国国庆日是哪一天?") print(…

【stm32学习】STM32F103相关特性

| 名称 | 缩写 | 频率 | 外部连接 | 功能 | 用途 | 特性 | |--------------------|------|----------------|---------------|------------|--------------|----------------| | 外部高速晶体振荡器 | HSE | 4~16MHz …

ModernBERT 为我们带来了哪些启示?

当谷歌在 2018 年推出 BERT 模型时&#xff0c;恐怕没有料到这个 3.4 亿参数的模型会成为自然语言处理领域的奠基之作。 六年后的今天&#xff0c;面对动辄千亿参数的大语言模型浪潮&#xff0c;Answer.AI、LightOn与 HuggingFace 联手打造的 ModernBERT 却选择了一条返璞归真的…

IO进程寒假作业DAY6

请使用互斥锁 和 信号量分别实现5个线程之间的同步 使用互斥锁 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include &…

揭示Baklib企业内容管理系统CMS的核心功能与应用价值

内容概要 企业内容管理系统&#xff08;CMS&#xff09;是指通过一系列工具和技术&#xff0c;帮助企业高效地创建、存储、管理和分发数字内容的系统。这些系统在现代企业运作中发挥着至关重要的作用&#xff0c;尤其是在信息量大、业务流程复杂的环境中。Baklib作为一个突出的…

冯诺依曼系统及操作系统

目录 一.冯诺依曼体系结构 二.操作系统 三.系统调用和库函数概念 一.冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是由一…

Vue5---

目录 一、学习目标 1.自定义指令 2.插槽 3.综合案例&#xff1a;商品列表 4.路由入门 二、自定义指令 1.指令介绍 2.自定义指令 3.自定义指令的语法 三、自定义指令-指令的值 1.需求 2.语法 3.代码示例 五、插槽-默认插槽 1.作用 2.需求 4.使用插槽的基本语法…

搭建Spring Boot开发环境

JDK&#xff08;1.8及以上版本&#xff09; Apache Maven 3.6.0 修改settings.xml 设置本地仓库位置 <localRepository>D:/repository</localRepository> 设置远程仓库镜像 <mirror><id>alimaven</id><name>aliyun maven</name&…

Visio2021下载与安装教程

这里写目录标题 软件下载软件介绍安装步骤 软件下载 软件名称&#xff1a;Visio2021软件语言&#xff1a;简体中文软件大小&#xff1a;4.28G系统要求&#xff1a;Windows10或更高&#xff0c;64位操作系统硬件要求&#xff1a;CPU2GHz &#xff0c;RAM4G或更高下载链接&#…

AI 浪潮席卷中国年,开启科技新春新纪元

在这博主提前祝大家蛇年快乐呀&#xff01;&#xff01;&#xff01; 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;其影响力已经渗透到社会生活的方方面面。在中国传统节日 —— 春节期间&#xff0c;AI 技术也展现出了巨大的潜力&#xff0c;为中国年带…

【C++高并发服务器WebServer】-1:Linux中父子进程fork创建及关系、GDB多进程调试

本文目录 一、进程创建二、GDB多进程调试 一、进程创建 在Linux中输入man 2 fork可以查看man文档中的fork的相关函数信息。 fork的作用就是创建一个子进程。 通过fork我们可以知道&#xff0c;创建子进程的时候&#xff0c;复制父进程的信息。 我们看看翻译的man文档信息&am…

中科大:LLM检索偏好优化应对RAG知识冲突

&#x1f4d6;标题&#xff1a;RPO: Retrieval Preference Optimization for Robust Retrieval-Augmented Generation &#x1f310;来源&#xff1a;arXiv, 2501.13726 &#x1f31f;摘要 &#x1f538;虽然检索增强生成&#xff08;RAG&#xff09;在利用外部知识方面表现出…

Antd React Form使用Radio嵌套多个Select和Input的处理

使用Antd React Form使用Radio会遇到嵌套多个Select和Input的处理&#xff0c;需要多层嵌套和处理默认事件和冒泡&#xff0c;具体实现过程直接上代码。 实现效果布局如下图 代码 <Formname"basic"form{form}labelWrap{...formItemLayoutSpan(5, 19)}onFinish{on…

【deepseek】deepseek-r1本地部署-第一步:下载LM Studio

要下载LM Studio&#xff0c;可以按照以下步骤进行&#xff1a; 一、访问LM Studio官方网站 打开必应&#xff08;注意&#xff01;百度无法打开官网&#xff09;&#xff0c;输入LM Studio的官方网址&#xff1a;LM Studio - Discover, download, and run local LLMs。进入L…

爬虫基础之爬取某基金网站+数据分析

声明: 本案例仅供学习参考使用&#xff0c;任何不法的活动均与本作者无关 网站:天天基金网(1234567.com.cn) --首批独立基金销售机构-- 东方财富网旗下基金平台! 本案例所需要的模块: 1.requests 2.re(内置) 3.pandas 4.pyecharts 其他均需要 pip install 模块名 爬取步骤: …

Day27-【13003】短文,什么是栈?栈为何用在递归调用中?顺序栈和链式栈是什么?

文章目录 第三章栈和队列总览第一节栈概览栈的定义及其基本操作如何定义栈和栈的操作&#xff1f;合理的出栈序列个数如何计算&#xff1f;栈的两种存储方式及其实现&#xff1f;顺序栈及其实现&#xff0c;还有对应时间复杂度*、清空栈&#xff0c;初始化栈5、栈空&#xff0c…

Linux:多线程 [1]概念理解

char *str "hello bfr"; *str "H"; "hello bfr"这个字符串存储在虚拟地址空间的代码区中&#xff0c;令str指向它之后当要修改*str时&#xff0c;也就是修改代码区中"hello bfr"位置的值&#xff0c;再将它通过页表映射成物理内存时…

electron typescript运行并设置eslint检测

目录 一、初始化package.json 二、安装依赖 1、安装electron 2、安装typescript依赖 3、安装eslint 三、项目结构 四、配置启动项 一、初始化package.json 我的&#xff1a;这里的"main"没太大影响&#xff0c;看后面的步骤。 {"name": "xlo…

国内优秀的FPGA设计公司主要分布在哪些城市?

近年来&#xff0c;国内FPGA行业发展迅速&#xff0c;随着5G通信、人工智能、大数据等新兴技术的崛起&#xff0c;FPGA设计企业的需求也迎来了爆发式增长。很多技术人才在求职时都会考虑城市的行业分布和发展潜力。因此&#xff0c;国内优秀的FPGA设计公司主要分布在哪些城市&a…

基于微信小程序的电子竞技信息交流平台设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…