Bedrock Wiki
  • QQ
  • 原站
新手入门指南
  • Guide
    • 1. 简介
      指南
    • 2. 附加组件详解
    • 3. 软件与准备工作
    • 4. 项目设置
    • 5. 创建自定义物品
    • 6. 创建自定义实体
    • 7. Blockbench:建模、贴图与动画制作
    • 8. 添加战利品表、生成规则与合成配方
  • Extra
    • a. 理解JSON
    • b. 下载示例包
    • c. 故障排除
      帮助
    • d. 高级清单文件指南
    • e. 格式版本
    • f. Android 项目设置
JSON UI
  • General
    • JSON UI 入门指南
      指南
    • 最佳实践
      指南
  • Tutorials
    • Aseprite 动画
    • 保留标题文本
      中级
    • 修改服务器表单
      中级
    • 字符串与数字转换
      中级
    • 按钮与开关
      新手
    • 添加HUD界面元素
      新手
  • Documentation
    • JSON UI 文档
Meta
  • Style Guide
  • 使用JSON模式(Schemas)
  • 实用链接
  • 版本控制
  • 附加包性能优化
  • Q&A
    • GameTest 问答集 2021/08/06
    • 世界生成问答 2024/11/15
    • 延迟渲染技术预览问答 2024/02/23
    • 方块与物品问答 2024/08/30
    • 脚本与编辑器问答 2023/09/22
NBT
  • General
    • .mcstructure
  • Tutorials
    • 扩展结构限制
      简单
    • 教育版中的实验功能
      简单
  • NBT in Depth
    • NBT 库列表
      专家
    • NBT读取示例
      专家
    • 关于NBT(命名二进制标签)
      专家
世界生成
  • General
    • 世界生成入门
      指南
      实验性
    • 特性类型
      实验性
    • 生物群系
      指南
      实验性
  • Tutorials
    • 特征(Feature)的方块条件
      实验性
    • 生成地表区块
      实验性
    • 生成自定义矿石
      实验性
    • 生成自定义结构
      实验性
    • 高度图噪声地形
      实验性
  • Documentation
    • 生物群系标签
动画控制器
  • 动画控制器入门指南
    指南
  • 实体命令
    中级
  • AFK检测器
  • 将Molang变量转换为计分板数值
  • 死亡指令
  • 重生指令
命令
  • General
    • 命令方块入门指南
    • 函数
    • NBT 命令
    • 坐标系
    • 方块状态
    • 理解目标选择器
    • 记分板操作
  • Commands
    • Execute
      简单
    • Playanimation
    • 伤害
    • 播放音效
  • On Event Systems
    • 玩家首次加入时
    • On Player Join
    • 玩家离开时触发
    • 玩家死亡事件
    • 玩家重生事件系统
    • 首次加载世界时
  • Scoreboard Systems
    • 实体计数器
    • 计分板计时器
    • 比较与获取分数
  • Techniques
    • 执行逻辑门
    • MBE - Max的方块实体
    • FMBE - 创建显示实体的新方法
    • 视线检测
    • 移动状态检测
    • 轨道摄像机
  • Useful Creations
    • 多人位置重排系统
      函数
    • 自定义合成台
      简单
实体
  • General
    • Intro to Entities BP
      指南
      新手
    • 实体资源包入门
      指南
      新手
    • 实体问题排查指南
      帮助
    • NPC对话系统
      中级
    • 实体事件
      新手
    • 实体属性
    • 渲染控制器
      新手
    • 生成规则
  • Tutorials
    • 任意坐标系间的坐标转换(世界、实体、骨骼)
      中级
    • 创建船只
      中级
    • 实体手持物品
      中级
    • 实体攻击机制
      中级
    • 实体睡眠机制
      中级
    • 实体碰撞体
      中级
    • 实体移动
    • 实体计时器
      中级
    • 无敌实体
      新手
    • 村庄机制实现指南
    • 检测其他实体
      中级
    • 生成已驯服的实体
      脚本
      中级
    • 视线检测实体
      中级
    • 禁用队友伤害
      中级
    • 范围效果云入门指南
      中级
    • 虚拟实体
      新手
    • 飞行实体控制
      中级
  • Documentation
    • Vanilla Usage Spawn Rules
    • 原版使用组件
    • 抛射物
    • 虚拟组件
    • 运行时标识符
    • 非生物实体运行时标识符
战利品、配方与交易
  • General
    • 交易行为
  • Documentation
    • 战利品表
    • 交易表
    • 合成配方
    • 物品函数
  • Tutorials
    • 随机化结构战利品
      简单
