IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]Minecraft源码分析(3) - 刷怪笼(MobSpawner)逻辑

    qp120291570发表于 2016-05-05 00:44:31
    love 0

    Minecraft刷怪笼顾名思义就是刷怪的笼子,遍布Minecraft各地,除了在水中、浮空和末地不存在外,一般来说刷怪笼都存在于低于地平线的位置,但是也有一些刷怪笼生成于高于地面的山中,有时会出现多个刷怪笼密集生成于一片区域的情况,比如蜘蛛刷怪笼。每种刷怪笼只会刷出一种怪,比如骷髅刷怪笼只会刷出骷髅弓箭手。当周围有刷怪笼时,可以听到独特的嘶嘶声,并且如果感觉某种怪物数量骤增,前赴后继地来攻击你,那周围很可能有刷怪笼存在。另外大部分刷怪笼都处于一个特制的房间中,其特点就是地板和墙壁是苔石铺成的,如果在挖掘时遇到苔石墙就得留意,后面很可能有刷怪笼存在。刷怪笼为了保护旁边箱子里的“宝物”(很可能只是几片面包),在人靠近时会不断刷出怪,当然也存在旁边没有箱子的刷怪笼,比如蜘蛛刷怪笼。



    图1. 刷怪笼示意图



    还有一种刷怪车的东西,作用基本和刷怪笼一样。




    图2. 刷怪车示意图



    刷怪笼是作为一种TileEntity存在的

    public class TileEntityMobSpawner extends TileEntity implements IUpdatePlayerListBox
    {
        private final MobSpawnerBaseLogic spawnerLogic = new MobSpawnerBaseLogic()
        {
            public void setBlockEvent(int eventid)
            {
                TileEntityMobSpawner.this.worldObj.addBlockEvent(TileEntityMobSpawner.this.pos, Blocks.mob_spawner, eventid, 0);
            }
            public World getSpawnerWorld()
            {
                return TileEntityMobSpawner.this.worldObj;
            }
            public BlockPos getPos()
            {
                return TileEntityMobSpawner.this.pos;
            }
            public void setRandomEntity(MobSpawnerBaseLogic.WeightedRandomMinecart randomEntity)
            {
                super.setRandomEntity(randomEntity);
    
                if (this.getSpawnerWorld() != null)
                {
                    this.getSpawnerWorld().markBlockForUpdate(TileEntityMobSpawner.this.pos);
                }
            }
        };
    
        public void readFromNBT(NBTTagCompound compound)
        {
            super.readFromNBT(compound);
            this.spawnerLogic.readFromNBT(compound);
        }
    
        public void writeToNBT(NBTTagCompound compound)
        {
            super.writeToNBT(compound);
            this.spawnerLogic.writeToNBT(compound);
        }
    
        /**
         * Updates the JList with a new model.
         */
        public void update()
        {
            this.spawnerLogic.updateSpawner();
        }
    
        /**
         * Allows for a specialized description packet to be created. This is often used to sync tile entity data from the
         * server to the client easily. For example this is used by signs to synchronise the text to be displayed.
         */
        public Packet getDescriptionPacket()
        {
            NBTTagCompound nbttagcompound = new NBTTagCompound();
            this.writeToNBT(nbttagcompound);
            nbttagcompound.removeTag("SpawnPotentials");
            return new S35PacketUpdateTileEntity(this.pos, 1, nbttagcompound);
        }
    
        public boolean receiveClientEvent(int id, int type)
        {
            return this.spawnerLogic.setDelayToMin(id) ? true : super.receiveClientEvent(id, type);
        }
    
        public MobSpawnerBaseLogic getSpawnerBaseLogic()
        {
            return this.spawnerLogic;
        }
    }


    TileEntityMobSpawner的Update其实就是由MobSpawnerBaseLogic来决定的。


    Minecraft Wiki里面对刷怪箱的刷怪逻辑如下

    玩家距离刷怪箱16个方块内时,刷怪箱才会工作。当刷怪箱工作时,会以刷怪箱方块为中心的8×3×8(8格长宽,3格高)的有效区域生成生物,这意味着生物可以在一个9×9的区域,或距离刷怪箱3.5格的位置生成。生物可以在此区域符合生物生成要求的任意一处生成,生物更有可能生成在靠近刷怪箱而不是远离刷怪箱的地方。

    当生物生成的 X 和 Z 坐标(注:不一定与刷怪箱对齐)是小数时,它们会生成在 Y 坐标是整数的地方。生物可以生成在8×8平面区域内的任意一处,但生成的生物脚的高度会与刷怪箱方块在同一层,或者比它高一层或低一层。

    对于一些在生成区域以外生成的生物来说,必须远离不透明方块以确保可以容纳生物的高度和宽度,或由其它规则支配它们的每个生成区域。对于一些需要2格高或以上的空间才能生成的生物(如僵尸、骷髅或在Y轴最上面生成的烈焰人)来说,上面的空间必须只包含空气。

    刷怪箱方块会尝试在有效区域内随机选择的位置生成4个生物,每次生成后会等待200-799刻(10-39.95秒)。在等待时,刷怪箱方块里面的生物会越转越快。除了对地面的生成要求,生物的其它生成要求也必须要满足(例如不能生成在固体方块里、亮度范围要正确等),因此刷怪箱常常不能生成4个生物。当刷怪箱生成了生物时,它会发出嘶嘶声,刷怪箱内火焰升腾。如果刷怪箱在有效区域内找不到任何符合要求的位置生成生物,则每一刻都会尝试一次。如果在生成阶段刷怪箱周围17×9×17的空间存在至少6个生物,则刷怪箱内火焰会升腾(表示已经“生成”了新的生物),但实际上生成过程被跳过,进入下一个周期。

    当在一个没有有效位置生成生物的刷怪箱附近进行开采时,有时候刷怪箱会在方块被开采后立即生成一只怪物。

    这些逻辑都在MobSpawnerBaseLogic.Java中定义


    public abstract class MobSpawnerBaseLogic
    {
        /** The delay to spawn. */
        private int spawnDelay = 20;
        private String mobID = "Pig";
        /** List of minecart to spawn. */
        private final List minecartToSpawn = Lists.newArrayList();
        private MobSpawnerBaseLogic.WeightedRandomMinecart randomEntity;
        /** The rotation of the mob inside the mob spawner */
        private double mobRotation;
        /** the previous rotation of the mob inside the mob spawner */
        private double prevMobRotation;
        private int minSpawnDelay = 200;
        private int maxSpawnDelay = 800;
        private int spawnCount = 4;
        /** Cached instance of the entity to render inside the spawner. */
        private Entity cachedEntity;
        private int maxNearbyEntities = 6;
        /** The distance from which a player activates the spawner. */
        private int activatingRangeFromPlayer = 16;
        /** The range coefficient for spawning entities around. */
        private int spawnRange = 4;
    
        /**
         * Gets the entity name that should be spawned.
         */
        private String getEntityNameToSpawn()
        {
            if (this.getRandomEntity() == null)
            {
                if (this.mobID.equals("Minecart"))
                {
                    this.mobID = "MinecartRideable";
                }
    
                return this.mobID;
            }
            else
            {
                return this.getRandomEntity().entityType;
            }
        }
    
        public void setEntityName(String entityName)
        {
            this.mobID = entityName;
        }
    
        /**
         * Returns true if there's a player close enough to this mob spawner to activate it.
         */
        private boolean isActivated()
        {
            BlockPos blockpos = this.getBlockPos();
            return this.getSpawnerWorld().hasPlayerInRange((double)blockpos.getX() + 0.5D, (double)blockpos.getY() + 0.5D, (double)blockpos.getZ() + 0.5D, (double)this.activatingRangeFromPlayer);
        }
    
        public void updateSpawner()
        {
            if (this.isActivated())
            {
                BlockPos blockpos = this.getBlockPos();
                double posX;
    
                if (this.getSpawnerWorld().isRemote)
                {
                    double d0 = (double)((float)blockpos.getX() + this.getSpawnerWorld().rand.nextFloat());
                    double d1 = (double)((float)blockpos.getY() + this.getSpawnerWorld().rand.nextFloat());
                    posX = (double)((float)blockpos.getZ() + this.getSpawnerWorld().rand.nextFloat());
                    this.getSpawnerWorld().spawnParticle(EnumParticleTypes.SMOKE_NORMAL, d0, d1, posX, 0.0D, 0.0D, 0.0D, new int[0]);
                    this.getSpawnerWorld().spawnParticle(EnumParticleTypes.FLAME, d0, d1, posX, 0.0D, 0.0D, 0.0D, new int[0]);
    
                    if (this.spawnDelay > 0)
                    {
                        --this.spawnDelay;
                    }
    
                    this.prevMobRotation = this.mobRotation;
                    this.mobRotation = (this.mobRotation + (double)(1000.0F / ((float)this.spawnDelay + 200.0F))) % 360.0D;
                }
                else
                {
                    if (this.spawnDelay == -1)
                    {
                        this.resetTimer();
                    }
    
                    if (this.spawnDelay > 0)
                    {
                        --this.spawnDelay;
                        return;
                    }
    
                    boolean flag = false;
    
                    for (int i = 0; i < this.spawnCount; ++i)
                    {
                        Entity entity = EntityList.createEntityByName(this.getEntityNameToSpawn(), this.getSpawnerWorld());
    
                        if (entity == null)
                        {
                            return;
                        }
    
                        int j = this.getSpawnerWorld().getEntitiesWithinAABB(entity.getClass(), (new AxisAlignedBB((double)blockpos.getX(), (double)blockpos.getY(), (double)blockpos.getZ(), (double)(blockpos.getX() + 1), (double)(blockpos.getY() + 1), (double)(blockpos.getZ() + 1))).expand((double)this.spawnRange, (double)this.spawnRange, (double)this.spawnRange)).size();
    
                        if (j >= this.maxNearbyEntities)
                        {
                            this.resetTimer();
                            return;
                        }
    
                        posX = (double)blockpos.getX() + (this.getSpawnerWorld().rand.nextDouble() - this.getSpawnerWorld().rand.nextDouble()) * (double)this.spawnRange + 0.5D;
                        double posY = (double)(blockpos.getY() + this.getSpawnerWorld().rand.nextInt(3) - 1);
                        double posZ = (double)blockpos.getZ() + (this.getSpawnerWorld().rand.nextDouble() - this.getSpawnerWorld().rand.nextDouble()) * (double)this.spawnRange + 0.5D;
                        EntityLiving entityliving = entity instanceof EntityLiving ? (EntityLiving)entity : null;
                        entity.setLocationAndAngles(posX, posY, posZ, this.getSpawnerWorld().rand.nextFloat() * 360.0F, 0.0F);
    
                        if (entityliving == null || entityliving.getCanSpawnHere() && entityliving.handleLavaMovement())
                        {
                            this.InstantiateEntity(entity, true);
                            this.getSpawnerWorld().playAuxSFX(2004, blockpos, 0);
    
                            if (entityliving != null)
                            {
                                entityliving.spawnExplosionParticle();
                            }
    
                            flag = true;
                        }
                    }
    
                    if (flag)
                    {
                        this.resetTimer();
                    }
                }
            }
        }
    
        private Entity InstantiateEntity(Entity entity, boolean needSpawn)
        {
            if (this.getRandomEntity() != null)
            {
                NBTTagCompound nbttagcompound = new NBTTagCompound();
                entity.writeToNBTOptional(nbttagcompound);
                Iterator iterator = this.getRandomEntity().nbtCompund.getKeySet().iterator();
    
                while (iterator.hasNext())
                {
                    String s = (String)iterator.next();
                    NBTBase nbtbase = this.getRandomEntity().nbtCompund.getTag(s);
                    nbttagcompound.setTag(s, nbtbase.copy());
                }
    
                entity.readFromNBT(nbttagcompound);
    
                if (entity.worldObj != null && needSpawn)
                {
                    entity.worldObj.spawnEntityInWorld(entity);
                }
    
                NBTTagCompound nbttagcompounposX;
    
                for (Entity entity1 = entity; nbttagcompound.hasKey("Riding", 10); nbttagcompound = nbttagcompounposX)
                {
                    nbttagcompounposX = nbttagcompound.getCompoundTag("Riding");
                    Entity entity2 = EntityList.createEntityByName(nbttagcompounposX.getString("id"), entity.worldObj);
    
                    if (entity2 != null)
                    {
                        NBTTagCompound nbttagcompound1 = new NBTTagCompound();
                        entity2.writeToNBTOptional(nbttagcompound1);
                        Iterator iterator1 = nbttagcompounposX.getKeySet().iterator();
    
                        while (iterator1.hasNext())
                        {
                            String s1 = (String)iterator1.next();
                            NBTBase nbtbase1 = nbttagcompounposX.getTag(s1);
                            nbttagcompound1.setTag(s1, nbtbase1.copy());
                        }
    
                        entity2.readFromNBT(nbttagcompound1);
                        entity2.setLocationAndAngles(entity1.posX, entity1.posY, entity1.posZ, entity1.rotationYaw, entity1.rotationPitch);
    
                        if (entity.worldObj != null && needSpawn)
                        {
                            entity.worldObj.spawnEntityInWorld(entity2);
                        }
    
                        entity1.mountEntity(entity2);
                    }
    
                    entity1 = entity2;
                }
            }
            else if (entity instanceof EntityLivingBase && entity.worldObj != null && needSpawn)
            {
                ((EntityLiving)entity).func_180482_a(entity.worldObj.getDifficultyForLocation(new BlockPos(entity)), (IEntityLivingData)null);
                entity.worldObj.spawnEntityInWorld(entity);
            }
    
            return entity;
        }
    
        private void resetTimer()
        {
            if (this.maxSpawnDelay <= this.minSpawnDelay)
            {
                this.spawnDelay = this.minSpawnDelay;
            }
            else
            {
                int i = this.maxSpawnDelay - this.minSpawnDelay;
                this.spawnDelay = this.minSpawnDelay + this.getSpawnerWorld().rand.nextInt(i);
            }
    
            if (this.minecartToSpawn.size() > 0)
            {
                this.setRandomEntity((MobSpawnerBaseLogic.WeightedRandomMinecart)WeightedRandom.getRandomItem(this.getSpawnerWorld().rand, this.minecartToSpawn));
            }
    
            this.setBlockEvent(1);
        }
    
        public void readFromNBT(NBTTagCompound nbtCompound)
        {
            this.mobID = nbtCompound.getString("EntityId");
            this.spawnDelay = nbtCompound.getShort("Delay");
            this.minecartToSpawn.clear();
    
            if (nbtCompound.hasKey("SpawnPotentials", 9))
            {
                NBTTagList nbttaglist = nbtCompound.getTagList("SpawnPotentials", 10);
    
                for (int i = 0; i < nbttaglist.tagCount(); ++i)
                {
                    this.minecartToSpawn.add(new MobSpawnerBaseLogic.WeightedRandomMinecart(nbttaglist.getCompoundTagAt(i)));
                }
            }
    
            if (nbtCompound.hasKey("SpawnData", 10))
            {
                this.setRandomEntity(new MobSpawnerBaseLogic.WeightedRandomMinecart(nbtCompound.getCompoundTag("SpawnData"), this.mobID));
            }
            else
            {
                this.setRandomEntity((MobSpawnerBaseLogic.WeightedRandomMinecart)null);
            }
    
            if (nbtCompound.hasKey("MinSpawnDelay", 99))
            {
                this.minSpawnDelay = nbtCompound.getShort("MinSpawnDelay");
                this.maxSpawnDelay = nbtCompound.getShort("MaxSpawnDelay");
                this.spawnCount = nbtCompound.getShort("SpawnCount");
            }
    
            if (nbtCompound.hasKey("MaxNearbyEntities", 99))
            {
                this.maxNearbyEntities = nbtCompound.getShort("MaxNearbyEntities");
                this.activatingRangeFromPlayer = nbtCompound.getShort("RequiredPlayerRange");
            }
    
            if (nbtCompound.hasKey("SpawnRange", 99))
            {
                this.spawnRange = nbtCompound.getShort("SpawnRange");
            }
    
            if (this.getSpawnerWorld() != null)
            {
                this.cachedEntity = null;
            }
        }
    
        public void writeToNBT(NBTTagCompound nbtCompound)
        {
            nbtCompound.setString("EntityId", this.getEntityNameToSpawn());
            nbtCompound.setShort("Delay", (short)this.spawnDelay);
            nbtCompound.setShort("MinSpawnDelay", (short)this.minSpawnDelay);
            nbtCompound.setShort("MaxSpawnDelay", (short)this.maxSpawnDelay);
            nbtCompound.setShort("SpawnCount", (short)this.spawnCount);
            nbtCompound.setShort("MaxNearbyEntities", (short)this.maxNearbyEntities);
            nbtCompound.setShort("RequiredPlayerRange", (short)this.activatingRangeFromPlayer);
            nbtCompound.setShort("SpawnRange", (short)this.spawnRange);
    
            if (this.getRandomEntity() != null)
            {
                nbtCompound.setTag("SpawnData", this.getRandomEntity().nbtCompund.copy());
            }
    
            if (this.getRandomEntity() != null || this.minecartToSpawn.size() > 0)
            {
                NBTTagList nbttaglist = new NBTTagList();
    
                if (this.minecartToSpawn.size() > 0)
                {
                    Iterator iterator = this.minecartToSpawn.iterator();
    
                    while (iterator.hasNext())
                    {
                        MobSpawnerBaseLogic.WeightedRandomMinecart weightedrandomminecart = (MobSpawnerBaseLogic.WeightedRandomMinecart)iterator.next();
                        nbttaglist.appendTag(weightedrandomminecart.generateNBT());
                    }
                }
                else
                {
                    nbttaglist.appendTag(this.getRandomEntity().generateNBT());
                }
    
                nbtCompound.setTag("SpawnPotentials", nbttaglist);
            }
        }
    
        /**
         * Sets the delay to minDelay if parameter given is 1, else return false.
         */
        public boolean setDelayToMin(int delay)
        {
            if (delay == 1 && this.getSpawnerWorld().isRemote)
            {
                this.spawnDelay = this.minSpawnDelay;
                return true;
            }
            else
            {
                return false;
            }
        }
    
        @SideOnly(Side.CLIENT)
        public Entity cacheEntity(World worldIn)
        {
            if (this.cachedEntity == null)
            {
                Entity entity = EntityList.createEntityByName(this.getEntityNameToSpawn(), worldIn);
    
                if (entity != null)
                {
                    entity = this.InstantiateEntity(entity, false);
                    this.cachedEntity = entity;
                }
            }
    
            return this.cachedEntity;
        }
    
        private MobSpawnerBaseLogic.WeightedRandomMinecart getRandomEntity()
        {
            return this.randomEntity;
        }
    
        public void setRandomEntity(MobSpawnerBaseLogic.WeightedRandomMinecart _randomEntity)
        {
            this.randomEntity = _randomEntity;
        }
    
        public abstract void setBlockEvent(int eventId);
    
        public abstract World getSpawnerWorld();
    
        public abstract BlockPos getBlockPos();
    
        @SideOnly(Side.CLIENT)
        public double getMobRotation()
        {
            return this.mobRotation;
        }
    
        @SideOnly(Side.CLIENT)
        public double getPrevMobRotation()
        {
            return this.prevMobRotation;
        }
    
        public class WeightedRandomMinecart extends WeightedRandom.Item
        {
            private final NBTTagCompound nbtCompund;
            private final String entityType;
    
            public WeightedRandomMinecart(NBTTagCompound nbtCompound)
            {
                this(nbtCompound.getCompoundTag("Properties"), nbtCompound.getString("Type"), nbtCompound.getInteger("Weight"));
            }
    
            public WeightedRandomMinecart(NBTTagCompound nbtCompound, String entityType)
            {
                this(nbtCompound, entityType, 1);
            }
    
            private WeightedRandomMinecart(NBTTagCompound nbtCompound, String entityType, int weight)
            {
                super(weight);
    
                if (entityType.equals("Minecart"))
                {
                    if (nbtCompound != null)
                    {
                        entityType = EntityMinecart.EnumMinecartType.byNetworkID(nbtCompound.getInteger("Type")).getName();
                    }
                    else
                    {
                        entityType = "MinecartRideable";
                    }
                }
    
                this.nbtCompund = nbtCompound;
                this.entityType = entityType;
            }
    
            public NBTTagCompound generateNBT()
            {
                NBTTagCompound nbttagcompound = new NBTTagCompound();
                nbttagcompound.setTag("Properties", this.nbtCompund);
                nbttagcompound.setString("Type", this.entityType);
                nbttagcompound.setInteger("Weight", this.itemWeight);
                return nbttagcompound;
            }
        }
    }

    这里面实现了一个WeightedRandomMinecart,他是一个WeightRandom.Item,也就是带权值的随机Item,权值越大,概率也就越大。


    public class WeightedRandom
    {
    
        /**
         * Returns the total weight of all items in a collection.
         *  
         * @param collection Collection to get the total weight of
         */
        public static int getTotalWeight(Collection collection)
        {
            int i = 0;
            WeightedRandom.Item item;
    
            for (Iterator iterator = collection.iterator(); iterator.hasNext(); i += item.itemWeight)
            {
                item = (WeightedRandom.Item)iterator.next();
            }
    
            return i;
        }
    
        /**
         * Returns a random choice from the input items, with a total weight value.
         *  
         * @param collection Collection of the input items
         */
        public static WeightedRandom.Item getRandomItem(Random random, Collection collection, int seed)
        {
            if (seed <= 0)
            {
                throw new IllegalArgumentException();
            }
            else
            {
                int j = random.nextInt(seed);
                return getRandomItem(collection, j);
            }
        }
    
        public static WeightedRandom.Item getRandomItem(Collection collection, int totalWeight)
        {
            Iterator iterator = collection.iterator();
            WeightedRandom.Item item;
    
            do
            {
                if (!iterator.hasNext())
                {
                    return null;
                }
    
                item = (WeightedRandom.Item)iterator.next();
                totalWeight -= item.itemWeight;
            }
            while (totalWeight >= 0);
    
            return item;
        }
    
        /**
         * Returns a random choice from the input items.
         *  
         * @param collection Collection to get the random item from
         */
        public static WeightedRandom.Item getRandomItem(Random random, Collection collection)
        {
            /**
             * Returns a random choice from the input items, with a total weight value.
             *  
             * @param collection Collection of the input items
             */
            return getRandomItem(random, collection, getTotalWeight(collection));
        }
    
        public static class Item
            {
                /** The Weight is how often the item is chosen(higher number is higher chance(lower is lower)) */
                public int itemWeight;
    
                public Item(int itemWeightIn)
                {
                    this.itemWeight = itemWeightIn;
                }
            }
    }

    参考

    Minecraft wiki

    Minecraft Forge



沪ICP备19023445号-2号
友情链接