前言

最近在尝试制作一个基于网格地图的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)生成网格地图相关推荐

  1. [算法]柏林噪声 Perlin Noise

    时间:2017/1/26 大三上学年寒假 每次寒假都挺闲的,恰好最近家里网又不太稳定,不能LOL!于是想趁这个机会,利用OpenGL,尝试着写一个仿造的<Minecraft>.但是,思来想 ...

  2. 3D数学之柏林噪声(Perlin Noise)

    好了,转入正题. 其实它的原理并不是很难,但是由于网上实现的版本太多太杂,真要实现起来竟然不知从何处下手,而且自己写的时候会遇到各种各样的问题.最终写出来了,所以很欣然. 先看下,我在网上找的一些资料 ...

  3. Unity无限地形生成(基于柏林噪声的简单生成)

    Unity无限地形生成(基于柏林噪声的简单生成) 要求:构建一个户外开放世界游戏,为该游戏添加天空,地形,植物,并支持场景里自由漫游.这里实现一个无限地形的产生: 实现漫游 漫游的功能由玩家移动和摄像 ...

  4. Unity 动态网格地图的生成:基于Perlin Noise创建地形

    前言: 在前面的文章中,写了一个简单的网格地图生成脚本,不过是基于二维空间来完成的.为了更好的拓展该脚本,同时去了解学习大世界地图加载的一些知识,这段时间会通过一个系列的文章做一个类似于我的世界那样的 ...

  5. 木木的Unity学习笔记(四)—— Unity中的柏林噪声(Perlin Noise)

    木木的Unity学习笔记(四)-- Unity中的柏林噪声 柏林噪声是一个非常强大算法,经常用于程序生成随机内容,在游戏和其他像电影等多媒体领域广泛应用.算法发明者Ken Perlin也因此算法获得奥 ...

  6. html 生成image java makenoise,[图形学] 柏林噪声 (perlin noise)

    参考论文:<An Image Synthesizer> Ken Perlin 如果你是游戏玩家,你也许曾在游戏风景中驻足,并仔细观察草木和岩石的贴图,并感叹于它那看似杂乱而又自然的纹脉.你 ...

  7. 【转载】柏林噪声算法

    转载自:https://www.cnblogs.com/leoin2012/p/7218033.html   原作者:立航 柏林噪声是一个非常强大算法,经常用于程序生成随机内容,在游戏和其他像电影等多 ...

  8. 一篇文章搞懂柏林噪声算法,附代码讲解

    本文目的是以一种通俗简单的方式介绍Ken Perlin的改进版柏林噪声算法,讲解代码采用c#编写,开源免费使用.如果你只是想看完整代码,点击这里. 柏林噪声是一个非常强大算法,经常用于程序生成随机内容 ...

  9. Unity记录3.3-地图-柏林噪声生成 2D 地图

    文章首发及后续更新:https://mwhls.top/4486.html,无图/无目录/格式错误/更多相关请至首发页查看. 新的更新内容请到mwhls.top查看. 欢迎提出任何疑问及批评,非常感谢 ...

最新文章

  1. C++——构造函数析构函数
  2. 使用Spring Cloud Function框架进行面向函数的编程
  3. 【已解决】Errors during downloading metadata for repository ‘appstream‘: - Status code: 404 for
  4. java 中时间计算_java中关于时间的计算
  5. adb启动app_adb命令实战十三步
  6. C# 动态语言扩展(学习笔记)
  7. 【Iterm2】如何解决iterm2窗口自动隐藏的问题
  8. setAttribute在设置属性 ieBug
  9. Maven将本地jar包推到本地仓库 mvn install:install-file maven命令使用教程
  10. 如何编译Linux内核源码?
  11. 交叉连接、内连接和外连接的区别及使用方式
  12. 银行卡四要素检测 API接口数据
  13. 北京计算机暑期培训,北京暑期计算机培训心得体会.doc
  14. 精细化用电侧能源管控 解码光伏电站运维痛点
  15. vue 之手机号验证、正则验证手机号是否正确、手机号验证码信息弹窗
  16. Hadoop 3.x|第二天|配置完全分布式运行模式(重点)
  17. 国产统信UOS系统运行小程序的探索
  18. Android 实现禁用中文键盘
  19. i春秋首届全国数据安全大赛部分复盘
  20. 5.27 使用木刻命令制作宣传海报 [Illustrator CC教程]

热门文章

  1. Mysql的语句练习
  2. 什么是应用服务器,常用的应用服务器有哪些
  3. 我到底应该学SwiftUI?UIKit?还是both?
  4. 浅析PMO的发展规划
  5. mysql凡人入门_SQL入门学习
  6. Linux网络编程(IP地址,端口,通信协议,网络字节序)
  7. 移动端敏捷开发之环境部署
  8. 大顶堆排序java实现
  9. drcom linux怎么运行,Ubuntu下drcom使用方法
  10. 洛斐的两平米,究竟“挤”了多少寻找有趣生活的年轻人?