我的世界Minecraft源码分析(2):Block,Section和Chunk

发表于2017-09-07
评论0 7.4k浏览

这个系列通过对我的世界Minecraft源码进行拆分讲解,让大家可以清除的了解一款游戏是怎么一步步被实现出来的,下面就介绍Minecraft源码第二篇关于BlockSectionChunk的使用。

Block

block是Minecraft中最基本的组成元素,也就是常说的“块”。其类图如下



图1. Block结构


简单说明一下Block基类:

pos:块的位置

lightOpacity:透光系数

lightValue:当前块的光照值

blockHardness:块的坚硬度,和挖掘次数有关

slipperriness:摩擦系数

stepSound:踩在Block上的脚步声


函数主要是一些Get/Set方法,还有一些回掉函数。

有了Block基类之后,其他的Block只要继承它就可以了,然后override掉要自定义的函数。



Section和Chunk

如下图所示,一个Section由16*16*16,一个Chunk由16个Section组成,最下面是0号,最上面是15号。


图2 Chunk和Section示意图


而整个世界就是一个个Chunk组成。

Section在MC中用ExtendedBlockStorage来描述

  1. public class ExtendedBlockStorage  
  2. {  
  3.     /** Contains the bottom-most Y block represented by this ExtendedBlockStorage. Typically a multiple of 16. */  
  4.     private int yBase;  
  5.     /** A total count of the number of non-air blocks in this block storage's Chunk. */  
  6.     private int blockRefCount;  
  7.     /** 
  8.      * Contains the number of blocks in this block storage's parent chunk that require random ticking. Used to cull the 
  9.      * Chunk from random tick updates for performance reasons. 
  10.      */  
  11.     private int tickRefCount;  
  12.     private char[] data;  
  13.     /** The NibbleArray containing a block of Block-light data. */  
  14.     private NibbleArray blocklightArray;  
  15.     /** The NibbleArray containing a block of Sky-light data. */  
  16.     private NibbleArray skylightArray;  
  17.   
  18.     public ExtendedBlockStorage(int y, boolean storeSkylight)  
  19.     {  
  20.         this.yBase = y;  
  21.         this.data = new char[4096];  
  22.         this.blocklightArray = new NibbleArray();  
  23.   
  24.         if (storeSkylight)  
  25.         {  
  26.             this.skylightArray = new NibbleArray();  
  27.         }  
  28.     }  
  29.   
  30.     public IBlockState get(int x, int y, int z)  
  31.     {  
  32.         IBlockState iblockstate = (IBlockState)Block.BLOCK_STATE_IDS.getByValue(this.data[y << 8 | z << 4 | x]);  
  33.         return iblockstate != null ? iblockstate : Blocks.air.getDefaultState();  
  34.     }  
  35.   
  36.     public void set(int x, int y, int z, IBlockState state)  
  37.     {  
  38.         if (state instanceof net.minecraftforge.common.property.IExtendedBlockState)  
  39.             state = ((net.minecraftforge.common.property.IExtendedBlockState) state).getClean();  
  40.         IBlockState iblockstate1 = this.get(x, y, z);  
  41.         Block block = iblockstate1.getBlock();  
  42.         Block block1 = state.getBlock();  
  43.   
  44.         if (block != Blocks.air)  
  45.         {  
  46.             --this.blockRefCount;  
  47.   
  48.             if (block.getTickRandomly())  
  49.             {  
  50.                 --this.tickRefCount;  
  51.             }  
  52.         }  
  53.   
  54.         if (block1 != Blocks.air)  
  55.         {  
  56.              this.blockRefCount;  
  57.   
  58.             if (block1.getTickRandomly())  
  59.             {  
  60.                  this.tickRefCount;  
  61.             }  
  62.         }  
  63.   
  64.         this.data[y << 8 | z << 4 | x] = (char)Block.BLOCK_STATE_IDS.get(state);  
  65.     }  
  66.   
  67.     /** 
  68.      * Returns the block for a location in a chunk, with the extended ID merged from a byte array and a NibbleArray to 
  69.      * form a full 12-bit block ID. 
  70.      */  
  71.     public Block getBlockByExtId(int x, int y, int z)  
  72.     {  
  73.         return this.get(x, y, z).getBlock();  
  74.     }  
  75.   
  76.     /** 
  77.      * Returns the metadata associated with the block at the given coordinates in this ExtendedBlockStorage. 
  78.      */  
  79.     public int getExtBlockMetadata(int x, int y, int z)  
  80.     {  
  81.         IBlockState iblockstate = this.get(x, y, z);  
  82.         return iblockstate.getBlock().getMetaFromState(iblockstate);  
  83.     }  
  84.   
  85.     /** 
  86.      * Returns whether or not this block storage's Chunk is fully empty, based on its internal reference count. 
  87.      */  
  88.     public boolean isEmpty()  
  89.     {  
  90.         return this.blockRefCount == 0;  
  91.     }  
  92.   
  93.     /** 
  94.      * Returns whether or not this block storage's Chunk will require random ticking, used to avoid looping through 
  95.      * random block ticks when there are no blocks that would randomly tick. 
  96.      */  
  97.     public boolean getNeedsRandomTick()  
  98.     {  
  99.         return this.tickRefCount > 0;  
  100.     }  
  101.   
  102.     /** 
  103.      * Returns the Y location of this ExtendedBlockStorage. 
  104.      */  
  105.     public int getYLocation()  
  106.     {  
  107.         return this.yBase;  
  108.     }  
  109.   
  110.     /** 
  111.      * Sets the saved Sky-light value in the extended block storage structure. 
  112.      */  
  113.     public void setExtSkylightValue(int x, int y, int z, int value)  
  114.     {  
  115.         this.skylightArray.set(x, y, z, value);  
  116.     }  
  117.   
  118.     /** 
  119.      * Gets the saved Sky-light value in the extended block storage structure. 
  120.      */  
  121.     public int getExtSkylightValue(int x, int y, int z)  
  122.     {  
  123.         return this.skylightArray.get(x, y, z);  
  124.     }  
  125.   
  126.     /** 
  127.      * Sets the saved Block-light value in the extended block storage structure. 
  128.      */  
  129.     public void setExtBlocklightValue(int x, int y, int z, int value)  
  130.     {  
  131.         this.blocklightArray.set(x, y, z, value);  
  132.     }  
  133.   
  134.     /** 
  135.      * Gets the saved Block-light value in the extended block storage structure. 
  136.      */  
  137.     public int getExtBlocklightValue(int x, int y, int z)  
  138.     {  
  139.         return this.blocklightArray.get(x, y, z);  
  140.     }  
  141.   
  142.     public void removeInvalidBlocks()  
  143.     {  
  144.         this.blockRefCount = 0;  
  145.         this.tickRefCount = 0;  
  146.   
  147.         for (int i = 0; i < 16;  i)  
  148.         {  
  149.             for (int j = 0; j < 16;  j)  
  150.             {  
  151.                 for (int k = 0; k < 16;  k)  
  152.                 {  
  153.                     Block block = this.getBlockByExtId(i, j, k);  
  154.   
  155.                     if (block != Blocks.air)  
  156.                     {  
  157.                          this.blockRefCount;  
  158.   
  159.                         if (block.getTickRandomly())  
  160.                         {  
  161.                              this.tickRefCount;  
  162.                         }  
  163.                     }  
  164.                 }  
  165.             }  
  166.         }  
  167.     }  
  168.   
  169.     public char[] getData()  
  170.     {  
  171.         return this.data;  
  172.     }  
  173.   
  174.     public void setData(char[] dataArray)  
  175.     {  
  176.         this.data = dataArray;  
  177.     }  
  178.   
  179.     /** 
  180.      * Returns the NibbleArray instance containing Block-light data. 
  181.      */  
  182.     public NibbleArray getBlocklightArray()  
  183.     {  
  184.         return this.blocklightArray;  
  185.     }  
  186.   
  187.     /** 
  188.      * Returns the NibbleArray instance containing Sky-light data. 
  189.      */  
  190.     public NibbleArray getSkylightArray()  
  191.     {  
  192.         return this.skylightArray;  
  193.     }  
  194.   
  195.     /** 
  196.      * Sets the NibbleArray instance used for Block-light values in this particular storage block. 
  197.      */  
  198.     public void setBlocklightArray(NibbleArray newBlocklightArray)  
  199.     {  
  200.         this.blocklightArray = newBlocklightArray;  
  201.     }  
  202.   
  203.     /** 
  204.      * Sets the NibbleArray instance used for Sky-light values in this particular storage block. 
  205.      */  
  206.     public void setSkylightArray(NibbleArray newSkylightArray)  
  207.     {  
  208.         this.skylightArray = newSkylightArray;  
  209.     }  
  210. }  


