Choices, and why one is locked
Taking a choice, and drawing an option the player can see but cannot take — with the reason the engine already wrote.
All documentation — Playing a story
Start here
Concepts
Authoring for a build
Playing a story
The generated assets and code
The plugin and sync
On this page
When a step is awaiting a choice, Get Choices holds one resolved choice per exit the player can see. Some of them may not be takeable, and drawing a locked option well is most of what makes a requirement feel like part of the game rather than a bug.
#include "TaleShardChoiceLibrary.h"#include "TaleShardStepLibrary.h"#include "TaleShardStepTypes.h"
namespace TaleShardDocSamples{ // Describes each offered choice, and why a locked one is locked. TArray<FString> DescribeChoices(const FTaleShardPlayStep& Step) { TArray<FString> Lines;
for (const FTaleShardResolvedChoice& Choice : UTaleShardStepLibrary::GetChoices(Step)) { // One rule for takeable, already applied: enabled, and pointing somewhere. FString Line = Choice.bIsTakeable ? TEXT("[ ] ") : TEXT("[x] ");
// The destination lends a choice its text. The rule is already applied upstream, so // read the content rather than deriving which fields supply it. Line += UTaleShardChoiceLibrary::GetPlainText(Choice).ToString(); if (Choice.bVisited) { Line += TEXT(" (seen)"); } Lines.Add(Line);
// A list, because a choice can be refused by its own gate AND by requirement rows // on the node it leads to. Empty means takeable. Each entry is already a sentence: // the author's own when they wrote one, otherwise what the player needs beside what // they hold. for (const FText& Reason : UTaleShardChoiceLibrary::GetBlockedBy(Choice)) { Lines.Add(TEXT(" ") + Reason.ToString()); } }
return Lines; }}Taking one
Three verbs, and the difference is what they key on:
| Verb | Keys on | When to use it |
|---|---|---|
| Choose | the choice struct itself | the button holds the choice it was built from. Survives a re-draw |
| Choose By Id | the choice’s Id | the button holds a string |
| Choose At | the index into Get Choices | positional, so it goes stale: a re-draw rebuilds the list, and a gate whose value changed can drop an option out of it. Prefer Choose anywhere the index is not read in the same frame it was produced |
A choice that is drawn but cannot be taken is refused, with a reason, and nothing changes. A Choose while the story is not awaiting a choice at all is ignored silently — that is a mistimed press, not an error.
Identity is Choice Id, not Port Id. A handle legitimately carries several edges — two replies out of one outgoing handle are two edges reporting the same port — so keying your buttons by port id collapses them, and Choose By Id refuses a port id by name.
Is Takeable is the one rule, and it is already applied: enabled, and pointing somewhere. Do not compose your own from the parts.
What a choice carries
| Pin | What it holds |
|---|---|
| Choice Id | this choice’s own identity, unique within the step |
| Port Id | which handle this exit leaves by. Not unique within a step |
| Kind | Exit for a way out of the node on screen, Board Entry when the path ended and this is one of the board’s remaining ways in |
| Is Takeable | can the player actually take this. Grey the option out when false |
| Enabled | every gate and requirement passed, or there were none |
| Visited | the player has taken this exit before |
| Has Target, Target Node Id, Target Board Id | where it leads. Has Target is false for a dangling exit: a port with no edge, or an edge to a missing node |
| Has Picture Key, Picture Key | the key of the choice’s face field on the step — a media or entity reference to draw the option as a tile with. Has Picture Key is false for a choice with no picture, which is every choice on a node whose options are not repeater rows carrying a picture field |
Its label is its content — the resolved fields its destination lends it — read through the TaleShard Choice library’s Get Content and Get Plain Text. That is why the plugin’s dialogue screen reads the content rather than deriving which fields supply the text.
Drawing a choice as a tile
When a node’s options are the rows of a repeater and the row’s block carries a picture — a media field, or an entity whose portrait is the picture — each choice names that field as its face. Get Picture Key (or the Picture Key pin) gives the field’s key; pass it to the step’s Find Field By Key, and the resolved field carries the reference. Resolving the reference to a texture is yours, through the same media and entity reads every other field uses — see Media and Entity display. The key is in the same key space as the choice’s content, so a field found this way and a field from Get Content are the same kind of handle.
Why an option is locked
| Read | What it gives |
|---|---|
| Get Blocked By | every reason this choice is drawn but not takeable, one sentence each, in the engine’s order: the exit’s own gate first, then the destination’s requirements. Empty for a choice the player can take |
| Get Block Reason | why the player cannot take this, as one text for a tooltip: the author’s own sentence when they wrote one, otherwise what the player needs and what they hold, otherwise the requirement’s category |
It is a list because a choice can be refused twice at once: by its own gate, and by a requirement
row on its destination. The sentences are already localised through the story’s own language and
already carry the progress reading — 6/8 rather than a bare “locked” — so a Blueprint draws them
rather than assembling them.
Where a requirement actually lives
The author writes a requirement once, on the destination — “you need STRENGTH 8 to get in here” — and the runtime surfaces it on every choice that leads there. That is why a check survives an edge being deleted and redrawn, and why it travels with copy and paste.
Two consequences worth knowing:
- A row whose policy is
hidenever reaches you. The option is not in Get Choices at all. - An authored jump target bypasses requirements. An override of the graph overrides this too.
Next
One handler per node type — where a node type’s own logic goes.