Skip to content

Entity display: name, colour, portrait

EntityDisplay resolves a speaker once — and colour is user data, so a game asks whether there is one rather than assuming.

All documentation — Playing a story
On this page

An entity reference on a step arrives already looked up. EntityDisplay is the one place that turns it into the three things a game actually draws: a name, a colour, and a portrait.

SpeakerLabel.cs
using TaleShard.Runtime.Engine;
using TaleShard.Unity;
using UnityEngine;
namespace TaleShard.Docs.Samples
{
public static class SpeakerLabel
{
public static string Describe(ResolvedActor actor)
{
// The one place that decides what a speaker looks like, so a game never reads the raw
// entity fields for this.
EntityDisplay display = EntityDisplay.Of(actor);
// Colour is user data: an author may leave it unset, and HasColor is how you find out.
// ColorOr takes your own fallback rather than inventing one.
Color tint = display.ColorOr(Color.white);
return display.Name + " " + UnityColors.ToHex(tint);
}
}
}

Use it, and UnityColors for the hex conversion, rather than reading the entity’s raw fields. Every entity carries a small set of intrinsics — name, code, colour, voice, description, portrait — under reserved keys, and those are what make a shipped preset possible: a binding to a custom field code binds one that may exist in no project, while a binding to an intrinsic works everywhere.

$voice is the code of the voice this character is cast to, and it is absent when they are uncast. It is what answers which voice speaks this line with no naming convention: the speaker is already resolved per step, so the voice for a line is the speaker’s — which is why a voice clip on a step carries that code rather than making you look it up. $description and $portrait are absent the same way, when nothing was written and when no picture is set.

Colour is user data

Inventing a colour is worse than it sounds. A game that substitutes its own accent for an unset entity colour will show two characters in the same colour and imply a relationship the author did not author.

Contrast

The display carries the outcome of a readability check alongside the colour, so a game can find out that an author’s colour would be unreadable on its background before drawing it — rather than discovering it in a screenshot. What to do about it is the game’s decision: darken it, outline it, or use the fallback. The plugin reports, and does not silently correct, because silently correcting would mean the colour on screen is not the colour the author picked and nothing says so.

ReadableColorOr is the common decision made for you: the author’s colour exactly, or your fallback when text in it cannot be read on the background you name (its APCA contrast is under the floor, 30 by default). It never produces a shade the author did not pick.

ReadableNameColour.cs
using TaleShard.Runtime.Engine;
using TaleShard.Unity;
using UnityEngine;
namespace TaleShard.Docs.Samples
{
public static class ReadableNameColour
{
// Measure against the lightest panel the name is drawn on: a colour that clears it clears
// every darker one. The author's colour is used exactly, or not at all.
public static Color For(ResolvedEntity speaker, Color houseColour) =>
EntityDisplay.Of(speaker).ReadableColorOr("#f2efe8", houseColour);
}
}

Description and the other intrinsics

Every entity has a few fields whatever its definition says: its name, code, colour, voice, description and portrait. They are in ResolvedEntity.Intrinsics, under the keys EntityIntrinsics names ($name, $description, …), and not in Fields, which holds only what the definition declares — including what it inherits from its parent and what its traits add, a trait’s field named with the trait’s prefix (stats.armor), the same name a script writes. An intrinsic the author left empty is absent, not blank.

EntityDescription.cs
using TaleShard.Runtime.Engine;
namespace TaleShard.Docs.Samples
{
public static class EntityDescription
{
// The description is an intrinsic, not one of the definition's fields, so it is not in
// entity.Fields. Null means the author wrote none: draw nothing.
public static string? Describe(ResolvedEntity entity)
{
ResolvedField? description = EntityIntrinsics.DescriptionOf(entity);
return description?.Text();
}
}
}

An entity that is an input action

A project can declare its input actions as records — Crouch, Interact — whose definition says a mention of them draws as a keycap. EntityDisplay.IsAction tells you an entity is one, and its Code is the name your game maps to its own input action; the record’s Name is the action in words, translated like any name.

ToMentionLook() marks an @-mention of such a record as an action and leaves the glyph to you, so a line that says Hold @Crouch can draw the button the player has bound right now. The hook that adds it, and how a glyph is treated, are on drawing a line of text.

Portraits

The portrait arrives as a media reference, not as a loaded asset. Loading is yours, and that is deliberate — when and whether to load is a game’s decision, and a plugin that loaded eagerly would make it for you at the worst possible moment.

What this page does not yet prove

The integration layer that this type lives in is used throughout the reference game, so the API is adopted rather than theoretical. But the current build of that layer has not been run end to end since its most recent round of changes — it is verified by compilation and by its own tests, not by a playthrough. Treat the shapes here as reliable and the runtime behaviour as not yet independently confirmed.

Next

Implementing commands — how a story makes your game do something.

Written for
Unity developers
Last updated