从编程的角度头编程来看,Minecraft 是怎么样设计的

从编程的角度来看,Minecraft 是怎么样设计的?
我的图书馆
从编程的角度来看,Minecraft 是怎么样设计的?
11.30更新:见后面地形生成和总结部分。过百赞了知乎小透明好开森,谢谢大家!研究过MC地形生成和基础架构的强答一发。长文预警。先说架构。MC分客户端和服务端两面,客户端主要负责渲染,服务端是本地服务器或者远程服务器(本地游戏两个都要跑,真正的MC服只跑服务端)。其实这俩就共享一个IWorld接口,然后这个接口里面乱的一塌糊涂。地图的基础单元是chunk,就是16*256*16的大方块,里面包含小方块。小方块可以是普通IBlock,可以带特殊数据(metadata),也可以有TileEntity(按照方块处理的实体)。区块里面还存了Entity就是实体(人啊,僵尸啊,猪啊还有掉落的物品之类的),实体的位置和方向都是浮点数float或double(双精度),因此拥有更大的自由度。前后端(客户端和服务端)存储数据都靠的这个chunk,传输和生成也是以chunk为单位,所以会看到服务器卡的时候是一块一块冒出来的。chunk放到HashMap里面备用。保存(1.2之后的原版服务端anvil文件格式)把32*32个chunk放到一个.mca(老版是.mcp)文件里面,用一种碎片化管理的方式来节省空间同时减少IO句柄数量,具体的格式MC Wiki上有详细的解释。所有MC的存储数据(注意不包括方块和音乐本身这种理论上是做死了的数据)都用一种二进制数据格式存储(NBT Named Binary Tags 命名二进制标签 ——Minecraft Wiki),里面包含数组数据、浮点数、整数或者字符串,其实结构相当简单,项目有树形结构,每个子项还有字符串形式的名字。它其实可以和JSON简单互译,做命令方块地图时会用到的JSON格式的实体数据就是这个东西。服务端动态加载/保存区块,然后用一个单线程来每个tick更新整个地图(所以很慢啊喂!),具体方式既粗暴又简单:遍历每个方块,看到有计划更新(比如水、岩浆、刷怪笼、植物这种)的就让他更新一下,看到TileEntity或者Entity每个tick都更新,同时处理玩家的数据:放方块/砸方块/开箱子/熔铁锭等等。对了,服务端还要负责更新光照(看后面)。要是玩家走到了没有生成过的荒野,就会启动区块生成器来生成新的区块。生成这个特别复杂,也是我主要研究的问题,一会儿再讲。客户端维护一个非常 *智障* 的GUI系统,里面的渲染基本上都是legacy opengl直接瞎写的,GUI更新基本上都是全屏更新的,鼠标行动基本上全是轮询的,根本不管什么事件啦dirty区域啦缓存啦,就两个字:粗暴。当然MC总共也没什么GUI好谈,所以也就算了。这里提一句其实服务器里面花花绿绿的字符的实现原理也非常暴力,就是用小结号§来代表下一个字符是格式转义字符,从k到r(好像?)都代表不同的意义,颜色或者加粗或者倾斜或者随机颜色,封面MINECRAFT大字旁边那行字就是用这个渲染的。什么?你问§怎么办?没办法。(摊手)除了GUI就是游戏主要的渲染部分。MC应用15年前的opengl技术成功地把每一个chunk编译成16个list(opengl中的列表,可以理解为把要画的步骤先告诉显卡以后再用只要告诉他列表的号码就行了,用来提升效率),每个16*16*16,不包括TileEntity和Entity——它们是每帧单独渲染的,所以MC生物一多就卡,而用bug透视的时候看不到方块但是能看得到生物和箱子。客户端每帧要是看到新的改变过的chunk(被什么东西更新过了)就把它和它四周的chunk都重新生成一遍list(这里你会看到要是服务器加载很慢的话最远处的chunk是看得到截面的),这个过程叫做tessellation,就是把每个小的面片(长方形)放到一起组成一个大的顶点数组(就是一个大模型),一股脑儿传给显卡(因为从cpu传连续数据比零零散散地传数据要快很多)。最后渲染的时候也是通过数学计算把在视野(按照玩家视角)中的list找出来一一调用渲染。这里有个小bug,就是筛选是否在视野中是假定这个list里面的所有东西都包含在这16*16*16的大方块里面的,所以如果你搞一个信标的话,它的光柱会一直延伸到天空上,但是一旦整个list被判断不可见,这个光柱也会一起消失掉。至于光照,光照也是在服务器端算好的(是不是难以想象?),我们看到的有渐变的平滑光照也是在方块光照上加工计算出来的。光照有两部分,一部分叫做天光(sky light),也就是不算任何方块光源这个位置的光照强度。细节上,他是这么计算的:如果他直接被阳光照射(上面没有别的方块),那么亮度是16(或者说15,取决于你是从0开始数数还是从1开始),如果上面一个是半透明方块,那么亮度是上面的亮度减去版透明方块的阻碍值(比如树叶是2,玻璃是1),如果上面是不透明方块(比如石头),那么亮度是0。你可能要问,山洞里面也不是全黑啊?别急,慢慢讲。区块生成的时候只会这样一遍地生成最简单的光照,而把光照的扩散放到一个队列里面等待服务端更新时一点点处理,每个tick能上限好像是1个事件。(理论上这是为了加快速度不让光照计算拖慢服务器来着,然而。。)处理事件是一个递归的过程,从这个方块出发,如果他6个方向(上下左右前后)的方块的亮度大于等于它,那就不递归(这种时候定义空气的光阻为1),否则把那个方向上的方块+(自身亮度-1)放进队列。当然要是减到0就不动了。按照mojang员工的天真想法,既然光照最大是15(因为我从0开始数数),每一格至少减弱1,那么跟我源头上这一个坐标差距大于十五的我就不用管了咯?所以他们限制每次递归最多递归到这么远,之后不管亮度多少都留到下一个tick去吧。但是这里会有一个bug:如果山洞顶上被开了一个洞,从天上漏光,结果从山洞边缘开始计算天光,算到洞附近发现开始变亮了,但是又碍于距离限制不能再算下去,所以有时候就会看到非常突兀的亮度变化。光照的另一部分叫做方块光(block light),也就是萤石啊南瓜灯啊火柴啊这种方块的灯光。这种也是碰到空气就-1,碰到不透明的石头就变成0,碰到半透明的就减去他的光阻量(代码里面叫做opacity,意为不透明度)。然后每个方块上的亮度以最大值为准,同样塞到前面的那个队列里面。服务端更新的时候计算。光照当然是用来渲染的,而这也是MC渲染极其重要的一部分。每个方块的光照强度等于方块光和天光的最大值,那么晚上怎么办呢?其实天光在晚上的亮度就等于 原先亮度 - 夜晚的亮度降,也就是原来12级光,白天是15级阳光,算出来就是12级,晚上变成11级月光,就是 12 - (15 - 11) = 8级光。当然这个值不能小于0。最早的光照对应就是一个线性的亮度,15级就是100%的亮度,7级就是50%,0级就是0%,后来发现这样效果不好,就改成了对数型:第(n-1)级的亮度是第n级的90%,这样亮度变化比较自然,而且哪怕光照为0也不是全黑(地狱/下界的这个系数是90%,0级光实际上还有46%的亮度,所以显得下界特别亮)。后来觉得还不好,所以就干脆弄了一个二维的亮度图,横坐标是天光,纵坐标是方块光,这个在minecraft.jar里面找得到一个纹理文件(哎呀名字我忘了),就是这个。因此可以看到方块光偏暖色,天光偏冷色,也是这么来的。渲染更重要的当然就是大家关心的纹理,所谓MC像素之称的来源之一。如果打开beta以后的minecraft,jar文件就会看到,纹理图片都是分开的图片,但是一个list(还记得list吗)只能用一个纹理单元,因此MC就用了一种取巧的做法:程序启动的/更换材质包时候把所有要用的纹理加载一遍然后拼到一张图上面,有时候会存到.minecraft/下的一个临时文件夹里面,再加载进显卡,就可以一起用了。当然Entity和TileEntity的纹理是不用这么干的,因为他们每个都是独立渲染的,不需要这么弄。这也是为什么MC的API要求方块必须在使用之前就在Registry里面注册好,这样才能知道你到底要哪些纹理啊。(貌似这个问题在1.8采用了新的model模式后有了改善)这也是为什么在切换语言之后MC都要卡好久,因为切换语言也算作切换材质包,要把所有的小纹理图片再拼接一遍,还要把所有的chunk已经生成好的list重新生成一遍。(卡!)然后就要根据玩家的位置,角度,以及人称(第一人称/背后第三人称/面对第二人称)来计算投影矩阵(也就是摆摄像机)。这个过程是在客户端处理的,所以就算服务端卡爆了你的脑袋还是可以自由旋转的,至于为什么你可以看到多人中别的玩家的脑袋转,我猜是时不时地传一些数据回去做插值弄的。最后直接丢给opengl画出来,三维渲染部分就基本完成了。其实这里MC还允许像光影mod那样的后期处理(post-processing),1.7左右的版本有一个“Super Secret Setting”的按钮在游戏暂停的设置里面,点一下可以在好多后期特效之间切换。还要画天空、太阳、月亮、天气和云层,当然太阳月亮就是贴个图(月有阴晴圆缺哦,纹理文件有一个专门管月相的),下雨下雪就是一个动画,云层也就是一堆半透明的方块。最后一个部分:第一人称的手和手上拿的东西,这个也是三维渲染的,不过难度不大,相信大家想想就明白了,也就不多说了。然后是介于三维和二维之间的粒子系统。粒子系统其实原来是用来模拟像水滴、碎片、树叶这样数量巨大的东西,以至于渲染成真正的三维模型代价过高,不得不用一个永远对着玩家的面来代替,后来就管所有这种永远面向玩家的面叫做“粒子”(particle),或者“标志板”(bulletin board)。MC的粒子系统主要都是一些效果:入水/钓鱼是的水滴,吃饭时的食物残渣,多人模式中别人头顶上的名字,魔药产生的效果,经验球,爆炸的“冲击波”,都是粒子。(是不是有点大)反正粒子一般放在同样的三维环境中渲染,用同样的投影矩阵,但是往往会被归为一个特殊的粒子引擎,因为它们作为没有体积的物体却经常要表现出一种重力感(就比如说水滴会跳起来再落下去),不能放到一般的三维场景中渲染。三维画完了是二维:下面的道具栏和聊天内容这种,按E可以赛艇(划掉)打开的完整物品栏(inventories)、各种可交互菜单(箱子、铁毡、熔炉、村民……)。这些凌驾于所有三维内容之上(因为不会被挡掉),最后被渲染,同时也充当与玩家交互的功能。(所以说MC的GUI很乱啦。。这里都不分清楚的)说到交互,肯定大家会想,电脑到底是怎么知道我选中了哪个面,往哪里放了东西了呢?其实判断选中的面还简单,做点小计算都不难,真正的难点在于客户端和服务器之间的传输协议。传输协议,顾名思义,就是两者之间关于信息传输所商定的协议。具体的技术细节在这里有详细说,我就不展开了。总结一下,就是:MC里面除了区块(chunk)的传输是全局的以外,其他各种东西的传输协议都是个管个的:箱子有箱子的传输协议,玩家有玩家的协议,熔炉有熔炉的协议。你作为玩家不管干了什么,改变了什么东西,都要通过相应的协议把实际内容传输给服务器端监听这个数据的东西,然后再由它对地图中的内容进行操作。这里也能看出MC的一大毛病:该耦合的不耦合,该分离的不分离,协议一大堆各自为政,各种方块(block)又互相调用,乱成一团——一个基础的Block类足足有几千行。---------------------------------------------分割线-----------------------------------------------------说了这么多,我们来理一下思路吧。MC启动你的游戏有两种:本地 or 远程。本地游戏需要 玩家操作GUI生成或加载世界 -> 启动本地服务端(IntegratedServer)-> 加载地图 -> 以单人单机模式提供游戏。或者远程服务器由服务器管理员启动(官网上有下载原版的minecraft_server.jar,但是基本上现在用的都是水桶bukkit服),然后 启动服务端(原版是MinecraftServer) -> 加载地图 -> 以多人联机模式提供游戏。服务端启动后的基本流程是: 等待玩家连接并通过验证 -> 发送初始数据 -> 按需发送/加载/生成地 图 -> 更新(tick)地图内容,加上散播生物啊之类的操作 -> 发送地图 -> ……而客户端则是: 连接服务端 -> 获取初始数据(时间、玩家位置、验证信息等) -> 获取地图(区块)数据 -> 渲染地图 -> 接受玩家输入(鼠标/键盘操作) -> 发送数据包给服务端 -> 获取更新过的地图 -> ……当然理论上这里很多步骤都应该是多线程并行进行的,但是MC的这个代码写的啊……很多都没有优化,这也是为什么MC老是卡的重要原因之一。这大致就是整个MC里面在发生的事情的总结,接下去的内容比较技术向,请不愿看者直接跳到再下一个分割线。-------------------------------------------分割线---------------------------------------------------说说MC的地形生成吧。这个我研究的比较多,也想借此机会说一说。多图杀猫预警!!!!!!!!!!!更新:来填坑啦!是时候祭出我尘封已久的mod开发工具了!(本段内容基于1.10.2的fml反编译,使用的是IntelliJ idea进行开发,教程。。有空再说吧)好吧这是今年8月从fml官网上直接生成的,前面那个换电脑的时候丢了。。悲伤。。不过这都不能阻止我的脚步!ctrl+alt+F10运行!OK!好吧说正事。微软接手之后MC开发组搞了一个功能:生成世界时进入更多世界选项(英文版More world options)世界类型点到“自定义”(Customized),(这里1.10.2直接点就可以了,我记得老一点的版本要按住alt还是ctrl再点才回出来),然后按“自定义”(Customize)按钮就能调整世界生成的一些参数:(调成中文吧。。)可以自定义的选项有四页,可以通过调整它们来直观地看到MC世界生成的各种参数:第三、第四页比较高级,之后说到具体的生成原理的时候再说,先来玩玩前两页的参数:抬高海平面,变成岩浆海,乱七八糟的东西都不要,河流越少越好,生物群系Extreme Hills+(全是山),生成!厉害了我的哥!这里菱形的亮区也证明了我前面说的:光照更新的时候只能更新15格以外的,更远的哪怕有变化也不会继续更新下去。过一会儿就好了。可以看到MC加载地图是加载了一个方形的区域。远处的树是悬空的,因为另一边的区块还没生成出来。因为离岩浆太近,草地上的雪都化了。按E可以赛艇。再来一个:泥土大理石统统不要,我就是要石头直接暴露在阳光之下。感觉发家致富都靠这个了。别的统统不要,Extreme Hills生物群系,生成!金矿与钻石的海洋!我要完成一个小目标了!(虽然是创造)好吧就玩到这里,我们来看代码吧。警告:接下来有大量程序猿内容,患有代码恐惧症者可跳过。地形生成的代码主要在这几个包里面:net.minecraft.world.gen底下的是 不同世界类型(主世界/下界/末地/超平坦)、重要的地形元素(洞穴/峡谷)、噪声生成器(用来生成随机的东西):net.minecraft.world.gen.features是各种特征,有树(包括树的变种针叶林之类的)、植被(仙人掌、花草之类的)、地质(黏土clay、沙子sand)、地貌(湖泊、沼泽、下界的岩浆)、人文景观(地牢、沙井,但是村庄女巫房海底遗迹这些不在这里):net.minecraft.world.gen.layers其实才是生成生物群系(biome),也就是从二维上确定每个竖列(1*256*1)是什么类别,net.minecraft.world.biome管的是生成生物群系之后确定每个生物群系的特征。这个包里面的东西很重要,一会儿一起说:net.minecraft.world.biome正如前面所说是各种生物群系的内容,这个也之后单独说:net.minecraft.world.gen.structure管的是复杂的大型人文景观生成(村庄,1.9之后的末地城,废弃矿洞,下界要塞,海底遗迹):net.minecraft.world.gen.structure.template是按照文件中定义的方块模板来确定真正使用的方块,就像自然生成的草地并不是千篇一律的,而是会随机地旋转一定的角度,就是这个东西在帮忙:net.minecraft.village和net.minecraft.world.end是管理村庄和末地的特殊活动(和村民交♂易,打末影龙):各个包的介绍就到这里,接下来介绍一下生成一个区块的整体流程,这里我截一段net.minecraft,world.gen.ChunkProvideOverworld(生成主世界区块)里面的代码来描述,加了中文注释:public Chunk provideChunk(int x, int z) { // 生成一个区块专属的伪随机种子,这个种子只跟x和z的位置有关 this.rand.setSeed((long)x * L + (long)z * L); ChunkPrimer chunkprimer = new ChunkPrimer(); // 封装了一下方块设置的操作 // 生成低分辨率的生物群系、密度图、方块,后面会讲 this.setBlocksInChunk(x, z, chunkprimer); // 生成标准大小的生物群系 this.biomesForGeneration = this.worldObj.getBiomeProvider().loadBlockGeneratorData(this.biomesForGeneration, x * 16, z * 16, 16, 16); // 按照生物群系替换掉高度图里面的普通方块 this.replaceBiomeBlocks(x, z, chunkprimer, this.biomesForGeneration); // 前面生成选项里面看到的各种设置,要不要洞穴之类的 if (this.settings.useCaves) { this.caveGenerator.generate(this.worldObj, x, z, chunkprimer); } if (this.settings.useRavines) { this.ravineGenerator.generate(this.worldObj, x, z, chunkprimer); } if (this.mapFeaturesEnabled) { if (this.settings.useMineShafts) { this.mineshaftGenerator.generate(this.worldObj, x, z, chunkprimer); } if (this.settings.useVillages) { this.villageGenerator.generate(this.worldObj, x, z, chunkprimer); } if (this.settings.useStrongholds) { this.strongholdGenerator.generate(this.worldObj, x, z, chunkprimer); } if (this.settings.useTemples) { this.scatteredFeatureGenerator.generate(this.worldObj, x, z, chunkprimer); } if (this.settings.useMonuments) { this.oceanMonumentGenerator.generate(this.worldObj, x, z, chunkprimer); } } // 构造区块对象,把方块和生物群系数据填进去 Chunk chunk = new Chunk(this.worldObj, chunkprimer, x, z); byte[] abyte = chunk.getBiomeArray(); for (int i = 0; i 重点的setBlocksInChunk函数:public void setBlocksInChunk(int x, int z, ChunkPrimer primer) { this.biomesForGeneration = this.worldObj.getBiomeProvider().getBiomesForGeneration(this.biomesForGeneration, x * 4 - 2, z * 4 - 2, 10, 10); // 这是一个低分辨率的生物群系表,用在generateHeightmap里面的 // 生成密度图,原文虽然写的是高度图,但是实际上是密度图,可能是fml的人理解有问题 this.generateHeightmap(x * 4, 0, z * 4); // 这里往下的这么一大段都是在插值,密度大于0的填上石头,低于海平面而且不是石头的 // 填上海水,别的都是空气。 // 话说这段真是乱七八糟,一堆magic number,估计fml的人也懒得看懂, // 既没加注释也没改变量名。。。 for (int i = 0; i
0.0D) { primer.setBlockState(i * 4 + k2, i2 * 8 + j2, l * 4 + l2, STONE); } else if (i2 * 8 + j2 感觉这一段还要解释一下。x和z不是16,32这样的世界坐标,而是区块坐标,是世界坐标的16/1,也就是区块(1, 2)代表了世界坐标上面的(16,32)到(31,47)的矩形区域。这里首先生成了一个低分辨率的生物群系,长宽是10*10,是因为接下来要用的密度图长宽是5*5。然后生成密度图,这个待会儿也会讲。之所以说是密度图而不是高度图是因为它是三维的5*33*5,而不是二维的结构,所以只能称之为密度。接下来是线性插值。一个区块是16*256*16,而为了让密度更为平滑,这里把密度图的长宽各放大4倍,高度放大8倍,再做线性插值就会比较光滑不会变化特别突兀。有人要问,5*4不是20吗?33*8不是264吗?其实因为5*33*5多出来的一圈是不用的,而且插值需要两个值才能计算,所以四个采样点其实需要5个数据,33也一样是这个道理。再浅显一点来说,密度图里面的0,1,2,3,4(java数组从0开始)对应的是区块x或z轴里面的0, 4, 8, 12, 16,既然我们要计算位置为13,14,15的方块,那就得用12和16对应的3和4来插值。插值的过程从循环变量为j2的那个循环开始,0.125就是8分之1,因为高度上放大8倍,后面的0.25也是因为放大了4倍,lvt_45_1其实可以写得易懂一点的,也是一个插值变量,跟前面的d1,d2,d3,d4,d10,d11是一样的,只不过mojang的程序猿为了装逼特地写了这么个东西罢了。。所以说代码质量差不仅在于架构,还在于其中各种细节的写法,像这种绝对就不算优秀的代码。先到这里吧。。还有好多。。各位先看着哈。。--------------------------------------------分割线-------------------------------------------------总结全文,深化主旨,首尾呼应:Minecraft是个好游戏,但代码写的确实不咋地。我最初接触MC还是13年,那时候大部分还在用1.4.2或者1.5,当然,都是盗版。一开始只是玩单机,打生存,搭房子,有时候开创造玩玩红石电路,特别喜欢一个人晚上独自边打MC边听他的bgm。C418的歌,很宁静,让人很放松。后来基本上大部分玩法都玩儿遍了,也不怎么愿意去花功夫做那种超大的红石电路或者建筑之类的,总想着找点别的花样。于是我装了Forge,下了很多mod,比如IndustrialCraft这种大名鼎鼎的,还有一些辅助性的mod,比如1.6.2之后就停止开发的SPC(Single Player Command 卖安利)。SPC是模仿WorldEdit的担任命令行mod,就是各种批量编辑,一个人搭建筑非常方便(因为WorldEdit WE只有多人能用,SPC可以单人模式用)。结果换1.7.2之后SPC不支持了,于是就萌生了自己开发一个的想法。(好吧最后还是流产了,当时水平不够做出来的性能很差,现在有能力做了又没了当初那种激情)又因为刚好学java,听说MC也是用java写的,所以就网上找教程写mod。我还记得14年的时候架着100K不到的梯子屁颠屁颠地装fml的时候,去maven central仓库上下东西动不动就停住了,结果一晚上都没装好。国内(哪怕到今天)都缺少真正有质量的mod开发教程,特别是中文的基本没有。贴吧上曾找到过一篇讲mod开发环境配置的,不过也止于1.6.2,到了1.7之后FML的整个API变了好多,原来的教程都用不了了。后来在网上找到了一篇文章: ,是MC最早的开发者Markus Persson(马库斯·泊松,网名notch)的个人博客,还有一篇讲述他的地形生成算法的博文:大概翻译一段(水平有限):In the very earliest version of Minecraft, I used a 2D Perlin noise heightmap to set the shape of the world. Or, rather, I used quite a few of them. One for overall elevation, one for terrain roughness, and one for local detail. For each column of blocks, the height was (elevation + (roughness*detail))*64+64. Both elevation and roughness were smooth, large scale noises, and detail was a more intricate one. This method had the great advantage of being very fast as there’s just 16*16*(noiseNum) samples per chunk to generate, but the disadvantage of being rather dull. Specifically, there’s no way for this method to generate any overhangs.在Minecraft最早的版本中,我将一张2维perlin noise图作为高度图来确定世界的形状。或者说,好几张perlin noise图。一张是整体海拔,一张是地形的粗糙度,一张是小范围的细节。对于每一列方块,其高度是 (海拔+(粗糙度*细节))*64+64。 海拔和粗糙度的图都是连续光滑的,大尺度的噪声图,而细节则更加参差不齐。这个生成方式有一个巨大的优点就是奇快无比,因为对于每个区块,只不过需要生成16*16*(噪声图的数量) 个采样点,然而缺点就是地图相当单调无趣。更重要的是,这种方式根本生成不了悬崖。So I switched the system over into a similar system based off 3D Perlin noise. Instead of sampling the “ground height”, I treated the noise value as the “density”, where anything lower than 0 would be air, and anything higher than or equal to 0 would be ground. To make sure the bottom layer is solid and the top isn’t, I just add the height (offset by the water level) to the sampled result.于是我改成了一种有些相似的、基于3维perlin noise的生成方式。我并不是对“地面高度”进行采样,而是将这个噪声的数值看做是“密度”(因此我说前面代码中应该是密度图而不是高度图 ——译者注),其中密度小于0的点都是空气而大于等于0的会成为大地(其实就是实心方块 ——译者注)。为了确保地图底端是地面而顶端是空气,我便把采样值加上采样点的高度(海拔高度)。(其实应该说是“减去采样点的高度”更易懂,因为大于0的才是方块,而水下的高度是负值 ——译者注)Unfortunately, I immediately ran into both performance issues and playability issues. Performance issues because of the huge amount of sampling needed to be done, and playability issues because there were no flat areas or smooth hills. The solution to both problems turned out to be just sampling at a lower resolution (scaled 8x along the horizontals, 4x along the vertical) and doing a linear interpolation. Suddenly, the game had flat areas, smooth hills, and also most single floating blocks were gone.不幸的是,我立刻碰上了可玩性和效率两方面的麻烦。因为有大量的点需要被采样所以效率很成问题,又因为缺乏大片的平地或平滑的山丘而缺乏可玩性。这两个问题共同的解决方案最终定为了这样:用一个更低的分辨率去采样噪声图(横向8倍,纵向4倍)(代码里面是横向4倍纵向8倍,可能跟后来的代码改动有关 ——译者注)并且进行线性插值(见setBlocksInChunk函数 ——译者注)。突如其来地,世界上有了平原、丘陵,而且单独浮在空中的诡异方块中的绝大部分也都消失无踪了。(其实还有很多了啦 ——译者注)这激起了我浓厚的兴趣。于是我把长长的代码打印下来,8号字体,一面3列,足足十几张纸,每天一有空就看代码,读代码,用笔做注释,并且为我觉得命名不当的部分想一个更好的名称。到现在这些还藏在我的书桌里面。这些东西百度根本搜不到,google也很少有结果,而以fml对这些代码的注释程度来看他们根本懒得去读这些既混乱又不必要读懂的代码。当然后来还是不了了之了。我自己也仿造过MC,当时就是完全追星似的疯狂模仿,用的java,跟MC一样的LWJGL库,同样(好吧我承认这么做有盗版的嫌疑)的纹理,几乎相同的文件格式、区块大小、渲染原理、(基于notch文章的)地形生成、体系架构,几乎就是我前面写的这么多的一个自己的重现。我甚至还学习MC混淆了编译后的代码。可笑的是,迫于当时的水平,效率竟比MC原版还要低。最后开发进程是因为我U盘的丢失而终止的,源码自然也丢了,只剩一份速度奇慢的编译版本,下载地址:,有兴趣的自己下载来玩玩吧。今年想过要重制,结果忙了一个夏天又泡汤了,现在想来总觉得有点可惜。后来被朋友忽悠着去打了一段时间MC服务器,也买了正版,不过服务器没有打过一个月的,基本两三个星期就崩掉关服不开放了,也不知道是我的问题还是整个现状都这样。(我真的没有干过坏事啊!)据说国内开一个服很快就会有黑客来勒索要钱否则就DDoS攻击(Distributed Denial of Service分布式拒绝服务攻击,用大量肉鸡的流量淹没对方,让对方或因为过高的流量费用而关闭服务,或因为过低的响应速度而降低服务质量到一个不可接受的程度),也不知道是不是真的,反正崩了好几个服,打拼好久的家园东西都没了,也就不打了。最近确实玩的不多,也就偶尔休息的时候像以前那样,搞搞建筑,搭搭红石电路之类的,都是单机玩,一方面没空,一方面也有点享受那种独自一人的感觉和C418的背景音乐。我知道这段算不上问题的“从编程的角度来看”,不过写了前面这么多,也使我回想起来以前发生过的不少。要不是这个答案,也不知道这段往事会被封存到什么时候。就借此机会,一吐为快吧。希望大家不必计较,并且欢迎任何技术上的讨论。以上。各位看官看完记得点个赞再走啊。。。谢啦。。。终于不是知乎小透明了咯好开森。。。
TA的最新馆藏
喜欢该文的人也喜欢

我要回帖

更多关于 从cpu的编程结构角度 的文章

 

随机推荐