【unity小技巧】Unity2D TileMap+柏林噪声生成随机地图(附源码)

文章目录

  • 前言
  • 柏林噪声
  • 素材导入
  • Rule Tile配置
  • 生成随机地图
  • 问题
  • 扩展问题
  • 添加植被
  • 源码
  • 参考
  • 完结

前言

我的上一篇文章介绍了TileMap的使用,主要是为我这篇做一个铺垫,看过上一篇文章的人,应该已经很好的理解TileMap的使用了,这里我就不需要过多的解释一些繁琐而基础的知识了,省去很多时间。所有没看过上一篇文章的小伙伴我强烈建议先去看看:
【Unity小技巧】Unity2D TileMap的探究(最简单,最全面的TileMap使用介绍)

先来看看本文实现的最终效果
在这里插入图片描述
源码在文章末尾

柏林噪声

柏林噪声(Perlin noise)是由Ken Perlin于1983年提出的一种随机数生成算法,常用于计算机图形学中的纹理、地形和粒子系统等领域。它产生了一种平滑、连续的随机分布,常用于生成自然风格的纹理和地形。

柏林噪声和直接随机有以下几个区别:

  1. 平滑性:柏林噪声生成的值在空间上变化连续平滑,不会出现剧烈的跳变。而直接随机生成的值可能会出现突然的变化,不够平滑。

  2. 一致性:柏林噪声的生成结果是基于一个固定的种子值,因此每次使用相同的种子值生成的结果都是一致的。而直接随机生成的结果每次都不同。

  3. 纹理性:柏林噪声生成的值可以用来模拟自然纹理,如山脉、云彩等。而直接随机生成的值没有这种纹理性,更加随机。

比如随机0-1可能生成跳动比较大的数据:0,0.8,0.1
而使用柏林噪声生成的数据大概率是:0,0.3,0.5

素材导入

在这里插入图片描述
在这里插入图片描述

Rule Tile配置

Rule Tile的使用我这里就不再解释了,不清楚的可以看我前面发的文章链接,这里就直接贴出配置图了,节省大家时间,配置起来也不难就是要多测试,费点时间
在这里插入图片描述

在这里插入图片描述

效果演示,可以看到无论我们如何绘制地图,都可以做很好的兼容
在这里插入图片描述

生成随机地图

新建脚本MapCreate,先定义两个方法

public class MapCreate : MonoBehaviour
{// 创建地图数据public void GenerateMap(){}// 清除地图数据public void CleanTileMap(){}
}

一直启动才生成地图,太慢了,为了加快我们的调试节奏,可以实现未启动unity生成地图效果,我们需要新建一个Editor文件夹
在这里插入图片描述
书写MapCreateEditor脚本

using UnityEditor;
using UnityEngine;[CustomEditor(typeof(MapCreate))]// 自定义编辑器,目标为我们前面创建的MapCreate
public class MapCreateEditor : Editor
{public override void OnInspectorGUI()// 重写OnInspectorGUI方法{base.DrawDefaultInspector();// 绘制默认的检查器if (GUILayout.Button("创建地图"))// 如果GUILayout的按钮被按下,按钮名为"创建地图"{((MapCreate)target).GenerateMap();// 目标MapGenerator生成地图}if (GUILayout.Button("清除地图"))// 如果GUILayout的按钮被按下,按钮名为"清除地图"{((MapCreate)target).CleanTileMap();// 目标MapGenerator清理地图}}
}

