Skip to content

DisplayKit Examples

davidsebesta edited this page Jun 14, 2026 · 12 revisions

Examples

Practical code examples for common DisplayKit use cases.

Table of Contents


Example 1: Simple Welcome Message

A simple global welcome message shown to all players.

In-Game Preview

Centered welcome message with shadow effect: Welcome Message

using System;
using DisplayKit.Elements;
using DisplayKit.Enums;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using MEC;
using UnityEngine;
using UnityEngine.UIElements;

public class WelcomeUI : CustomEventsHandler
{
    private DisplayCanvas _canvas;
    private DisplayText _welcomeText;

    /// <inheritdoc />
    public override void OnServerWaitingForPlayers()
    {
        _canvas = DisplayCanvas.Create();
        _canvas.DefaultVisibility = CanvasVisibility.Visible;

        _canvas.Flex.Grow = 1f;

        _welcomeText = _canvas.AddText("Welcome to the Server!");
        _welcomeText.Text.Font = FontType.RobotoBold;
        _welcomeText.Text.FontSize = 48f;
        _welcomeText.Text.Color = Color.white;
        _welcomeText.Text.Align = TextAnchor.MiddleCenter;
        _welcomeText.Text.TextShadow = new TextShadow
        {
            color = Color.black,
            offset = new Vector2(3, 3),
            blurRadius = 5f
        };

        // Center using flex on canvas
        _canvas.Align.AlignItems = Align.Center;
        _canvas.Align.JustifyContent = Justify.Center;
    }

    /// <inheritdoc />
    public override void OnPlayerJoined(PlayerJoinedEventArgs ev)
    {
        _canvas?.Spawn(ev.Player.Connection.connectionId);

        Timing.CallDelayed(7f, () =>
        {
            _canvas?.Hide(ev.Player.Connection.connectionId);
        });
    }

    /// <inheritdoc />
    public override void OnServerRoundEnded(RoundEndedEventArgs ev)
    {
        _canvas?.Destroy();
        _canvas = null;
    }
}

Example 2: Player Health Bar (Per-Player)

Individual health bars for each player.

In-Game Preview

Health Bar - Green Health bar at 100% (green)

Health Bar - Low Health Health bar at critical level (red)

