当前位置:首页 > CN2资讯 > 正文内容

Unity我的世界地形生成算法

9小时前CN2资讯


我的世界minecraft地形生成

目录

我的世界minecraft地形生成

Perlin Noise (柏林噪声)

基本单位立方体

地形生成

 种树

添加玩家

添加阴影


 

官网提供了一个minecraft案例:examples/webgl_geometry_minecraft.html

生成的效果,中间是一个直径10的小球,我作为参考用的,这里面的立方体在three.js中的单位是100。

案例中的地形生成,主要是采用Perlin Noise随机生成。也可以采用灰度图来生成。难点是理解Perlin Noise算法。

Perlin Noise (柏林噪声)

柏林噪声是由Ken Perlin于1983年提出的一种梯度噪声(Gradient Noise,通常由计算机模拟得到的一组噪声,相较于传统的离散数值噪声value noise要更加连续平滑)他在1985年的SIGGRAPH会议上,做了一场以“An Image Synthesizer”为题的学术报告,正式提出他的这一发现。

      柏林噪声的应用非常广泛: 合成地形高度图、生成物体表面的复杂纹理、火焰烟雾特效、波动效果的模拟等等。
 Ken Perlin的个人主页:http://mrl.nyu.edu/~perlin/(纽约大学-媒体研究实验室 nyu Media Research Lab)

看了一上午,这个perlin noise 的坑有点大,还是直接跳过,不跳进去了,免得出不来。泪奔啊,这几天每天上午写bug,下午找bug,一天时间就浪费了。

基本单位立方体

我的世界,是由“像素块”组成的,以一个立方体为单位。我想以立方体为基本模型单位来生成整个地形。但是研究官方的代码之后,发现人家更高明。一个立方体是有5个面组成的,底部的面直接省略掉,牛。

案例中修改了uv的坐标,这样的好处是使用一张图就可以贴出不同的效果。下面是未修改uv的贴图情况。关于attributes.uv.array在文档中没有找到说明。自己的理解就是 平面有四个顶点,对于的uv坐标是8个值,存在数组中,顺序是上左,上右,下左,下右,先x后y。这样就可以生成侧面和顶面的纹理了。

 原贴图文件很小,16X32像素。放大后呈现方块化效果,正符合我的世界的风格。主要是设置了magFilter属性,指定了纹理的放大方式,nearestFilter最邻近过滤。

// 右边 var pxGeometry = new THREE.PlaneBufferGeometry( 100, 100 ); pxGeometry.attributes.uv.array[ 1 ] = 0.5; pxGeometry.attributes.uv.array[ 3 ] = 0.5; pxGeometry.rotateY( Math.PI / 2 ); pxGeometry.translate( 50, 0, 0 ); //左边 var nxGeometry = new THREE.PlaneBufferGeometry( 100, 100 ); nxGeometry.attributes.uv.array[ 1 ] = 0.5; nxGeometry.attributes.uv.array[ 3 ] = 0.5; nxGeometry.rotateY( - Math.PI / 2 ); nxGeometry.translate( - 50, 0, 0 ); //上面 var pyGeometry = new THREE.PlaneBufferGeometry( 100, 100 ); pyGeometry.attributes.uv.array[ 5 ] = 0.5; pyGeometry.attributes.uv.array[ 7 ] = 0.5; pyGeometry.rotateX( - Math.PI / 2 ); pyGeometry.translate( 0, 50, 0 ); //前面 var pzGeometry = new THREE.PlaneBufferGeometry( 100, 100 ); pzGeometry.attributes.uv.array[ 1 ] = 0.5; pzGeometry.attributes.uv.array[ 3 ] = 0.5; pzGeometry.translate( 0, 0, 50 ); //后面 var nzGeometry = new THREE.PlaneBufferGeometry( 100, 100 ); nzGeometry.attributes.uv.array[ 1 ] = 0.5; nzGeometry.attributes.uv.array[ 3 ] = 0.5; nzGeometry.rotateY( Math.PI ); nzGeometry.translate( 0, 0, -50 );

地形生成

部分代码,先定义了地图的长宽。然后是生成的noise数组,放在data中。