文档
  • Shared Constructs
  • Molang 查询详解
  • Vanilla Materials
    专家
  • 声音定义
  • 文件类型
  • 材质配置文件说明
    专家
  • 菜单分类
  • 资源包文件夹结构
  • 雾效ID
  • 高级Molang指南
方块
  • General
    • 方块入门指南
      指南
      新手
    • 方块组件
    • Block Tags
    • 方块状态
    • Block Traits
    • 方块排列组合
    • 方块事件
      脚本
    • 方块事件迁移指南
      帮助
    • 方块物品化
      中级
    • 方块问题排查指南
      帮助
  • Visuals
    • 方块剔除
      中级
    • 方块模型
      指南
      新手
      简单
    • 方块着色
      简单
    • 方块纹理动画
      中级
    • 方块纹理变体
      中级
  • Tutorials
    • Precise Interaction
      专家
      脚本
    • Precise Rotation
      专家
      脚本
    • 伪方块
      中级
    • 可旋转方块
    • 应用持续效果
      简单
      脚本
    • 矿石战利品表
      简单
      脚本
    • 规避状态值限制
      专家
  • Vanilla Re-Creations
    • 自定义作物
      中级
      脚本
    • 自定义活板门
      中级
      脚本
    • 自定义玻璃
      新手
      简单
      已弃用
    • 自定义釉面陶瓦
      简单
  • Documentation
    • 原版方块模型
      新手
    • 方块形状
    • 方块格式历史
    • 方块音效
服务器
  • Software
    • Bedrock Server Software
  • Protocols
    • Bedrock Protocol
    • NetherNet 协议
    • RakNet 协议
概念
  • contents.json
  • Molang
    中级
  • Rawtext
  • textures_list.json
  • 命名空间
  • 子包
  • 文本与本地化
  • 着色器
  • 纹理图集
    中级
  • 表情符号与特殊字符
  • 覆盖资源
    中级
  • 音效
    中级
物品
  • General
    • 物品入门指南
      指南
      新手
    • 物品组件
    • 物品标签
    • 物品事件
      脚本
    • Item Event Migration
      帮助
    • 物品问题排查指南
      帮助
  • Tutorials
    • Custom Pottery Sherds
    • 可投掷物品
      中级
    • 生成物品
      中级
    • 自定义武器
      简单
    • 自定义盔甲
    • 自定义食物
      简单
      脚本
    • 通过装备物品执行命令
      实验性
      中级
    • 高分辨率物品
  • Documentation
    • 附魔
    • Numerical Item IDs
    • Vanilla Usage Components
    • 原版物品标识符
      已弃用
    • 可附着物
      新手
    • 物品格式历史记录
视觉效果
  • General
    • 实体视觉效果简介
      指南
    • 基岩版建模指南
    • 动画中的特效
    • 基于数学的动画
      中级
    • 材质
      专家
    • 材质创作
      专家
    • 皮肤包制作指南
    • 自定义死亡动画
      中级
  • Tutorials
    • Glowing Entity Texture
    • 受伤动画
      中级
    • 实体纹理动画
      中级
    • 栓绳位置调整
      简单
    • 玩家几何模型
      新手
    • 移除实体阴影
      中级
    • 重绘生成蛋纹理
      新手
  • Ideas
    • 结构展示技巧
粒子效果
  • General
    • 粒子效果入门
      指南
  • Tutorials
    • 禁用粒子效果
      新手
  • Documentation
    • 原版粒子效果
脚本编写
  • General
    • 脚本编程入门
    • 什么是Script API?
    • API 模块
  • Tutorials
    • GameTests
      实验性
    • 简易聊天命令
      实验性
    • 脚本核心功能
    • 脚本表单
      实验性
    • 脚本请求API
      实验性
    • 阻止方块放置
  • Documentation
    • JavaScript 问题排查指南
    • Script Resources
    • Script Watchdog
      实验性
    • TypeScript
    • 引擎环境
虚拟现实
  • General
    • 启用VR模式
      指南
    • 配置资源包
      专家
  • Tutorials
    • 编辑你的第一个模型
      专家

Precise Interaction

expert
scripting
Precise Interaction
  • How it Works
  • FaceSelectionPlains Class
    • Methods
    • Usage
  • SelectionBoxes Class
    • Methods
    • Usage
  • Pigeonholes Example
  • Double Flower Pot Example
  • Importing Scripts
  • Download Example Pack

FORMAT & MIN ENGINE VERSION 1.21.70

This tutorial assumes an advanced understanding of blocks and scripting. Check out the blocks and scripting guides before starting.

The ability to create custom blocks that the player can interact with can be very basic to implement, yet still allow for complex functionality. However, sometimes the default interaction mode, which is based on simply right-clicking or tapping the block without location-specific conditions, is not enough to achieve the desired functionality.

