Starting a session
The TaleShard Player component, what it needs, the two ways to get a session, and the verbs that drive one.
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
A session is the one object a game holds to play a story. It is not a widget and ships none: bind its On Step Presented event, draw what it hands you, and call its verbs when the player acts. There are two ways to get one, and the difference is who owns it.
The component route
Add a TaleShard Player component to an actor and fill in its Details panel:
| Property | What it is |
|---|---|
| Payload | the story to play, a payload asset written by the sync |
| Policy Asset | what this host shows the player — empty for the defaults: nothing silenced, nothing forced, a path that ends ends |
| Command Handler Class | where the game’s command logic lives; one is created for the session and asked to register its commands |
| Node Handlers | one handler class per node type the game reacts to, in any order |
| Play On Attach | call Play as soon as a story is attached |
| Start Board, Start Node | where to begin, or empty for the author’s own start. Start Board picks from the project’s boards under TaleShard.Board |
| Start Language | the language a session is created in: a language code, or None — see language. Get Language reads the one the story plays in now |
| Follow Game Language | on by default. With no Start Language set, play in the game’s current language when the project has it, and switch when the game’s language changes |
The component owns its session for as long as the actor lives and releases it when the actor ends play. Every event of the session is re-raised on the component, and every verb and read of the session is forwarded from it under the same name — so a Blueprint that drives a story from the actor never touches the session object directly, and it can bind before a story exists.
#pragma once
#include "CoreMinimal.h"#include "GameFramework/Actor.h"#include "TaleShardStepTypes.h"#include "DocStoryActor.generated.h"
class UTaleShardPlayerComponent;class UTaleShardSession;
// An actor that plays a story: the component owns the session, the actor draws what it presents.UCLASS()class ADocStoryActor : public AActor{ GENERATED_BODY()
public: ADocStoryActor();
protected: virtual void BeginPlay() override;
private: UFUNCTION() void HandleStepPresented(UTaleShardSession* Session, const FTaleShardPlayStep& Step);
UPROPERTY(VisibleAnywhere, Category = "TaleShard") TObjectPtr<UTaleShardPlayerComponent> Player;};#include "DocStoryActor.h"
#include "Support/DocFictions.h"#include "TaleShardPlayerComponent.h"#include "TaleShardStepLibrary.h"
ADocStoryActor::ADocStoryActor(){ // The payload, the policy asset, the command handler class and the node handlers are set // on the component in the Details panel. Play On Attach is off here only so that this // actor can bind before the first step; with it on, the component plays the moment the // actor begins play. Player = CreateDefaultSubobject<UTaleShardPlayerComponent>(TEXT("Player")); Player->bPlayOnAttach = false;}
void ADocStoryActor::BeginPlay(){ Super::BeginPlay();
// Bind to the component, not to the session: these bindings can be made before a story // exists, which removes the ordering trap of waiting for the session to be built. Player->OnStepPresented.AddDynamic(this, &ADocStoryActor::HandleStepPresented);
// Play the author's start, or name a board and node. Every verb answers false with a // reason instead of failing quietly. FText Reason; if (!Player->Play(FGameplayTag(), NAME_None, Reason)) { FDocGameLog::Note(Reason.ToString()); }}
void ADocStoryActor::HandleStepPresented(UTaleShardSession* Session, const FTaleShardPlayStep& Step){ // Presented, not Fields: the engine has already decided what belongs on screen. FDocSpeechBubble::Show(UTaleShardStepLibrary::GetPlainText(Step).ToString());}FDocGameLog and FDocSpeechBubble are not plugin API. They stand in for your own game code,
and they are declared in a shim header on this site so that is visible rather than implied.
The code route
A session can also be built without a component, from the TaleShard game-instance subsystem:
| Function | What it does |
|---|---|
| Create Session | builds a session over a payload asset, with a policy and a language |
| Create Session From Directory | reads the payload from a sync output directory — the entry point that needs no asset and no generated code |
| End Session | stops and releases a session built here |
| Get Default Session | the first session built here that has not been ended |
A session built this way is owned by whoever built it. A component can still take it over with Adopt, which is how a story that outlives a scene gets a new presenter: build the session on the subsystem, adopt it from each actor that draws it, and let Release drop the actor’s hold without stopping the story.
Both routes take a policy at construction, and it freezes there. One policy setting can only be honoured at that moment: the one that asks the engine to offer a board’s remaining ways in when a path ends. The engine takes it as a construction argument and never re-reads it, which is why a policy cannot be swapped on a live session.
The verbs
Every verb answers true when it ran and false with a reason when it was refused, and a refusal is also reported on the diagnostics channel. A mistimed press is different: Advance while nothing is presenting, or Choose while nothing is awaited, is ignored silently, because a player mashing Continue during a fade is normal.
| Verb | What it does |
|---|---|
| Play | begin at the author’s start, or at the named board and node. Not a restart — it clears history, taken choices and said lines but leaves every variable where the previous run left it |
| Restart | begin again from the beginning, variables and all: every variable back to its default, the overlay and visit counts cleared, the random generator re-seeded |
| Advance | move on from a step the player has read |
| Choose, Choose By Id, Choose At | take one of the current step’s choices — see choices |
| Lapse | the offered moment’s countdown ran out: take the first flow exit that is not one of the options |
| Back, Forward | roll one moment back, variables included, and re-take it. Backing out of the first moment ends the playthrough with the rewound reason, and Forward takes it back in |
| Stop | end the playthrough. Always works, including mid-command, and every in-flight command token goes stale |
| Redraw | re-present the current step because something it depended on has resolved. Nothing is re-traversed and no script re-runs |
| Set Language | read the story in another language without restarting it |
| Next Line | ask a node for its next line — a bark the world triggers — without presenting a step or moving the story; see node handlers |
| Save, Restore | the whole playthrough, out and back — see saving |
| Save Story to Slot, Load Story from Slot | on the component, and on the TaleShard Story actor: Save and a save slot, or a save slot and Restore, in one node each |
The events
| Event | When |
|---|---|
| On Step Presented | a moment the player sees. Fires for every step, before any node handler runs |
| On Error Raised | the engine could not do what the data asked and carried on. Not the end of the playthrough; with nothing bound, an error goes to the log |
| On Ended | the playthrough is over, however it ended. Raised exactly once per playthrough, with the reason: end of path, stopped, error, or rewound |
| On Language Changed | every drawn step has already been re-drawn in the new language |
| On Diagnostic | everything the engine reports, at every level, with its location. Always on the game thread |
| On Command | a command the story called that nothing registered a handler for. On the session and on the command handler only — the component does not re-raise it, so a Blueprint built on the component answers it in its command handler’s On Command |
The one ending On Ended does not announce is a playthrough abandoned by starting another: Restart replaces the story without the old one ever leaving a playing state.
Five things that are easy to get wrong
- Play is not Restart. Play keeps the world; Restart throws it away. A “new game” button that calls Play plays the new game with the old game’s variables.
- Start at the author’s start node, not just the board. Leaving Start Node empty does this. Naming only a board falls back to that board’s derived way in, which is a root with an outgoing edge — a real board is full of isolated strays and every one of them is a root.
- A transfer node produces no step. A node that carries the playthrough into another board is traversal, not presentation, so it looks like nothing happened. That is correct.
- Read the step’s Awaiting rather than guessing from the verb. Advance on a step awaiting a choice does nothing, and so does Choose on a step awaiting an advance.
- Back past the first step still rewinds the variables. The state as of before the first step is captured, so navigation and rollback cannot disagree.
Language
A host deals in language codes. The engine’s storage key stops at this boundary: a project whose
master language is English keys that content under a default key rather than under en, so the
engine’s language is that key while the player is reading English.
| You want | Use |
|---|---|
| the language the story is being read in | Get Language — the project’s code |
| to follow it in your own chrome | On Language Changed, which carries the code |
| to build a picker | Get Languages: a label to draw and a code to hand back |
| to change it | Set Language — every drawn step is re-resolved before it returns. In Blueprint its language pin is a dropdown of the project’s languages, filled by the last sync |
| the story to follow the game’s own language | nothing: the component’s Follow Game Language does it while its Start Language is None. It matches the game’s current culture in full first, pt-BR, then its language alone, pt, and plays the master language when the project has neither |
| to talk to the engine’s view directly | Get Language Key, the only read on the type that speaks storage spelling |
Get Language is empty when the project never named a master language. That is a real answer rather
than a gap to paper over — inventing en would put the wrong language on a story not written in it.
Next
Reading a step — what the engine hands you, and how to read it.