可以看到,每一个Section都用一个int来表示它是chunk中的第几个Section,data是一个4096个char大小的数组,注意,Java由于使用的unicode编码,所以一个char要用两个Byte,每个byte是4个bit。

4096 =  16 * 16 * 16,也就是每个block用一个char来表示Block的类型以及状态。这里的状态用id来表示,范围是0-65535,不仅包含了块的种类,比如石块,土块等,还包含了比如门是打开的还是关闭的。


接下来是用两个NibbleArray来存储Block的光照信息。一个是点光源,一个是skylight。

NibbleArray里面用了一个2048大小的byte数组来进行存储信息。关于这2048byte的数据分布,MC中的光照强度分0-15级,用4bit来表示,所以一共用4 * 4096 bit,也就是2048个byte来存储。


接下来看一下Chunk

Chunk相关的一些类的结构如下


图3 Chunk相关的一些类


可以看到这里采用了用了Provider模式,将Chunk的接口进行封装了一遍,分别实现了服务器端的ChunkProviderServer和客户端的ChunkProviderClient,服务器端的Prover还含有一个ChunkLoader成员用于处理Chunk的加载和卸载。


Chunk的几个主要成员

posX,posZ:chunk坐标

SectionStorageArray:就是Section

blockBiomeArray:Chunk的Biome信息