For example, what if you want to create a block that has multiple buttons on one side, and each one triggers a different action? Or a segment display, where multiple individually-lit lamps can be contained within one block.

This is where precise interaction comes in! The following methods of precise interaction allow you to define multiple areas in a block that can be interacted with separately, and assign different functions to each area. In this tutorial, we will show you how to add precise interaction to your blocks using scripts, with examples of each method.

Note: Precise interaction does not enable blocks to have multiple/custom-shaped minecraft:selection_box components. The selection box must be within all defined areas for precise interaction to function properly.

Showcase image displaying example Pigeonholes and Double Flower Pot blocks

How it Works ​

The provided methods of precise interaction use faceLocation, a property of the player interact event.

This value tells us where on the block's minecraft:selection_box was selected/hit, which is what precise interaction relies on.

Note: faceLocation is supposed to be relative to the bottom north-west corner of the interacted block, however it is currently relative to the world origin, meaning we will have to perform an additional calculation to make it relative. When this issue is resolved, this calculation will no longer be needed.

FaceSelectionPlains Class ​

This class allows you to define 2D areas on a block's face and get the selected plain.

To use this method for precise interaction, create the file BP/scripts/utilities/face_selection_plains.js and paste the below code into it.

FaceSelectionPlains Code
BP/scripts/utilities/face_selection_plains.js
js
import { Direction } from "@minecraft/server";

const isInRange = (value, min, max) => value >= min && value <= max;

export default class FaceSelectionPlains {
    /**
     * Allows you to define 2D areas on a block's face and get the selected plain.
     *
     * @param {Object[]} plains Spread array defining the 2D areas on a block's face which may be selected.
     * @param {[number, number]} plains[].origin [U, V] array defining the offset of the plain from the top left of the block face in pixels.
     * @param {[number, number]} plains[].size [U, V] array defining the size of the plain, extending from the top left in pixels.
     * @param {string} [plains[].name] Custom name to easily identify this plain when it is selected.
     */
    constructor(...plains) {
        this.plains = plains;
    }
    /**
     * @param {Object} selection
     * @param {Direction} selection.face
     * @param {import("@minecraft/server").Vector3} selection.faceLocation
     *
     * @param {Object} [options]
     * @param {boolean} [options.invertU] Horizontal axis extends `right -> left` rather than `left -> right` if true.
     * @param {boolean} [options.invertV] Vertical axis extends `bottom -> top` rather than `top -> bottom` if true.
     *
     * @returns Selected plain ID, or plain index if an ID is not provided. If no plains apply to the selection, `undefined` is returned.
     */
    getSelected(selection, options) {
        const { face, faceLocation } = selection;

        // Create a new object so the original is not mutated
        let location = { ...faceLocation };

        const horizontalAxis = face === Direction.East || face === Direction.West ? "z" : "x";
        const verticalAxis = face === Direction.Up || face === Direction.Down ? "z" : "y";

        if (face !== Direction.Down) location[verticalAxis] = 1 - location[verticalAxis];
        if (face !== Direction.South && face !== Direction.West)
            location[horizontalAxis] = 1 - location[horizontalAxis];

        if (options?.invertU) location[horizontalAxis] = 1 - location[horizontalAxis];
        if (options?.invertV) location[verticalAxis] = 1 - location[verticalAxis];

        for (let i = 0; i < this.plains.length; i++) {
            const plain = this.plains[i];

            const inHorizontalRange = isInRange(
                location[horizontalAxis],
                plain.origin[0] / 16,
                (plain.origin[0] + plain.size[0]) / 16
            );
            const inVerticalRange = isInRange(
                location[verticalAxis],
                plain.origin[1] / 16,
                (plain.origin[1] + plain.size[1]) / 16
            );

            if (inHorizontalRange && inVerticalRange) return plain.name ?? i;
        }
    }
}
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

Methods ​

  • constructor ​

    ts
    new FaceSelectionPlains(...plains: { origin: [number, number]; size: [number, number]; name?: string }[])
    1

    Creates a new FaceSelectionPlains instance.

    Parameters
    • plains: Object[]

      Array defining the 2D areas on a block's face which may be selected.

      • origin: [number, number]

        [U, V] array defining the offset of the plain from the top left of the block face.

      • size: [number, number]

        [U, V] array defining the size of the plain, extending from the top left.

      • name?: string

        Custom name to easily identify this plain when it is selected.

  • getSelected ​

    ts
    getSelected(selection: { face: Direction; faceLocation: Vector3 }, options?: { invertU?: boolean; invertV?: boolean }): number | string | undefined
    1

    Returns the involved plain's array index, or name if provided. If no plain is selected, undefined is returned.

    Parameters
    • selection: Object

      Object containing details about the selection.

      • face: Direction

        The selected face of the block.

      • faceLocation: Vector3

        Selection location relative to the bottom north-west corner of the block.

    • options?: Object

      Optionally configure how the selected plain is calculated.

      • invertU?: boolean

        Horizontal axis goes right -> left rather than left -> right if true.

      • invertV?: boolean

        Vertical axis goes bottom -> top rather than top -> bottom if true.