var worldWidth = 128, worldDepth = 128, worldHalfWidth = worldWidth / 2, worldHalfDepth = worldDepth / 2, data = generateHeight( worldWidth, worldDepth ); function generateHeight( width, height ) { var data = [], perlin = new ImprovedNoise(), size = width * height, quality = 2, z = Math.random() * 100; for ( var j = 0; j < 4; j ++ ) { if ( j === 0 ) for ( var i = 0; i < size; i ++ ) data[ i ] = 0; for ( var i = 0; i < size; i ++ ) { var x = i % width, y = ( i / width ) | 0; data[ i ] += perlin.noise( x / quality, y / quality, z ) * quality; } quality *= 4; } return data; } function getY( x, z ) { return ( data[ x + z * worldWidth ] * 0.2 ) | 0; } var matrix = new THREE.Matrix4(); var geometries = []; for ( var z = 0; z < worldDepth; z ++ ) { for ( var x = 0; x < worldWidth; x ++ ) { var h = getY( x, z ); matrix.makeTranslation( x * 100 - worldHalfWidth * 100, h * 100, z * 100 - worldHalfDepth * 100 ); var px = getY( x + 1, z ); var nx = getY( x - 1, z ); var pz = getY( x, z + 1 ); var nz = getY( x, z - 1 ); geometries.push( pyGeometry.clone().applyMatrix( matrix ) ); if ( ( px !== h && px !== h + 1 ) || x === 0 ) { geometries.push( pxGeometry.clone().applyMatrix( matrix ) ); } if ( ( nx !== h && nx !== h + 1 ) || x === worldWidth - 1 ) { geometries.push( nxGeometry.clone().applyMatrix( matrix ) ); } if ( ( pz !== h && pz !== h + 1 ) || z === worldDepth - 1 ) { geometries.push( pzGeometry.clone().applyMatrix( matrix ) ); } if ( ( nz !== h && nz !== h + 1 ) || z === 0 ) { geometries.push( nzGeometry.clone().applyMatrix( matrix ) ); } } } var geometry = THREE.BufferGeometryUtils.mergeBufferGeometries( geometries ); geometry.computeBoundingSphere(); var texture = new THREE.TextureLoader().load( 'minecraft/atlas.png' ); texture.magFilter = THREE.NearestFilter; var mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { map: texture, side: THREE.DoubleSide } ) ); scene.add( mesh );

 种树

场景有点单调,我的得种上一点树。种树思路,把地形的坐标都保存在数组中,然后随机的生成树。百度一张树的图片,要左右对称。用作plane的贴图。两个相交效果就可以了。比我的世界的树要简单很多,当然效果也差很多。

// tree 种树 function drTree() { var length = planeXYZ.length; for (var i = 0; i < 100; i++) { var po = Math.round(Math.random() * length); tree(3, planeXYZ[po].xx, planeXYZ[po].xh + 50 + 3 * 90, planeXYZ[po].xz); } } function tree(h, x, y, z) { var texture = new THREE.TextureLoader().load('minecraft/tree1.png'); texture.magFilter = THREE.NearestFilter; var tGeometry = new THREE.PlaneBufferGeometry(120 * h, 180 * h); // tGeometry.rotateY( - Math.PI / 2 ); var rGeometry = new THREE.PlaneBufferGeometry(120 * h, 180 * h); rGeometry.rotateY(Math.PI / 2); var mater = new THREE.MeshPhongMaterial({ map: texture, transparent: true, depthWrite: false, side: THREE.DoubleSide, // blending: THREE.AdditiveBlending, //opacity: .9, }); var tree = new THREE.Mesh(tGeometry,mater); tree.add(new THREE.Mesh(rGeometry,mater)); tree.castShadow = true; tree.position.set(x, y, z); scene.add(tree); return tree }

树种完,还要种花花草草的。原理是一样。但是,种的多了,随机的方法就会出现 花草叠加在一起的情况。想的可以用es6种的集合代替数组,可以避免位置重叠。

 

添加玩家

添加一个小人,用键盘wsad键控制移动。关机地方是移动的时候要适应地图的高度。

这个小球就代表玩家。可以移动。但是在上台阶和下台阶的时候有bug,我没有去处理判断,会悬空,会进入草地。暂时实现基本功能就行了。

 代码很简单。创建小球,添加键盘事件,移动小球就ok。

