這是適用于Minecraft Java版1.21.4的Fabric模組開發系列教程專欄第十四章——方塊實體。想要閱讀其他內容,請查看或訂閱上面的專欄。
方塊實體(Block Entity) 指的是一種用于存儲方塊額外數據的方法。但這種數據和為了控制方塊狀態而在自定義方塊類中創建的屬性不太相同,通常方塊實體中的數據可能需要進行邏輯處理和計算,且一般不會直接體現在方塊上。
不過,本章中創建的帶有方塊實體的自定義方塊始終使用了方塊實體中的數據來控制方塊外觀——光源方塊(light_block),玩家通過使用(右鍵點擊)“光源方塊”來動態切換其光照強度,每點擊一下,方塊的光照強度將+1;如果光照強度已經為15,則重置光照強度為0。
要創建“光源方塊”,需要按照以下步驟推進:
- 創建方塊實體類;
- 創建方塊實體注冊類;
- 注冊方塊實體;
- 創建帶有方塊實體功能的方塊類;
- 注冊方塊并將其添加到物品組中;
- 完成創建方塊的其他工作;
- 在方塊實體類中添加數據;
- 在方塊類中使用方塊實體;
創建方塊實體類
要創建方塊對應的實體,需要創建一個方塊實體類,并使其繼承BlockEntity
類。
方塊實體類BlockEntity
BlockEntity
類一般作為方塊實體類的父類,用于保存一個世界中方塊的額外數據。通常,方塊的數據由預定義的、有限的方塊狀態數據集合保存。但是,方塊需要存儲一些不能被預定義的數據,例如箱子里的物品、告示牌上的文字等,方塊實體可以存儲這些數據。
通常,繼承了BlockEntity
類的自定義方塊實體類需要調用其構造方法;
public BlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {this.type = type;this.pos = pos.toImmutable();this.validateSupports(state);this.cachedState = state;
}
其中需要傳遞方塊實體類型BlockEntityType
對象、方塊位置BlockPos
對象和方塊狀態BlockState
對象。
方塊實體類型類BlockEntityType
BlockEntityType
類用于定義和管理方塊實體類型。在創建完實體類后,還需要在游戲注冊表中注冊實體類,此時將返回一個BlockEntityType
對象,需要在實體類的構造方法中使用。
1.創建com/example/test/block/entity/custom
目錄,然后在其中創建LightBlockEntity
并繼承BlockEntity
類;
public class LightBlockEntity extends BlockEntity {}
2.聲明構造方法,在方法體中調用其父類的構造方法;
public LightBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {super(type, pos, state);
}
稍后我們將修改此處的BlockEntityType
對象。
創建方塊實體注冊類
方塊實體注冊類用于將所有方塊實體注冊到游戲注冊表中。一般,其中有三部分內容:
- 封裝的
register()
方法:用于注冊方塊實體,并返回一個BlockEntityType
對象; - 類型為
BlockEntityType<T>
的靜態常量:代表已經注冊的方塊實體。稍后將被對應的方塊實體類的構造方法所使用; initialize()
:用于初始化方塊實體注冊類。需要在入口點類的onInitialize()
方法中調用;
1.在com/example/test/block/entity
目錄中創建ModBlockEntities.java
;
public class ModBlockEntities {}
2.聲明靜態私有方法register()
。根據官方文檔,register()
的寫法應該如下:
private static <T extends BlockEntity> BlockEntityType<T> register(String name,FabricBlockEntityTypeBuilder.Factory<? extends T> entityFactory,Block... blocks
) {Identifier id = Identifier.of(FabricDocsReference.MOD_ID, name);return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, FabricBlockEntityTypeBuilder.<T>create(entityFactory, blocks).build());
}
方法提供三個參數,分別為注冊的方塊實體名name
、Fabric方塊實體類型構造器工廠內部類FabricBlockEntityTypeBuilder.Factory
對象的表達式和方塊對象block
。方法首先調用了Identifier.of`()
方法創建方塊實體的標識符對象,其中使用了模組Id和參數中的方塊實體名name
,最后返回BlockEntityType
對象,同時也是Registry.register()
方法的返回值,其中傳遞了注冊鍵類型為方塊實體類型Registries.BLOCK_ENTITY_TYPE
、標識符對象以及build()
方法返回的BlockEntityType
對象;
3.聲明initialize()
方法,用于初始化方塊實體注冊類;
public static void initialize() {Test.LOGGER.info("Registering ModBlockEntities !!!");
}
其中可以輸出日志信息;
4.在入口點類的onInitialize()
方法中調用ModBlockEntities.initialize()
方法;
public void onInitialize() {//...ModBlockEntities.initialize();//...
}
完成方塊實體注冊類的初始化。
注冊方塊實體
現在,我們可以直接在方塊實體注冊類中將方塊實體注冊到游戲注冊表中。
1.在ModBlockEntities
類中聲明靜態常量LIGHT_BLOCK_ENTITY
,調用register()
方法對其初始化;
public static final BlockEntityType<LightBlockEntity> LIGHT_BLOCK_ENTITY =register("light", LightBlockEntity::new, ModBlocks.LIGHT_BLOCK);
方法中分別傳遞了方塊實體路徑名“light”、Fabric方塊實體類型構造器工廠內部類FabricBlockEntityTypeBuilder.Factory
對象的表達式LightBlockEntity::new
,這里使用了方塊實體類構造方法的表達式,以及方塊對象ModBlocks.LIGHT_BLOCK
;
這里的ModBlocks.LIGHT_BLOCK
指的是方塊對象,稍后我們將在方塊注冊類中聲明并對其初始化;
2.完成后,回到方塊實體類LightBlockEntity
,修改構造方法;
public LightBlockEntity(BlockPos pos, BlockState state) {super(ModBlockEntities.LIGHT_BLOCK_ENTITY,pos, state);
}
首先刪除BlockEntityType<?> type
參數,然后將super()
方法中第一個參數改為剛剛注冊完畢的方塊實體對象ModBlockEntities.LIGHT_BLOCK_ENTITY
。
創建帶有方塊實體功能的方塊類
與普通方塊創建的過程有所不同,創建帶有方塊實體功能的方塊類時,方塊類需要繼承BlockWithEntity
類并重寫相關方法。
1.創建com/example/test/block/custom
目錄,在其中創建LightBlock.java
并繼承BlockWithEntity
類;
public class LightBlock extends BlockWithEntity {}
2.聲明構造方法;
public LightBlock(Settings settings) {super(settings);
}
3.重寫getCodec()
方法,處理方塊實體中數據的序列化與反序列化;
@Override
protected MapCodec<? extends BlockWithEntity> getCodec() {return createCodec(LightBlock::new);
}
其中調用createCodec()
方法,其中傳遞抽象方塊設置內部類和自定義方塊類的函數表達式,此處使用了方塊類的構造方法;
4.重寫createBlockEntity()
方法,在方法體中調用方塊實體類的構造方法,用于在創建方塊時完成方塊實體的初始化。
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {return new LightBlockEntity(pos, state);
}
注冊方塊并將其添加到物品組中
現在,我們在方塊注冊類中使用自定義方塊類注冊方塊,這與其他方塊的注冊方式相同。
1.在方塊注冊類ModBlocks
中聲明靜態常量LIGHT_BLOCK
,調用register()
方法對其進行初始化;
public static final Block LIGHT_BLOCK = register("light_block",LightBlock::new,AbstractBlock.Settings.create(),true
);
按順序依次設置了方塊名為“light_block”、抽象方塊設置內部類對象和方塊對象的函數式LightBlock::new
、抽象方塊設置內部類對象并要求注冊方塊對應的方塊物品;
2.在入口點類的onInitialize()
方法中,將方塊添加到指定物品組中。
ItemGroupEvents.modifyEntriesEvent(CUSTOM_ITEM_GROUP_KEY).register((itemGroup) -> {...itemGroup.add(ModBlocks.LIGHT_BLOCK.asItem());...});
完成創建方塊的其他工作
與創建普通方塊相同,我們還要為方塊:
- 創建模型文件;
- 創建模型描述文件;
- 創建方塊紋理;
- 創建方塊狀態文件;
- 在語言文件中為其添加翻譯鍵值對;
1.在assets/test/models/block
目錄中創建light_block.json
作為方塊的模型文件;
{"parent": "minecraft:block/cube_all","textures": {"all": "test:block/light_block"}
}
2.在assets/test/items
目錄中創建light_block.json
作為方塊的模型描述文件;
{"model": {"type": "minecraft:model","model": "test:block/ice_wood"}
}
3.在assets/test/textures/block
目錄中創建方塊的紋理圖light_block.png
;
4.在assets/test/blockstates
目錄中創建light_block.json
作為方塊狀態文件;
{"variants": {"": {"model": "test:block/light_block"}}
}
5.在zh-cn.json
中添加一條翻譯鍵值對;
{..."item.test.light_block": "光源方塊",...
}
啟動游戲測試
現在,方塊的基礎內容以及創建完畢,游戲中應當可以正常顯示方塊。
1.打開游戲,在創造模式物品欄中找到“光源方塊”;
名稱、紋理等可以正常顯示;
2.將其放置在地面上,可以看到方塊模型顯示正常;
但是方塊現在沒有任何功能,我們要通過方塊實體為其添加數據。
在方塊實體類中添加數據
下面,我們將方塊發光時的光照等級數值存儲在方塊實體中并編寫數據變化的邏輯。
1.聲明私有變量level
,使其初始值為0,用于存儲光照等級;
private int level = 0;
2.為level
變量聲明一個getLevels()
方法,用于獲取光照等級;
public int getLevels() {return level;
}
3.聲明calculateLevels()
方法,用于為光照等級數值添加計算邏輯;
public void calculateLevels() {if(level >= 15){level = 0;}else {level++;}
}
一般方塊的最高光照等級為15。因此,右鍵使用方塊時,當光照等級小于15時,光照等級將+1;否則,將光照等級重置為0。
在方塊類中使用方塊實體
現在,我們可以使用方塊實體中的數據來控制“光源方塊”的光照強度。
不過,想要控制方塊的光照等級,依然需要在方塊類中創建用于修改方塊狀態的屬性,然后通過方塊實體將已經計算完畢,或者說已經執行完邏輯處理的數據傳遞給方塊屬性,從而修改方塊的光照強度;
實際上,方塊實體中的數據是可以直接使用的。只是在這個例子中,修改方塊光源的方法luminance()
由內部類AbstractBlock.Settings
提供,而其中傳遞的是泛型為BlockState
的函數式接口ToIntFunction
的函數表達式,這使得方塊的光照強度只能通過方塊狀態修改;
所以,我們要先為方塊類添加用于修改方塊狀態的屬性,然后再使用方塊實體。
整型屬性類IntProperty
IntProperty
類用于為方塊提供控制方塊狀態的整型屬性。其構造方法已經私有化,所以想要創建一個IntProperty
對象,通常調用其靜態方法of()
;
public static IntProperty of(String name, int min, int max) {return new IntProperty(name, min, max);
}
可以看到方法體中直接調用了構造方法,其中需要傳遞三個參數:
String name
:指定屬性名;int min
:指定屬性的最小值;int max
:指定屬性的最大值;
通過min
和max
變量限制整型數據值的范圍。
1.聲明靜態常量LIGHT_LEVEL
,類型為IntProperty
,調用靜態方法IntProperty.of()
對其初始化;
public static final IntProperty LIGHT_LEVEL = IntProperty.of("light_level", 0, 15);
方法中分別設置了屬性名為"light_level"以及數值的范圍在0~15;
2.重寫appendProperties()
方法,將屬性LIGHT_LEVEL
添加到方塊中;
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {builder.add(LIGHT_LEVEL);
}
調用StateManager.Builder
對象的add()
方法,將屬性附加到方塊中;
3.修改構造方法,在super()
方法中調用AbstractBlock.Settings
對象的luminance()
方法,在構造方塊對象時直接修改方塊的光照強度;
public LightBlock(Settings settings) {super(settings.luminance(state -> state.get(LIGHT_LEVEL)));
}
在luminance()
方法中傳遞通過當前方塊狀態對象的get()
方法返回的光照強度的LIGHT_LEVEL
的值;
4.重寫onUse()
方法,在方法體中使用方塊實體中的數據來修改當前方塊的光照強度LIGHT_LEVEL
;
@Override
protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {LightBlockEntity lightBlockEntity = (LightBlockEntity) world.getBlockEntity(pos);if(!world.isClient && lightBlockEntity != null) {lightBlockEntity.calculateLevels();world.setBlockState(pos, state.with(LIGHT_LEVEL, lightBlockEntity.getLevels()));}return ActionResult.SUCCESS;
}
首先調用world.getBlockEntity()
方法獲取指定方塊坐標的方塊實體對象,方法中傳遞一個BlockPos
對象,即當前方塊的坐標對象,將返回的方塊實體對象存儲在lightBlockEntity
中;
接著,在確保當前世界對象為服務器世界對象且方塊實體對象不為null的基礎上,調用方塊實體對象的calculateLevels()
方法,處理光照強度的變化邏輯;
然后調用world.setBlockState()
方法修改當前方塊的方塊狀態,方法中傳遞要修改的方塊坐標對象pos
,以及修改后的方塊狀態對象,這里調用了state.with()
方法,將當前光照等級修改為方塊實體中光照等級lightBlockEntity.getLevels()
的值;
最后返回ActionResult.SUCCESS
,代表操作成功;
5.現在,方塊所有功能已經通過方塊實體實現,可以再次啟動游戲進行測試;
打開游戲,修改世界時間為13000,將“光源方塊”放置在地面上,持續右鍵點擊方塊,可以發現方塊光照強度越來越高。
本章小結
本章詳細闡述了方塊實體的作用與創建的過程。如果沒有方塊創建和方塊狀態等前置知識的支撐,閱讀本文的難度較高,還請參考專欄的其他文章。感謝各位的閱讀,有興趣可以訂閱此專欄!