Skip to content

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
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:

PropertyWhat it is
Payloadthe story to play, a payload asset written by the sync
Policy Assetwhat this host shows the player — empty for the defaults: nothing silenced, nothing forced, a path that ends ends
Command Handler Classwhere the game’s command logic lives; one is created for the session and asked to register its commands
Node Handlersone handler class per node type the game reacts to, in any order
Play On Attachcall Play as soon as a story is attached
Start Board, Start Nodewhere to begin, or empty for the author’s own start. Start Board picks from the project’s boards under TaleShard.Board
Start Languagethe 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 Languageon 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.

DocStoryActor.h
#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;
};
DocStoryActor.cpp
#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:

FunctionWhat it does
Create Sessionbuilds a session over a payload asset, with a policy and a language
Create Session From Directoryreads the payload from a sync output directory — the entry point that needs no asset and no generated code
End Sessionstops and releases a session built here
Get Default Sessionthe 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.

VerbWhat it does
Playbegin 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
Restartbegin again from the beginning, variables and all: every variable back to its default, the overlay and visit counts cleared, the random generator re-seeded
Advancemove on from a step the player has read
Choose, Choose By Id, Choose Attake one of the current step’s choices — see choices
Lapsethe offered moment’s countdown ran out: take the first flow exit that is not one of the options
Back, Forwardroll 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
Stopend the playthrough. Always works, including mid-command, and every in-flight command token goes stale
Redrawre-present the current step because something it depended on has resolved. Nothing is re-traversed and no script re-runs
Set Languageread the story in another language without restarting it
Next Lineask a node for its next line — a bark the world triggers — without presenting a step or moving the story; see node handlers
Save, Restorethe whole playthrough, out and back — see saving
Save Story to Slot, Load Story from Sloton 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

EventWhen
On Step Presenteda moment the player sees. Fires for every step, before any node handler runs
On Error Raisedthe 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 Endedthe playthrough is over, however it ended. Raised exactly once per playthrough, with the reason: end of path, stopped, error, or rewound
On Language Changedevery drawn step has already been re-drawn in the new language
On Diagnosticeverything the engine reports, at every level, with its location. Always on the game thread
On Commanda 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 wantUse
the language the story is being read inGet Language — the project’s code
to follow it in your own chromeOn Language Changed, which carries the code
to build a pickerGet Languages: a label to draw and a code to hand back
to change itSet 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 languagenothing: 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 directlyGet 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.

Written for
Unity developers
Last updated