| name | define_new_block |
| description | Defines a new block using BlockBuilder. Use this when asked to add a new block type to the game. Also provides guidance on textures. |
The main block API is in perovskite_game_api/src/blocks.rs. Blocks are defined using BlockBuilder and registered via game_builder.add_block(...).
Basic Structure
let built_block = builder.add_block(
BlockBuilder::new(BLOCK_NAME)
.set_cube_single_texture(SOME_TEXTURE),
)?;
BlockBuilder::new takes a BlockName (typically a StaticBlockName constant). The returned BuiltBlock contains the block ID and associated item ID.
Textures
Textures are identified by name strings. Two types exist:
StaticTextureName — a Copy newtype over &'static str. Use this for compile-time constant names (the common case).
OwnedTextureName — a newtype over String. Use this when the name is computed at runtime (e.g., programmatically generated color names, or names built from a dynamic prefix).
Both implement the TextureName trait and can be passed anywhere a texture is expected.
Declaring and registering a texture from a file
The typical pattern, taken from basic_blocks.rs:
pub const DIRT_TEXTURE: StaticTextureName = StaticTextureName("default:dirt");
include_texture_bytes!(game_builder, DIRT_TEXTURE, "textures/dirt.png")?;
include_texture_bytes! is a convenience macro that calls game_builder.register_texture_bytes(tex_name, include_bytes!(file_name)). The file path is resolved relative to the current source file (same semantics as include_bytes!).
Using OwnedTextureName for dynamic names
When the texture name can't be a 'static string — e.g., it's assembled at runtime from a variant value or a config string — use OwnedTextureName:
let tex = OwnedTextureName(format!("myplugin:lamp_{}", color));
game_builder.register_texture_bytes(&tex, &png_bytes)?;
StaticTextureName converts into OwnedTextureName via From, so you can .into() freely.
CSS color placeholder
When no texture asset is available yet, generate a solid-color placeholder without any image file:
OwnedTextureName::from_css_color("#ff00ff")
OwnedTextureName::from_css_color("rgb(120 120 120)")
OwnedTextureName::from_css_color("orange")
Appearance
Important policy: This project welcomes LLM-generated code, but does not permit ML-generated textures or media assets. If the necessary texture is unavailable, use either an existing texture,
a placeholder generated using a command-line imagemagick call or similar, or a CSS color using OwnedTextureName::from_css_color("#ff00ff"). Do not call a diffusion model or other AI image generator under any circumstances.
A common fallback pattern for placeholder textures is a bright magenta, or inverted version of an existing texture.
Pick exactly one appearance method:
Cube (simple, same texture all faces)
.set_cube_single_texture(TEXTURE)
Cube (per-face textures)
.set_cube_appearance(
CubeAppearanceBuilder::new()
.set_individual_textures(left, right, top, bottom, front, back)
.set_needs_transparency()
.set_needs_translucency()
.set_rotate_laterally()
)
Plant-like (crossed-planes, like grass/flowers)
.set_plant_like_appearance(
PlantLikeAppearanceBuilder::new()
.set_texture(TEXTURE)
.set_wave_effect_scale(0.5)
.set_is_solid(false)
)
Plant-like blocks typically also need .set_allow_light_propagation(true) and .set_allow_weather_propagation(true).
Axis-aligned boxes (custom geometry)
.set_axis_aligned_boxes_appearance(
AxisAlignedBoxesAppearanceBuilder::new()
.add_box(
AaBoxProperties::new_single_tex(TEXTURE, TextureCropping::AutoCrop, RotationMode::RotateHorizontally),
(-0.5, 0.5),
(-0.5, 0.5),
(-0.5, 0.5),
)
.add_box_with_variant_mask(box_props, x, y, z, variant_mask)
)
AaBoxProperties constructors:
new_single_tex(texture, crop_mode, rotation_mode) — same texture all faces
new(left, right, top, bottom, front, back, crop_mode, rotation_mode) — per-face
new_custom_usage(...) — control is_visible, is_colliding, is_tool_hitbox independently
new_plantlike(texture, rotation_mode) — crossed-plane appearance within a box
As of 2026, due to LLM limitations in spatial reasoning, consider generating placeholder boxes and asking the user to refine them iteratively while testing in-game.
Item and Display
.set_display_name("Human Readable Name")
.set_inventory_texture(TEXTURE)
.set_item_sort_key("namespace:category:name")
.add_item_group(some_item_group)
Block Groups and Diggability
Block groups control tool effectiveness and other game logic:
.add_block_group(block_groups::STONE)
.add_block_group(block_groups::FIBROUS)
.add_block_groups([GROUP_A, GROUP_B])
.set_not_diggable()
.set_wear_multiplier(2.0)
set_matter_type(MatterType::...) also adds the corresponding block group automatically:
MatterType::Solid (default)
MatterType::Liquid — also call .set_liquid_flow(Some(duration)) if you need automatic flow.
MatterType::Gas
Physics / Behavior
.set_allow_light_propagation(true)
.set_allow_weather_propagation(true)
.set_light_emission(4)
.set_falls_down(true)
.set_trivially_replaceable(true)
.set_liquid_flow(Some(Duration::from_secs(1)))
.set_footstep_sound(Some(sound_key))
Blocks used for map generation: important
If a block will be used for map generation (e.g. it is a new raw material), take care to include the following
details that help some autobuild heuristics:
- If the block is a soft material that isn't likely in high-value builds (e.g. dirt, sand), add the
NATURAL_GROUND block group.
- If the block is a material we could expect in high-value builds (stone, desert stone, wood, etc), add the
NATURAL_AND_STRUCTURAL block group.
- Call
set_track_placer() on the block builder, to enable some additional metadata used for detecting who placed a block.
Blocks listed under NATURAL_GROUND but not NATURAL_AND_STRUCTURAL will be automatically overwritten by autobuilds if encountered - the logic being
that players placing dirt and similar are either terraforming the contour, which autobuild should treat as ground - or are making a temporary
scaffold. On the other hand, NATURAL_AND_STRUCTURAL blocks are less likely as terraforms, and more likely as parts of high-value player creations.
Dropped Items
BlockBuilder automatically defines an item corresponding to the block. Placing that item
will place the block at the given location.
By default the block drops that automatically-generated item. Override with:
.set_simple_dropped_item(ITEM_NAME.0, count)
.set_no_drops()
.set_dropped_item(DroppedItem::...)
.set_dropped_item_closure_extended(|param| (ITEM_NAME, count))
For example, dirt-with-grass has a corresponding dirt-with-grass item available to creative-mode players, but because digging it destroys the grass, it drops a normal dirt block.
Interactions
.add_interact_key_menu_entry("internal_name", "Display Label")
Advanced
LOD (Far Geometry) Color
This is an optional override. If unset, the block's texture will be automatically analyzed.
.override_lod_colors(0xffrrggbb_top, 0xffrrggbb_side, orientation_bias)
.set_lod_orientation_bias(0.5)
Extended Data and Variants
These require additional logic to produce gameplay effects.
.set_extended_data_initializer(Box::new(|ctx, coord, tool, | {
}))
.set_extra_variant_func(Box::new(|ctx, coord, tool, _| {
}))
Consult the advanced_block_features skill document.
add_modifier and add_item_modifier (Escape Hatches)
These are placeholders for functionality not yet exposed as dedicated builder methods. They run just before registration and provide direct access to the underlying structs.
.add_modifier(|block: &mut BlockType| {
block.interact_key_handler = Some(Box::new(move |ctx, coord, menu_entry| { ... }));
block.dig_handler_full = Some(Box::new(move |ctx, coord, tool| { ... }));
block.tap_handler_full = Some(Box::new(move |ctx, coord, tool| { ... }));
block.step_on_handler_full = Some(Box::new(move |coord, player| { ... }));
block.client_info.physics_info = Some(PhysicsInfo::Air(Empty {}));
})
.add_item_modifier(|item: &mut Item| {
item.place_handler = Some(Box::new(move |ctx, coord, anchor, tool| { ... }));
})
Common fields set via add_modifier:
block.interact_key_handler — called when player presses interact key
block.dig_handler_{full, inline} — called when block is dug
block.tap_handler_{full, inline} — called on a tap/hit
block.step_on_handler — called when a player steps on the block
block.client_info.physics_info — override physics (e.g., PhysicsInfo::Air for pass-through)
If writing any handlers, consult the advanced_block_features skill document first.
Worked Examples
Simple stone-like block
BlockBuilder::new(MY_BLOCK)
.add_block_group(block_groups::STONE)
.set_display_name("My Block")
.set_cube_single_texture(MY_BLOCK_TEX)
Leaves (transparent, light-propagating)
BlockBuilder::new(MY_LEAVES)
.add_block_group(block_groups::FIBROUS)
.add_block_group(block_groups::TREE_LEAVES)
.set_cube_appearance(
CubeAppearanceBuilder::new()
.set_single_texture(MY_LEAVES_TEX)
.set_needs_transparency(),
)
.set_allow_light_propagation(true)
.set_allow_weather_propagation(true)
Flower (plant-like, passable)
BlockBuilder::new(MY_FLOWER)
.set_plant_like_appearance(
PlantLikeAppearanceBuilder::new().set_texture(MY_FLOWER_TEX),
)
.set_display_name("My Flower")
.set_inventory_texture(MY_FLOWER_TEX)
.set_allow_light_propagation(true)
.set_allow_weather_propagation(true)
.add_modifier(|block: &mut BlockType| {
block.client_info.physics_info = Some(PhysicsInfo::Air(Empty {}));
})
Glowing block
BlockBuilder::new(MY_LAMP)
.set_cube_single_texture(MY_LAMP_TEX)
.set_light_emission(15)
.set_display_name("My Lamp")
Interactive block with axis-aligned boxes
BlockBuilder::new(MY_DEVICE)
.set_axis_aligned_boxes_appearance(
AxisAlignedBoxesAppearanceBuilder::new()
.add_box(
AaBoxProperties::new_single_tex(MY_TEX, TextureCropping::NoCrop, RotationMode::RotateHorizontally),
(-0.5, 0.5), (-0.5, 0.0), (-0.5, 0.5),
),
)
.set_allow_light_propagation(true)
.set_allow_weather_propagation(true)
.set_display_name("My Device")
.add_interact_key_menu_entry("", "Use")
.add_modifier(|block: &mut BlockType| {
block.interact_key_handler = Some(Box::new(move |ctx, coord, _| {
Ok(None)
}));
})