using DisplayKit.Elements;
using DisplayKit.Enums;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.CustomHandlers;
using LabApi.Features.Wrappers;
using MEC;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class HealthUI : CustomEventsHandler
{
    /// <summary>
    /// Private class for handling data. Private just for the example simplicity.
    /// </summary>
    public class PlayerHealthData
    {
        /// <summary>
        /// Creates a new canvas for specified player.
        /// </summary>
        public static PlayerHealthData Create(Player player)
        {
            DisplayCanvas canvas = DisplayCanvas.Create();

            canvas.SortOrder = 100;
            canvas.Align.AlignItems = Align.Center;
            canvas.Position.Top = 20;
            canvas.Size.Width = Length.Percent(100); // height stays automatic

            DisplayText text = canvas.AddText($"{player.Health} / {player.MaxHealth}");
            text.Text.Font = FontType.RobotoBold;
            text.Text.FontSize = 18f;
            text.Text.Color = Color.white;

            DisplayElement background = canvas.AddElement();
            background.Position.Top = 10;
            background.Size.Width = 400f;
            background.Size.Height = 30f;
            background.Background.Color = new Color(0.2f, 0.2f, 0.2f);
            background.Border.Color = Color.white;
            background.Border.Width = 2f;
            background.Border.Radius = 5f;

            DisplayElement fill = background.AddElement();
            fill.Flex.Grow = 1f;
            fill.Background.Color = Color.green;
            fill.Border.Radius = 2.5f;

            canvas.Spawn(player);

            return new PlayerHealthData(player, canvas, background, fill, text);
        }

        public Player Player { get; }
        public DisplayCanvas Canvas { get; }

        public DisplayElement Background { get; }
        public DisplayElement Fill { get; }
        public DisplayText HealthText { get; }

        public float CurrentHealth { get; set; }
        public float CurrentMaxHealth { get; set; }

        private PlayerHealthData(Player player, DisplayCanvas canvas, DisplayElement background, DisplayElement fill, DisplayText healthText)
        {
            Player = player;
            Canvas = canvas;
            Background = background;
            Fill = fill;
            HealthText = healthText;
        }

        public bool NeedsUpdate() => CurrentHealth != Player.Health || CurrentMaxHealth != Player.MaxHealth;

        public void Update()
        {
            CurrentHealth = Player.Health;
            CurrentMaxHealth = Player.MaxHealth;

            HealthText.Content = $"{CurrentHealth:F0} / {CurrentMaxHealth:F0}";

            // Update bar width
            float healthPercent = CurrentHealth / CurrentMaxHealth;
            Fill.Size.Width = Length.Percent(healthPercent * 100f);

            // Change color based on health
            if (healthPercent > 0.6f)
                Fill.Background.Color = Color.green;
            else if (healthPercent > 0.3f)
                Fill.Background.Color = Color.yellow;
            else
                Fill.Background.Color = Color.red;
        }

        public void Destroy()
        {
            Canvas?.Destroy();
        }
    }

    public Dictionary<Player, PlayerHealthData> PlayerHealths { get; } = new Dictionary<Player, PlayerHealthData>();

    private CoroutineHandle _handle;

    /// <inheritdoc />
    public override void OnServerWaitingForPlayers()
    {
        if (_handle.IsValid)
            Timing.KillCoroutines(_handle);

        _handle = Timing.RunCoroutine(UpdateLoop());
    }

    /// <inheritdoc />
    public override void OnPlayerJoined(PlayerJoinedEventArgs ev)
    {
        PlayerHealthData data = PlayerHealthData.Create(ev.Player);

        PlayerHealths.Add(ev.Player, data);
    }

    /// <inheritdoc />
    public override void OnPlayerLeft(PlayerLeftEventArgs ev)
    {
        if (!PlayerHealths.TryGetValue(ev.Player, out PlayerHealthData hData))
            return;

        hData.Destroy();

        PlayerHealths.Remove(ev.Player);
    }

    /// There are multiple ways to do this, you can also subscrive to HealthStat's OnValueChanged event for example
    private IEnumerator<float> UpdateLoop()
    {
        while (true)
        {
            foreach (PlayerHealthData hData in PlayerHealths.Values)
            {
                // We check if the values has changed, as this is not done internally
                if (!hData.NeedsUpdate())
                    continue;

                hData.Update();
            }

            yield return Timing.WaitForSeconds(0.1f);
        }
    }
}

Example 3: Global Server Announcements

Server-wide announcements that all players see.

In-Game Preview

Server Announcement Server announcement banner shown to all players

