【Unity3D】Tilemap俯视角像素游戏案例

目录

一、导入Tilemap

二、导入像素风素材

三、使用Tilemap制作地图

3.1 制作Tile Palette素材库 

3.2 制作地图

四、实现A*寻路

五、待完善


一、导入Tilemap

Unity 2019.4.0f1 已内置Tilemap
需导入2D Sprite、2D Tilemap Editor、以及一个我没法正常搜出的2D Tilemap Extras

GitHub - Unity-Technologies/2d-extras: Fun 2D Stuff that we'd like to share!

 2D Tilemap Extras搜索对应Unity版本的下载压缩包并解压到工程Assets下

二、导入像素风素材

Assets · Kenney

三、使用Tilemap制作地图

3.1 制作Tile Palette素材库 

首先打开Tile Palette窗口(相当于素材库)

创建一个TilePalette文件

此时你这里是空的,点击Edit,然后去到Project窗口选择所有图片拖追到Tile Palette窗口编辑区域。会生成这些Tile文件

每个tile文件都会有如上信息,图片、颜色、碰撞体类型(Sprite依赖精灵透明度生成碰撞盒、Grid直接生成矩形网格)

至此你就可以开始在Scene场景上用这个资源库去绘制2D地图了,但是为了效率制作有规则的地形,我们可以制作一些Rule Tile规则瓦片来进行加速绘制地形。

自上而下分别是:tile_0025、tile_0012、tile_0014、tile_0036、tile_0038、tile_0026、tile_0024、tile_0037、tile_0013、tile_0039、tile_0040、tile_0041、tile_0042。

这个九宫格红色×和绿色剪头分别代表:空地形、非空地形,如上图则是代表这个左上角的图片,它的出现规则是当左边和上边是空地形,且右边和下边是非空地形时会出现。这里有个小bug,即这组素材没有内边,例如弄一个“回”地形的中空地形会出现问题。

之后,将我们制作好的Rule Tile拖拽到Tile Palette素材库

为了直观化可以弄成3*3样式,如下,点击Edit,再进行如下操作、选中+绘制

类似的灰色的地形也是如此。

3.2 制作地图

摄像机调整,俯视角(正交),控制可视范围,如宽度[-20,20],那么就要设置Size为11.25

 即20 * 高宽比(1080/1920)

创建Terrain地形tilemap

需要给Tilemap新增如下3个组件,并设置,其中Composite Collider 2D是合并碰撞盒,并采用几何网格形式合并(默认Outlines 边框碰撞体),必须要使用几何网格形式,因为我们之后要对这个2D碰撞体进行2D射线检测,若是边框碰撞体则无法正常射线检测到,你可以理解边框碰撞体是镂空的碰撞体,它只有边缘的2D线条是碰撞实体。

之后在Scene场景绘制地形即可,如下操作,先打开素材库Tile Palette,再选中画笔后,选择素材库的其中一个素材,例如泥土地形,然后直接去到Scene窗口左键白色描边格子绘制。注意要选中的是我们Rule Tile相关的泥土地形才能生效我们的九宫格规则去创建地形。

此时你会发现若想在地形上创类似树、房子、井盖等其他非地形素材时,会破坏已有地形的。

为此我们需要再创一个Build建筑Tilemap去绘制我们其他的非地形素材,注意这2个tilemap的位置、偏移、锚点啥的要保持一致,这个Tilemap不需要刚体、碰撞体。

需要将Build层级修改比Terrain大(Terrain Order In Layer是0)即可,如下

四、实现A*寻路

参考:【Unity3D】A*寻路(2D究极简单版)_unity2d a星巡路-CSDN博客

using System.Collections;
using System.Collections.Generic;
using UnityEngine;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);GameLogicMap.Instance.PlayAstar(v3Int);}}}public Vector3Int GetPos(){Vector3 pos = transform.position;return new Vector3Int(Mathf.FloorToInt(pos.x - 0.5f), Mathf.FloorToInt(pos.y - 0.5f), 0);}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Tilemaps;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;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}}//terrainTilemap.getworld//PlayAstar(new Vector3Int(-8, -6, 0));}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 void 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("寻路失败;");}}private 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;}//过滤越界、墙体(非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);}
}

