2.3 方块的其他属性
2.3.1 被爆破行为
你是否好奇过,多个 TNT 方块放在一起时,引爆其中一个是如何引发连锁反应的?
实际上,通过实现父类 Block
的方法 onBlockExploded
,就能控制在方块受到爆炸影响后的行为,例如连锁爆炸、播放自定义音频,等等:
@Override
public void onBlockExploded(World world, BlockPos pos, Explosion explosion) {
world.createExplosion(null, pos.getX(), pos.getY(), pos.getZ(), 4.0F, true, Explosion.Mode.DESTROY);
super.wasExploded(world, pos, explosion);
}
在 forge 中,唯一和方块、爆炸有关的的分别位于 IForgeBlock
接口以及 Block
类的自定义方法(AbstractBlock
中没有):

注意到虚函数的默认实现是:当方块被爆炸波及后,先将方块设置为 “和空气的默认方块状态一样” 的状态(等价于破坏了当前方块),然后调用 Block
对象自己定义的 onBlockExploded
方法。这个方法默认方法体为空(什么都不做),所有我们才能通过重写 OnBlockExploded
来自定义被爆破时的行为。
2.3.1-EX 拓展:世界事件处理(World
类)
注意到一个好玩的类:World
,我们看到了 World
至少有爆炸(explode
)、控制方块状态属性的方法(setBlockState
),两种方法。感兴趣的同学可以检查它的源码,上面能看到超多 World
的接口,例如:
-
isRemote()
:查看当前世界对象是在client
侧还是server
侧; -
isValidXZPosition(BlockPos pos)
:查询当前方块位置是否是合法的X / Z
轴位置,可以看到合法范围在正负 30000000; -
isValid() & isValidPosition()
:两个函数用途不同:- 前者检查世界中的自然生成物 / 放置物的位置是否合法,高度最大 256,最小 0;判断逻辑是
isValidXZPosition() && !isYOutOfBounds()
; - 后者检查这个位置是否在世界中,或者说这个坐标是否合理。这个合理的位置不一定可以放置方块;高度最大 20000000;判断逻辑是
isValidXZPosition() && isValidYPosition()
;
- 前者检查世界中的自然生成物 / 放置物的位置是否合法,高度最大 256,最小 0;判断逻辑是
-
getChunk()
:按参数信息获取指定区块。其中有一个有趣的重载函数,能告诉我们 Minecraft 的区块划分方法: -
setBlockState() / getBlockState()
:设置 / 获取方块状态; -
isDaytime() / isNightTime()
:获取当前日夜间模式;注意到
DimensionType
类型,其中对世界维度的特性进行定义,例如环境光、是否有固定的日夜时间。MC Forge 对Overworld
、The Nether
(地狱)、The End
(末地)有 vanilla 定义; -
playSound()
:播放指定音频,我们可以通过这个方法底层播放声音(与物品或其他对象相关的声音建议在对于的类中定义); -
addParticle()
(@OnlyIn(Dist.CLIENT)
):渲染指定粒子效果。关于粒子效果我们会在后面详细叙述; -
add/set/get/removeTileEntity() / addTileEntities() / tickBlockEntities() / guardEntityTick()
:和 Block Entity(Tile Entity)相关的接口,用于按策略处理这个世界中的所有 Block Entity 的活动。我们将在后面叙述 Block Entity; -
createExplosion()
:创建爆炸对象并且爆炸。这里顺带注意到我们之前遇到的参数类型Explosion
类型。注意到
Explosion
类型的构造函数全部由@OnlyIn(Dist.CLIENT)
修饰,意味着爆炸对象创建完全在Client
端完成。大致了解一下
Explosion
类型的构造函数是传入爆炸的位置、引发实体、爆炸半径、爆炸种类(损坏、彻底破坏);创建好
Explosion
对象后,就进行以下 3 步:- 执行 forge event factory 中注册的
onExplosionStart
,一旦返回的是true
代表不进行本次爆炸的效果(立即返回); - 否则执行
Explosion#doExplosionA()
,这步的作用是计算需要破坏的方块; - 下一步执行
Explosion#doExplosionB()
,这步的作用是产生爆炸后的音效、粒子效果、掉落物;
最终这个
createExplosion
都会返回创建的Explosion
对象; - 执行 forge event factory 中注册的
-
isBlockPresent(BlockPos)
:检查指定位置是否存在方块, 先检查 Y 轴高度是否在能生成方块的位置,再检查所在区块是否存在(可以看出,Minecraft 中管理方块的生成是以区块为单位的); -
isDirectionSolid() / isTopSolid()
:通过BlockState
检查指定方块的指定方向的 碰撞箱 是否覆盖该方块表面。关于方块的碰撞箱的定义,我们将在 “模型” 一节继续讨论; -
getSeaLevel()
:获取世界水平面高度,默认是Y = 63
; -
getStrongPower() / getRedstonePower() / getRedstonePowerFromNeighbors()
与isSidePowered() / isBlockPowered()
:前 3 个方法用于处理世界的红石信号。如果不能理解含义,请阅读 Chapter 4 红石的相关内容。getStrongPower(BlockPos)
计算获取指定位置上的方块所收到最强的红石信号(因为可能多个面都收到红石信号);getRedstonePower(BlockPos, Direction)
:检查指定方块的红石信号(考虑是否为弱充能或强充能);getRedstonePowerFromNeighbors(BlockPos)
:检查当前方块的相邻方块中最大红石信号值;
-
get/setThunderStrength() / get/setRainStrength()
:获取当前世界的雷暴 / 降水强度;注意到一个有趣的现象:降水等级是使用
[this.prevRainingStrength, this.rainingStrength]
间斜率为delta
的线性插值计算。而雷暴等级则使用线性插值 乘以 降水等级计算。
-
isRaining() / isRainingAt(BlockPos) / isThundering()
:查询当前世界(指定位置)是否存在雷暴/降水;
2.3.2 植物种植
那么植物是如何判定可以种植在哪些方块上的呢?我们只需要重写 vanilla Block#canSustainPlant
方法,就能决定哪些方块可以种植在自定义的 Block
类型上了。
可以看到
Block
类原生对植物能否种植的判断比较复杂,不过判断的是 vanilla block,因此新加入的方块几乎什么植物都不能种植。
事实上,植物在 Minecraft 中也作为一种方块存在,它们都实现了 IPlantable
接口,只不过它们的模型各有差别。
IPlantable
接口只有两个方法 getPlantType
和 getPlant
,前者返回预定义的 PlantType
,后者返回方块状态 BlockState
;
所以在 canSustainBlock
的实现中,我们只需要取出 plantable
植物对应的方块,判断这个方块是否就是指定的已注册的植物,根据情况返回 boolean 值,就能实现方块是否允许种植特定植物的特性了。举例:
@Override
public boolean canSustainPlant(BlockState state, IBlockReader world, BlockPos pos, Direction facing, net.minecraftforge.common.IPlantable plantable) {
Block plantBlock = plantable.getPlant(world, pos).getBlock();
// Can sustain cactus.
if (plantBlock == Blocks.CACTUS) {
return true;
} else {
// Use vanilla logic, which is always TRUE.
return super.canSustainPlant(state, world, pos, facing, plantable);
}
}
2.3.3 随机时刻行为
在一些情况下,方块需要随机时刻渲染 / 执行相应逻辑,这可能是庄稼成长,或者是杂草蔓延,等等,详细信息参见 wiki - random ticks;
如果我们需要方块具有随机行为,我们可以重写 AbstractBlock#randomTick
方法(已弃用,可以重写 AbstractBlockState#randomTick
方法):
@Override
public void randomTick(BlockState state, ServerWorld worldIn, BlockPos pos, Random random) {
BlockState aboveState = worldIn.getBlockState(pos.up());
if (aboveState.isAir()) {
// set block & update (flag = 1)
worldIn.setBlockState(pos.up(), Blocks.CACTUS.getDefaultState(), 1);
}
}
并且,务必指定传入的 AbstractBlock.Properties
设置 tickRandomly()
,否则 main loop 永远不会调用这个方法。
如果我们需要调试随机时刻行为的效果,我们可以在进入世界时执行 /gamerule randomTickSpeed 9999
来加快随机时刻速度以供调试;
2.3.4 含水方块
参见 5.1 节。