heighmap:256长度的int数组记录每一个colume的height

isChunkLoaded:记录Chunk是否被加载

tileEntityList:Chunk中的tileEntity,比如宝箱之类的

entityList:Chunk中的mob等


Chunk的函数成员主要是一些Get/Set方法和回调函数,下面具体分析一下其中的几个重要函数。


Chunk的加载和卸载


卸载对应的函数是Chunk.onChunkUnload
  1. /** 
  2.   * Called when this Chunk is unloaded by the ChunkProvider 
  3.   */  
  4.  public void onChunkUnload()  
  5.  {  
  6.      this.isChunkLoaded = false;  
  7.      Iterator iterator = this.chunkTileEntityMap.values().iterator();  
  8.   
  9.      while (iterator.hasNext())  
  10.      {  
  11.          TileEntity tileentity = (TileEntity)iterator.next();  
  12.          this.worldObj.markTileEntityForRemoval(tileentity);  
  13.      }  
  14.   
  15.      for (int i = 0; i < this.entityLists.length;  i)  
  16.      {  
  17.          this.worldObj.unloadEntities(this.entityLists[i]);  
  18.      }  
  19.      MinecraftForge.EVENT_BUS.post(new ChunkEvent.Unload(this));  
  20.  }  

主要是将Chunk内的TileEntity和普通entity都添加到World中对应的List,world可以对这些entity做出对应的处理。


chunk卸载的时候,需要将chunk信息全部存储到NBT格式的文件中,包括光照信息,entity信息,block信息等。具体处理对应的类是AnvilChunkLoader。


Chunk的加载调用的是 ChunkProviderServer.loadChunk方法, 主要发生

