本篇教程的视频
本篇教程的源代码
介绍
本篇教程其实比较简单,我们将仿照原版的各种建筑类方块,增加一系列内容,包括楼梯
、台阶
、栅栏
、墙
、栅栏门
、门
、活板门
、按钮
、压力板
等等。
根据我们前面所学的东西,要写这些东西其实还算是简单的,无非在于注册,实例化他们各自对应的方块类,然后是一些资源文件和数据文件。
而本篇教程就不再讲解源代码的内容了,如有不清楚的地方可以自行搜索源代码进行研究
同样的,你也可以尝试仿写这些方块所属的方块类,看他们为什么能够实现这些功能。
比如楼梯、栅栏为什么有那么多状态,门的“半门”特性是啥,按钮和压力板为什么能输出红石信号,两个台阶怎么变成一个完整的方块等等。这些其实都可以去研究
注册方块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static final Block ICE_ETHER_STAIRS = register("ice_ether_stairs", new StairsBlock(ICE_ETHER_BLOCK.getDefaultState(), AbstractBlock.Settings.copy(ICE_ETHER_BLOCK))); public static final Block ICE_ETHER_SLAB = register("ice_ether_slab", new SlabBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK))); public static final Block ICE_ETHER_BUTTON = register("ice_ether_button", new ButtonBlock(BlockSetType.OAK, 10, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK))); public static final Block ICE_ETHER_PRESSURE_PLATE = register("ice_ether_pressure_plate", new PressurePlateBlock(BlockSetType.OAK, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK))); public static final Block ICE_ETHER_FENCE = register("ice_ether_fence", new FenceBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK))); public static final Block ICE_ETHER_FENCE_GATE = register("ice_ether_fence_gate", new FenceGateBlock(WoodType.OAK, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK))); public static final Block ICE_ETHER_WALL = register("ice_ether_wall", new WallBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK))); public static final Block ICE_ETHER_DOOR = register("ice_ether_door", new DoorBlock(BlockSetType.STONE, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK))); public static final Block ICE_ETHER_TRAPDOOR = register("ice_ether_trapdoor", new TrapdoorBlock(BlockSetType.IRON, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
|
这里我们注册了一些方块,楼梯、台阶、栅栏、墙、栅栏门、门、活板门、按钮、压力板
它们各自实例化的方块是不同的,其中一些的参数及其顺序和1.20.X
的不太一样,请注意
楼梯是StairsBlock
,其中的参数分别是基础方块的状态、设置。这个基础方块是其实也不难理解,我们的楼梯是从一个完整的方块上衍生出来的,比如原版的各种楼梯,都有对应的一个完整的方块,所以我们这里也用之前写的方块作为其基础方块
台阶是SlabBlock
,其参数就一个设置,我们可以直接复制我们之前写的方块
按钮是ButtonBlock
,其中的参数是按钮的类型(木制、石质等等)、恢复时间(按下到弹起的时间,单位tick)、设置。
原版的按钮有些是被箭或者三叉戟射中也会激活,这个和前面的那个方块类型有关系,还要单独写Tag
压力板是PressurePlateBlock
,其中的参数是压力板的类型(木制、石质等等)、设置。在1.20中,我们还有一个参数是关于敏感度的,但是现在没了。
而这个敏感度是与它的类型有关的,木制
(任意种类的木头,比如这里是橡木)是可以检测所有实体,包括掉落物;但如果是石质的,只能检测玩家和生物实体
栅栏是FenceBlock
,其参数就一个设置,直接复制我们之前写的方块。但是栅栏也得写Tag
,不然它无法连接
栅栏门是FenceGateBlock
,其参数是栅栏门的类型(木制、石质等等)、设置
墙是WallBlock
,其参数就一个设置,直接复制我们之前写的方块。墙和栅栏一样得写Tag
,不然它无法连接
门是DoorBlock
,其参数是门的类型(木制、石质等等,其中铁质只能用红石信号开)、设置
活板门是TrapdoorBlock
,其参数是活板门的类型(木制、石质等等,其中铁质只能用红石信号开)、设置
说实话,这里面有些参数挺迷的,1.20到1.21大改一通,差点没给我绕进去
加入物品栏
1 2 3 4 5 6 7 8 9
| entries.add(ModBlocks.ICE_ETHER_STAIRS); entries.add(ModBlocks.ICE_ETHER_SLAB); entries.add(ModBlocks.ICE_ETHER_BUTTON); entries.add(ModBlocks.ICE_ETHER_PRESSURE_PLATE); entries.add(ModBlocks.ICE_ETHER_FENCE); entries.add(ModBlocks.ICE_ETHER_FENCE_GATE); entries.add(ModBlocks.ICE_ETHER_WALL); entries.add(ModBlocks.ICE_ETHER_DOOR); entries.add(ModBlocks.ICE_ETHER_TRAPDOOR);
|
记得加入物品栏,不然你的方块就没法在游戏中看到了
BlockTag
上面提到了一些方块得写Tag
才能正常运行,这里我们就来写一下
1 2 3 4 5 6
| getOrCreateTagBuilder(BlockTags.FENCES) .add(ModBlocks.ICE_ETHER_FENCE); getOrCreateTagBuilder(BlockTags.FENCE_GATES) .add(ModBlocks.ICE_ETHER_FENCE_GATE); getOrCreateTagBuilder(BlockTags.WALLS) .add(ModBlocks.ICE_ETHER_WALL);
|
我们给栅栏、栅栏门、墙写了Tag
,这样他们就能正常连接了
但是,如果只是这样写,那么我们的栅栏默认是和砖质的栅栏(原版也就一个下界砖栅栏)连接的,如果要和木制的栅栏连接,我们还得写一个Tag
1 2
| getOrCreateTagBuilder(BlockTags.WOODEN_FENCES) .add(ModBlocks.ICE_ETHER_FENCE);
|
但请注意,二者只能选其一,写了这个以后就不能和砖质栅栏进行连接
另外还有一个按钮,如果说我们希望我们的按钮被箭或者三叉戟射中也能激活,首先我们得确保注册的时候,其类型是木制的,同时还得写一个Tag
1 2
| getOrCreateTagBuilder(BlockTags.BUTTONS) .add(ModBlocks.ICE_ETHER_BUTTON);
|
模型文件
在生成模型文件之前,我们还得创建一个类。
虽然说这篇教程我们没去看源代码,但是当我们自己去找原版的那些关于模型数据生成的东西的时候,我们没看到有什么ModelProvider
,但是你会发现一个BlockFamilies
类
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static final BlockFamily OAK = register(Blocks.OAK_PLANKS) .button(Blocks.OAK_BUTTON) .fence(Blocks.OAK_FENCE) .fenceGate(Blocks.OAK_FENCE_GATE) .pressurePlate(Blocks.OAK_PRESSURE_PLATE) .sign(Blocks.OAK_SIGN, Blocks.OAK_WALL_SIGN) .slab(Blocks.OAK_SLAB) .stairs(Blocks.OAK_STAIRS) .door(Blocks.OAK_DOOR) .trapdoor(Blocks.OAK_TRAPDOOR) .group("wooden") .unlockCriterionName("has_planks") .build();
|
我暂且将其直译为方块族
,我们取其中的一个BlockFamily
,比如OAK
,我们可以看到,这个BlockFamily
包含了很多关于橡木的方块
进一步思考,它后面的那些方块都是从原本的OAK_PLANKS
衍生出来的,然后除了门和活板门,其余方块是共用同一个材质的!
(其实进一步挖下去可以发现,门和活板门的材质路径是单独确定的)
另外,我们也在BlockStateModelGenerator
中发现了此类的调用
1 2 3
| BlockFamilies.getFamilies() .filter(BlockFamily::shouldGenerateModels) .forEach(family -> this.registerCubeAllModelTexturePool(family.getBaseBlock()).family(family));
|
这里是遍历了里面的所有BlockFamily
,然后调用了registerCubeAllModelTexturePool
方法,这个方法是用来生成模型文件的
同时,我们在BlockStateModelGenerator
没有看到上面那些方块的生成语句
所以说我们还得先创建一个ModBlockFamilies
类,然后再生成模型文件
创建ModBlockFamilies类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class ModBlockFamilies { private static final Map<Block, BlockFamily> BASE_BLOCKS_TO_FAMILIES = Maps.<Block, BlockFamily>newHashMap();
public static final BlockFamily ICE_ETHER = register(ModBlocks.ICE_ETHER_BLOCK) .stairs(ModBlocks.ICE_ETHER_STAIRS) .slab(ModBlocks.ICE_ETHER_SLAB) .button(ModBlocks.ICE_ETHER_BUTTON) .pressurePlate(ModBlocks.ICE_ETHER_PRESSURE_PLATE) .fence(ModBlocks.ICE_ETHER_FENCE) .fenceGate(ModBlocks.ICE_ETHER_FENCE_GATE) .wall(ModBlocks.ICE_ETHER_WALL) .door(ModBlocks.ICE_ETHER_DOOR) .trapdoor(ModBlocks.ICE_ETHER_TRAPDOOR) .unlockCriterionName("has_ice_ether_block") .build(); public static BlockFamily.Builder register(Block baseBlock) { BlockFamily.Builder builder = new BlockFamily.Builder(baseBlock); BlockFamily blockFamily = (BlockFamily)BASE_BLOCKS_TO_FAMILIES.put(baseBlock, builder.build()); if (blockFamily != null) { throw new IllegalStateException("Duplicate family definition for " + Registries.BLOCK.getId(baseBlock)); } else { return builder; } } public static Stream<BlockFamily> getFamilies() { return BASE_BLOCKS_TO_FAMILIES.values().stream(); } }
|
这里的很多方法是从BlockFamilies
类中搬过来的,然后getFamilies
方法是用于数据生成的调用的
这里我们没有告示牌
这类东西,就没有写上,group
也是可写可不写的
告示牌
后面有单独的教程会讲,这玩意是实体,不借助第三方API还有点难写
生成模型文件
1 2 3 4
| ModBlockFamilies.getFamilies() .filter(BlockFamily::shouldGenerateModels).forEach( blockFamily -> blockStateModelGenerator.registerCubeAllModelTexturePool(blockFamily.getBaseBlock()) .family(blockFamily));
|
用这里这条语句代替
原来的blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.ICE_ETHER_BLOCK);
,一定得把原来的语句去掉,不然会运行的时候会报错
随后我们跑数据生成的时候,就可以生成一堆模型文件了
其他数据文件
语言文件
1 2 3 4 5 6 7 8 9
| translationBuilder.add(ModBlocks.ICE_ETHER_STAIRS, "Ice Ether Stairs"); translationBuilder.add(ModBlocks.ICE_ETHER_SLAB, "Ice Ether Slab"); translationBuilder.add(ModBlocks.ICE_ETHER_BUTTON, "Ice Ether Button"); translationBuilder.add(ModBlocks.ICE_ETHER_PRESSURE_PLATE, "Ice Ether Pressure Plate"); translationBuilder.add(ModBlocks.ICE_ETHER_FENCE, "Ice Ether Fence"); translationBuilder.add(ModBlocks.ICE_ETHER_FENCE_GATE, "Ice Ether Fence Gate"); translationBuilder.add(ModBlocks.ICE_ETHER_WALL, "Ice Ether Wall"); translationBuilder.add(ModBlocks.ICE_ETHER_DOOR, "Ice Ether Door"); translationBuilder.add(ModBlocks.ICE_ETHER_TRAPDOOR, "Ice Ether Trapdoor");
|
战利品列表
这个视频教程里面忘记讲了,我们copy的方块是我们之前写的,设置了requireTool
,所以也别忘了写
同时,门和台阶这种具有特殊状态的方块也得注意其写法,它们有单独的方法
1 2 3 4 5 6 7 8 9 10
| addDrop(ModBlocks.ICE_ETHER_STAIRS); addDrop(ModBlocks.ICE_ETHER_FENCE); addDrop(ModBlocks.ICE_ETHER_FENCE_GATE); addDrop(ModBlocks.ICE_ETHER_BUTTON); addDrop(ModBlocks.ICE_ETHER_PRESSURE_PLATE); addDrop(ModBlocks.ICE_ETHER_WALL); addDrop(ModBlocks.ICE_ETHER_TRAPDOOR);
addDrop(ModBlocks.ICE_ETHER_DOOR, doorDrops(ModBlocks.ICE_ETHER_DOOR)); addDrop(ModBlocks.ICE_ETHER_SLAB, slabDrops(ModBlocks.ICE_ETHER_SLAB));
|
配方文件的话就不再赘述,可以根据原版的配方文件进行仿写
随后我们就可以运行游戏,看看我们的方块了