Extending: the API for code users
You don't need code for the main jobs. This page is for when you want to drive building from your own controller,
inventory, save system or rules. Everything is in the RomanoAssets.RuntimeBuildingKit namespace, with full source.
The pieces of the system #
| Type | What it is |
|---|---|
PieceDefinition |
An asset describing one buildable piece: model, footprint, sockets, placement settings, cost, upgrade. Made by Make Buildable. |
PieceCatalog |
The pieces a world allows (and that saves and multiplayer can name). |
BuildWorld |
One per scene: placed pieces (Registry), rules (Validator), costs (ResourceProvider), Sandbox, Permission, saving. |
Builder |
On the player: aims, shows the preview, and sends requests. Tools: Place, Remove, Move, Upgrade. |
BuildRequest / BuildResult |
Every change is a request checked by the world's Authority (single player: LocalBuildAuthority; multiplayer: the host). |
BuildPiece |
The component on every placed piece: its definition, instance ID, socket links, who placed it. |
Drive the Builder from your own controller #
The Builder only needs a ray. With Update Automatically on it aims from Aim Origin (usually the camera) each frame; turn it off to aim yourself, for example from the mouse in a top-down game:
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
builder.Refresh(ray); // preview and reason, no change yet
if (clicked) builder.Confirm(ray); // sends the request; true when it was accepted
Other calls: SelectSlot(i), SelectPiece(definition), SetHotbar(pieces), Rotate(steps), SetYaw(degrees),
AdjustHeight(steps), SetTool(BuilderTool.Remove), Undo(), Redo(), Cancel(). Set ReachFrom to the character
when the camera is far from it (third person, top down): reach and the host's position check use it.
Events (UnityEvents, so you can also wire them in the Inspector): selectionChanged, previewChanged
(PlacementResult: valid or not, and the reasons), placed, rejected, applied (BuildResult with what was
charged and refunded), toolChanged.
Costs from your own inventory #
Implement IResourceProvider and assign it to the Build World (Resource Provider, or world.ResourceProvider in
code). Amounts are per player ID (always 0 in single player).
// "Bag" stands for your own inventory.
public sealed class MyInventory : MonoBehaviour, IResourceProvider
{
public int GetAmount(uint playerId, ResourceType resource) => Bag.Count(resource.name);
public bool TrySpend(uint playerId, IReadOnlyList<ResourceAmount> amounts) => Bag.TryRemoveAll(amounts);
public void Give(uint playerId, IReadOnlyList<ResourceAmount> amounts) => Bag.AddAll(amounts);
}
TrySpend must take all or nothing. Only the authority charges; the preview just reads GetAmount to show
"Needs 4 Wood (you have 2)". world.Sandbox = true builds for free.
Your own placement rule #
Add an IPlacementRule to the world's validator. The preview and the authority both run it, so the player sees your
reason before clicking, and the host checks it in multiplayer.
public sealed class NoBuildingUnderWater : IPlacementRule
{
public float WaterLevel = 0f;
public void Evaluate(PlacementContext context, PlacementResult result)
{
if (context.Pose.position.y < WaterLevel)
result.Add(PlacementReasonCode.Custom, "Can't build under water");
}
}
// Once, for example in Awake:
world.Validator.Rules.Add(new NoBuildingUnderWater());
PlacementContext has the piece, its pose and scale, the surface hit, the snap target, the requester and the cost.
Your own HUD and menu #
The Builder only raises events; the kit's Build Presentation turns them into words (from the Text Table), sounds and
effects, and its Build HUD draws them. To use your own UI, implement IBuildHudView (or use the Build Presentation's
events) and IBuildMenu, and turn the Build HUD's parts off in the theme. Details and examples:
Make it yours.
Who may change what #
IBuildPermission.CanChange(requesterId, piece, kind) returns null when allowed, or the reason. The included
Owner Only Build Permission lets players change only their own pieces (the host may change all). Assign yours to
world.Permission. In multiplayer, implement IBuildPermissionSync too so clients' previews give the host's reasons.
Saving somewhere else #
world.Save("slot") / world.Load("slot") use JSON files by default. To save to the cloud, a database or your own
save system, implement ISaveBackend (Write, TryRead, Exists, Delete) and pass it:
world.Save("slot", myBackend). Or take the data yourself: BuildSaveData data = world.CaptureSave(),
data.ToJson(), and later BuildSaveData.TryFromJson(json, out data, out error) and world.LoadSave(data). The
BuildLoadReport says how many pieces loaded and which definitions were missing.
Requests without the Builder #
For AI builders, scripted events or your own UI, send requests to the authority directly:
world.Authority.Completed += result => Debug.Log(result.Status + " " + result.Placement.Summary);
world.Authority.Submit(BuildRequest.Remove(piece, playerPosition, requesterId: 0));
BuildRequest.FromCandidate, Move, Remove, Upgrade, Undo and Redo make the requests. A candidate comes from
PlacementSolver.Solve (the same solver the Builder uses).
Watching the world #
world.Registry lists placed pieces and raises Registered, Unregistered and Changed. Each BuildPiece has
Definition, InstanceId, PlacedBy, Connections and LinkedInstanceId(socketId).
Multiplayer with another networking library #
NetworkedBuildAuthority does the host's work over any transport: give it an IBuildMessenger that sends byte
messages between host and clients, and set its Players to an IPlayerLocator for the position check. The Netcode
for GameObjects adapter (NetcodeBuildAuthority) is a short example of both. See Multiplayer.