標籤:
Entity種類
生物大致劃分為四種:攻擊型,被動型,水生型(也就是魷魚)和環境型(也就是蝙蝠)。攻擊型生物有一個每遊戲刻(1/20秒)一次的組建循環。被動型和水生型生物只有每400刻(20秒)一次的組建循環。因為這一點,攻擊型生物可以在任何時候產生,而動物產生則非常少。另外,大部分動物在建立世界時產生的區塊中產生。源碼中對應的類是
EnumCreatureType.java
public enum EnumCreatureType{ MONSTER(IMob.class, 70, Material.air, false, false), CREATURE(EntityAnimal.class, 10, Material.air, true, true), AMBIENT(EntityAmbientCreature.class, 15, Material.air, true, false), WATER_CREATURE(EntityWaterMob.class, 5, Material.water, true, false); /** * The root class of creatures associated with this EnumCreatureType (IMobs for aggressive creatures, EntityAnimals * for friendly ones) */ private final Class creatureClass; private final int maxNumberOfCreature; private final Material creatureMaterial; /** A flag indicating whether this creature type is peaceful. */ private final boolean isPeacefulCreature; /** Whether this creature type is an animal. */ private final boolean isAnimal; private EnumCreatureType(Class class, int _maxNumberOfCreature, Material _creatureMaterial, boolean _isPeacefulCreature, boolean _isAnimal) { this.creatureClass = class; this.maxNumberOfCreature = _maxNumberOfCreature; this.creatureMaterial = _creatureMaterial; this.isPeacefulCreature = _isPeacefulCreature; this.isAnimal = _isAnimal; } public Class getCreatureClass() { return this.creatureClass; } public int getMaxNumberOfCreature() { return this.maxNumberOfCreature; } /** * Gets whether or not this creature type is peaceful. */ public boolean getPeacefulCreature() { return this.isPeacefulCreature; } /** * Return whether this creature type is an animal. */ public boolean getAnimal() { return this.isAnimal; }}
怪物容量(可理解為人口)與適合產生的區塊總數直接成比例。要計算容量的話,產生地區在每一方向上均擴充一個區塊,所以有17*17區塊的大小,然後總的區塊數被代入到下式中:
容量=常量*區塊數/289
每一種生物均具有自己的容量計算和公式中不同的常量值:
攻擊型 = 70
被動型 = 10
環境型(蝙蝠) = 15
水生型 = 5
伺服器架構
圖1. MC伺服器架構
MinecraftServer作為伺服器,主要負責服務端的更新,裡面可以包含多個WorldServer,WorldClent作為服務端,當玩家加入一個伺服器的時候,就會建立一個在本地。SpawnerAnimals作為刷怪的工具類,主要用來處理刷怪邏輯。
首先來看WorldServer的Tick
註:如無特別說明,所有tick都是一秒20次。
圖2. WorldServer Tick 流程
很清晰的邏輯,這裡主要看一下findChunksForSpawning的實現。
在單人遊戲模式下,區塊計數總為17*17=289,那麼各種生物的容量也就是上面列出的數值。在多人遊戲中,在多個玩家範圍內的區塊只被計算一次,所以玩家越分散,更多地區塊會被覆蓋且會有更高的生物容量。
在每次組建循環的開始都會檢查一次容量。如果存活的生物數量超過它的容量,整個組建循環就會被跳過。
在每一組建循環中,會在每一個合適的區塊中進行一次產生一組生物的嘗試。該區塊內選擇一個隨機地點作為這組生物的中心點。為產生這組生物,中心方塊對水生生物而言必須是水方塊,對所有其它生物來說則必須是空氣方塊。注意在後面的情形中,它一定得是空氣方塊。任何其它方塊,哪怕是一個透明方塊都會阻止整組生物的產生。
圖3. 陸地怪物的產生條件
如果該組位置合適,會在以中心方塊為原點41*1*41的範圍(就是41*41格大小的方型,有1格高的地區)內進行12次嘗試以產生多至4個的生物(狼是8個,惡魂是1個)。生物將會在這一地區產生其身體的最下部分。在每次產生嘗試中,會在這一地區中隨機播放一個方塊的地點。儘管產生地區能擴充到中心21格之外,但是隨機選出的地點強烈地向該組的中心集中。大約有85%的產生將會在該組中心的5格以內,99%會落在10格以內
組內所有的生物都是相同的種類。在該組第一次產生嘗試時從該地區所適合產生的種類中隨機挑選一種以決定整組的種類。
具體的種類可以參考要Minecraft的Wiki。
findChunksForSpawning函數實現的就是上面描述的邏輯。看一下SpawnerAnimals.java這個類。
public final class SpawnerAnimals{ private static final int MOB_COUNT_DIV = (int)Math.pow(17.0D, 2.0D); /** The 17x17 area around the player where mobs can spawn */ private final Set eligibleChunksForSpawning = Sets.newHashSet(); private static final String __OBFID = "CL_00000152"; /** * adds all chunks within the spawn radius of the players to eligibleChunksForSpawning. pars: the world, * hostileCreatures, passiveCreatures. returns number of eligible chunks. */ public int findChunksForSpawning(WorldServer server, boolean spawnHostileMobs, boolean spawnPeacefulMobs, boolean isSpecialSpawnTick) { if (!spawnHostileMobs && !spawnPeacefulMobs) { return 0; } else { this.eligibleChunksForSpawning.clear(); int chunkCount = 0; Iterator iterator = server.playerEntities.iterator(); int k; int creatureCount; while (iterator.hasNext()) { EntityPlayer entityplayer = (EntityPlayer)iterator.next(); if (!entityplayer.isSpectator()) { int j = MathHelper.floor_double(entityplayer.posX / 16.0D); k = MathHelper.floor_double(entityplayer.posZ / 16.0D); byte b0 = 8; for (int l = -b0; l <= b0; ++l) { for (creatureCount = -b0; creatureCount <= b0; ++creatureCount) { boolean flag3 = l == -b0 || l == b0 || creatureCount == -b0 || creatureCount == b0; ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(l + j, creatureCount + k); if (!this.eligibleChunksForSpawning.contains(chunkcoordintpair)) { ++chunkCount; if (!flag3 && server.getWorldBorder().contains(chunkcoordintpair)) { this.eligibleChunksForSpawning.add(chunkcoordintpair); } } } } } } int totalEntityCount = 0; BlockPos blockpos2 = server.getSpawnPoint(); EnumCreatureType[] aenumcreaturetype = EnumCreatureType.values(); k = aenumcreaturetype.length; for (int i = 0; i < k; ++i) { EnumCreatureType enumcreaturetype = aenumcreaturetype[i]; if ((!enumcreaturetype.getPeacefulCreature() || spawnPeacefulMobs) && (enumcreaturetype.getPeacefulCreature() || spawnHostileMobs) && (!enumcreaturetype.getAnimal() || isSpecialSpawnTick)) { creatureCount = server.countEntities(enumcreaturetype, true); int maxCreatureCount = enumcreaturetype.getMaxNumberOfCreature() * chunkCount / MOB_COUNT_DIV; if (creatureCount <= maxCreatureCount) { Iterator iterator1 = this.eligibleChunksForSpawning.iterator(); ArrayList<ChunkCoordIntPair> tmp = new ArrayList(eligibleChunksForSpawning); Collections.shuffle(tmp); iterator1 = tmp.iterator(); label115: while (iterator1.hasNext()) { ChunkCoordIntPair chunkcoordintpair1 = (ChunkCoordIntPair)iterator1.next(); BlockPos blockpos = getRandomChunkPosition(server, chunkcoordintpair1.chunkXPos, chunkcoordintpair1.chunkZPos); int j1 = blockpos.getX(); int k1 = blockpos.getY(); int l1 = blockpos.getZ(); Block block = server.getBlockState(blockpos).getBlock(); if (!block.isNormalCube()) { int entityCountOnChunk = 0; int j2 = 0; while (j2 < 3) { int k2 = j1; int l2 = k1; int i3 = l1; byte b1 = 6; BiomeGenBase.SpawnListEntry spawnlistentry = null; IEntityLivingData ientitylivingdata = null; int j3 = 0; while (true) { if (j3 < 4) { label108: { k2 += server.rand.nextInt(b1) - server.rand.nextInt(b1); l2 += server.rand.nextInt(1) - server.rand.nextInt(1); i3 += server.rand.nextInt(b1) - server.rand.nextInt(b1); BlockPos blockpos1 = new BlockPos(k2, l2, i3); float f = (float)k2 + 0.5F; float f1 = (float)i3 + 0.5F; //Check must be away from Player by 24 block, and away from player spawn point. 576 = 24 * 24 if (!server.CheckCanSpawnHere((double)f, (double)l2, (double)f1, 24.0D) && blockpos2.distanceSq((double)f, (double)l2, (double)f1) >= 576.0D) { if (spawnlistentry == null) { spawnlistentry = server.GetSpawnListEntry(enumcreaturetype, blockpos1); if (spawnlistentry == null) { break label108; } } if (server.CheckChunkHasSpawnEntry(enumcreaturetype, spawnlistentry, blockpos1) && canCreatureTypeSpawnAtLocation(EntitySpawnPlacementRegistry.GetSpawnPointType(spawnlistentry.entityClass), server, blockpos1)) { EntityLiving entityliving; try { entityliving = (EntityLiving)spawnlistentry.entityClass.getConstructor(new Class[] {World.class}).newInstance(new Object[] {server}); } catch (Exception exception) { exception.printStackTrace(); return totalEntityCount; } entityliving.setLocationAndAngles((double)f, (double)l2, (double)f1, server.rand.nextFloat() * 360.0F, 0.0F); Result canSpawn = ForgeEventFactory.canEntitySpawn(entityliving, server, f, l2, f1); if (canSpawn == Result.ALLOW || (canSpawn == Result.DEFAULT && (entityliving.getCanSpawnHere() && entityliving.handleLavaMovement()))) { if (!ForgeEventFactory.doSpecialSpawn(entityliving, server, f1, l2, f1)) ientitylivingdata = entityliving.getEntityData(server.getDifficultyForLocation(new BlockPos(entityliving)), ientitylivingdata); if (entityliving.handleLavaMovement()) { ++entityCountOnChunk; server.spawnEntityInWorld(entityliving); } if (entityCountOnChunk >= ForgeEventFactory.getMaxSpawnPackSize(entityliving)) { continue label115; } } totalEntityCount += entityCountOnChunk; } } ++j3; continue; } } ++j2; break; } } } } } } } return totalEntityCount; } } protected static BlockPos getRandomChunkPosition(World worldIn, int x, int z) { Chunk chunk = worldIn.getChunkFromChunkCoords(x, z); int k = x * 16 + worldIn.rand.nextInt(16); int l = z * 16 + worldIn.rand.nextInt(16); int creatureCount = MathHelper.Ceiling(chunk.getHeight(new BlockPos(k, 0, l)) + 1, 16); int j1 = worldIn.rand.nextInt(creatureCount > 0 ? creatureCount : chunk.getTopFilledSegment() + 16 - 1); return new BlockPos(k, j1, l); } public static boolean canCreatureTypeSpawnAtLocation(EntityLiving.SpawnPlacementType placeType, World worldIn, BlockPos pos) { if (!worldIn.getWorldBorder().contains(pos)) { return false; } else { Block block = worldIn.getBlockState(pos).getBlock(); if (placeType == EntityLiving.SpawnPlacementType.IN_WATER) { return block.getMaterial().isLiquid() && worldIn.getBlockState(pos.down()).getBlock().getMaterial().isLiquid() && !worldIn.getBlockState(pos.up()).getBlock().isNormalCube(); } else { BlockPos blockpos1 = pos.down(); if (!worldIn.getBlockState(blockpos1).getBlock().canCreatureSpawn(worldIn, blockpos1, placeType)) { return false; } else { Block block1 = worldIn.getBlockState(blockpos1).getBlock(); boolean flag = block1 != Blocks.bedrock && block1 != Blocks.barrier; return flag && !block.isNormalCube() && !block.getMaterial().isLiquid() && !worldIn.getBlockState(pos.up()).getBlock().isNormalCube(); } } } } /** * Called during chunk generation to spawn initial creatures. */ public static void performWorldGenSpawning(World worldIn, BiomeGenBase biomeGenBase, int chunkCenterX, int chunkCenterY, int rangeX, int rangeY, Random rand) { List list = biomeGenBase.getSpawnableList(EnumCreatureType.CREATURE); if (!list.isEmpty()) { while (rand.nextFloat() < biomeGenBase.getSpawningChance()) { BiomeGenBase.SpawnListEntry spawnlistentry = (BiomeGenBase.SpawnListEntry)WeightedRandom.getRandomItem(worldIn.rand, list); int creatureCount = spawnlistentry.minGroupCount + rand.nextInt(1 + spawnlistentry.maxGroupCount - spawnlistentry.minGroupCount); IEntityLivingData ientitylivingdata = null; int j1 = chunkCenterX + rand.nextInt(rangeX); int k1 = chunkCenterY + rand.nextInt(rangeY); int l1 = j1; int entityCountOnChunk = k1; for (int j2 = 0; j2 < creatureCount; ++j2) { boolean flag = false; for (int k2 = 0; !flag && k2 < 4; ++k2) { BlockPos blockpos = worldIn.getTopSolidOrLiquidBlock(new BlockPos(j1, 0, k1)); if (canCreatureTypeSpawnAtLocation(EntityLiving.SpawnPlacementType.ON_GROUND, worldIn, blockpos)) { EntityLiving entityliving; try { entityliving = (EntityLiving)spawnlistentry.entityClass.getConstructor(new Class[] {World.class}).newInstance(new Object[] {worldIn}); } catch (Exception exception) { exception.printStackTrace(); continue; } entityliving.setLocationAndAngles((double)((float)j1 + 0.5F), (double)blockpos.getY(), (double)((float)k1 + 0.5F), rand.nextFloat() * 360.0F, 0.0F); worldIn.spawnEntityInWorld(entityliving); ientitylivingdata = entityliving.func_180482_a(worldIn.getDifficultyForLocation(new BlockPos(entityliving)), ientitylivingdata); flag = true; } j1 += rand.nextInt(5) - rand.nextInt(5); for (k1 += rand.nextInt(5) - rand.nextInt(5); j1 < chunkCenterX || j1 >= chunkCenterX + rangeX || k1 < chunkCenterY || k1 >= chunkCenterY + rangeX; k1 = entityCountOnChunk + rand.nextInt(5) - rand.nextInt(5)) { j1 = l1 + rand.nextInt(5) - rand.nextInt(5); } } } } } }}
怪物的生命週期
待續。
特殊刷怪機制
刷怪箱邏輯
待續。
參考
Minecraft Wiki
Minecraft Forge
Minecraft源碼分析(1) - 刷怪邏輯