three.js开发我的世界minecraft(一)地形的生成
我的世界minecraft地形生成
目录
官网提供了一个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;值要大,配的上场景才行。下一部减少场景比例。阴影也耗性能,暂时可以考虑不添加。
添加鸡
鸡这个元素在游戏中很神奇,不能少。我得添加一些公鸡母鸡,在场景溜达,还能下金蛋。
添加小怪兽
四处游荡在场景中的小怪兽,天黑了小怪兽就出来了,会攻击玩家,会吃掉金蛋。
添加玩家交互
玩家用手中的工具,可以收集花草,可以攻击怪兽。
玩家自定义场景
玩家可以自己添加立方体,可以删除立方体。初始化的场景的地形是合并了的,不好修改。只能创建和消除自己建立的立方体了。
更多推荐
所有评论(0)