Unity 使用柏林噪声(Perlin Noise)生成网格地图
前言
最近在尝试制作一个基于网格地图的RPG游戏,所以想着自己生成一个网格地图。
但是网格地图的生成有很多的方式,大多数的方式都达不到我的要求,我需要一个地图可以随机生成各种地形,也可以储存一些地形数据。
柏林噪声是一个非常好的地形生成选择,但是我查阅了很多的资料,发现国内关于柏林噪声生成地图的文章寥寥无几,而且很少有特别详细的使用方法,所以我在这里把我自己制作地图的过程整理了一下,供大家参考。
预制体制作
为了方便演示,我直接使用了Cube,更改了一下颜色,其中白色的模拟(Mountian),绿色的模拟(Ground)
基础网格地图
简易方案(废弃)
我最开始制作网格地图想着直接用脚本生成一个 Vector3数列,将网格的位置信息存储进去就可以,但是实践之后发现并不是很实用,因为地图的每个单元格(Grid)都需要存储它自身的信息。
这里我先把我之前的网格地图代码放出来,如果需要比较简易的版本,可以直接使用这个方案。
using System.Collections;using System.Collections.Generic;using UnityEngine;public class MapManager : MonoBehaviour{public GameObject mapPointPrefab;public GameObject[,] mapPoint;public GameObject mapPoints;// Start is called before the first frame updatevoid Start(){CreateMap();}public void CreateMap(){mapPoint = new GameObject[10, 10];for (int i = 0; i < 10; i++){for (int j = 0; j < 10; j++){Vector2 point = new Vector2(-4.5f + i, -4.5f + j);mapPoint[i,j] = Instantiate(mapPointPrefab, point, this.transform.rotation,mapPoints.transform);}}}}
此方案详细信息见:HOneiii/MapSystem
单例模式MapManager创建
简易方案最大的问题就是不容易扩展,作为一个地图管理器,它需要被其他的物体方便地访问,所以我MapManger脚本以单例模式重写了。
泛型单例模式的脚本这里就不呈现了。
设计一个地图管理器,首先我们需要设计它的几个基本信息。
public enum GridState //单元格状态{ground,mountain,}public struct Map//地图信息结构体{public GameObject gridCube;//Map[x,z]代表的GameObjectpublic Vector3 gridLocation;//位置信息public GridState gridState;//单元格的状态public bool canWalk;//是否可以行走}public class MapManager : Singleton<MapManager>{public Map[,] map; //地图数据}
我在上面设计了一个Map结构体,里面有地图的几个基本信息。GridState中我列出了两种基本的形态,后期还可以视情况添加。
然后我在MapManager中声明了一个Map[,]二维数组,用来存储地图上的单元格信息。
随机高度地图生成
既然是一个地图,那么高度相同是没有任何意义的,我们需要改变单元格高度来创造一个真实的地形。
所以我用随机函数随机了gridHeight的数值,并且生成了地图。
public void Init(int xSize//x轴格数, int zSize//y轴格数, float cellSize//单元格大小){map = new Map[xSize, zSize];[Header("地图块")]public GameObject Ground;public GameObject Mountain;//随机高度map[x,z].gridHeight = Random.Range(0f, 5f);for (int x = 0; x < xSize; x++){for (int z = 0; z < zSize; z++){//生成地图二维坐标map[x, z].gridLocation = new Vector3(cellSize * x, 0, cellSize * z);//判断高度 给单元格赋上状态if (gridHeight < 3.6f){map[x, z].gridState = GridState.ground;}else if (gridHeight >= 3.6f){map[x, z].gridState = GridState.mountain;}//根据单元格的状态生成单元格switch (map[x, z].gridState){case GridState.ground:map[x, z].gridCube = Instantiate(Ground, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, gridHeight, 0);map[x,z].canWalk = true;break;case GridState.mountain:map[x, z].gridCube = Instantiate(Mountain, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, gridHeight, 0);map[x,z].canWalk = false;break;}}
在生成单元格时我还将canWalk的属性进行了设置,当然这是为之后角色的加入服务,所以可以忽略。
那么这样生成的地图呈现出来的效果是这样的。
可以看到,高山和平原都已经有了雏形,但是我们发现依靠简单随机数生成的地图杂乱无章,毫无规律可言,与现实中的地形并没有什么关联。
引入柏林噪声(Perlin Noise)
首先摘抄一段柏林噪声的解释
Perlin Noise 是 Ken Perlin 在 1983 年开发的一种梯度噪音,这是一种用于在计算机生成的表面上产生自然出现纹理的技术,使用 Perlin 噪声合成的纹理通常用于 CGI,通过模仿自然界中纹理的受控随机外观,使计算机生成的视觉元素(如物体表面,火焰,烟雾或云)看起来更自然。
作者: Wilen Wu 链接: 柏林噪声(Python) | Wilen's Blog 来源: Wilen's Blog
柏林噪声在二维平面生成的结果就像上面的图片。
熟悉地形制作的朋友肯定对这样的图片比较熟悉,这样的灰度图可以当作地形的高度使用,所以我们就将柏林噪声和我们的高度绑定。
using System.Collections;using System.Collections.Generic;using UnityEngine;public enum GridState{ground,mountain,lake}public struct Map{public GameObject gridCube;public Vector3 gridLocation;public GridState gridState;public bool canWalk;}public class MapManager : Singleton<MapManager>{public Map[,] map; //地图数据public float sightDistance;[Header("地图块")]public GameObject Ground;public GameObject Mountain;public GameObject Lake;[Header("柏林噪声参数")]public float frequency;//柏林噪声频率public float scale;//柏林噪声振幅public void Init(int xSize, int zSize, float cellSize){map = new Map[xSize, zSize];//柏林噪声伪随机误差float xRandom = Random.Range(0f, 100f);float zRandom = Random.Range(0f, 100f);for (int x = 0; x < xSize; x++){for (int z = 0; z < zSize; z++){//生成地图二维坐标map[x, z].gridLocation = new Vector3(cellSize * x, 0, cellSize * z);//柏林噪声生成地图高度信息float xFloat = x;float zFloat = z;float xSizeFloat = xSize;float zSizeFloat = zSize;float gridHeight = Mathf.PerlinNoise(xFloat / xSizeFloat * frequency + xRandom, zFloat / zSizeFloat * frequency + zRandom) * scale;//地图单位格高度判断并更改类型//生成 高山 平原 湖泊if (gridHeight > 1.5f && gridHeight < 3.6f){map[x, z].gridState = GridState.ground;}else if (gridHeight >= 3.6f){map[x, z].gridState = GridState.mountain;}else if (gridHeight <= 1.5f){map[x, z].gridState = GridState.lake;}//地图单位格高度设置以及生成switch (map[x, z].gridState){case GridState.ground:map[x, z].gridCube = Instantiate(Ground, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, gridHeight, 0);map[x,z].canWalk = true;break;case GridState.mountain:map[x, z].gridCube = Instantiate(Mountain, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, gridHeight + 1f, 0);map[x,z].canWalk = false;break;case GridState.lake:map[x, z].gridCube = Instantiate(Lake, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, 1.4f, 0);map[x,z].canWalk = false;break; }}}}}
在代码中,我新建了一个Lake的状态,在高度为1.5以下时表现为湖泊。
最重要的代码是这段
float xRandom = Random.Range(0f, 100f);float zRandom = Random.Range(0f, 100f);------------------------------------------float xFloat = x;float zFloat = z;float xSizeFloat = xSize;float zSizeFloat = zSize;float gridHeight = Mathf.PerlinNoise(xFloat / xSizeFloat * frequency + xRandom, zFloat / zSizeFloat * frequency + zRandom) * scale;
前面的几个变量声明是为了将int型的x,z转换为float型,这是一个非常关键的点,由于Mathf.PerlinNoise最后输出结果与输入数据的小数相关,所以如果为int型,最后出现的单元格高度都是一样的。
Matf.PerlinNoise中的frequency为频率,scale为振幅。我们可以将其看作一块褶皱的布,频率就是褶皱的密度,振幅就相当于褶皱的高度。
上面的xRandom与zRandom所起到的作用为每次创建的地图都不相同,所以需要将其放在循环外,否则创建的地图还是没有任何的关联性,依旧杂乱无章。
现在创建完成的地图如下,已经很像Minecraft中的地形感觉了。
加入更多地形
那么这样一个地形如何能够添加更多的单元格类型呢,我就在这里贴上增加了森林、沙漠的代码,供大家参考。
using System.Collections;using System.Collections.Generic;using UnityEngine;public enum GridState{ground,mountain,lake,forest,desert}public struct Map{public GameObject gridCube;public Vector3 gridLocation;public GridState gridState;public bool canWalk;}public class MapManager : Singleton<MapManager>{public Map[,] map; //地图数据public float sightDistance;[Header("地图块")]public GameObject Ground;public GameObject Mountain;public GameObject Lake;public GameObject Forest;public GameObject Desert;[Header("柏林噪声参数")]public float frequency;//柏林噪声频率public float scale;//柏林噪声振幅public void Init(int xSize, int zSize, float cellSize){map = new Map[xSize, zSize];//柏林噪声伪随机误差float xRandom = Random.Range(0f, 100f);float zRandom = Random.Range(0f, 100f);for (int x = 0; x < xSize; x++){for (int z = 0; z < zSize; z++){//生成地图二维坐标map[x, z].gridLocation = new Vector3(cellSize * x, 0, cellSize * z);//柏林噪声生成地图高度信息float xFloat = x;float zFloat = z;float xSizeFloat = xSize;float zSizeFloat = zSize;float gridHeight = Mathf.PerlinNoise(xFloat / xSizeFloat * frequency + xRandom, zFloat / zSizeFloat * frequency + zRandom) * scale;float gridType = Mathf.PerlinNoise(xFloat / xSizeFloat * frequency + xRandom/3, zFloat / zSizeFloat * frequency + zRandom/3) * scale;//地图单位格高度判断并更改类型//生成 高山 平原 湖泊if (gridHeight > 1.5f && gridHeight < 3.6f){map[x, z].gridState = GridState.ground;}else if (gridHeight >= 3.6f){map[x, z].gridState = GridState.mountain;}else if (gridHeight <= 1.5f){map[x, z].gridState = GridState.lake;}//生成 森林 沙漠if (map[x, z].gridState == GridState.ground){if (gridType >= 3.2f){map[x, z].gridState = GridState.forest;}else if (gridType <= 1.5f){map[x, z].gridState = GridState.desert;}}//地图单位格高度设置以及生成switch (map[x, z].gridState){case GridState.ground:map[x, z].gridCube = Instantiate(Ground, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, gridHeight, 0);map[x,z].canWalk = true;break;case GridState.mountain:map[x, z].gridCube = Instantiate(Mountain, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, gridHeight + 1f, 0);map[x,z].canWalk = false;break;case GridState.lake:map[x, z].gridCube = Instantiate(Lake, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, 1.4f, 0);map[x,z].canWalk = false;break;case GridState.forest:map[x, z].gridCube = Instantiate(Forest, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, gridHeight, 0);map[x,z].canWalk = true;break;case GridState.desert:map[x, z].gridCube = Instantiate(Desert, map[x, z].gridLocation, Quaternion.identity, this.transform);map[x, z].gridCube.transform.position = map[x, z].gridLocation + new Vector3(0, gridHeight, 0);map[x,z].canWalk = true;break;}}}}}
生成的地图如下
具体代码大家可以自己查看,我这里就简述一下思路。
在创建完成基础的高山、平原、湖泊之后,我又新建了一层柏林噪声,用它的高度再次模拟出了森林和沙漠,并且在原来的基础上将森林和沙漠赋值给单元格(其中高山和湖泊不会被替换)。
其中的原理有些类似于PS的蒙版,最基础的地形需要放在最下层,上层可以放森林、沙漠等,再往上还可以生成村庄、城市等,也可以在湖泊中生成码头,岛屿等地形。
写在最后
柏林噪声真的是一个非常有用的工具,相较于直白的Random,可以更加生动地完成更多东西的制作。
如果喜欢这篇文章可以点个赞,也可以关注BoomGisland - By a game designer
如果文章中有什么阐述不清的,也可以评论区问我。
Unity 使用柏林噪声(Perlin Noise)生成网格地图相关推荐
- [算法]柏林噪声 Perlin Noise
时间:2017/1/26 大三上学年寒假 每次寒假都挺闲的,恰好最近家里网又不太稳定,不能LOL!于是想趁这个机会,利用OpenGL,尝试着写一个仿造的<Minecraft>.但是,思来想 ...
- 3D数学之柏林噪声(Perlin Noise)
好了,转入正题. 其实它的原理并不是很难,但是由于网上实现的版本太多太杂,真要实现起来竟然不知从何处下手,而且自己写的时候会遇到各种各样的问题.最终写出来了,所以很欣然. 先看下,我在网上找的一些资料 ...
- Unity无限地形生成(基于柏林噪声的简单生成)
Unity无限地形生成(基于柏林噪声的简单生成) 要求:构建一个户外开放世界游戏,为该游戏添加天空,地形,植物,并支持场景里自由漫游.这里实现一个无限地形的产生: 实现漫游 漫游的功能由玩家移动和摄像 ...
- Unity 动态网格地图的生成:基于Perlin Noise创建地形
前言: 在前面的文章中,写了一个简单的网格地图生成脚本,不过是基于二维空间来完成的.为了更好的拓展该脚本,同时去了解学习大世界地图加载的一些知识,这段时间会通过一个系列的文章做一个类似于我的世界那样的 ...
- 木木的Unity学习笔记(四)—— Unity中的柏林噪声(Perlin Noise)
木木的Unity学习笔记(四)-- Unity中的柏林噪声 柏林噪声是一个非常强大算法,经常用于程序生成随机内容,在游戏和其他像电影等多媒体领域广泛应用.算法发明者Ken Perlin也因此算法获得奥 ...
- html 生成image java makenoise,[图形学] 柏林噪声 (perlin noise)
参考论文:<An Image Synthesizer> Ken Perlin 如果你是游戏玩家,你也许曾在游戏风景中驻足,并仔细观察草木和岩石的贴图,并感叹于它那看似杂乱而又自然的纹脉.你 ...
- 【转载】柏林噪声算法
转载自:https://www.cnblogs.com/leoin2012/p/7218033.html 原作者:立航 柏林噪声是一个非常强大算法,经常用于程序生成随机内容,在游戏和其他像电影等多 ...
- 一篇文章搞懂柏林噪声算法,附代码讲解
本文目的是以一种通俗简单的方式介绍Ken Perlin的改进版柏林噪声算法,讲解代码采用c#编写,开源免费使用.如果你只是想看完整代码,点击这里. 柏林噪声是一个非常强大算法,经常用于程序生成随机内容 ...
- Unity记录3.3-地图-柏林噪声生成 2D 地图
文章首发及后续更新:https://mwhls.top/4486.html,无图/无目录/格式错误/更多相关请至首发页查看. 新的更新内容请到mwhls.top查看. 欢迎提出任何疑问及批评,非常感谢 ...
最新文章
- C++——构造函数析构函数
- 使用Spring Cloud Function框架进行面向函数的编程
- 【已解决】Errors during downloading metadata for repository ‘appstream‘: - Status code: 404 for
- java 中时间计算_java中关于时间的计算
- adb启动app_adb命令实战十三步
- C# 动态语言扩展(学习笔记)
- 【Iterm2】如何解决iterm2窗口自动隐藏的问题
- setAttribute在设置属性 ieBug
- Maven将本地jar包推到本地仓库 mvn install:install-file maven命令使用教程
- 如何编译Linux内核源码?
- 交叉连接、内连接和外连接的区别及使用方式
- 银行卡四要素检测 API接口数据
- 北京计算机暑期培训,北京暑期计算机培训心得体会.doc
- 精细化用电侧能源管控 解码光伏电站运维痛点
- vue 之手机号验证、正则验证手机号是否正确、手机号验证码信息弹窗
- Hadoop 3.x|第二天|配置完全分布式运行模式(重点)
- 国产统信UOS系统运行小程序的探索
- Android 实现禁用中文键盘
- i春秋首届全国数据安全大赛部分复盘
- 5.27 使用木刻命令制作宣传海报 [Illustrator CC教程]