在B站刷到了隨機地圖生成的視頻,隨手學習下并做下記錄
注: 本篇使用javafx應用作演示,算是了解這個算法的使用,后續會再出篇libgdx生成地圖的示例
說明
拋開算法實現,首先認知柏林噪音算法
一般我們想要隨機數,會指定個范圍,如0.0-1.0之間任意小數,而柏林算法的結果范圍就是[-1,1]
柏林算法已經實現了對應一維到四維的算法,本文以二維來進行講解,實現我自己的一個游戲隨機地圖效果,我以五種顏色區分不同地形,通過javafx應用展示出一個600*600的游戲地圖地形,如下圖所示
代碼實現
需求如下:
使用一個二維數組來進行存儲當前地圖,每個數值里存放一個int類型,用來標明地圖的地形,類型有0-4(即int數值可取[0,4]范圍)
我們如果使用單純的隨機算法,地形可能就會很亂,所以我們采用柏林噪音算法來得到我們的對應地形,使其有個平滑的過渡效果,方法如下:
//這里我使用的是java實現版本,FastNoiseLite這個類可見下文列出的參考鏈接val noise = FastNoiseLite()
noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2)
noise.SetSeed(1000)
val result = noise.GetNoise(1f, 2f)
柏林算法最初是由Ken Perlin
,算法的命名由此得來,然后后續發展又繼續了優化,所以有以下幾種類型,對應著?FastNoiseLite.NoiseType
這個枚舉類型,具體介紹如下:
OpenSimplex2
:OpenSimplex2 是關于 OpenSimplex 噪聲的一種改進版本,提供更好的性能和質量。它通常用于生成連續、無縫的噪聲,適用于圖形學和自然模擬等領域。OpenSimplex2S
:OpenSimplex2S 是 OpenSimplex2 的另一個改進版本,其主要目標是在保持質量的同時提高性能。它通常比 OpenSimplex2 更快速、效率更高,適合需要大量計算的場景。Cellular
:Cellular Noise 是一種基于 Voronoi 圖的噪聲算法,產生類似于細胞結構的噪聲效果。它常用于生成有規律排列的點、線或區域,以及模擬生物組織和自然紋理。Perlin
:Perlin Noise 是由 Ken Perlin 發明的一種經典噪聲算法,用于生成自然風格的連續噪聲圖案。它被廣泛應用于圖形學、游戲開發和計算機模擬等領域。ValueCubic
:ValueCubic Noise 是一種基于立方插值的噪聲算法,通常用于生成平滑的、無縫的噪聲效果。它提供了更細致的控制和調整選項,適合需要更多定制化的噪聲生成需求。Value
:Value Noise 是一種簡單的噪聲算法,通過對噪聲值進行插值來生成連續的、均勻分布的噪聲效果。雖然相對簡單,但它在一些場景中也有其應用,如生成地形、紋理等。
柏林算法最終生成的結果數值與輸入的x,y,還有seed(隨機種子)有關,如果三者固定相同,則得到的數值是相同的;
一般來說我們拿一個隨機數來作為隨機種子(如使用Random.nextInt(1000)
方法,獲取1000以內的一個隨機數)
但這樣還不是最優的,我們還可以使用正弦疊加的方法繼續優化得到的數值,使其可以更加平滑
數學函數: result =noise(x,y) + (1/2) * noise(2x,2y) + (1/4) * noise(4x,4y) + (1/8) * noise(8x,8y)
這里我只疊加到8,當然還可以繼續疊加下去,平滑過渡的效果就越好
轉為代碼:
val noise = FastNoiseLite()
noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2)
noise.SetSeed(1000)
val result = noise.GetNoise(1f , 2f ) +(1/2 )* noise.GetNoise(1f *2, 2f * 2)+ (1/4 )* noise.GetNoise(1f *4, 2f * 4)+(1/8 )* noise.GetNoise(1f *8, 2f * 8)
對上面方法封裝下,得到下面代碼:
object MyUtil{val noise by lazy {val noise = FastNoiseLite()noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2)//1000以內隨機,可以自行更改noise.SetSeed(Random.nextInt(1000))noise}//size默認為4,表明進行3次疊加,如上面示例fun noiseData(x: Float, y: Float, size: Int = 4): Double {val result = (0 until size).sumOf {//位與操作,相當于的2的n次方// 1 shr 0 = 1// 1 shr 1 = 2val num = 1 shl it1.0 * (1 / num) * noise.GetNoise(x * num, y * num)}return result}
}
這里需要注意的是,我們封裝得保證我們的每次調用函數的時候FastNoiseLite
用的是同個對象,否則就會出現其他效果,和我們預期不符,所以上面封裝我使用了單例模式
使用:
fun main(){//4*4的二維數據val size = 4 val re = Array(size) { IntArray(size) }(0 until size).forEach {x->var buffer = StringBuffer()(0 until size).forEach {y->val data = noiseData(x.toFloat(),y.toFloat())//todo 這里還需要將data轉為0-4的int數值并存放在二維數組中buffer.append(data.toString()+",")}println(buffer.toString())buffer=StringBuffer()}
}
上面只是得到的對應的噪音數據,而我們按照范圍,將其轉為對應的0-4數值,代碼如下:
fun getType(result: Double): Int {return when {result >= -1 && result < -0.6 -> 0result >= -0.6 && result < -0.2 -> 1else -> 3}
}
result的數據范圍在[-1,1]之間,所以可以根據自己的需求,符合對應范圍,自行給數值即可,上面代碼只是一個簡單的示例,你可以隨意劃分;
于是按照我的需求,我自行封裝成下面的方法(可能有些難以理解,大概解釋就是劃分為n等分,每個區間有個下標,然后數值符合對應區間的,則返回對應區間下標)
fun getType(result: Double): Int {//劃分為5等分val size = 5val temp = 2.0 / sizefor (i in (0 until size)) {val it = ival start = -1.0 + (it * temp)val end = -1.0 + (it + 1) * tempif (result >= start && result < end) {return it}}return size-1
}
最終這里我是使用一個簡單的javafx應用來展示地圖數據(每個地形則是不同顏色)
import javafx.application.Application
import javafx.scene.Scene
import javafx.scene.layout.HBox
import javafx.scene.layout.VBox
import javafx.stage.Stage
import site.starsone.demo.MyUtil.noiseData
import kotlin.random.Randomclass MyApp : Application() {override fun start(primaryStage: Stage?) {val root = VBox().apply {prefWidth = 600.0prefHeight = 600.0}val scene = Scene(root, 600.0, 600.0)val array = getData()array.forEach {val hbox = HBox()it.forEach { resultType ->val node = VBox().apply {prefWidth = 1.0prefHeight = 1.0style = "-fx-background-color: ${getColor(resultType)};"}hbox.children.add(node)}root.children.add(hbox)}primaryStage!!.title = "Hello World Example"primaryStage!!.scene = sceneprimaryStage!!.show()}val list = listOf("#FF0000", // Red"#00FF00", // Green"#0000FF", // Blue"#FFA500", // Orange"#800080" // Purple)fun getColor(type: Int): String {return list[type]}fun getData(): Array<IntArray> {val size = 600val re = Array(size) { IntArray(size) }(0 until size).forEach { x ->(0 until size).forEach { y ->val data = noiseData(x.toFloat(), y.toFloat())re[x][y] = getType(data)}}return re}fun getType(result: Double): Int {val size = 5val temp = 2.0 / sizefor (i in (0 until size)) {val it = ival start = -1.0 + (it * temp)val end = -1.0 + (it + 1) * tempif (result >= start && result < end) {return it}}return size-1}}fun main() {Application.launch(MyApp::class.java, "")
}
生成的截圖如下所示: