Table of Contents

Entity Text (Screen-Space)

A gallery of everything EntityTextComponent can do, one feature per pole: anchoring, shadows, backgrounds, scaling, rotation, opacity, distance fading, several texts on one entity, and HUD text pinned to window corners that survives resizing. Screen-space text keeps its pixel size at any distance and is never hidden by geometry.

The Program.cs file shows how to:

  • The text renderer registers itself with the first label
  • Centring a label over an object with Anchor, not TextAlignment
  • Shadow and background for readability over a 3D scene
  • Scale as the cheap way to resize, instead of FontSize
  • Distance fading with FadeStartDistance and MaxDistance
  • Several EntityTextComponents on one entity
  • HUD text that survives window resizing: TextPositionMode.Anchored

Entity Text (Screen-Space)

View on GitHub.

using Stride.CommunityToolkit.Bepu;
using Stride.CommunityToolkit.Engine;
using Stride.CommunityToolkit.Rendering;
using Stride.CommunityToolkit.Rendering.ProceduralModels;
using Stride.CommunityToolkit.Rendering.Text;
using Stride.CommunityToolkit.Skyboxes;
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Graphics;

// A gallery of everything EntityTextComponent can do. Each pole on the ground carries a label whose
// text describes the settings that produced it, so the scene is its own documentation.
//
// EntityTextComponent is SCREEN-SPACE text: the entity's position is projected to a point on the
// screen and flat pixels are drawn there. The text is always the same size however far away the
// entity is, and it is never hidden by geometry. For text that lives inside the scene - shrinking
// with distance, blocked by walls - see E03_3D_WorldText.

using var game = new Game();

game.Run(new SceneSetup3D { Resizable = true, DpiAware = true }.UseBepu().UseSkybox(), start: Start);

void Start(Scene scene)
{
    // --- Row 1: placement ----------------------------------------------------------------------

    AddStation(scene, new Vector3(-4.5f, 0, -2), "Plain label\n(all defaults)");

    AddStation(scene, new Vector3(-1.5f, 0, -2), "Anchor = BottomCenter\nOffset = (0, -12)", label =>
    {
        // Centres the label above its pole. Anchor does the centring, not Alignment, which only
        // arranges the lines of a multi-line block relative to each other.
        label.Anchor = Anchor.BottomCenter;
        label.Offset = new Vector2(0, -12);
    });

    AddStation(scene, new Vector3(1.5f, 0, -2), "FontSize = 26", label => label.FontSize = 26);

    AddStation(scene, new Vector3(4.5f, 0, -2), "Scale = 1.6\n(cheaper to animate\nthan FontSize)", label =>
    {
        // Scale resizes without re-rasterising glyphs, so animate it rather than FontSize
        label.Scale = 1.6f;
    });

    // --- Row 2: readability --------------------------------------------------------------------

    AddStation(scene, new Vector3(-4.5f, 0, 1), "EnableShadow = true", label => label.EnableShadow = true);
    AddStation(scene, new Vector3(-1.5f, 0, 1), "EnableBackground = true", label => label.EnableBackground = true);

    AddStation(scene, new Vector3(1.5f, 0, 1), "TextColor = Orange\nOpacity = 0.55", label =>
    {
        label.TextColor = Color.Orange;
        label.Opacity = 0.55f;
    });

    AddStation(scene, new Vector3(4.5f, 0, 1), "Rotation = 0.35 rad", label => label.Rotation = 0.35f);

    // --- Row 3: distance -----------------------------------------------------------------------

    AddStation(scene, new Vector3(-4.5f, 0, 4), "FadeStartDistance = 8\nMaxDistance = 14\n(walk backwards!)", label =>
    {
        // World-positioned text can fade with camera distance; past MaxDistance it stops drawing
        label.EnableShadow = true;
        label.FadeStartDistance = 8;
        label.MaxDistance = 14;
    });

    // Two components on one entity: a name and a subtitle. LayerDepth decides who wins overlaps.
    var titled = AddStation(scene, new Vector3(-1.5f, 0, 4), "Two components,\none entity", label =>
    {
        label.Anchor = Anchor.BottomCenter;
        label.Offset = new Vector2(0, -26);
        label.EnableShadow = true;
    });

    titled.Add(new EntityTextComponent
    {
        Text = "(the subtitle)",
        FontSize = 12,
        TextColor = Color.LightGray,
        Anchor = Anchor.BottomCenter,
        Offset = new Vector2(0, -12),
        EnableShadow = true,
    });

    // --- HUD: not attached to anything in the scene --------------------------------------------

    // Anchored and Screen modes ignore the entity's position, so a HUD entity can sit anywhere
    var hud = new Entity("Hud")
    {
        new EntityTextComponent
        {
            Text = "PositionMode = Anchored, TopLeft",
            PositionMode = TextPositionMode.Anchored,
            ScreenAnchor = Anchor.TopLeft,
            Offset = new Vector2(16, 16),
            EnableShadow = true,
        },
        new EntityTextComponent
        {
            Text = "Anchored, BottomRight\n(resize the window - I stay put)",
            PositionMode = TextPositionMode.Anchored,
            ScreenAnchor = Anchor.BottomRight,
            Offset = new Vector2(16, 40),
            EnableShadow = true,
        },
        new EntityTextComponent
        {
            Text = "PositionMode = Screen at (16, 300)",
            PositionMode = TextPositionMode.Screen,
            ScreenPosition = new Vector2(16, 300),
            TextColor = Color.LightGreen,
            EnableShadow = true,
        },
    };

    hud.Scene = scene;
}

// A pole carrying a label, which configure adjusts. Returns the pole so a station can add more
// components to it.
Entity AddStation(Scene scene, Vector3 position, string text, Action<EntityTextComponent>? configure = null)
{
    var pole = game.CreatePrimitive(Primitive3D.Cylinder(0.15f, 1f), new PrimitiveEntityOptions
    {
        Body = BodyMode.None,
        EntityName = "Pole",
        Position = position + new Vector3(0, 0.5f, 0),
    });

    var label = new EntityTextComponent
    {
        Text = text,
        FontSize = 14,
        Alignment = TextAlignment.Center,
    };

    configure?.Invoke(label);
    pole.Add(label);

    pole.Scene = scene;

    return pole;
}