效果
在这里插入图片描述
继续完善我们的MapCreate代码,代码我加了详细的中文注释,这里不过多解释了

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Tilemaps;// 创建地图的类
public class MapCreate : MonoBehaviour
{public Tilemap groundTileMap; // 地图的Tilemap组件public int width; // 地图的宽度public int height; // 地图的高度public int seed; // 生成地图的种子public bool useRandomSeed; // 是否使用随机种子public float lacunarity; // 柏林噪声的频率,决定地形的空隙度[Range(0, 1f)]public float waterProbability; // 水域的概率public TileBase groundTile; // 地面的Tilepublic TileBase waterTile; // 水域的Tileprivate bool[,] mapData; // 地图数据,True表示地面,False表示水域// 创建地图数据public void GenerateMap(){GenerateMapData(); // 生成地图数据GenerateTileMap(); // 生成Tile地图}// 生成地图数据private void GenerateMapData(){// 对于种子的应用if (!useRandomSeed) seed = Time.time.GetHashCode(); // 如果不使用随机种子,则使用当前时间的哈希值作为种子UnityEngine.Random.InitState(seed); // 初始化随机状态float randomOffset = UnityEngine.Random.Range(-10000, 10000); // 随机偏移量mapData = new bool[width, height]; // 初始化地图数据for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 使用柏林噪声生成地图数据float noiseValue = Mathf.PerlinNoise(x * lacunarity + randomOffset, y * lacunarity + randomOffset);mapData[x, y] = noiseValue < waterProbability ? false : true; // 如果噪声值小于水域概率,则该位置为水域,否则为地面}}}// 生成Tile地图private void GenerateTileMap(){CleanTileMap(); // 清除地图数据// 生成地面for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 如果地图数据为True,则该位置为地面,否则为空TileBase tile = mapData[x, y] ? groundTile : waterTile;groundTileMap.SetTile(new Vector3Int(x, y), tile); // 设置Tile}}}// 清除地图数据public void CleanTileMap(){groundTileMap.ClearAllTiles(); // 清除所有的Tile}
}

挂载脚本,配置数据
在这里插入图片描述
运行效果
在这里插入图片描述

问题

这里还有一个问题,如果我们把lacunarity值设置很小,且把水的比例调的比较高或者比较低的时候,你会发现没有按我们要的比例生成效果
在这里插入图片描述
Mathf.PerlinNoise(x, y)函数在Unity中用于生成柏林噪声,它的返回值是在0到1之间的浮点数。这个函数的两个参数通常是在一个连续的范围内变化的,例如时间或者空间的坐标。

前面我们使用了x * lacunarity + randomOffset和y * lacunarity + randomOffset作为输入。lacunarity是一个控制频率的参数,randomOffset是一个随机偏移量。

当lacunarity很小的时候,x * lacunarity和y * lacunarity的值会非常接近,这意味着我们在查询柏林噪声的时候,查询的点非常接近。柏林噪声的特性是,查询的点越接近,返回的值越接近。所以,当lacunarity很小的时候,我们得到的噪声值的范围可能会小于0到1。

如果你想让噪声值的范围更接近0到1,你可以尝试增大lacunarity的值。这样,你查询柏林噪声的点就会更分散,返回的噪声值的范围就会更大。但是,这也会影响到生成的地图的样子,可能会使地图的特征更大或者更小,这取决于你的需求。

另外,我们也可以在得到噪声值之后,对其进行一些数学处理,例如缩放或者偏移,来使其范围更接近0到1。例如,可以使用Mathf.InverseLerp来确保噪声值在0到1之间。

Mathf.InverseLerp 是 Unity 中的一个函数,用于反向插值计算。它接受三个参数:a,b 和 value。

函数的工作原理是这样的:首先,它会找到 value 在 a 和 b 之间的相对位置。然后,它会返回一个介于 0 和 1 之间的值,这个值表示 value 在 a 和 b 之间的相对位置。如果 value 等于 a,则返回 0;如果 value 等于 b,则返回 1。如果 value 在 a 和 b 之间,则返回一个介于 0 和 1 之间的值。

例如,Mathf.InverseLerp(0, 10, 5) 将返回 0.5,因为 5 是 0 和 10 之间的中点。

这个函数在需要将一个值映射到 0 到 1 的范围时非常有用,例如在归一化操作中。

