Skip to main content

Crafting

A recipe is an asset. Crafting is one call. If the output does not fit, nothing is consumed.

Recipes come in two kinds, and a third shape of crafting — cooking over time — lives in Cooking and processing.

KindThe question it answersWhere the inputs come from
Shapelessdo you HAVE these things?anywhere in one container
Shapedhave you ARRANGED these things?specific cells of a grid container

One asset covers both: they share an output, a transaction and a place in a station's recipe list, and differ only in how the inputs are found. Kind defaults to Shapeless, so a recipe authored before shapes existed keeps its meaning.

The recipe​

UEDCraftingRecipe is a data asset:

FieldMeaning
KindShapeless or Shaped. Decides which of the two input fields below is read.
Ingredients(Shapeless) Items and counts.
Pattern(Shaped) The arrangement — see below.
OutputAn item and a count.
Required TagsOptional gate — a station, a learned skill.

Every item here is an FEDItemRef — an item-definition picker in the details panel, an asset id underneath — so a recipe holds no hard references and costs nothing to keep loaded. It is the same reference type a loot entry, a vendor offer and a prerequisite use.

Crafting​

// Can it be crafted right now?
const bool bPossible = UEDCraftingStatics::CanCraft(Container, Recipe);

// Do it. Safe from a client: on the authority it runs now, otherwise it travels to the server.
UEDCraftingStatics::RequestCraft(Container, Recipe);
Can Craft      (Container, Recipe)  → Boolean
Request Craft (Container, Recipe)

RequestCraft runs one FEDCraftTransaction: ingredients consumed and output inserted in a single atomic step. If the output will not fit, the whole thing rolls back and the ingredients are still there. There is no state in which a player has paid and received nothing.

Shaped recipes​

Set Kind to Shaped and fill Pattern: a row-major Pattern Width × Pattern Height block where an unset cell means must be empty and every filled cell carries its own count. The recipe is matched against a container that uses a Grid layout — so a crafting table is a container with a shape, and it reuses the same grid widget, the same drag-and-drop and the same replication as any other panel.

FEDShapedCraftMatch Match;

// One recipe: does the grid currently hold this pattern?
const bool bMatches = UEDCraftingStatics::MatchGrid(Grid, Recipe, Match);

// A list: which one, if any? (What a table's result slot previews.)
UEDCraftingRecipe* Found = UEDCraftingStatics::FindGridRecipe(Grid, Recipes, Match);

// Take it: consumes the pattern from the grid, puts the output where you say.
UEDCraftingStatics::RequestCraftFromGrid(Grid, Found, OutputContainer);
Match Grid             (Grid, Recipe)                  → Boolean, Match
Find Grid Recipe (Grid, Recipes) → Recipe, Match
Request Craft From Grid (Grid, Recipe, Output Container)
Get Grid Size (Grid) → Boolean, Size

Matching is strict about the things players notice:

  • The pattern slides. It is trimmed to its own bounding box first, so a 1×2 recipe laid in the corner of a 3×3 grid is the same recipe as one laid in the middle.
  • Leftovers reject the match. Anything in the grid the pattern does not account for means this is not that recipe — otherwise a stray item in a corner would silently vanish into the craft.
  • Mirroring is on by default (Allow Mirrored). Handedness is rarely meaningful, and a player who lays a tool out the other way round has not made a different thing. Turn it off per recipe.

The transaction re-matches on the server before spending anything: what the client says its grid contains is a hint, never the authority.

The result slot​

The classic layout is a board, an arrow and a result slot — and the result slot is not a container slot at all. Make it a button wearing the output's icon: nothing can be dropped INTO a result, and taking from it IS the craft. Preview with FindGridRecipe on the grid's change event, and call RequestCraftFromGrid when it is clicked.

A recipe list that stays honest​

CanCraft reads live container contents, so a UI can poll it — but the better pattern is to bind the container's change event and refresh:

Container->OnInventoryChanged.AddDynamic(this, &UMyCraftingPanel::HandleInventoryChanged);

Each row then shows its ingredients with have-counts, tinted by availability, and greys its button when CanCraft is false. The demo's UEDCraftingPanelWidget does exactly this and is worth reading as a worked example.

Stations​

The framework has no station concept — a station is an actor in your game that decides which recipes to offer.

The usual shape: an actor holds a recipe list and an interaction opens a panel bound to the player's own container. Crafting consumes from the player's inventory, not from the station, because the container passed to RequestCraft is the one that pays.

To gate recipes by station, put a tag on the station and filter the list before showing it, or use the recipe's Required Tags.

Which container pays​

Whichever one you pass. That is a real decision:

  • Pass the main grid and crafting spends only what is carried there.
  • Pass a container that spans more, or check the inventory first, if you want it to spend from anywhere.

The framework does not assume, because "can I craft from my bank?" is a design question with no universal answer.

Where next​