本节将分析彩虹桥方块的动画效果,建筑小助手是如何触及极远的区域的,最后我们会造一个便利的建筑方块

知识速览:

  • ITickable TileEntity
  • BlockRayTraceResult

# 天之苍苍,其正色耶?

说起 Botania 的彩虹桥法杖,相信读者并不陌生,其美丽的特效出自浪漫的 Vazkii 之手,观察彩虹桥方块我们可以发现,其模型应该是一个不断变色的方块,并且不时会散发出有植物气息的粒子效果,彩虹桥方块会在其产生后一段时间内自动消失,不难想象应该是实现了 ITickable 接口的 TileEntity.

从彩虹桥方块中我们可以发现如下三个特性,我们一个一个来看

  • 透明方块的动态材质
  • 在方块周围生成指定的粒子效果
  • 过一段时间后自我消失

# 透明方块的动态材质

关于动态材质相信读者都不会太陌生,因为模组的模型加载说到底还是跟随了原版的机制,所以我们固然可以想到动态资源包中常用的 mcmeta 格式以及瀑布般的长条材质,其实 Minecraft 中重复简单的方块动画都可以用 mcmeta 配合一个包含动画过程关键帧的图片格式轻松实现,其实彩虹桥方块的动画也是这样的,在 Botania 的 resources 目录中我们可以找到 bifrost.json, 这就是彩虹桥方块的模型

{
  "parent": "minecraft:block/cube_all",
  "textures": {
    "all": "botania:blocks/bifrost"
  }
}
Botania开源地址: https://github.com/Vazkii/Botania
节选自(1.12-final分支): /resources/assets/botania/models/block/bifrost.json

显然彩虹桥方块的模型与普通方块是相同的,换句话说我们只需要关注动画材质就可以了,根据 Model 里面声明的路径我们去找对应的贴图以及 mcmeta 文件.

这里就不多说什么了,如果我们也想要一个动态模型只需如图一样创建贴图和 mcmeta 标记文件就可以了

注: mcmeta 的文件名应为贴图名 +.mcmeta

frametime: 动画播放的帧率

interpolate: 是否开启差值补帧 (使动画渐变更流畅)

之后我们来看透明的实现,在 Bifrost 的方块声明中我们可以找到如下代码

public class BlockBifrostPerm extends BlockMod implements ILexiconable {

	public BlockBifrostPerm(String name) {
		super(Material.GLASS, name);
		setLightOpacity(0);
		setLightLevel(1F);
		setSoundType(SoundType.GLASS);
	}

	@Override
	public boolean isOpaqueCube(IBlockState state) {
		return false;
	}

	@Override
	public boolean isFullCube(IBlockState state) {
		return false;
	}

	@Override
	public boolean shouldSideBeRendered(IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos, EnumFacing side) {
		if (world.getBlockState(pos.offset(side)).getBlock() == this) {
			return false;
		}
		return super.shouldSideBeRendered(state, world, pos, side);
	}

	@SideOnly(Side.CLIENT)
	@Nonnull
	@Override
	public BlockRenderLayer getRenderLayer() {
		return BlockRenderLayer.TRANSLUCENT;
		
	}
}
Botania开源地址: https://github.com/Vazkii/Botania
节选自(1.12-final分支): /common/block/BlockBifrostPerm.java

显然我们的彩虹桥方块需要像玻璃一样可以透光,在 Minecraft1.15.2 中我们可以让方块直接继承自 AbstractGlassBlock 从而直接得到类似玻璃的透光能力,但在 1.12.2 我们还不能这样做,于是我们可以覆盖 isOpaqueCube isFullCube getRenderLayer 等一系列方法,使方块可以透光,为了拥有更好的显示效果,Vazkii 还覆盖了 shouldSideBeRendered 方法,重叠的面将不会渲染,这里笔者就不多说了,读者可以自行阅读上文该方法的代码

# 在方块周围生成指定的粒子效果

有心的读者如果去 Github 阅读了 BlockBifrostPerm 的源码可以发现我们列举的代码中缺少了如下部分

@Override
public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random rand) {
    if(rand.nextBoolean())
        Botania.proxy.sparkleFX(pos.getX() + Math.random(), pos.getY() + Math.random(), pos.getZ() + Math.random(), (float) Math.random(), (float) Math.random(), (float) Math.random(), 0.45F + 0.2F * (float) Math.random(), 6);
}
Botania开源地址: https://github.com/Vazkii/Botania
节选自(1.12-final分支): /common/block/BlockBifrostPerm.java

显然这段代码的含义便是粒子效果的渲染,randomDisplayTick 会在贴图刷新的随机游戏刻执行,可以看到 Vazkii 在代码的开头使用 if (rand.nextBoolean ()) 使下面代码执行的概率变为 50%, 防止生成太多粒子造成卡顿. Vazkii 自己实现了一套 proxy 来运行 sparkleFX () 来保持客户端渲染,并且让粒子可以运动,其中大多使用 GL 直接渲染,这里的实现略微有点复杂我们就不展开了,读者如果希望粒子效果包含运动的效果,可以自行阅读 Botania 源码,其代码大多位于 fx 包中

若要渲染普通的粒子效果,使用 world.spawnParticle (), 并且保证位于 ClientSide 执行

# 过一段时间后自我消失

在 BlockBifrost 类中,我们可以发现彩虹桥方块绑定了 TileEntity, 其类为 TileBifrost, 用于储存数据,我们来看一下 TileBifrost 类

public class TileBifrost extends TileMod implements ITickable {

	private static final String TAG_TICKS = "ticks";

	public int ticks = 0;

	@Override
	public void update() {
		if(!world.isRemote) {
			if(ticks <= 0) {
				world.setBlockToAir(pos);
			} else ticks--;
		}
	}

	@Nonnull
	@Override
	public NBTTagCompound writeToNBT(NBTTagCompound par1nbtTagCompound) {
		NBTTagCompound ret = super.writeToNBT(par1nbtTagCompound);
		ret.setInteger(TAG_TICKS, ticks);
		return ret;
	}

	@Override
	public void readFromNBT(NBTTagCompound par1nbtTagCompound) {
		super.readFromNBT(par1nbtTagCompound);
		ticks = par1nbtTagCompound.getInteger(TAG_TICKS);
	}

}
Botania开源地址: https://github.com/Vazkii/Botania
节选自(1.12-final分支): /common/block/tile/TileBifrost.java

显然 TileBifrost 实现了 ITickable, 这意味着这个方块具备了刷新数据的能力,阅读 update 方法我们发现,其实质就是自减内部储存的 tick 值,如果减没了,那么就使方块消失,writeToNBT 和 readFromNBT 的用途不言而喻,常写 TileEntity 的读者一定对其有所了解,Minecraft 会在合适的时候 (一般是保存世界和读取世界的时候) 调用这两个方法,以便于在进入和退出存档之前读写方块里面的数据

善于思考的读者此时可能发问了,这个 TileEntity 的 tick 初始值应该是什么呢,在上述对彩虹桥方块的分析过程中我们并没有看到对于 tick 初始值的声明.... 不妨再来想想彩虹桥方块是怎么被放置在世界中的呢.... 对了,就是通过彩虹桥法杖,所以不难想象设置 tick 的初始值的代码应该写在法杖中,读者可自行验证猜想,代码位于 /common/item/rod/ItemRainbowRod.java 的 onItemRightClick 方法中


# 其远而无所至极邪?

建筑小助手是 direwolf20 作为模组开发者的处女作,其中各种小助手可以隔数十格远放置方块,手中握着小助手就可以追踪到目光所及之处的方块,通过查阅建筑小助手 GadgetBuilding 类的代码,我们可以找到如下方法

private void build(ServerPlayerEntity player, ItemStack stack) {
	······
    BlockRayTraceResult lookingAt = VectorHelper.getLookingAt(player, stack);
    if (world.isAirBlock(lookingAt.getPos())) //If we aren't looking at anything, exit
    return;
	Direction sideHit = lookingAt.getFace();
	······
	placeBlock(world, player, index, builder, coordinate, blockData);
	······
}
BuildingGadgets开源地址: https://github.com/Direwolf20-MC/BuildingGadgets
节选自(master分支): /common/items/gadgets/GadgetBuilding.java

可以猜想,在 GadgetBuilding 类的 build 方法中,实现了建筑小助手追踪视线并放置方块的特性,细心的读者可能发现,build 方法里面还具有针对 Collection 和 Undo 操作功能实现,的这里由于篇幅所限,不能为读者介绍到更为详细的部分,感兴趣的读者可以自行阅读查看

根据节选的代码片段,相信读者可以发现,追踪方块的实现依赖与 VectorHelper.getLookingAt () 这个方法,他的返回值为一个 BlockRayTraceResult, 对这个光追结果进行 getPos (), 就可以获取到目光所及之处了,所以我们更进一步,来看看 VectorHelper 的具体实现

public class VectorHelper {
    public static BlockRayTraceResult getLookingAt(PlayerEntity player, RayTraceContext.FluidMode rayTraceFluid) {
        double rayTraceRange = Config.GENERAL.rayTraceRange.get();
        RayTraceResult result = player.pick(rayTraceRange, 0f, rayTraceFluid != RayTraceContext.FluidMode.NONE);

        return (BlockRayTraceResult) result;
    }
}
BuildingGadgets开源地址: https://github.com/Direwolf20-MC/BuildingGadgets
节选自(master分支): /common/util/helpers/VectorHelper.java

VectorHelper 中包含了对于 getLookingAt () 的多个重写,显然,我们的目光重点应该放在 player.pick () 方法上,相信聪明的读者已经意识到了什么,player.pick () 就是原版为我们提供好的 RayTrace 的方法,我们只需要提供 Range (第一个参数), 和流体追踪模式 (第三个参数) 就可以了


# 海运则将徙于南冥

之后我们就要着手来将这两个点子融合,我们再来回顾一下我们的想法,既利用彩虹桥方块的发光效果,以及建筑小助手 getLookingAt () 方法来制作一个便利的建筑方块,

更新于