修改MapCreate代码

private float[,] mapData; // 地图数据private void GenerateMapData()
{//。。。mapData = new float[width, height]; // 初始化地图数据float minValue = float.MaxValue;float maxValue = float.MinValue;for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 使用柏林噪声生成地图数据float noiseValue = Mathf.PerlinNoise(x * lacunarity + randomOffset, y * lacunarity + randomOffset);mapData[x, y] = noiseValue;if (noiseValue < minValue) minValue = noiseValue;if (noiseValue > maxValue) maxValue = noiseValue;}}// 平滑到0~1for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){mapData[x, y] = Mathf.InverseLerp(minValue, maxValue, mapData[x, y]);}}
}// 生成Tile地图
private void GenerateTileMap()
{//。。。// 如果地图数据为True,则该位置为地面,否则为水TileBase tile = mapData[x, y] > waterProbability ? groundTile : waterTile;
}

float.MaxValue是C#中浮点数类型(float)可以表示的最大值,大约为3.4E+38。float.MinValue是浮点数类型(float)可以表示的最小负值,大约为-3.4E+38。

运行效果,可以看到,现在的效果就是我们的预期,水域显示占比没有问题了
在这里插入图片描述

扩展问题

我这里的地图瓦片是比较全面的,各个方位形状的都有,所有直接生成出来的地形不会出现什么问题,不过有时候我们的输出可能只包括常见的四方向瓦片,那么生成的地图多多少少会出现一些问题

比如:
在这里插入图片描述消除错误没有意义的瓦片,你可以去寻找他们的一个共性,比如就是瓦片都只有一个邻居,消除的大概思路就是遍历每个瓦片进行判断,如果他只有一个邻居就把它变为水。

参考代码

public void GenerateMap()
{// 地图处理 处理次数for (int i = 0; i < 3; i++){if (!RemoveSeparateTile()) // 如果本次操作什么都没有处理,则不进行循环{break;}}
}//移除孤立的瓷砖    
private bool RemoveSeparateTile()
{bool res = false; // 是否是有效的操作for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 是地面且只有一个邻居也是地面if (IsGround(x, y) && GetFourNeighborsGroundCount(x, y) <= 1){groundTileMap.SetTile(new Vector3Int(x, y), tile);// 设置为水res = true;}}}return res;
}// 获取四方向地面邻居的数量
private int GetFourNeighborsGroundCount(int x, int y)
{int count = 0;// topif (IsInMapRange(x, y + 1) && IsGround(x, y + 1)) count += 1;// bottomif (IsInMapRange(x, y - 1) && IsGround(x, y - 1)) count += 1;// leftif (IsInMapRange(x - 1, y) && IsGround(x - 1, y)) count += 1;// rightif (IsInMapRange(x + 1, y) && IsGround(x + 1, y)) count += 1;return count;
}// 是否在地图范围内
public bool IsInMapRange(int x, int y)
{return x >= 0 && x < width && y >= 0 && y < height;
}// 是否是地面
public bool IsGround(int x, int y)
{return mapData[x, y] > waterProbability;
}

这样就可以消除错误或者没有意义的瓦片啦。

添加植被

定义一个类存放植被瓦片和权重

[Serializable]
public class ItemData
{public TileBase tile;public int wegith;
}

逻辑代码

public Tilemap itemTileMap;//植被的Tilemap组件
public List<ItemData> ItemData;//植被列表//生成植被
public void CreateItemData()
{// 植被权重和int weightTotal = 0;for (int i = 0; i < ItemData.Count; i++){weightTotal += ItemData[i].wegith;}//生成植被for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){//只有地面可以生成物品if (IsGround(x, y)){float randValue = UnityEngine.Random.Range(1, weightTotal + 1);float temp = 0;for (int i = 0; i < ItemData.Count; i++){temp += ItemData[i].wegith;if (randValue < temp){// 命中if (ItemData[i].tile) itemTileMap.SetTile(new Vector3Int(x, y), ItemData[i].tile);break;}}}}}
}

