Views
Actions
Difference between revisions of "BlockStates/1.17"
(Copy BlockStates to MC1.17 archive) |
(No difference)
|
Latest revision as of 05:49, 6 December 2021
Introduction of States
The block state system abstracts out the details of the block's properties from the other behaviors of the block.
Each property
of a block is described by an instance of Property<?>
. Examples of block properties include instruments (Property<NoteBlockInstrument>
), direction (Property<Direction>
), poweredness (Property<Boolean>
), etc. Each property has the value of the type T
parametrized by Property<T>
.
A unique triple can be constructed from the Block
, the set of Property<?>
, and the set of values for those properties. This unique triple is called a BlockState
. For example, a stone button which is facing east and is powered or held down is represented by minecraft:stone_button[facing=east,powered=true]
.
Proper Usage of Block States
BlockState
is a flexible and powerful system, but it also has limitations. BlockState
s are immutable, and all combinations of their properties are generated on startup of the game. This means that having a BlockState
with many properties and possible values will slow down the loading of the game, and befuddle anyone trying to make sense of your block logic.
Not all blocks and situations require the usage of BlockState
; only the most basic properties of a block should be put into one, and any other situation is better off with having a BlockEntity
or being a separate Block
. Always consider if you actually need to use a state for your purposes.
Tip
An example is making chair blocks: the direction of the chair should be a property, while the different types of wood should be separated into different blocks.
An "Oak Chair" facing east (oak_chair[facing=east]
) is different from a "Spruce Chair" facing west (spruce_chair[facing=west]
).
Implementing Block States
In your Block class, create or reference static final
Property<?>
objects for every property that your Block has. You are free to make your own Property<?>
implementations, but the means to do that are left as an exercise to the reader. The vanilla code provides several convenience implementations:
IntegerProperty
- Implements
Property<Integer>
. Defines a property that holds an integer value. - Created by calling
IntegerProperty.create(String propertyName, int minimum, int maximum)
.
- Implements
BooleanProperty
- Implements
Property<Boolean>
. Defines a property that holds atrue
orfalse
value. - Created by calling
BooleanProperty.create(String propertyName)
.
- Implements
EnumProperty<E extends Enum<E>>
- Implements
Property<E>
. Defines a property that can take on the values of an Enum class. - Created by calling
EnumProperty.create(String propertyName, Class<E> enumClass)
. - It is also possible to use only a subset of the Enum values (e.g.
RailShape
s that can only ascend and not turn). See the overloads ofEnumProperty.create
.
- Implements
DirectionProperty
- This is a convenience implementation of
EnumProperty<Direction>
- Several convenience predicates are also provided. For example, to get a property that represents the cardinal directions, call
DirectionProperty.create("<name>", Direction.Plane.HORIZONTAL)
; to get the X directions,DirectionProperty.create("<name>", Direction.Axis.X)
- This is a convenience implementation of
The class BlockStateProperties
contains shared vanilla properties which should be used or referenced whenever possible, in place of creating your own properties.
When you have your desired Property<>
objects, override Block#createBlockStateDefinition(StateDefinition$Builder)
in your Block class. In that method, call StateDefinition$Builder#add(...);
with the parameters as every Property<?>
you wish the block to have.
Every block will also have a "default" state that is automatically chosen for you. You can change this "default" state by calling the Block#registerDefaultState(BlockState)
method from your constructor. When your block is placed it will become this "default" state. An example from DoorBlock
:
this.registerDefaultState( this.stateDefinition.any() .setValue(FACING, Direction.NORTH) .setValue(OPEN, false) .setValue(HINGE, DoorHingeSide.LEFT) .setValue(POWERED, false) .setValue(HALF, DoubleBlockHalf.LOWER) );
If you wish to change what BlockState
is used when placing your block, you can overwrite Block#getStateForPlacement(BlockPlaceContext)
. This can be used to, for example, set the direction of your block depending on where the player is standing when they place it.
Because BlockState
s are immutable, and all combinations of their properties are generated on startup of the game, calling BlockState#setValue(Property<T>, T)
will simply go to the Block
's StateHolder
and request the BlockState
with the set of values you want.
Because all possible BlockState
s are generated at startup, you are free and encouraged to use the reference equality operator (==
) to check if two BlockState
s are equal.
Using BlockStates
You can get the value of a property by calling BlockState#getValue(Property<?>)
, passing it the property you want to get the value of.
If you want to get a BlockState
with a different set of values, simply call BlockState#setValue(Property<T>, T)
with the property and its value.
You can get and place BlockState
s in the level using Level#setBlockAndUpdate(BlockPos, BlockState)
and Level#getBlockState(BlockState)
. If you are placing a Block
, call Block#defaultBlockState()
to get the "default" state, and use subsequent calls to BlockState#setValue(Property<T>, T)
as stated above to achieve the desired state.