• Libgdx游戏开发(3)——通过柏林噪音算法地图随机地形


    本文为作者原创,允许转载,不过请在文章开头明显处注明链接和出处!!! 谢谢配合~
    作者:stars-one
    链接:https://www.cnblogs.com/stars-one/p/18248193

    本篇大约有4695个字,阅读预计需要5.87分钟


    原文: Libgdx游戏开发(3)——通过柏林噪音算法地图随机地形-Stars-One的杂货小窝

    在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 = 2
                val num = 1 shl it
                1.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 -> 0
    		result >= -0.6 && result < -0.2 -> 1
    		else -> 3
    	}
    }
    

    result的数据范围在[-1,1]之间,所以可以根据自己的需求,符合对应范围,自行给数值即可,上面代码只是一个简单的示例,你可以随意划分;

    于是按照我的需求,我自行封装成下面的方法(可能有些难以理解,大概解释就是划分为n等分,每个区间有个下标,然后数值符合对应区间的,则返回对应区间下标)

    fun getType(result: Double): Int {
    	//划分为5等分
    	val size = 5
    	
    	val temp = 2.0 / size
    
    	for (i in (0 until size)) {
    		val it = i
    
    		val start = -1.0 + (it * temp)
    		val end = -1.0 + (it + 1) * temp
    
    		if (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.Random
    
    
    class MyApp : Application() {
        override fun start(primaryStage: Stage?) {
            val root = VBox().apply {
                prefWidth = 600.0
                prefHeight = 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.0
                        prefHeight = 1.0
                        style = "-fx-background-color: ${getColor(resultType)};"
                    }
                    hbox.children.add(node)
                }
                root.children.add(hbox)
            }
    
    
    
    
            primaryStage!!.title = "Hello World Example"
            primaryStage!!.scene = scene
            primaryStage!!.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 {
            val size = 600
            val 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 = 5
            val temp = 2.0 / size
    
            for (i in (0 until size)) {
                val it = i
    
                val start = -1.0 + (it * temp)
                val end = -1.0 + (it + 1) * temp
    
                if (result >= start && result < end) {
                    return it
                }
            }
    
            return size-1
        }
    
    }
    
    fun main() {
        Application.launch(MyApp::class.java, "")
    }
    

    生成的截图如下所示:

    参考

  • 相关阅读:
    shiro笔记
    [Angular 基础] - 表单:响应式表单
    Spire.Office NET 7.7.6 重大重量 Spire.Office 7.7.X JAVA
    raw图片处理推荐 DxO PhotoLab 6 for Mac中文最新
    ssh服务攻防与加固
    Pygame中Sprite类的使用1
    【体系结构】IEEE754浮点数标准学习与机器码表示总结
    YOLOv5涨点优化:backbone改进 | TransXNet:聚合全局和局部信息的全新CNN-Transformer视觉主干| CVPR2024
    web大作业:简单的学生网页作业源码 基于html css javascript jquery实现智能分控网站
    郑州大学编译原理实验二语法分析器JAVA
  • 原文地址:https://www.cnblogs.com/stars-one/p/18248193