挂载脚本,配置权重参数,可以像我一样配置一个为空,及控制无植被的占比权重
在这里插入图片描述

运行效果
在这里插入图片描述

源码

后面整理好了,我会放上来。

参考

【视频】https://www.bilibili.com/video/BV1Js4y117C6?p=1

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

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

相关文章

C++,从“hello world“开始

一、"hello world" #inclue <iostream>using namespace std;int main() {cout << "hello world" << endl;return 0; } 1.1 #include&#xff1a;预处理标识 1.2 <iostream>&#xff1a;输入输出流类所在头文件 1.2.1 istream&a…

【学习日记】【FreeRTOS】延时列表的实现

前言 本文在前面文章的基础上实现了延时列表&#xff0c;取消了 TCB 中的延时参数。 本文是对野火 RTOS 教程的笔记&#xff0c;融入了笔者的理解&#xff0c;代码大部分来自野火。 一、如何更高效地查找延时到期的任务 1. 朴素方式 在本文之前&#xff0c;我们使用了一种朴…

Nginx 使用 HTTPS(准备证书和私钥)

文章目录 Nginx生成自签名证书和配置Nginx HTTPS&#xff08;准备证书和私钥&#xff09;准备证书和私钥 Nginx生成自签名证书和配置Nginx HTTPS&#xff08;准备证书和私钥&#xff09; 准备证书和私钥 生成私钥 openssl genrsa -des3 -out server.key 2048这会生成一个加密…

Python 程序设计入门(025)—— 使用 os 模块操作文件与目录

Python 程序设计入门&#xff08;025&#xff09;—— 使用 os 模块操作文件与目录 目录 Python 程序设计入门&#xff08;025&#xff09;—— 使用 os 模块操作文件与目录一、操作目录的常用函数1、os 模块提供的操作目录的函数2、os.path 模块提供的操作目录的函数 二、相对…

图像检索,目标检测map的实现

一、图像检索指标Rank1,map 参考&#xff1a;https://blog.csdn.net/weixin_41427758/article/details/81188164?spm1001.2014.3001.5506 1.Rank1: rank-k&#xff1a;算法返回的排序列表中&#xff0c;前k位为存在检索目标则称为rank-k命中。 常用的为rank1&#xff1a;首…

Flutter 宽高自适应

在Flutter开发中也需要宽高自适应&#xff0c;手动写一个工具类&#xff0c;集成之后在像素后面直接使用 px或者 rpx即可。 工具类代码如下&#xff1a; import dart:ui;class HYSizeFit {static double screenWidth 0.0;static double screenHeight 0.0;static double phys…

.net老项目中Jquery访问webservice

.net老项目中Jquery访问webservice 1. xml类型返回 jQuery.ajax({type: "POST",async: false,url: "WebService/Evection.asmx/GetCheckUpApplyEForm",contentType: "application/json",data: "{lngEvectionID:" eformSNOriginal &…

后端返回可下载的xlsx文件,但是前端接收下载后为乱码

我的接收数据进行处理的代码为&#xff1a; download_signal_list() {return new Promise((resolve, reject) > {get_download_signal({project_id: this.projectId,version_id: this.versionId}).then(response > {const url window.URL.createObjectURL(new Blob([res…

iBooker 布客技术评论 20230818

一、程序员自检手册 为了避免焦虑&#xff0c;你首先需要做的就是梳理你的业务&#xff1a; &#xff08;1&#xff09;你所在的行业是轻资产还是重资产&#xff1f; 重资产就是人绕着机器转&#xff0c;创业需要买一大堆设备。如果是重资产&#xff0c;赶紧换一个。 &…

Dockerfile文件详细

Dockerfile 是一个文本文件&#xff0c;里面包含组装新镜像时用到的基础镜像和各种指令&#xff0c;使用dockerfile 文件来定义镜像&#xff0c;然后运行镜像&#xff0c;启动容器。 dockerfile文件的组成部分 一个dockerfile文件包含以下部分&#xff1a; 基础镜像信息&…

SQL注入之万能用户名

文章目录 分析代码原理实现 分析代码 在安装的cms数据库目录C:\phpStudy\WWW\cms\admin下找到login.action.php文件&#xff0c;查看第20行&#xff0c;发现如下php代码&#xff1a; $user_row $db->getOneRow("select userid from cms_users where username "…

LightDB-A sequence支持MAXVALUE最大值与Oracle相同

功能介绍 Oracle数据库在创建sequence的时候可以支持设置maxvalue 为9999999999999999999999999999&#xff0c;这样的SQL在LightDB-A 23.3版本之前都是执行失败的。为了方便Oracle用户迁移到LightDB-A上&#xff0c;在LightDB-A 23.3版本上&#xff0c;增加了sequence支持max…

docker 04(docker 应用部署)

一、部署Mysql 需求: 在Docker容器中部署MySQL&#xff0c;并通过外部mysql客户端操作MySQLServer。 二、部署tomcat 三、部署nginx 四、部署redis

PHP 房产网站系统Dreamweaver开发mysql数据库web结构php编程计算机网页项目

一、源码特点 PHP 房产网站系统是一套完善的WEB设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 源码 https://download.csdn.net/download/qq_41221322/88233553 论文 https://download…

RISC-V公测平台发布 · 数据库在RISC-V服务器上的适配评估

前言 上一期讲到YCSB在RISC-V服务器上对MySQL进行性能测试&#xff08;RISC-V公测平台发布 使用YCSB测试SG2042上的MySQL性能&#xff09;&#xff0c;在这一期文章中&#xff0c;我们继续深入讨论RISC-V数据库的应用。本期就继续利用HS-2平台来测试数据库软件在RISC-V服务器…

抢红包小程序

抢红包小程序 红包大战 # urls.pyfrom django.urls import pathfrom . import viewsurlpatterns [ path(login/, views.login, namelogin), path(create_red_packet/, views.create_red_packet, namecreate_red_packet), path(join_red_packet/<int:red_packet_id…

Unity 之 Input类

文章目录 总述具体介绍 总述 Input 类是 Unity 中用于处理用户输入的重要工具&#xff0c;它允许您获取来自键盘、鼠标、触摸屏和控制器等设备的输入数据。通过 Input 类&#xff0c;您可以轻松地检测按键、鼠标点击、鼠标移动、触摸、控制器按钮等用户输入事件。以下是关于 I…

数学建模-模型详解(2)

微分模型 当谈到微分模型时&#xff0c;通常指的是使用微分方程来描述某个系统的动态行为。微分方程是描述变量之间变化率的数学方程。微分模型可以用于解决各种实际问题&#xff0c;例如物理学、工程学、生物学等领域。 微分模型可以分为两类&#xff1a;常微分方程和偏微分…

《学爸》成爆款背后,马栏山以BOT模式示范“文化+科技”路径

文|智能相对论 作者|范柔丝 今年暑期档的爆款电影&#xff0c;必有《学爸》一席之地。 这部给众多深陷教育旋涡的家长带来深刻思考的电影&#xff0c;就是马栏山视频文创产业园经过3年筹备&#xff0c;首部本土孵化出品的教育现实体裁院线大电影。 据猫眼专业版数据&#x…

初步认识OSPF的大致内容(第三课)

1 路由的分类 直连路由&#xff08;Directly Connected Route&#xff09;是指网络拓扑结构中相邻两个网络设备直接相连的路由&#xff0c;也称为直接路由。如果两个设备属于同一IP网络地址&#xff0c;那么它们就是直连设备。直连路由表是指由计算机系统生成的一种用于路由选择…