本篇教程的视频
本篇教程的源代码
本篇教程目标
- 理解原版各种建材类方块的注册
- 学会添加各种建材类方块
查看源代码
这里的建材类方块,指的是楼梯
、台阶
、墙
、门
、活板门
等这样的建材类方块
我们先来看看原版中的这些方块是怎么注册的
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| public static final Block OAK_STAIRS = register("oak_stairs", new StairsBlock(OAK_PLANKS.getDefaultState(), AbstractBlock.Settings.copy(OAK_PLANKS)));
public static final Block OAK_SLAB = register( "oak_slab", new SlabBlock( AbstractBlock.Settings.create().mapColor(MapColor.OAK_TAN).instrument(Instrument.BASS).strength(2.0F, 3.0F).sounds(BlockSoundGroup.WOOD).burnable() ) );
public static final Block OAK_BUTTON = register("oak_button", createWoodenButtonBlock(BlockSetType.OAK));
public static final Block OAK_PRESSURE_PLATE = register( "oak_pressure_plate", new PressurePlateBlock( PressurePlateBlock.ActivationRule.EVERYTHING, AbstractBlock.Settings.create() .mapColor(OAK_PLANKS.getDefaultMapColor()) .solid() .instrument(Instrument.BASS) .noCollision() .strength(0.5F) .burnable() .pistonBehavior(PistonBehavior.DESTROY), BlockSetType.OAK ) );
public static final Block OAK_FENCE = register( "oak_fence", new FenceBlock( AbstractBlock.Settings.create() .mapColor(OAK_PLANKS.getDefaultMapColor()) .solid() .instrument(Instrument.BASS) .strength(2.0F, 3.0F) .sounds(BlockSoundGroup.WOOD) .burnable() ) );
public static final Block OAK_FENCE_GATE = register( "oak_fence_gate", new FenceGateBlock( AbstractBlock.Settings.create().mapColor(OAK_PLANKS.getDefaultMapColor()).solid().instrument(Instrument.BASS).strength(2.0F, 3.0F).burnable(), WoodType.OAK ) );
public static final Block COBBLESTONE_WALL = register("cobblestone_wall", new WallBlock(AbstractBlock.Settings.copy(COBBLESTONE).solid()));
public static final Block OAK_DOOR = register( "oak_door", new DoorBlock( AbstractBlock.Settings.create() .mapColor(OAK_PLANKS.getDefaultMapColor()) .instrument(Instrument.BASS) .strength(3.0F) .nonOpaque() .burnable() .pistonBehavior(PistonBehavior.DESTROY), BlockSetType.OAK ) );
public static final Block OAK_TRAPDOOR = register( "oak_trapdoor", new TrapdoorBlock( AbstractBlock.Settings.create().mapColor(MapColor.OAK_TAN).instrument(Instrument.BASS).strength(3.0F).nonOpaque().allowsSpawning(Blocks::never).burnable(), BlockSetType.OAK ) );
|
这里的话,除了按钮
方块,其他的方块都是直接用register
方法来注册的,然后实例化
对应的方块类
按钮方块其实也一样,在它的那个方法里,实例化的也是按钮
对应的方块类
这里面具体的参数我们在之后再解释,这里就不解释了
方块族BlockFamilies
但是还没完,如果你查找其中一个字段引用
的地方,你会发现它们在一个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();
|
比如这里的OAK
,我们可以看到它调用了上面提到的大部分建材类方块
那么这个类用来干什么呢?我们再进一步找这个类调用的地方
1 2 3
| BlockFamilies.getFamilies() .filter(BlockFamily::shouldGenerateModels) .forEach(family -> this.registerCubeAllModelTexturePool(family.getBaseBlock()).family(family));
|
我们可以在BlockStateModelGenerator
类里找到这一串语句,这是其中一个
而这个类是干什么的?那自然就是数据生成
用的
待会我们写到数据生成
的时候,就会用到它
那么我们现在回过头来看看这个方块族,这个东西解释一下就是由一个主方块
引出的各种建材类方块
其中除了门
、活板门
还有告示牌
,其余的方块都是与主方块共用同一个材质
的(注:告示牌是方块实体,其材质是展开图
)
比如上面写到的OAK
里面,它的主方块是OAK_PLANKS
,即橡木木板
,而除了橡木门
、活板门
以及告示牌
,其他方块的材质都是橡木木板
的材质
所以这里就会冒出来一个方块族,同样的,这里的unlockCriterionName
也是与解锁配方的进度相关的
不过在实际编写的时候,是可以不写的
注册方块
我们到ModBlocks
类里,添加我们的方块
楼梯
1 2
| public static final Block ICE_ETHER_STAIRS = register("ice_ether_stairs", new StairsBlock(ICE_ETHER_BLOCK.getDefaultState(), AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
|
StairsBlock
的构造方法需要两个参数
第一个参数是基础方块
,这里的基础方块我们就直接拿我们前面写的ICE_ETHER_BLOCK
第二个参数是方块设置
台阶
1 2
| public static final Block ICE_ETHER_SLAB = register("ice_ether_slab", new SlabBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
|
SlabBlock
的构造方法只需要一个参数,就是方块设置
,这里直接复制ICE_ETHER_BLOCK
的设置即可
按钮
1 2
| public static final Block ICE_ETHER_BUTTON = register("ice_ether_button", new ButtonBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK), BlockSetType.STONE, 60, false));
|
ButtonBlock
的构造方法需要四个参数
第一个参数是方块设置
第二个参数是方块类型
第三个参数是按钮从按下
到弹起
的时间,单位tick
第四个是是否为木制
,其决定了是否能被箭
、三叉戟
这样的投掷物激活
压力板
1 2
| public static final Block ICE_ETHER_PRESSURE_PLATE = register("ice_ether_pressure_plate", new PressurePlateBlock(PressurePlateBlock.ActivationRule.EVERYTHING, AbstractBlock.Settings.copy(ICE_ETHER_BLOCK), BlockSetType.STONE));
|
PressurePlateBlock
的构造方法需要三个参数
第一个参数是激活规则
,这里我们用EVERYTHING
,表示任何实体
都会激活,另外一个类型是MOB
,则仅限生物实体
才能激活
第二个参数是方块设置
第三个参数是方块类型
栅栏
1 2
| public static final Block ICE_ETHER_FENCE = register("ice_ether_fence", new FenceBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
|
FenceBlock
的构造方法只需要一个参数,就是方块设置
栅栏门
1 2
| public static final Block ICE_ETHER_FENCE_GATE = register("ice_ether_fence_gate", new FenceGateBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK), WoodType.OAK));
|
FenceGateBlock
的构造方法需要两个参数
第一个参数是方块设置
第二个参数是木头类型
,原版所有的栅栏门都是木制的,所以它对应类的构造函数中也有这么一个参数,这里就拿个原版的木头类型过来就好了
墙
1 2
| public static final Block ICE_ETHER_WALL = register("ice_ether_wall", new WallBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK)));
|
WallBlock
的构造方法只需要一个参数,就是方块设置
门
1 2
| public static final Block ICE_ETHER_DOOR = register("ice_ether_door", new DoorBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK), BlockSetType.IRON));
|
DoorBlock
的构造方法需要两个参数
第一个参数是方块设置
第二个参数是方块类型
,注意,如果这里设置为IRON
,那么就是和原版的铁门一样,只能用红石信号
来打开,而不能直接打开
活板门
1 2
| public static final Block ICE_ETHER_TRAPDOOR = register("ice_ether_trapdoor", new TrapdoorBlock(AbstractBlock.Settings.copy(ICE_ETHER_BLOCK).nonOpaque(), BlockSetType.STONE));
|
TrapdoorBlock的构造方法需要两个参数
第一个参数是方块设置
,这里加上了一个nonOpaque(),表示这个方块是非实心的,如果说你要使用带完全或者半透明的材质,这个方法得加上
第二个参数是方块类型
,这个与上面的门一样
添加到物品栏
不要忘了物品栏
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);
|
数据生成
那么注册就是这一堆,接下来就要写它的各种数据文件
方块模型
方块模型呢,如果你要像之前写的registerSimpleCubeAll
那样类似地写,你会发现楼梯
、台阶
这种是没有对应方法的
这个时候,我们就要用到上面看源代码时提到的那个BlockFamilies
了
ModBlockFamilies
这里我们来创建一个ModBlockFamilies
类,用来存放我们自己的方块族
1 2 3
| public class ModBlockFamilies { }
|
然后里面从原版的BlockFamilies
类中搬一些要用到的东西过来
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private static final Map<Block, BlockFamily> BASE_BLOCKS_TO_FAMILIES = Maps.<Block, BlockFamily>newHashMap();
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(); }
|
我们从原版的类搬三块语句进来,这里的话,没有我们要改的东西,所以就直接搬过来
即可
最后一个方法是最后数据生成
时调用的
然后我们来写一个方块族
1 2 3 4 5 6 7 8 9 10 11
| 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) .build();
|
这里我们用到了上面搬过来的register
方法,然后依次添加我们注册的方块
数据生成调用
那么写好我们的方块族之后,我们就可以在数据生成中调用这个类了
在generateBlockStateModels
方法中写上
1 2 3 4 5 6
| ModBlockFamilies.getFamilies() .filter(BlockFamily::shouldGenerateModels) .forEach(family -> blockStateModelGenerator. registerCubeAllModelTexturePool(family.getBaseBlock()) .family(family));
|
这个其实也是照着BlockStateModelGenerator
中的语句写的,只是将this
替换为方法中的形参blockStateModelGenerator
这个呢,其实就是来遍历
方块族这个类中所以需要生成模型的方块,来生成它们的模型
registerCubeAllModelTexturePool
即为生成一个共用的材质池,生成的模型会共用同一个材质
注意,我们还要将原来的ICE ETHER BLOCK
相应的方块模型生成语句给去掉
,不然会导致生成重复的模型
因为在方块族中已经包括了ICE ETHER BLOCK
这个方块了
战利品列表
1 2 3 4 5 6 7 8 9
| addDrop(ModBlocks.ICE_ETHER_STAIRS); addDrop(ModBlocks.ICE_ETHER_SLAB, slabDrops(ModBlocks.ICE_ETHER_SLAB)); addDrop(ModBlocks.ICE_ETHER_BUTTON); addDrop(ModBlocks.ICE_ETHER_PRESSURE_PLATE); addDrop(ModBlocks.ICE_ETHER_FENCE); addDrop(ModBlocks.ICE_ETHER_FENCE_GATE); addDrop(ModBlocks.ICE_ETHER_DOOR, doorDrops(ModBlocks.ICE_ETHER_DOOR)); addDrop(ModBlocks.ICE_ETHER_TRAPDOOR); addDrop(ModBlocks.ICE_ETHER_WALL);
|
战利品列表的数据生成就台阶
和门
特殊一点
台阶两个组合之后变成一个完整的方块,但是破坏时,依旧会掉落两个台阶
而门有半门特性
,它分上下两个部分,但是破坏时,不论破坏哪个部分,都只会掉一个且完整的门
其他的方块则就像一般的方块
来写就可以了
语言文件
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");
|
测试
那么最后,我们就可以跑一下数据生成
,在此之后我们就可以启动我们的游戏进行测试了
出现Bug!
好,那么我们门和活板门是采用了透明
的材质,然后呢,现在该透明
的地方是一片黑
因为我们还得为方块配置渲染层
,告诉游戏怎么处理这些透明通道
我们到TutorialModClient
中的onInitializeClient
方法中写上
1 2 3
| BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.ICE_ETHER_DOOR, RenderLayer.getCutout()); BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.ICE_ETHER_TRAPDOOR, RenderLayer.getCutout()); BlockRenderLayerMap.INSTANCE.putBlock(ModBlocks.ICE_ETHER_BLOCK, RenderLayer.getTranslucent());
|
渲染
这个东西都是在客户端
完成的,就像你在服务器
(服务端)中,你加什么材质
、光影
,和服务器
没有任何关系
它们都是在本地
的客户端
渲染的
这里我们使用BlockRenderLayerMap.INSTANCE.putBlock
来设置方块的渲染层
第一个参数是方块,第二个是渲染层类型
设置
getCutout
为全透明材质使用,getTranslucent
为半透明材质使用
ICE ETHER BLOCK
的材质其实并不是完全不透明的,所以我们这里也可以设置一下
另外,不论完全还是半透明
的方块,只要有一个面是完全与其他方块贴合的,就要在注册方块时加上非实心
方法,即nonOpaque
因为MC
为了节约渲染开销,方块相接触的面
是不会被渲染的,如果不设置方块非实心,则会导致透视
好,那么现在我们就可以重新启动游戏去运行了