using DisplayKit.Elements;
using DisplayKit.Enums;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.CustomHandlers;
using MEC;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class AnnouncementUI : CustomEventsHandler
{
    private DisplayCanvas _canvas;
    private DisplayText _announcementText;

    private CoroutineHandle _handle;

    /// <inheritdoc />
    public override void OnServerWaitingForPlayers()
    {
        _canvas = DisplayCanvas.Create();
        _canvas.DefaultVisibility = CanvasVisibility.Hidden;
        _canvas.SortOrder = 200;

        DisplayElement backgroundPanel = _canvas.AddElement();
        backgroundPanel.Position.Position = Position.Absolute;
        backgroundPanel.Position.Top = 100f;
        backgroundPanel.Position.Left = Length.Percent(50f);
        backgroundPanel.Transform.Translate = new Translate(new Length(-250), new Length(0));
        backgroundPanel.Size.Width = 500f;
        backgroundPanel.Size.Height = 80f;
        backgroundPanel.Background.Color = new Color(0, 0, 0, 0.85f);
        backgroundPanel.Border.Color = Color.cyan;
        backgroundPanel.Border.Width = 3f;
        backgroundPanel.Border.Radius = 10f;
        backgroundPanel.Align.AlignItems = Align.Center;
        backgroundPanel.Align.JustifyContent = Justify.Center;

        _announcementText = backgroundPanel.AddText("");
        _announcementText.Text.Font = FontType.RobotoBold;
        _announcementText.Text.FontSize = 24f;
        _announcementText.Text.Color = Color.white;
        _announcementText.Text.Align = TextAnchor.MiddleCenter;

        _canvas.Spawn();
    }

    /// <inheritdoc />
    public override void OnPlayerJoined(PlayerJoinedEventArgs ev)
    {
        ShowAnnouncement($"Player {ev.Player.Nickname} has joined the game!");
    }

    /// <inheritdoc />
    public override void OnServerRoundStarted()
    {
        ShowAnnouncement($"Round started!");
    }

    /// <inheritdoc />
    public override void OnServerRoundEnded(RoundEndedEventArgs ev)
    {
        _canvas?.Destroy();
        _canvas = null;
    }

    private void ShowAnnouncement(string message, float duration = 5f)
    {
        if (_announcementText == null || _canvas == null)
            return;

        if (_handle.IsValid)
            Timing.KillCoroutines(_handle);

        _handle = Timing.RunCoroutine(AnnouncementHandle(message, duration));

    }

    private IEnumerator<float> AnnouncementHandle(string message, float duration)
    {
        _announcementText.Content = message;
        _canvas.Show();

        yield return Timing.WaitForSeconds(duration);

        _canvas.Hide();
    }
}

Example 4: Scoreboard

Global scoreboard.

In-Game Preview

Scoreboard Visible Scoreboard shown

using DisplayKit.Elements;
using DisplayKit.Enums;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.CustomHandlers;
using LabApi.Features.Wrappers;
using MEC;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;

/// <summary>
/// A permanently shown scoreboard example.
/// </summary>
public class ScoreboardUI : CustomEventsHandler
{
    /// <summary>
    /// Entry row for the scoreboard.
    /// </summary>
    public class ScoreboardEntry
    {
        public DisplayElement Element { get; }

        public DisplayText Name { get; }
        public DisplayText Score { get; }

        public ScoreboardEntry(DisplayElement playersList, string name, int score)
        {
            Element = playersList.AddElement();
            Element.Flex.Direction = FlexDirection.Row;
            Element.Align.JustifyContent = Justify.SpaceBetween;
            Element.Spacing.PaddingTop = 5f;
            Element.Spacing.PaddingBottom = 5f;
            Element.Border.BottomWidth = 1f;
            Element.Border.BottomColor = new Color(0.3f, 0.3f, 0.3f);

            Name = Element.AddText(name);
            Name.Text.Color = Color.white;

            Score = Element.AddText($"{score} xp");
            Score.Text.Color = Color.cyan;
            Score.Text.Font = FontType.RobotoMonoBold;
        }

        /// <summary>
        /// Cleanup the row entry.
        /// </summary>
        public void Destroy()
        {
            Element?.Remove();
        }
    }

    /// <summary>
    /// Data for the <see cref="ScoreboardEntry"/>.
    /// </summary>
    public class ScoreData
    {
        public Player Player { get; }

        public int Score { get; set; }

        public ScoreData(Player player, DisplayElement playersList)
        {
            Player = player;
        }
    }

    public Dictionary<Player, ScoreData> Scores { get; } = new Dictionary<Player, ScoreData>();
    public List<ScoreboardEntry> Entries { get; } = new List<ScoreboardEntry>();

    private DisplayCanvas _scoreCanvas;
    private DisplayElement _playersList;

    private CoroutineHandle _handle;

