自定义方块实体
方块实体(Block Entity)是方块的"大脑",让普通方块拥有数据存储、定时执行和自定义外观(动画、粒子特效)的能力。方块实体相当于给方块附加了一个持久化的数据容器和可选的 tick 循环,配合 Python Mod SDK 可以实现机器、陷阱、装饰品等复杂逻辑。
前置条件
请先阅读 自定义基础方块 了解方块的注册流程和 JSON 组件体系。方块实体需要 netease:block_entity 组件,不需要开启实验性玩法。
概述
方块实体在原版 MC 中就存在 — 箱子、附魔台、旗帜、刷怪笼等方块都拥有方块实体。中国版允许开发者为自定义方块添加方块实体,核心能力包括:
- 数据存储:通过
GetBlockEntityData接口读写键值数据,数据随方块持久保存 - 服务端 tick:
tick: true后,方块在模拟范围内时每秒触发 20 次ServerBlockEntityTickEvent - 客户端 tick:
client_tick: true后,客户端每秒触发 20 次ModBlockEntityTickClientEvent - 活塞控制:
movable控制是否可被粘性活塞拉动 - 实体模型与动画:支持挂载 BlockBench 实体模型,播放动画、控制 Molang 变量
- 粒子与音效特效:支持网易版粒子/序列帧特效和原版粒子/音效
限制
- 已有方块实体的特殊方块(如
base_block: "mob_spawner")不能再添加自定义方块实体 - 自定义方块实体与自定义含雪方块不兼容
- 避免在地图中放置过多
tick: true的方块实体,频繁事件回调会造成卡顿 - 通过
minecraft:ore_feature等原版特征放置的方块无法正常创建方块实体;如需使用特征放置,请用结构特征
netease:block_entity 组件
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tick | bool | false | 为 true 时,方块在模拟范围内每秒发送 20 次 ServerBlockEntityTickEvent |
client_tick | bool | false | 为 true 时,方块在模拟范围内每秒发送 20 次 ModBlockEntityTickClientEvent |
movable | bool | true | 为 true 时可被粘性活塞拉回,为 false 时不可拉回 |
"netease:block_entity": {
"tick": true,
"client_tick": false,
"movable": false
}数据读写
方块实体的数据通过 blockEntityData 组件管理,操作方式类似 Python dict:
import server.extraServerApi as serverApi
# 获取方块实体数据
comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(levelId)
blockEntityData = comp.GetBlockEntityData(dimension, (x, y, z))
# 写入前必须判空
if blockEntityData:
# 写入数据(支持 dict、list、int、float、str、bool、None)
blockEntityData['score'] = 100
blockEntityData['config'] = {"speed": 1.5, "active": True}
# 读取数据
score = blockEntityData['score'] # 100
missing = blockEntityData['notExist'] # None(不存在的键返回 None)TIP
数据随方块自动持久化保存。玩家退出后重新进入,数据依然存在。如果方块被破坏,数据也会随之销毁。
相关事件
| 事件 | 端 | 触发时机 |
|---|---|---|
ServerPlaceBlockEntityEvent | 服务端 | 玩家手动放置含方块实体的方块时 |
ServerBlockEntityTickEvent | 服务端 | tick: true 且方块在模拟范围内时,每秒 20 次 |
ModBlockEntityTickClientEvent | 客户端 | client_tick: true 且方块在模拟范围内时,每秒 20 次 |
ChunkGeneratedServerEvent | 服务端 | 通过特征放置的方块实体,区块生成完毕时触发,包含区块内方块实体列表 |
完整案例:计数器方块
一个会在 tick 中自动计数、玩家交互时显示计数值的方块。
行为包 JSON
behavior_pack/netease_blocks/counter_block.json:
{
"format_version": "1.10.0",
"minecraft:block": {
"description": {
"identifier": "mymod:counter_block",
"register_to_create_menu": true,
"category": "Construction"
},
"components": {
"minecraft:destroy_time": { "value": 2.0 },
"minecraft:explosion_resistance": { "value": 10.0 },
"minecraft:block_light_absorption": { "value": 0 },
"netease:block_entity": {
"tick": true,
"client_tick": false,
"movable": false
}
}
}
}服务端脚本
import server.extraServerApi as serverApi
ServerSystem = serverApi.GetServerSystemCls()
class CounterServer(ServerSystem):
def __init__(self, namespace, systemName):
super(CounterServer, self).__init__(namespace, systemName)
engineNs = serverApi.GetEngineNamespace()
engineSys = serverApi.GetEngineSystemName()
# 方块放置时初始化数据
self.ListenForEvent(engineNs, engineSys,
'ServerPlaceBlockEntityEvent', self, self.OnPlace)
# tick 事件
self.ListenForEvent(engineNs, engineSys,
'ServerBlockEntityTickEvent', self, self.OnTick)
# 玩家交互事件
self.ListenForEvent(engineNs, engineSys,
'ServerBlockUseEvent', self, self.OnUse)
def OnPlace(self, args):
dim = args['dimension']
pos = (args['posX'], args['posY'], args['posZ'])
comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(self.levelId)
data = comp.GetBlockEntityData(dim, pos)
if data:
data['count'] = 0
def OnTick(self, args):
dim = args['dimension']
pos = (args['posX'], args['posY'], args['posZ'])
blockName = args['blockName']
if blockName != 'mymod:counter_block':
return
comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(self.levelId)
data = comp.GetBlockEntityData(dim, pos)
if data:
current = data.get('count', 0)
data['count'] = current + 1
def OnUse(self, args):
blockName = args['blockName']
if blockName != 'mymod:counter_block':
return
playerId = args['playerId']
pos = (args['x'], args['y'], args['z'])
dimComp = serverApi.GetEngineCompFactory().CreateDimension(playerId)
dim = dimComp.GetPlayerDimensionId()
comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(self.levelId)
data = comp.GetBlockEntityData(dim, pos)
if data:
count = data.get('count', 0)
msgComp = serverApi.GetEngineCompFactory().CreateMsg(self.levelId)
msgComp.NotifyOneMessage(playerId, "计数器: {}".format(count), "§a")性能注意
- 避免在
ServerBlockEntityTickEvent中执行 print 或日志输出,每秒 20 次的高频率会严重影响性能 - 避免在地图中同时放置大量
tick: true的方块实体
方块实体外观
方块实体支持挂载实体模型和动画,让方块像附魔台一样"动起来"。这是中国版的扩展功能,支持:
- 实体模型与动画:使用 BlockBench 制作实体模型和动画,挂载到方块上
- Molang 变量控制:通过脚本设置 Molang 变量,切换动画状态
- 网易粒子/序列帧特效:在方块位置附加粒子和序列帧
- 原版粒子与音效:在动画控制器或动画中配置原版粒子和音效
- 多面向:支持四面向和六面向
配置实体模型
实体模型的配置分三步:准备模型 → 编写 entity.json → 在 blocks.json 中关联。
1. 准备模型与动画
使用 BlockBench 制作实体模型(不是方块模型),导出为基岩版几何体格式:
- 模型 JSON 放入
resource_pack/models/entity/ - 动画 JSON 放入
resource_pack/animations/ - 动画控制器 JSON 放入
resource_pack/animation_controllers/
BlockBench 注意事项
- 模型位置
(0,0,0)对应游戏中方块的实际位置 - 尺寸 16 = 游戏中一个方块的边长
- 模型在 BlockBench 中的面向和缩放效果与游戏中一致
2. 编写客户端实体定义
resource_pack/entity/my_block_entity.entity.json:
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "mymod:animated_block",
"geometry": {
"default": "geometry.my_block_model"
},
"textures": {
"default": "textures/entity/my_block_texture"
},
"materials": {
"default": "entity_alphatest"
},
"render_controllers": ["controller.render.default"],
"scripts": {
"scale": "0.9375",
"initialize": [
"variable.mod_is_active = 0.0;"
],
"animate": ["general"]
},
"animations": {
"idle": "animation.my_block.idle",
"active": "animation.my_block.active",
"general": "controller.animation.my_block.general"
}
}
}
}3. 在 blocks.json 中关联
{
"mymod:animated_block": {
"sound": "stone",
"client_entity": {
"identifier": "mymod:animated_block",
"hand_model_use_client_entity": true,
"block_icon": "my_block_icon",
"destroyed_textures": "my_block_destroy"
}
}
}| 字段 | 说明 |
|---|---|
identifier | 对应 entity.json 中的 identifier |
hand_model_use_client_entity | true 时,手持和掉落物也使用实体模型 |
block_icon | 物品栏图标贴图(在 terrain_texture.json 中定义) |
destroyed_textures | 可选。方块破坏粒子的取样贴图 |
脚本控制动画
通过 Molang 变量和客户端接口控制方块实体的动画状态:
| 接口 | 端 | 说明 |
|---|---|---|
SetBlockEntityMolangValue | 客户端 | 设置 Molang 变量值,用于切换动画状态 |
GetBlockEntityMolangValue | 客户端 | 获取 Molang 变量的当前值 |
SetEnableBlockEntityAnimations | 客户端 | 开启/关闭方块实体的动画效果 |
SetBlockEntityModelPosOffset | 客户端 | 设置模型位置偏移 |
SetBlockEntityModelRotation | 客户端 | 设置模型旋转角度 |
SetBlockEntityModelScale | 客户端 | 设置模型缩放 |
import client.extraClientApi as clientApi
# 切换动画状态
comp = clientApi.GetEngineCompFactory().CreateBlockEntityComp(levelId)
comp.SetBlockEntityMolangValue(dim, pos, "variable.mod_is_active", 1.0)配置粒子与序列帧特效
在 blocks.json 中添加 netease_particle_effects 和 netease_frame_effects 字段:
{
"mymod:animated_block": {
"sound": "stone",
"client_entity": {
"identifier": "mymod:animated_block",
"hand_model_use_client_entity": true,
"block_icon": "my_block_icon"
},
"netease_particle_effects": {
"spark": {
"path": "effects/my_spark_particle.json",
"pos": [0.5, 1.0, 0.5]
}
},
"netease_frame_effects": {
"glow": {
"path": "effects/my_glow_frame.json",
"pos": [0.5, 0.5, 0.5]
}
}
}
}| 字段 | 说明 |
|---|---|
path | 特效相对路径。粒子以 effects/ 开头;序列帧分两种:MC Studio 导出用 effects/ 开头(带 .json),Texture Packer 导出用 textures/ 开头(不带后缀) |
pos | 相对于方块位置的偏移坐标 [x, y, z] |
特效管理接口:
| 接口 | 说明 |
|---|---|
CreateParticleEffectForBlockEntity | 运行时创建粒子特效 |
GetParticleEffectIdInBlockEntity | 获取已创建的粒子 ID |
RemoveParticleEffectInBlockEntity | 移除粒子特效 |
SetBlockEntityParticlePosOffset | 调整粒子位置偏移 |
CreateFrameEffectForBlockEntity | 运行时创建序列帧特效 |
GetFrameEffectIdInBlockEntity | 获取已创建的序列帧 ID |
RemoveFrameEffectInBlockEntity | 移除序列帧特效 |
SetBlockEntityFramePosOffset | 调整序列帧位置偏移 |
原版粒子与音效
方块实体模型还支持在动画控制器或动画 JSON 中配置原版粒子和音效,方法与原版生物实体完全一致。
在 entity.json 中声明粒子与音效:
"particle_effects": {
"my_particle": "minecraft:water_fog"
},
"sound_effects": {
"my_sound": "custom.block.hum"
}在动画控制器中播放:
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.my_block.general": {
"initial_state": "idle",
"states": {
"idle": {
"animations": ["idle"],
"particle_effects": [
{ "effect": "my_particle", "locator": "center_locator" }
],
"sound_effects": [
{ "effect": "my_sound" }
],
"transitions": [
{ "active": "variable.mod_is_active == 1.0" }
]
},
"active": {
"animations": ["active"],
"transitions": [
{ "idle": "variable.mod_is_active == 0.0" }
]
}
}
}
}
}注意
format_version 为 1.8.0 的 animation_controllers.json 不支持 sound_effects,需要升级到 1.10.0。但 animations.json 的 1.8.0 版本可以使用 sound_effects。