Usage ​

The below example would split the targeted block face into quarters:

BP/scripts/blocks/example.js
js
import { world } from "@minecraft/server";
import FaceSelectionPlains from "../utilities/face_selection_plains";

const quadrants = new FaceSelectionPlains(
    { origin: [0, 0], size: [8, 8] },
    { origin: [8, 0], size: [8, 8] },
    { origin: [0, 8], size: [8, 8] },
    { origin: [8, 8], size: [8, 8] }
);
1
2
3
4
5
6
7
8
9

Additionally, names can be provided to easily identify each plain:

js
const quadrants = new FaceSelectionPlains(
    { origin: [0, 0], size: [8, 8], name: "top_left" },
    { origin: [8, 0], size: [8, 8], name: "top_right" },
    { origin: [0, 8], size: [8, 8], name: "bottom_left" },
    { origin: [8, 8], size: [8, 8], name: "bottom_right" }
);
1
2
3
4
5
6

This could be used in a custom component to get the selected quadrant:

js
const BlockQuadrantInteractionComponent = {
    onPlayerInteract({ block, face, faceLocation }) {
        // Work around the faceLocation bug - get the location relative to the block
        const relativeFaceLocation = {
            x: faceLocation.x - block.location.x,
            y: faceLocation.y - block.location.y,
            z: faceLocation.z - block.location.z,
        };

        // Returns the selected area's index (0, 1, 2 or 3), or name if provided (e.g. "top_left").
        // If no plain was selected, `undefined` is retured.
        const selectedQuadrant = quadrants.getSelected({
            face,
            faceLocation: relativeFaceLocation,
        });

        world.sendMessage(`Quadrant ${selectedQuadrant} was selected!`);
    },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

SelectionBoxes Class ​

WARNING

As with the minecraft:selection_box and minecraft:collision_box components, do not use Blockbench's displayed position values when setting up your SelectionBoxes as they are measured from the north-west, rather than the north-east. Instead, use the origin value from the exported .geo.json file.

If you wish to use Blockbench's values, the invertX option should be set to true in getSelected.

This class allows you to define 3D areas in a block and get the box which the face selection lies within.

To use this method for precise interaction, create the file BP/scripts/utilities/selection_boxes.js and paste the below code into it.

SelectionBoxes Code
BP/scripts/utilities/selection_boxes.js
js
const isInRange = (value, min, max) => value >= min && value <= max;

export default class SelectionBoxes {
    /**
     * Allows you to define 3D areas in a block and get the box which the face selection lies within.
     *
     * @param {Object[]} boxes Array defining the 3D areas within a block which may be selected.
     * @param {[number, number, number]} boxes[].origin [X, Y, Z] array defining the offset of the box from the block's horizontal middle and vertical bottom in pixels, extending from the north-east.
     * @param {[number, number, number]} boxes[].size [X, Y, Z] array defining the size of the box in pixels, extending from the north-east.
     * @param {string} [boxes[].name] Custom name to easily identify this box when it is selected.
     */
    constructor(...boxes) {
        this.boxes = boxes;
    }
    /**
     * Get the box which the `faceLocation` lies within.
     *
     * @param {import("@minecraft/server").Vector3} faceLocation Selection location relative to the bottom north-west corner of the block.
     *
     * @param {Object} [options] Optionally configure how the selected box is calculated.
     * @param {boolean} [options.invertX] X axis extends `west -> east` rather than `east -> west` if true, following [Blockbench](https://blockbench.net)'s displayed positions.
     * @param {boolean} [options.invertY] Y axis extends `up -> down` rather than `down -> up` if true.
     * @param {boolean} [options.invertZ] Z axis extends `south -> north` rather than `north -> south` if true.
     *
     * @returns {(string|number|undefined)} Selected box name, or box index if a name is not provided. If no boxes apply to the selection, `undefined` is returned.
     */
    getSelected(faceLocation, options) {
        // Create a new object so the original is not mutated
        let location = { ...faceLocation };

        // X is inverted to ensure measurements are relative to the bottom north-east.
        if (!options?.invertX) location.x = 1 - location.x;
        if (options?.invertY) location.y = 1 - location.y;
        if (options?.invertZ) location.z = 1 - location.z;

        for (let i = 0; i < this.boxes.length; i++) {
            const box = this.boxes[i];

            const from = {
                x: box.origin[0] + 8,
                y: box.origin[1],
                z: box.origin[2] + 8,
            };
            const to = {
                x: from.x + box.size[0],
                y: from.y + box.size[1],
                z: from.z + box.size[2],
            };

            const inXRange = isInRange(location.x, from.x / 16, to.x / 16);
            const inYRange = isInRange(location.y, from.y / 16, to.y / 16);
            const inZRange = isInRange(location.z, from.z / 16, to.z / 16);

            if (inXRange && inYRange && inZRange) return box.name ?? i;
        }
    }
}
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

Methods ​

  • constructor ​

    ts
    new SelectionBoxes(...boxes: { origin: [number, number, number]; size: [number, number, number]; name?: string }[])
    1

    Creates a new SelectionBoxes instance.

    Parameters
    • boxes: Object[]

      Array defining the 3D areas within a block which may be selected.

      • origin: [number, number, number]

        [X, Y, Z] array defining the offset of the box from the block's horizontal middle and vertical bottom in pixels, extending from the north-east.

      • size: [number, number, number]

        [X, Y, Z] array defining the size of the box in pixels, extending from the north-east.br>

      • name?: string

        Custom name to easily identify this box when it is selected.

  • getSelected ​

    ts
    getSelected(faceLocation: Vector3, options?: { invertX?: boolean; invertY?: boolean; invertZ?: boolean }): number | string | undefined
    1

    Get the box which the faceLocation lies within.

    Returns the involved box's array index, or name if provided. If no box is selected, undefined is returned.

    Parameters
    • faceLocation: Vector3

      Selection location relative to the bottom north-west corner of the block.

    • options?: Object

      Optionally configure how the selected box is calculated.

      • invertX?: boolean

        X axis extends west -> east rather than east -> west if true, following Blockbench's displayed positions.

      • invertY?: boolean

        Y axis extends up -> down rather than down -> up if true.

      • invertZ?: boolean

        Z axis extends south -> north rather than north -> south if true.

Usage ​

The below example would split the targeted block into its vertical halves:

BP/scripts/blocks/example.js
js
import { world } from "@minecraft/server";
import SelectionBoxes from "../utilities/selection_boxes";

const verticalHalves = new SelectionBoxes(
    { origin: [-8, 8, -8], size: [16, 8, 16], name: "top" },
    { origin: [-8, 0, -8], size: [16, 8, 16], name: "bottom" }
);
1
2
3
4
5
6
7

This could be used with an itemUseOn after event to get the selected box:

js
world.afterEvents.itemUseOn.subscribe((e) => {
    // Do nothing if the targeted block is not "wiki:example_block"
    if (e.block.typeId !== "wiki:example_block") return;

    // Returns the selected vertical half ("top" or "bottom").
    const selectedVerticalHalf = verticalHalves.getSelected(e.faceLocation);

    world.sendMessage(`The ${selectedVerticalHalf} of the block was selected!`);
});
1
2
3
4
5
6
7
8
9

Pigeonholes Example ​

Using our FaceSelectionPlains class, we can create a block which functions similarly to a Chiselled Bookshelf. Other assets (textures etc.) are included in the example pack.

Interacting with paper will fill the selected slot. Destroying the block releases all of the stored paper items.

Pigeonholes Showcase

Download Pigeonholes Model
Block JSON
BP/blocks/pigeonholes.json
json
{
    "format_version": "1.21.70",
    "minecraft:block": {
        "description": {
            "identifier": "wiki:pigeonholes",
            "menu_category": {
                "category": "items"
            },
            "states": {
                "wiki:slot_0_occupied": [false, true],
                "wiki:slot_1_occupied": [false, true],
                "wiki:slot_2_occupied": [false, true],
                "wiki:slot_3_occupied": [false, true],
                "wiki:slot_4_occupied": [false, true],
                "wiki:slot_5_occupied": [false, true]
            },
            "traits": {
                "minecraft:placement_direction": {
                    "enabled_states": ["minecraft:cardinal_direction"],
                    "y_rotation_offset": 180
                }
            }
        },
        "components": {
            "minecraft:custom_components": ["wiki:pigeonholes_storage"],
            "minecraft:destructible_by_mining": {
                "seconds_to_destroy": 1.5
            },
            "minecraft:geometry": {
                "identifier": "geometry.pigeonholes",
                "culling": "wiki:pigeonholes_culling",
                "bone_visibility": {
                    // Display each slot as empty/occupied
                    "empty_slot_0": "!q.block_state('wiki:slot_0_occupied')",
                    "empty_slot_1": "!q.block_state('wiki:slot_1_occupied')",
                    "empty_slot_2": "!q.block_state('wiki:slot_2_occupied')",
                    "empty_slot_3": "!q.block_state('wiki:slot_3_occupied')",
                    "empty_slot_4": "!q.block_state('wiki:slot_4_occupied')",
                    "empty_slot_5": "!q.block_state('wiki:slot_5_occupied')",
                    "occupied_slot_0": "q.block_state('wiki:slot_0_occupied')",
                    "occupied_slot_1": "q.block_state('wiki:slot_1_occupied')",
                    "occupied_slot_2": "q.block_state('wiki:slot_2_occupied')",
                    "occupied_slot_3": "q.block_state('wiki:slot_3_occupied')",
                    "occupied_slot_4": "q.block_state('wiki:slot_4_occupied')",
                    "occupied_slot_5": "q.block_state('wiki:slot_5_occupied')"
                }
            },
            "minecraft:material_instances": {
                "*": {
                    "texture": "stripped_bamboo_block_top"
                },
                // Material instances defined in model:
                "side": {
                    "texture": "stripped_bamboo_block"
                },
                "empty_slot": {
                    "texture": "wiki:pigeonholes_empty"
                },
                "occupied_slot": {
                    "texture": "wiki:pigeonholes_occupied"
                }
            }
        },
        "permutations": [
            // Facing north
            {
                "condition": "q.block_state('minecraft:cardinal_direction') == 'north'",
                "components": {
                    "minecraft:transformation": { "rotation": [0, 0, 0] }
                }
            },
            // Facing west
            {
                "condition": "q.block_state('minecraft:cardinal_direction') == 'west'",
                "components": {
                    "minecraft:transformation": { "rotation": [0, 90, 0] }
                }
            },
            // Facing south
            {
                "condition": "q.block_state('minecraft:cardinal_direction') == 'south'",
                "components": {
                    "minecraft:transformation": { "rotation": [0, 180, 0] }
                }
            },
            // Facing east
            {
                "condition": "q.block_state('minecraft:cardinal_direction') == 'east'",
                "components": {
                    "minecraft:transformation": { "rotation": [0, -90, 0] }
                }
            }
        ]
    }
}
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
Precise Interaction Script
BP/scripts/blocks/pigeonholes.js
js
import { world, EquipmentSlot, GameMode, ItemStack } from "@minecraft/server";
import FaceSelectionPlains from "../utilities/face_selection_plains"; // Import the FaceSelectionPlains class to use it

// Slot bounds
const slots = new FaceSelectionPlains(
    { origin: [0, 0], size: [6, 8] },
    { origin: [6, 0], size: [5, 8] },
    { origin: [11, 0], size: [5, 8] },
    { origin: [0, 8], size: [6, 8] },
    { origin: [6, 8], size: [5, 8] },
    { origin: [11, 8], size: [5, 8] }
);

const isFrontFace = (block, face) =>
    block.permutation.getState("minecraft:cardinal_direction") === face.toLowerCase();

const isSlotOccupied = (block, slot) => block.permutation.getState(`wiki:slot_${slot}_occupied`);

const occupySlot = (block, slot) =>
    block.setPermutation(block.permutation.withState(`wiki:slot_${slot}_occupied`, true));

const emptySlot = (block, slot) =>
    block.setPermutation(block.permutation.withState(`wiki:slot_${slot}_occupied`, false));

function handleInteract({ block, face, faceLocation, dimension, player }) {
    if (!player || !isFrontFace(block, face)) return;

    const equippable = player.getComponent("minecraft:equippable");
    if (!equippable) return;

    const relativeFaceLocation = {
        x: faceLocation.x - block.location.x,
        y: faceLocation.y - block.location.y,
        z: faceLocation.z - block.location.z,
    };

    const selectedSlot = slots.getSelected({ face, faceLocation: relativeFaceLocation });
    if (selectedSlot === undefined) return;

    const mainhand = equippable.getEquipmentSlot(EquipmentSlot.Mainhand);
    const isHoldingPaper = mainhand.hasItem() && mainhand.typeId === "minecraft:paper";

    if (isHoldingPaper && !isSlotOccupied(block, selectedSlot)) {
        if (player.getGameMode() !== GameMode.creative) {
            if (mainhand.amount > 1) mainhand.amount--;
            else mainhand.setItem(undefined);
        }

        occupySlot(block, selectedSlot);
        dimension.playSound("insert.chiseled_bookshelf", block.center());
    } else if (isSlotOccupied(block, selectedSlot)) {
        emptySlot(block, selectedSlot);

        const itemLocation = { ...faceLocation };
        itemLocation.y -= 0.5;
        dimension.spawnItem(new ItemStack("minecraft:paper"), itemLocation).clearVelocity();

        dimension.playSound("pickup.chiseled_bookshelf", block.center());
    }
}

// ------------------------------
//  Release paper on destruction
// ------------------------------
function releasePaper({ block, destroyedBlockPermutation, dimension }) {
    const states = destroyedBlockPermutation.getAllStates();

    for (const state in states) {
        const value = states[state];
        const isPaper = value === true;

        if (!isPaper) continue;

        dimension.spawnItem(new ItemStack("minecraft:paper"), block.center());
    }
}

/** @type {import("@minecraft/server").BlockCustomComponent} */
const BlockPigeonholesStorageComponent = {
    onPlayerInteract: handleInteract,
    onPlayerDestroy: releasePaper,
};

world.beforeEvents.worldInitialize.subscribe(({ blockComponentRegistry }) => {
    blockComponentRegistry.registerCustomComponent(
        "wiki:pigeonholes_storage",
        BlockPigeonholesStorageComponent
    );
});
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

Double Flower Pot Example ​

Using our SelectionBoxes class, the player can interact with each pot separately. The following files are the basis for implementing a new Double Flower Pot block, other assets (textures etc.) are included in the example pack.

Note: The pots in this example only support the planting of dandelions and cacti for simplicity - you could expand this further yourself.

Double Flower Pot Showcase

Download Double Flower Pot Model
Block JSON
BP/blocks/double_flower_pot.json
json
{
    "format_version": "1.21.70",
    "minecraft:block": {
        "description": {
            "identifier": "wiki:double_flower_pot",
            "menu_category": {
                "category": "items"
            },
            "states": {
                "wiki:pot_0_plant": ["none", "dandelion", "cactus"],
                "wiki:pot_1_plant": ["none", "dandelion", "cactus"]
            },
            "traits": {
                "minecraft:placement_direction": {
                    "enabled_states": ["minecraft:cardinal_direction"]
                }
            }
        },
        "components": {
            "minecraft:custom_components": ["wiki:double_flower_pot"],
            "minecraft:collision_box": {
                "origin": [-7, 0, -3],
                "size": [14, 6, 6]
            },
            // This must cover all boxes in the precise interaction script
            "minecraft:selection_box": {
                "origin": [-7, 0, -3],
                "size": [14, 6, 6]
            },
            "minecraft:geometry": {
                "identifier": "geometry.double_flower_pot",
                // Conditionally display plants in their pots
                "bone_visibility": {
                    "dandelion_0": "q.block_state('wiki:pot_0_plant') == 'dandelion'",
                    "dandelion_1": "q.block_state('wiki:pot_1_plant') == 'dandelion'",
                    "cactus_0": "q.block_state('wiki:pot_0_plant') == 'cactus'",
                    "cactus_1": "q.block_state('wiki:pot_1_plant') == 'cactus'"
                }
            },
            "minecraft:material_instances": {
                "*": {
                    "texture": "flower_pot",
                    "render_method": "alpha_test",
                    "ambient_occlusion": false
                },
                // Material instances defined in model:
                "dirt": {
                    "texture": "wiki:double_flower_pot_dirt", // Apply a darker tint to the dirt texture to replicate vanilla potted dirt
                    "render_method": "alpha_test",
                    "ambient_occlusion": false
                },
                "handle": {
                    "texture": "wiki:double_flower_pot_handle",
                    "render_method": "alpha_test"
                },
                "dandelion": {
                    "texture": "yellow_flower",
                    "render_method": "alpha_test",
                    "face_dimming": false,
                    "ambient_occlusion": false
                },
                "cactus_side": {
                    "texture": "cactus_side",
                    "render_method": "alpha_test"
                },
                "cactus_top": {
                    "texture": "cactus_top",
                    "render_method": "alpha_test"
                }
            }
        },
        "permutations": [
            {
                "condition": "q.block_state('minecraft:cardinal_direction') == 'west' || q.block_state('minecraft:cardinal_direction') == 'east'",
                "components": {
                    "minecraft:transformation": { "rotation": [0, 90, 0] } // Front of model facing east
                }
            }
        ]
    }
}
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
71
72
73
74
75
76
77
78
79
80
81
Precise Interaction Script
BP/scripts/blocks/double_flower_pot.js
js
import { world, ItemStack } from "@minecraft/server";
import SelectionBoxes from "../utilities/selection_boxes"; // Import the SelectionBoxes class to use it

// Support orientation along both horizontal axes
const pots = {
    x: new SelectionBoxes(
        { origin: [-7, 0, -3], size: [6, 6, 6] },
        { origin: [1, 0, -3], size: [6, 6, 6] }
    ),
    z: new SelectionBoxes(
        { origin: [-3, 0, -7], size: [6, 6, 6] },
        { origin: [-3, 0, 1], size: [6, 6, 6] }
    ),
};

// The state value and sound associated with each plant
const plants = {
    "minecraft:yellow_flower": {
        value: "dandelion",
        sound: "dig.grass",
    },
    "minecraft:cactus": {
        value: "cactus",
        sound: "dig.cloth",
    },
};

// Get the selected pot for the appropriate axis
const getSelectedPot = (e) =>
    pots[e.block.permutation.getState("wiki:axis")].getSelected(e.faceLocation);

const isPotOccupied = (block, pot) =>
    block.permutation.getState(`wiki:pot_${pot}_plant`) !== "none";

const setPotPlant = (block, pot, plant) =>
    block.setPermutation(block.permutation.withState(`wiki:pot_${pot}_plant`, plant));

// Our flower pots even have sound effects!
const playPlantSound = (dimension, location, sound) =>
    dimension.runCommand(`playsound ${sound} @a ${location.x} ${location.y} ${location.z} 0.5`);

// If a pot is not selected (the inbetween area is targeted) or is already filled, the item use is cancelled.
world.beforeEvents.itemUseOn.subscribe((e) => {
    if (e.block.typeId !== "wiki:double_flower_pot" || !plants[e.itemStack.typeId]) return;

    const selectedPot = getSelectedPot(e);

    if (selectedPot === undefined || isPotOccupied(e.block, selectedPot)) e.cancel = true;
});

// -------------------------------
//    Plant in the selected pot
// -------------------------------
world.afterEvents.itemUseOn.subscribe((e) => {
    if (
        e.block.typeId !== "wiki:double_flower_pot" ||
        !plants[e.itemStack.typeId] ||
        e.source.isSneaking
    )
        return;

    const selectedPot = getSelectedPot(e);
    const plant = plants[e.itemStack.typeId];

    setPotPlant(e.block, selectedPot, plant.value);
    playPlantSound(e.block.dimension, e.block.location, plant.sound);
});

// -------------------------------
//  Release plants on destruction
// -------------------------------
function releasePlants(e) {
    const states = (e.brokenBlockPermutation ?? e.explodedBlockPermutation).getAllStates();

    // Array of plant state values e.g. ["cactus", "dandelion"]
    const storedPlants = Object.entries(states)
        .filter(([state, value]) => state.startsWith("wiki:pot") && value !== "none")
        .map(([state, value]) => value);

    if (storedPlants.length === 0) return;

    // Centre loot in block
    const lootLocation = {
        x: e.block.location.x + 0.5,
        y: e.block.location.y + 0.5,
        z: e.block.location.z + 0.5,
    };

    // Create an item entity for every potted plant
    for (const plant of storedPlants) {
        const plantId = Object.keys(plants).find((key) => plants[key].value === plant);

        e.dimension.spawnItem(new ItemStack(plantId), lootLocation);
        playPlantSound(e.dimension, e.block.location, plants[plantId].sound);
    }
}

world.afterEvents.playerBreakBlock.subscribe((e) => {
    if (e.brokenBlockPermutation.type.id === "wiki:double_flower_pot") releasePlants(e);
});
world.afterEvents.blockExplode.subscribe((e) => {
    if (e.explodedBlockPermutation.type.id === "wiki:double_flower_pot") releasePlants(e);
});
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

Importing Scripts ​

Don't forget to import your scripts into your pack's entry file!

BP/manifest.json
json
{
    "modules": [
        {
            "type": "script",
            "language": "javascript",
            "entry": "index.js", // Your defined entry file
            "uuid": "...",
            "version": "1.0.0"
        }
    ],
    "dependencies": [
        {
            "module_name": "@minecraft/server",
            "version": "1.15.0"
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BP/scripts/index.js
js
// Import your precise interaction scripts here...
import "./blocks/pigeonholes";
import "./blocks/double_flower_pot";
1
2
3

Download Example Pack ​

Template pack made according to this tutorial, adding the Pigeonholes and Double Flower Pot blocks into the Items tab.

Download MCADDON

If you require extra assistance with precise interaction, feel free to ask in the Bedrock Add-Ons Discord! Remember to include a link to this page in your question, as the classes provided here are not built into Minecraft.

贡献者

编辑 Precise Interaction

本页面上的文本和图像内容根据 知识共享署名 4.0 国际许可协议

本页中的代码示例根据 MIT 许可证

Bedrock Wiki by Bedrock OSS ,Translate by 8aka-Team

"Minecraft"是Mojang AB的注册商标。

Bedrock OSS、Bedrock Wiki以及 bedrock.dev 与Microsoft及Mojang AB不存在任何隶属关系。

  • 隐私政策
  • 加入QQ社区
  • 参与贡献指南
  • 访问代码仓库