    /// <inheritdoc />
    public override void OnServerWaitingForPlayers()
    {
        if (_handle.IsValid)
            Timing.KillCoroutines(_handle);

        _handle = Timing.RunCoroutine(UpdateLoop());

        _scoreCanvas = DisplayCanvas.Create();
        _scoreCanvas.DefaultVisibility = CanvasVisibility.Visible;
        _scoreCanvas.SortOrder = 50;

        DisplayElement mainContainer = _scoreCanvas.AddElement();
        mainContainer.Position.Position = Position.Absolute;
        mainContainer.Position.Top = 100f;
        mainContainer.Position.Right = 50f;
        mainContainer.Size.Width = 300f;
        mainContainer.Background.Color = new Color(0, 0, 0, 0.8f);
        mainContainer.Border.Color = Color.white;
        mainContainer.Border.Width = 2f;
        mainContainer.Border.Radius = 10f;

        mainContainer.Spacing.PaddingTop = 15f;
        mainContainer.Spacing.PaddingBottom = 15f;
        mainContainer.Spacing.PaddingLeft = 15f;
        mainContainer.Spacing.PaddingRight = 15f;

        mainContainer.Flex.Direction = FlexDirection.Column;

        // Header
        DisplayElement headerPanel = mainContainer.AddElement();
        headerPanel.Flex.Direction = FlexDirection.Row;
        headerPanel.Align.JustifyContent = Justify.Center;
        headerPanel.Spacing.PaddingBottom = 10f;
        headerPanel.Border.BottomWidth = 2f;
        headerPanel.Border.BottomColor = Color.gray;

        DisplayText titleText = headerPanel.AddText("SCOREBOARD");
        titleText.Text.Font = FontType.RobotoBold;
        titleText.Text.FontSize = 20f;
        titleText.Text.Color = Color.yellow;

        // List headers
        DisplayElement listHeader = mainContainer.AddElement();
        listHeader.Flex.Direction = FlexDirection.Row;
        listHeader.Align.JustifyContent = Justify.SpaceBetween;
        listHeader.Spacing.PaddingBottom = 5f;

        DisplayText nameHeader = listHeader.AddText("Player");
        nameHeader.Text.Font = FontType.RobotoBold;
        nameHeader.Text.Color = Color.gray;

        DisplayText scoreHeader = listHeader.AddText("Score");
        scoreHeader.Text.Font = FontType.RobotoBold;
        scoreHeader.Text.Color = Color.gray;

        // Player list container
        _playersList = mainContainer.AddElement();
        _playersList.Flex.Direction = FlexDirection.Column;
        _playersList.Flex.Grow = 1f;

        _scoreCanvas.Spawn();
    }

    /// <inheritdoc />
    public override void OnServerRoundRestarted()
    {
        Entries.Clear();
        Scores.Clear();
        _scoreCanvas?.Destroy();
    }

    /// <inheritdoc />
    public override void OnPlayerJoined(PlayerJoinedEventArgs ev)
    {
        ScoreData data = new ScoreData(ev.Player, _playersList);

        Scores.Add(ev.Player, data);

        Entries.Add(new ScoreboardEntry(_playersList, ev.Player.Nickname, data.Score));
        UpdateEntries();
    }

    /// <inheritdoc />
    public override void OnPlayerLeft(PlayerLeftEventArgs ev)
    {
        if (!Scores.TryGetValue(ev.Player, out ScoreData sData))
            return;

        Scores.Remove(ev.Player);

        Entries.RemoveAll(x => x.Name.Content == ev.Player.Nickname);
        UpdateEntries();
    }

    private IEnumerator<float> UpdateLoop()
    {
        while (true)
        {
            try
            {
                foreach (ScoreData sData in Scores.Values)
                {
                    sData.Score += 1;
                }

                UpdateEntries();
            }
            catch (Exception ex)
            {
                Debug.LogError("Error in ScoreboardUI UpdateLoop: " + ex);
            }

            yield return Timing.WaitForSeconds(0.5f);
        }
    }

    private void UpdateEntries()
    {
        List<ScoreData> sorted = Scores.Values.OrderByDescending(static s => s.Score).ToList();

        for (int i = 0; i < Entries.Count; i++)
        {
            ScoreboardEntry entry = Entries[i];
            entry.Name.Content = $"{sorted[i].Player.Nickname}";
            entry.Score.Content = $"{sorted[i].Score} xp";
        }
    }
}

Next Steps

Clone this wiki locally