1)初次生成世界,需要预先加载一部分的块,对应的是Minecraft Server的 copy

  1. protected void initialWorldChunkLoad()  
  2. {  
  3.     boolean flag = true;  
  4.     boolean flag1 = true;  
  5.     boolean flag2 = true;  
  6.     boolean flag3 = true;  
  7.     int i = 0;  
  8.     this.setUserMessage("menu.generatingTerrain");  
  9.     byte b0 = 0;  
  10.     logger.info("Preparing start region for level "   b0);  
  11.     WorldServer worldserver = net.minecraftforge.common.DimensionManager.getWorld(b0);  
  12.     BlockPos blockpos = worldserver.getSpawnPoint();  
  13.     long j = getCurrentTimeMillis();  
  14.   
  15.     for (int k = -192; k <= 192 && this.isServerRunning(); k  = 16)  
  16.     {  
  17.         for (int l = -192; l <= 192 && this.isServerRunning(); l  = 16)  
  18.         {  
  19.             long i1 = getCurrentTimeMillis();  
  20.   
  21.             if (i1 - j > 1000L)  
  22.             {  
  23.                 this.outputPercentRemaining("Preparing spawn area", i * 100 / 625);  
  24.                 j = i1;  
  25.             }  
  26.   
  27.              i;  
  28.             worldserver.theChunkProviderServer.loadChunk(blockpos.getX()   k >> 4, blockpos.getZ()   l >> 4);  
  29.         }  
  30.     }  
  31.   
  32.     this.clearCurrentTask();  
  33. }  


2)服务器中添加了新的玩家,需要加载玩家所在块。

对应PlayerInstance中的

  1. public void addPlayer(EntityPlayerMP playerMP)  
  2. {  
  3.     if (this.playersWatchingChunk.contains(playerMP))  
  4.     {  
  5.         PlayerManager.pmLogger.debug("Failed to add player. {} already is in chunk {}, {}"new Object[] {playerMP, Integer.valueOf(this.chunkCoords.chunkXPos), Integer.valueOf(this.chunkCoords.chunkZPos)});  
  6.     }  
  7.     else  
  8.     {  
  9.         if (this.playersWatchingChunk.isEmpty())  
  10.         {  
  11.             this.previousWorldTime = PlayerManager.this.theWorldServer.getTotalWorldTime();  
  12.         }  
  13.   
  14.         this.playersWatchingChunk.add(playerMP);  
  15.         Runnable playerRunnable = null;  
  16.         if (this.loaded)  
  17.         {  
  18.         playerMP.loadedChunks.add(this.chunkCoords);  
  19.         }  
  20.         else  
  21.         {  
  22.             final EntityPlayerMP tmp = playerMP;  
  23.             playerRunnable = new Runnable()  
  24.             {  
  25.                 public void run()  
  26.                 {  
  27.                     tmp.loadedChunks.add(PlayerInstance.this.chunkCoords);  
  28.                 }  
  29.             };  
  30.             PlayerManager.this.getMinecraftServer().theChunkProviderServer.loadChunk(this.chunkCoords.chunkXPos, this.chunkCoords.chunkZPos, playerRunnable);  
  31.         }  
  32.         this.players.put(playerMP, playerRunnable);  
  33.     }  
  34. }  


还有玩家重生时候调用ServerConfiguratuinManager.recreateEntity也会Load对应的Chunk


3)当玩家移动的时候,要将玩家视线内的Chunk都加载进来。


Chunk加载对应的回调函数如下

  1. /** 
  2.   * Called when this Chunk is loaded by the ChunkProvider 
  3.   */  
  4.  public void onChunkLoad()  
  5.  {  
  6.      this.isChunkLoaded = true;  
  7.      this.worldObj.addTileEntities(this.chunkTileEntityMap.values());  
  8.   
  9.      for (int i = 0; i < this.entityLists.length;  i)  
  10.      {  
  11.          Iterator iterator = this.entityLists[i].iterator();  
  12.   
  13.          while (iterator.hasNext())  
  14.          {  
  15.              Entity entity = (Entity)iterator.next();  
  16.              entity.onChunkLoad();  
  17.          }  
  18.   
  19.          this.worldObj.loadEntities(com.google.common.collect.ImmutableList.copyOf(this.entityLists[i]));  
  20.      }  
  21.      MinecraftForge.EVENT_BUS.post(new ChunkEvent.Load(this));  
  22.  }  


加载块是从之前存储的NBT中读取,然后把Chunk恢复回来。


玩家移动引起的Chunk加载卸载在PlayerManager,updateMountedMovingPlayer()处理.


参考

Minecraft Wiki

Minecraft forge

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引