五、待完善

1、未有角色移动部分代码

2、A*寻路点击到的位置如果是障碍物(Build类型地形)那么就会死循环卡死,应该加层判断必须点击到的是非障碍物、可行走地形。

3、其他的游戏细节,例如如何与房子门交互,进门是换场景还是瞬移角色到另一个坐标(推荐是瞬移坐标),摄像机控制,可使用Cinemachine 2D的

例如:3个框代表3个场景,要做好场景划分,性能考虑按道理没有性能开销 都使用一个图集即可,若场景有2D粒子还是做好场景划分,可视才创建内容。

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

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

相关文章

企微SCRM驱动企业私域流量营销与客户关系管理的智慧革新

内容概要 在当今竞争激烈的商业环境中&#xff0c;企微SCRM逐渐成为企业实现私域流量营销和优化客户关系管理的重要工具。它的出现不仅提升了企业的工作效率&#xff0c;也改变了传统的营销方式。那么&#xff0c;究竟什么是企微SCRM呢&#xff1f;简单来说&#xff0c;它是将…

数据库、数据仓库、数据湖有什么不同

数据库、数据仓库和数据湖是三种不同的数据存储和管理技术&#xff0c;它们在用途、设计目标、数据处理方式以及适用场景上存在显著差异。以下将从多个角度详细说明它们之间的区别&#xff1a; 1. 数据结构与存储方式 数据库&#xff1a; 数据库主要用于存储结构化的数据&…

前端力扣刷题 | 6:hot100之 矩阵

73. 矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 法一&#xff1a; var setZeroes function(matrix) {let setX new Set(); // 用于存储需要置零的行索引let setY new Set(); //…

【编译系列】Torch.compile()训练编译——算子融合逻辑 工程化

1. 背景: torch.compile()中,Dynamo作为前端负责计算图的捕获,后端有inductor、tvm等进行编译优化。 Dynamo:在Python字节码层面注入pass,实现bytecode-to-bytecode的优化,通过对bytecode逐行进行解析构建FX GraphInductor:负责对FX Graph进行AOTAutograd生成joint-gra…

Docker 部署教程jenkins

Docker 部署 jenkins 教程 Jenkins 官方网站 Jenkins 是一个开源的自动化服务器&#xff0c;主要用于持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;过程。它帮助开发人员自动化构建、测试和部署应用程序&#xff0c;显著提高软件开发的效率和质量…

2025/2/3 云服务器数据库与idea相连

幸福就摆在你面前&#xff0c;你却把阴影当成山川瀑布&#xff0c;你说你无法幸福。 轻量应用服务器https://swasnext.console.aliyun.com/servers/cn-heyuanhttps://swasnext.console.aliyun.com/servers/cn-heyuanhttps://swasnext.console.aliyun.com/servers/cn-heyuanhttp…

【memgpt】letta 课程1/2:从头实现一个自我编辑、记忆和多步骤推理的代理

llms-as-operating-systems-agent-memory llms-as-operating-systems-agent-memory内存 操作系统的内存管理

6. 【Vue实战--孢子记账--Web 版开发】-- 主币种设置

从这篇文章开始我们将一起实现孢子记账的功能&#xff0c;这篇文章实现主币种设置。这个功能比较简单&#xff0c;因此我们从这个功能开始做。 一、功能 根据项目前期的需求调研&#xff0c;用户需要在设置主币种的时候查看汇率信息&#xff08;别问为什么有这么个需求&#…

51单片机(STC89C52)开发:点亮一个小灯

软件安装&#xff1a; 安装开发板CH340驱动。 安装KEILC51开发软件&#xff1a;C51V901.exe。 下载软件&#xff1a;PZ-ISP.exe 创建项目&#xff1a; 新建main.c 将main.c加入至项目中&#xff1a; main.c:点亮一个小灯 #include "reg52.h"sbit LED1P2^0; //P2的…

GESP2023年9月认证C++六级( 第三部分编程题(2)小杨的握手问题)