runball = sphereDr(50,0, getY(worldHalfWidth, worldHalfDepth) * 100+100, 0); document.body.addEventListener("keydown", function (k) { if(k.key == "w"){ runball.position.z -= 20 ; runball.position.y = getY(worldHalfWidth+Math.round(runball.position.x/100), worldHalfDepth+Math.round(runball.position.z/100)) * 100+100 } if(k.key == "s"){ runball.position.z += 20 ; runball.position.y = getY(worldHalfWidth+Math.round(runball.position.x/100), worldHalfDepth+Math.round(runball.position.z/100)) * 100+100 } if(k.key == "a"){ runball.position.x -= 20 ; runball.position.y = getY(worldHalfWidth+Math.round(runball.position.x/100), worldHalfDepth+Math.round(runball.position.z/100)) * 100+100 } if(k.key == "d"){ runball.position.x += 20 ; runball.position.y = getY(worldHalfWidth+Math.round(runball.position.x/100), worldHalfDepth+Math.round(runball.position.z/100)) * 100+100 } });

 

添加阴影

这个看起来有必要,不然场景光线有点单一。添加阴影后还能模拟太阳东升西落的效果。场景单位很大,导致我添加阴影失败,原来需要设置一下阴影的默认值。spotlight.shadow.camera.far = 8000;值要大,配的上场景才行。下一部减少场景比例。阴影也耗性能,暂时可以考虑不添加。

 

添加鸡

鸡这个元素在游戏中很神奇,不能少。我得添加一些公鸡母鸡,在场景溜达,还能下金蛋。

 

添加小怪兽

四处游荡在场景中的小怪兽,天黑了小怪兽就出来了,会攻击玩家,会吃掉金蛋。

 

添加玩家交互

玩家用手中的工具,可以收集花草,可以攻击怪兽。

 

玩家自定义场景

玩家可以自己添加立方体,可以删除立方体。初始化的场景的地形是合并了的,不好修改。只能创建和消除自己建立的立方体了。

 

    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/31808.html

    分享给朋友:

    “Unity我的世界地形生成算法” 的相关文章

    有效的被墙检测方法与工具指南

    被墙检测是指对于网站或网页进行一系列测试,以判断其是否被网络审查所封锁。这一过程不仅是技术上的探索,也是用户获取信息自由的重要环节。在如今的信息时代,能够顺利访问需要的信息,对个人和企业来说都是至关重要的。被墙检测帮助我们确认某些敏感网站或关键词的可达性,揭示了网络审查背后的复杂机制。 被墙检测的重...

    推荐高效的CN2 GIA VPS解决方案与商家分析

    在如今快速发展的互联网时代,对于个人用户和企业来说,服务器的选择显得尤为重要。CN2 GIA VPS,作为一种高效的虚拟专用服务器,逐渐成为许多人青睐的选择。它是什么?到底能为我们提供什么样的服务呢?我来分享一下我对CN2 GIA VPS的理解。 CN2 GIA VPS,是一种通过中国电信的CN2...

    BT下载机的使用技巧与软件下载推荐

    在数字时代,文件共享变得越来越普遍,BT下载机作为一种基于BitTorrent协议的P2P(Peer-to-Peer)文件共享工具,扮演着重要的角色。我记得第一次接触BT下载机时,发现它的操作不仅简单,还能快速下载大型文件,这让我对它产生了浓厚的兴趣。BT下载机允许用户通过种子文件(.torrent...

    AS4837线路概述与技术特点解析,适合预算有限用户的高速网络选择

    AS4837线路的概述 当我开始了解AS4837线路时,就会发现它不仅仅是一个技术名词,更是中国联通的一部分。AS4837,简称为China169,是中国联通的骨干网线路,诞生于20世纪90年代。这条线路架起了中国大陆与全球之间的桥梁,特别是连接了香港、美国、日本和韩国等重要地区。对于那些追求高速互...

    Win10一键安装SQLite脚本:简化你的数据库配置过程

    在开始使用SQLite之前,首先需要确保它已经正确安装在你的Windows 10系统上。这个过程包括几个简单的步骤。我会逐步带你完成这些操作,让你能快速进入SQLite的世界。 访问SQLite官网 首先,前往SQLite的官方网站,网址是https://www.sqlite.org/downloa...

    Rocky Linux 更新源配置及优化方法

    我最近对Rocky Linux这款操作系统有了更深入的了解。Rocky Linux是一个以开源为基础的企业级操作系统,跟Red Hat Enterprise Linux(RHEL)兼容。它的设计宗旨在于为用户提供一个稳定和可靠的平台。因此,更新源就显得非常重要,影响着系统的升级和软件的安装。 选择合...