Show a notification or open a HUD screen
Problem. You want to tell the player something, or send them to the research screen, without building any UI.
Solution. HUDEvents is a bag of public multicast events that the HUD listens to.
Firing one is the cheapest possible player-facing output.
Getting hold of HUDEvents
There is no static accessor for the HUD. The reliable route is to capture the instance
as the HUD constructs itself — HUDPart.Construct is public and receives it:
public void Construct(HUDEvents events, Player player, ILogger logger, IAnalyticsTracker analytics)
So postfix that and keep the reference:
private HUDEvents Events;
public MyMod(ILogger logger)
{
ConstructHook = DetourHelper.CreatePostfixHook<HUDPart, HUDEvents, Player, ILogger, IAnalyticsTracker>(
(part, events, player, log, analytics) => part.Construct(events, player, log, analytics),
(part, events, player, log, analytics) => Events = events);
}
This fires once per HUD part, which is many times — you are simply overwriting the same
reference with the same object, which is harmless. Events is null until the first HUD
part constructs, so null-check before using it.
Show a notification
Events?.ShowNotification.Invoke(new HUDNotificationData(
HUDNotificationType.Info,
new RawText("Overlay enabled")));
The full constructor:
HUDNotificationData(
HUDNotificationType type,
IText text,
Sprite overrideIcon = null,
Action action = null,
float showDuration = 5f)
text— anIText.new RawText("…")for literal strings;"key".T()for anything a player will read in their own language (translations).action— a callback invoked if the player clicks the notification. This is the cheapest interactive UI in the game: notify and offer a jump to the relevant thing.showDuration— seconds. Default 5.
Use RawText only for debugging output. Anything shipped should be a translation key.
Open a HUD screen
The same event bag drives most of the game's screens:
Events?.ShowResearch.Invoke();
Events?.ShowStatistics.Invoke();
Events?.ShowBlueprintLibrary.Invoke();
Events?.ShowWiki.Invoke();
Events?.ShowWikiEntry.Invoke(new WikiEntryId("…"));
Events?.ShowResearchShop.Invoke();
Events?.ShowResearchSideQuests.Invoke();
Events?.ShowResearchPlayerLevelAndFocusOn.Invoke(new PlayerLevelGoalId("…"));
Events?.RequestAddBlueprintToLibrary.Invoke(blueprint, slot);
ShowPauseMenu, ShowPauseMenuSettings, and ShowPauseMenuAdditionalContent are there
too. Browse HUDEvents in the API reference for the current full list — it is a flat class of public fields, so it reads as documentation.
Two patterns these enable without writing UI:
- Notification with an action that opens a screen — "3 platforms are starved" → click → statistics.
- Deep-link into the wiki for your own building, if you register a wiki entry.
Registering instead of invoking
The same events can be listened to, which is how you react to the player opening something:
Events.ShowStatistics.Register(OnStatisticsOpened);
// …
Events.ShowStatistics.Unregister(OnStatisticsOpened);
MultiRegisterEvent supports many handlers, so registering does not displace the
game's own. Always unregister on dispose.
Gotchas
- Do not spam notifications. They queue and stack on screen. Rate-limit anything driven by a per-tick condition, and prefer one summary notification over one per building.
Eventsisnullbefore the HUD exists — during mod construction and at the main menu. Null-check every call, as above.- Hooking
HUDPart.Constructis a broad hook on a hot-ish path. It runs once per HUD part at session start, not per frame, so the cost is fine — but keep the postfix body to an assignment. - Dispose the hook in
IMod.Dispose().
Note
The HUDPart.Construct capture is the cleanest route I could verify. If ShapezShifter
later exposes a HUD accessor, prefer it — this is reaching into the game's DI, and it
is exactly the kind of thing an update can move.