参考程序1&#xff08;暴力枚举&#xff09; #include <iostream> using namespace std;int main() {int n 0;cin >> n; // 读入同学的数量int num[300000]; // 存储同学的学号for (int i 0; i < n; i) {cin >> num[i]; // 读入同学的进入顺序}long…

【C++篇】哈希表

目录 一&#xff0c;哈希概念 1.1&#xff0c;直接定址法 1.2&#xff0c;哈希冲突 1.3&#xff0c;负载因子 二&#xff0c;哈希函数 2.1&#xff0c;除法散列法 /除留余数法 2.2&#xff0c;乘法散列法 2.3&#xff0c;全域散列法 三&#xff0c;处理哈希冲突 3.1&…

GPT与Deepseek等数据驱动AI的缺点

当前数据驱动的AI&#xff08;包括GPT与Deepseek等各种大小模型&#xff09;只进行了数/物理性的初步探索&#xff0c;尚未触及人机环境生态系统的复杂性。也就是说&#xff0c;当前的数据驱动型 AI&#xff0c;虽然在处理大量数据、解决特定任务方面取得了显著进展&#xff0c…

阿里云盘PC端打不开解决办法

阿里云盘服务中心 搜索&#xff1a;PC端无法启动怎么办 选择问题 PC端双击云盘图标没有反应&#xff08;windows系统&#xff09; 下载null.sys&#xff0c;先执行压缩包里面的 null.reg 注册表&#xff0c;再按官方文档操作&#xff0c;不然会报错&#xff0c;搞完建议重启一…

树莓派pico入坑笔记,故障解决:请求 USB 设备描述符失败,故障码(43)

今天心血来潮&#xff0c;拿出吃灰的pico把玩一下&#xff0c;打开thonny&#xff0c;上电&#xff0c;然后...... 上电识别不到端口&#xff0c;windows报错&#xff0c;请求 USB 设备描述符失败&#xff0c;故障码&#xff08;43&#xff09; 一开始以为是坏了&#xff08;磕…

Linux——文件系统

一、从硬件出发 1&#xff09;磁盘的主要构成 通常硬盘是由盘片、主轴、磁头、摇摆臂、马达、永磁铁等部件组成&#xff0c;其中一个硬盘中有多块盘片和多个磁头&#xff0c;堆叠在一起&#xff0c;工作时由盘片旋转和摇摆臂摇摆及逆行寻址从而运作&#xff0c;磁头可以对盘片…

FPGA 时钟多路复用

时钟多路复用 您可以使用并行和级联 BUFGCTRL 的组合构建时钟多路复用器。布局器基于时钟缓存 site 位置可用性查找最佳布局。 如果可能&#xff0c;布局器将 BUFGCTRL 布局在相邻 site 位置中以利用专用级联路径。如无法实现&#xff0c;则布局器将尝试将 BUFGCTRL 从…

C++底层学习预备:模板初阶

文章目录 1.编程范式2.函数模板2.1 函数模板概念2.2 函数模板原理2.3 函数模板实例化2.3.1 隐式实例化2.3.2 显式实例化 2.4 模板参数的匹配原则 3.类模板希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 进入STL库学习之前我们要先了解有关模板的…

Baklib如何在知识管理领域成为领军者与六款产品的综合评析

内容概要 在知识管理领域&#xff0c;Baklib凭借其卓越的技术和创新的产品线&#xff0c;已经确立了行业的领导地位。作为一个全面的知识管理平台&#xff0c;Baklib为企业提供了高效、便捷的知识存储和管理方案&#xff0c;帮助组织有效整合内外部知识资源。其主要特点包括强…

Baklib阐明企业内容管理与内容中台的本质差异

内容概要 在快速发展的数字时代&#xff0c;企业对信息的管理愈加重视。内容管理在企业日常运营中扮演了重要角色&#xff0c;而随着技术的不断进步&#xff0c;内容中台的概念逐渐走入视野。了解这两者的不同&#xff0c;不仅有助于企业更有效地管理内容&#xff0c;还能提升…

Java 大视界 -- Java 大数据在智能电网中的应用与发展趋势(71)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…