Skip to content

自定义方块实体 ​

方块实体(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 组件 ​

键类型默认值说明
tickboolfalse为 true 时,方块在模拟范围内每秒发送 20 次 ServerBlockEntityTickEvent
client_tickboolfalse为 true 时,方块在模拟范围内每秒发送 20 次 ModBlockEntityTickClientEvent
movablebooltrue为 true 时可被粘性活塞拉回,为 false 时不可拉回
json
"netease:block_entity": {
  "tick": true,
  "client_tick": false,
  "movable": false
}

数据读写 ​

方块实体的数据通过 blockEntityData 组件管理,操作方式类似 Python dict:

python
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:

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
      }
    }
  }
}

服务端脚本 ​

python
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:

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 中关联

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_entitytrue 时,手持和掉落物也使用实体模型
block_icon物品栏图标贴图(在 terrain_texture.json 中定义)
destroyed_textures可选。方块破坏粒子的取样贴图

脚本控制动画 ​

通过 Molang 变量和客户端接口控制方块实体的动画状态:

接口端说明
SetBlockEntityMolangValue客户端设置 Molang 变量值,用于切换动画状态
GetBlockEntityMolangValue客户端获取 Molang 变量的当前值
SetEnableBlockEntityAnimations客户端开启/关闭方块实体的动画效果
SetBlockEntityModelPosOffset客户端设置模型位置偏移
SetBlockEntityModelRotation客户端设置模型旋转角度
SetBlockEntityModelScale客户端设置模型缩放
python
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 字段:

json
{
  "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 中声明粒子与音效:

json
"particle_effects": {
  "my_particle": "minecraft:water_fog"
},
"sound_effects": {
  "my_sound": "custom.block.hum"
}

在动画控制器中播放:

json
{
  "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。

下一步 ​