Blocks
The mod loader provides a way to add custom block types to the game. Note: In this document (and much of the mod loader) "block" and "block type" are used interchangeably in the context of modded blocks.
There is a step-by-step guide to creating blocks available as well.
Creating a Block
Create blocks using the
createblock
console command.
This will create a block XML file and set it up properly as described below.
Basics
Blocks are defined using an XML file containing a Block
element.
This file is referenced in the Blocks
section of the mod manifest.
The manifest reference looks like this:
<Mod>
....
<Blocks>
<Block path="SomeBlock.xml" />
</Blocks>
</Mod>
The path
attribute is relative to the mod's directory, i.e. the directory that contains
the Mod.xml file.
A template block XML file can be found at the bottom of this page. The same content is also
in the XML file created by createblock
.
For a full list and description of all the possible child elements of the Block
element,
see Block element.
ID
Each block in a mod must be identified by an integer ID using the
ID
element of Block
.
The ID must uniquely identify the block among all blocks in the same mod; the easiest way to handle this is to just use sequential IDs (1, 2, 3, ...).
It must only be unique in your mod though! The game automatically distinguishes between blocks of different mods.
Do note however, that changing this ID will break compatibility with machines that were saved using the old ID. Since the ID is internal and not user-facing, it is thus recommended to never change these IDs once the mod was first published.
Coordinate System
All child elements obviously take local coordinates, i.e. coordinates relative to the block's position and rotation. This local coordinate system is set up such that the Z axis always points "forward", i.e. away from the other block that the block itself was placed onto.
Adding Behaviour to Blocks
There are two ways of adding behaviour to modded blocks: By writing a custom BlockScript or by using pre-defined modules without writing any code.
These methods are not exclusive, a block can have both a custom script and modules attached.
BlockScript
The first step of using a custom BlockScript is loading custom code into the
game. Then declare the BlockScript using the Script
child element
of Block
.
<Block>
....
<Script>SomeMod.Blocks.SomeBlock</Script>
</Block>
In this example, SomeMod.Blocks.SomeBlock
is the fully qualified name of a class
that extends Modding.BlockScript
. This class must be defined in one of the assemblies
listed in the mod manifest.
For information on the APIs provided by the BlockScript
class and guidelines on writing
block behaviour, see below.
Modules
Modules are sets of predefined behaviour that can be added to blocks using only their XML file. There are several modules included in the game by default and mods can also add new modules that can be used by other mods.
See the Modules article for more information on how to use modules.
Guidelines on writing BlockScripts
For a full list of the properties and methods provided by BlockScript
, see
the API documentation.
Some general pointers on how to write BlockScript
s:
Call
AddKey
,AddSlider
, ... inSafeAwake
.They need to always be called on each game instance as well as for both simulation and building clones.
Use
SafeAwake
instead ofAwake
Be careful when overriding methods that are not explicitly marked as callbacks.
It is permissible to do so, however remember to call the corresponding base method, otherwise some of the other callbacks or convenience properties may stop working.
Additionally, when writing block scripts, special care needs to be taken when writing
"lifecycle" method. While the normal Unity Update()
, FixedUpdate()
, etc. methods are
available, their use is discouraged.
It is very important to think about what should be executed on what games instances and when, especially when writing a block that should also work in multiplayer.
In singleplayer, there is of course only one game instance that always handles all physics. This can be treated as a "host" instance.
In multiplayer, generally the host simulates all physics while the other instances (clients) don't and only receive simulation data from the host. The only exception is when a client is in local simulation mode. Then, only with respect to the blocks of that machine, that client acts as a host, while the regular host and all other clients act as clients.
As such, BlockScript
provides methods to differentiate between updates during building
mode and simulation mode. Simulation mode updates are also differentiated between updates
on the instance simulating physics for that block (host) and all other instances in MP.
This leads to the following methods:
BuildingUpdate
: Called everyUpdate()
during build mode, on all instances.SimulateUpdateHost
: Called everyUpdate
during simulation mode, on the instance acting as host for this block.SimulateUpdateClient
: Called everyUpdate()
during simulation mode, on instances acting as clients for this block.SimulateUpdateAlways
: Called everyUpdate()
during simulation mode, on all instances.
All of these functions also have FixedUpdate()
and LateUpdate()
versions.
Handling Key Emulation
More recent versions of Besiege include blocks that can emulate key input to activate other
blocks. Key emulation is run on a FixedUpdate
tick (though not necessarily every FixedUpdate
)
instead of during Update
s like normal key input to avoid timescale and framerate significantly
changing how machines behave.
For this reason, emulated key input needs to be explicitly handled in block code. If your block
could be used for mechanisms that are timing-sensitive, you should handle emulation in the
KeyEmulationUpdate
method as described below. If you are sure that exact timing of responses
to emulated key input is not relevant to your block, you can also check for emulated input in
the SimulateUpdateHost
method, like normal key input.
A very simple example for handling emulate input with accurate timing:
private MKey activateKey;
public override void SimulateUpdate() {
if (activateKey.IsPressed) OnActivateKeyPressed();
}
public override void KeyEmulationUpdateHost() {
if (activateKey.EmulationPressed()) OnActivateKeyPressed();
}
KeyEmulationUpdate
is called at a fixed tick rate, like FixedUpdate
, on all instances. (Make
sure to check SimPhysics
to decide whether to only apply visual effects or also physics etc. in
response to an emulated key press). It is not called at all if no blocks that can emulate any keys
are present on the machine.
For handling emulated key input, use the EmulationPressed()
, EmulationHeld()
, and
EmulationReleased()
methods of MKey
.
Make sure to handle intersecting normal key input and emulation input in a way that makes sense for
your block (e.g., the player is holding a key and an emulation block starts emulation of the same
key. This will produce an EmulationPressed()
event, but you have to decide how your block should
handle it.)
Emulating Keys
Modded blocks can also emulate keys themselves. This section explains the steps necessary to do so.
First, override the EmulatesAnyKeys
property in your behaviour (BlockScript
or
BlockModuleBehaviour
) to return true
, otherwise the emulation may not register correctly.
The AddEmulatorKey
method can be used like AddKey
to add a field for specifying a key to emulate
in the block mapper.
Lastly, during simulation, use the EmulateKeys
method to start or stop emulation of a specific
key. This should always be called during SendKeyEmulationUpdateHost
to support machines requiring
precise and consistent timing. (As the name suggests, this method is only called on the host.
EmulateKeys
does not work when called on any client instances not simulating physics.)
EmulateKeys
' first argument is the set of keys that your block itself responds to, to avoid any
infinite loops where the block activates itself. The second argument is the key to emulate, as
returned by AddEmulatorKey
. The last one specifies whether to toggle emulation on or off.
Note that since you toggle emulation on or off using EmulateKeys
, it is not necessary to call it
every frame. Only call it when the emulation state should actually be changed.
Skins
Modded blocks can also be included in skin packs, just like regular blocks.
The mechanism works almost exactly the same way, there is just one difference: To alleviate conflicts between mods, the skin for the block must be identified (in the skin folder name) not by its in-game name (like for normal blocks) but by a combination of the mod ID and the ID assigned to the block within the mod.
For example, next to the StartingBlock
, Wheel
, etc. folders in a skin pack, there can
also be a a0e993c0-952e-4516-b661-20e4329ae5b2-0
folder, for the block with ID 0 (-0
)
in the mod with ID a0e993c0-952e-4516-b661-20e4329ae5b2
.
This skin will then work exactly like for other blocks for players who have the mod installed but just ignored by the game if the mod is not installed.
Sample Block XML File
<Block>
<!-- Block definition file.
Optional elements are mostly out-commented.
Remember to insert appropriate values where specified,
the mod will not load correctly until you do.
Restart the game to load the block once this file is completed.
Values that should always be changed are marked with "TODO".
See the documentation for further information on any of these elements.
-->
<!-- Optional. Enables debug mode.
In debug mode, colliders and adding points are shown visually
to assist in positioning them correctly.
(Capsule colliders are shown as cubes, imagine their edges were rounded off.) -->
<Debug>True</Debug>
<!-- ID of your block. See "Note on IDs" in Mod.xml.
The ID must be unique among blocks in your mod.
It may conflict with blocks of other mods, the mod loader handles this.
The easiest way of assigning IDs is to use 1, 2, 3, etc.-->
<ID>%ID%</ID>
<!-- Name of the block, shown in the user interface. -->
<Name>%NAME%</Name>
<!-- TODO: Change the mass to something appropriate -->
<Mass>0.3</Mass>
<!-- Additional keywords that can be used to search for this block
in the search tab of the block bar.
Blocks can always be searched for by name and author,
additional keywords can be specified here. -->
<!--<SearchKeywords>
<Keyword>Some Keyword</Keyword>
</SearchKeywords>-->
<!-- Optional.
Only has an effect if the OnFlip method in the block script is not overriden.
Causes the Flipped property for the script to be set correctly. This is also used by
certain modules, like Spinning or Steering.
If an Arrow element is included, it is automatically flipped too. -->
<!-- <CanFlip>true</CanFlip> -->
<!-- Specify that this block is a replacement of an old modded block.
If this block has an equivalent that was created with the old community mod/block loader,
specifying its id here will make the game load this block when loading machines that contain the old block. -->
<!-- <Replaces>410</Replaces> -->
<!-- Normally, when a machine with a modded block is loaded, but that block is not loaded, the block will be ignored.
If the block has a fallback specified here, the fallback block is loaded instead in this scenario.
Valid values are entries of the BlockType enum or the numeric ID of a block. Only normal blocks can be specified as
fallback, not modded blocks. -->
<!--<Fallback>DoubleWoodenBlock</Fallback>-->
<!-- <Script>Full Name of a BlockScript class, optional.</Script> -->
<!-- Blocks can have certain predefined behaviour added without any custom code.
These behaviours are called modules.
The Shooting, Spewing, Spinning, and Steering modules are included by default
and mods can also add new modules.
Check the documentation for more information on how to use modules. -->
<!--<Modules>
</Modules>-->
<!-- Include to make block take damage. -->
<!-- <Health>20</Health> -->
<!-- Optional.
The game generates "stripped" versions of the prefab,
these have some components and child objects removed and are used in MP where the full
object is not always necessary.
If you find that this stripping removes some components or child objects that you added to the prefab manually
and need on the stripped version, you can include a list of objects to keep using this. -->
<!-- <KeepWhenStripped>
<Object>SomeObjectName</Object>
</KeepWhenStripped> -->
<!-- Include to enable block to burn.
The Trigger element is optional. -->
<!-- <FireInteraction burnDuration="5">
<SphereTrigger>
<Position x="0" y="0" z="0.61" />
<Radius>1.5</Radius>
</SphereTrigger>
</FireInteraction> -->
<!-- Include to make block freezable. -->
<!-- <IceInteraction /> -->
<!-- Optionally specify type of damage done to entities.
Can be one of "Blunt", "Sharp", "Fire" -->
<!-- <DamageType>Blunt</DamageType> -->
<Mesh name="<!-- TODO: Insert mesh resource name here. -->"> <!-- Must be defined as a resource in the manifest. -->
<!--<Position x="0.0" y="0.0" z="0.0" />
<Rotation x="0.0" y="0.0" z="0.0" />
<Scale x="1.0" y="1.0" z="1.0" /> -->
</Mesh>
<Texture name="<!-- TODO: Insert texture resource name here. -->" /> <!-- Must be defined as a resource in the manifest. -->
<Icon>
<Position x="0.0" y="0.0" z="0.0" />
<Rotation x="0.0" y="0.0" z="0.0" />
<Scale x="1.0" y="1.0" z="1.0" />
</Icon>
<!-- Including this causes a direction arrow, like the one on wheels and other turnable blocks,
to be displayed. The child elements define how and where it is displayed. -->
<!--<Arrow>
<Position x="0" y="0" z="0" />
<Rotation x="0" y="0" z="0" />
<Scale x="1" y="1" z="1" />
</Arrow>-->
<!-- Optional.
Both child elements are optional.
Hammer can be used to specify the position and rotation of the end of the nail at the start of the hammer animation.
Colliders can be used to specify a different set of colliders to use for the ghost.
If it is not present, the colliders of the normal block will be used.
It is also possible to specify ignoreForGhost attributes for some of the normal colliders to use the normal set of
colliders with a few of them removed on the ghost.
If the Colliders element here is present, all ignoreForGhost attributes are ignored. -->
<!-- <Ghost>
<Hammer>
<Position x="0" y="0" z="0.8" />
<Rotation x="0" y="0" z="0" />
</Hammer>
<Colliders>
<BoxCollider>
<Position x="0.0" y="0.0" z="0.0" />
<Rotation x="0.0" y="0.0" z="0.0" />
<Scale x="1.0" y="1.0" z="1.0" />
</BoxCollider>
</Colliders>
</Ghost> -->
<Colliders>
<!-- TODO: Insert Collider definitions here.
Examples: -->
<BoxCollider>
<Position x="0.0" y="0.0" z="0.0" />
<Rotation x="0.0" y="0.0" z="0.0" />
<Scale x="1.0" y="1.0" z="1.0" />
</BoxCollider>
<SphereCollider>
<Position x="0.0" y="0.0" z="0.0" />
<Radius>1.0</Radius>
</SphereCollider>
<CapsuleCollider>
<Position x="0.0" y="0.0" z="0.0" />
<Rotation x="0.0" y="0.0" z="0.0" />
<Capsule direction="X" radius="1.0" height="2.0" />
</CapsuleCollider>
</Colliders>
<BasePoint hasAddingPoint="false">
<Stickiness enabled="true" radius="0.6" />
<!-- Can only have motion if sticky -->
<Motion x="false" y="false" z="false" /> <!-- Optional -->
</BasePoint>
<AddingPoints>
<!-- TODO: Insert AddingPoint definitions here. Example:-->
<AddingPoint>
<Position x="0.0" y="0.0" z="0.0" />
<Rotation x="0.0" y="0.0" z="0.0" />
<Stickiness enabled="false" radius="0"/>
</AddingPoint>
</AddingPoints>
</Block>