-
Notifications
You must be signed in to change notification settings - Fork 39
DisplayKit Examples
davidsebesta edited this page Jun 14, 2026
·
12 revisions
Practical code examples for common DisplayKit use cases.
- Simple Welcome Message
- Player Health Bar (Per-Player)
- Global Server Announcements
- Scoreboard (Toggle Visibility)
A simple global welcome message shown to all players.
Centered welcome message with shadow effect:

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;
}
}Individual health bars for each player.
Health bar at 100% (green)
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);
}
}
}Server-wide announcements that all players see.
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();
}
}Global scoreboard.
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";
}
}
}- Style Properties - Explore all styling options
- Making Plugins
- Features
- Guides