Skip to content

UI shell and HUD

All · 15 min read · No code

The main menu: entries on the left, one card per active party member.

The main menu: entries on the left, one card per active party member.

What the player sees: during play, a HUD with the party's health plates, the minimap, the quest tracker, pickup badges and banners; in battle, the target plate and the Arts palette. The menu button opens an XC1-style main menu (entries on the left, a full-height card per party member) from which every other screen opens. Menus pause the game.

XenoCore's interface is built on Lyra's UI framework, CommonUI, and follows Lyra's look: clean, dark and restrained. Everything on screen falls into two groups. HUD plates sit over gameplay, take no input, and fade in and out. Screens are full menus; while one is up the game pauses and gameplay input switches off. The idea that makes the rest make sense: a HUD plate hides by fading its opacity, never by collapsing, and a screen owns the input while it is on top. Nearly every UI trap in this project is one of those two rules broken.

What you can change

How it fits together

Lyra's root layout, W_OverallUILayout, holds a stack per layer tag. XenoCore uses three of them:

Layer What goes there
UI.Layer.Game The HUD: UserInterface/HUD/W_XenoCore_HUDLayout
UI.Layer.Menu Every XenoCore screen: the menu shell and its screens, quest offer, shop, trade, save, title, boot cards, tutorial cards
UI.Layer.Modal Lyra's own full-screen confirmation dialog. XenoCore's popups deliberately do not use it

A layer stack shows only its top widget, so pushing a screen onto the menu layer hides the one under it until the new one closes.

The HUD

Experiences/LAS_XenoCore_HUD, an action set used by B_XenoCore_Experience, adds:

  • W_XenoCore_HUDLayout to UI.Layer.Game. It derives from Lyra's LyraHUDLayout and places the party member plates, the item pickup badges, the quest tracker, the quest reward banner, the level-up banner, the party join banner and the autosave badge.
  • B_XenoCore_NumberPopComponent to the player controller, for damage numbers.

The layout also carries five empty extension points (Lyra's UIExtensionPointWidget), each named by a tag. Lyra's Add Widgets action puts your own widget into one, so a new HUD element needs no edit to the layout:

Slot tag Position Stacking
HUD.Slot.XenoCore.TopCenter Top centre, 48 px down Column, centred
HUD.Slot.XenoCore.TopRight Top right, below the minimap and its prompt Column, right-aligned
HUD.Slot.XenoCore.Center Screen centre Overlaid
HUD.Slot.XenoCore.BottomCenter Bottom centre, 48 px up Column, centred
HUD.Slot.XenoCore.FullScreen Whole screen, behind the rest of the HUD Overlaid

They are empty and let clicks through until something is added. See Add a HUD element.

The top-right corner holds the minimap (Minimap), and the full-screen L3 view (MinimapOverlay) sits behind everything else. Both are widgets in the layout, not extension points. See Minimap.

Other HUD elements are created by the systems that own them. The ones C++ creates by itself take their widget class from Project Settings › Game › XenoCore UI (see The two settings pages):

Element Created by Class from
Target plate, player plate, Arts palette The target mode ability, Battle/Targeting/GA_XenoCore_TargetMode The ability's Class Defaults
"A Talk" prompt (W_XenoCore_InteractPrompt) The interaction system, drawn under the menu layers Interact Prompt
Dialogue box (W_XenoCore_DialogueBox) The dialogue anchor, a widget component drawn in screen space The anchor's Box Widget Class, else Dialogue Box
NPC name plates, enemy and party overhead bars Widget components on actors, drawn in screen space The actor's Class Defaults
Battle notifications (W_XenoCore_BattleNotify) The battle system Battle Notify
Loot panel (W_XenoCore_LootPopup) Opening a chest Loot Popup
Damage-taken and heal numbers The damage system Damage Taken Numbers, Heal Numbers (Niagara systems)

The menu

Input/Abilities/GA_XenoCore_ToggleMenu (the menu button, see Controls) pushes UserInterface/W_XenoCore_MenuShell onto the menu layer. The shell holds no hand-placed buttons: its Construct calls Build Menu In Panel on its ButtonsBox, which makes one button per menu entry from Project Settings › Game › XenoCore UI › Menu Entries (plus any Additional Menu Entry Sets), sorted by Sort Order, and focuses the first. It rebuilds every time the menu opens, which is what re-asks each entry's Visible If condition.

The shell is laid out as Xenoblade Chronicles' main menu. Its parent class, XenoCoreMainMenuBase, fills in everything except the buttons each time the menu opens:

  • Party cards. One full-height card per active party member (up to seven), in party order with the leader first. The row is divided into Card Slots (7) equal slots, so a full party fills the screen and a smaller one keeps the same card width. A card shows the slot number, "Leader" on the first, the member's full-body portrait, the skill tree they are learning, their name, a divider, then Lv and HP. The cards come from Card Class on the shell's Class Defaults; the default, XenoCorePartyCardBase, builds its own look. To restyle it, make a Widget Blueprint child of XenoCorePartyCardBase and name its parts PortraitImage, SlotText, LeaderText, BranchText, NameText, LevelValueText and HealthValueText. A part you leave out is not drawn.
  • Portraits. The card draws the member definition's Menu Portrait, a full-body texture, and falls back to Portrait (the HUD bust) when it is empty. The seven sample members ship with one each. The card shows the top of it, from the crown down, filling the card's height: Portrait Crop on the card class (0.55, crown to hips) sets how much; 1.0 shows the whole body.
  • Entries are XenoCoreMenuEntryButton rows, styled like the Party Tactics list. Change Menu Button Class in the settings to use your own button. Cards, entries and the shell share the tactics panel's colours (navy panel, blue rim, Orbitron), so the menu matches the HUD.
  • Gold and play time, top right (GoldText, PlayTimeText).
  • Description, along the bottom (DescriptionText): the highlighted entry's Description.
  • Intro. Each time the menu opens it fades in, the entry list slides in from the left and the cards rise in one after another. Timings and distances are under XenoCore > Intro on the shell's Class Defaults; untick Play Intro to turn it off.
  • Groups. An entry with Starts Group set has a gap above it (Menu Group Gap in the settings, 20 px). The shipped list starts one at Save.
Sort Entry Opens
100 Equipment W_XenoCore_EquipmentScreen
200 Arts Open Arts Screen action: the Arts Screen setting
300 Skills Open Skills Screen action: the Skills Screen setting
400 Collectables W_XenoCore_ItemMenu
500 Party W_XenoCore_PartyManager
600 Quest Log Open Quest Log action: the Quest Log Screen setting
700 Change Time The C++ screen XenoCoreChangeTimeScreen: pick an hour, the screen fades and the clock jumps. See Time of day and weather
800, 900 Save, Load Open Save Screen / Open Load Screen actions: the Save Screen setting, in Save or Load mode
1000 Tutorials The C++ screen XenoCoreTutorialLogBase: every tutorial card seen so far, and the Tips on/off switch. See Tutorial cards
1100 Settings Lyra's W_LyraSettingScreen
1200, 1300 Quit to Title, Quit Game Return To Title / Quit Game actions; each asks first if there is unsaved progress

An entry either pushes its Screen onto its Layer (empty means UI.Layer.Menu) and focuses that screen's desired target, or runs its Action (a small Blueprint you make with XenoCoreMenuAction as its parent). Entries with an Id are merged: a later entry with the same Id replaces the earlier one. See Add a menu screen.

Lifecycle

Opening a screen. A screen is a Common Activatable Widget pushed onto UI.Layer.Menu. When the menu layer shows anything, the game pauses and CommonUI switches to Menu input mode. The project's input config then filters every gameplay mapping context out, so A cannot also jump behind a menu. See Input and camera. The title screen opts out of the pause so its scene keeps moving.

Back. B or Escape deactivates the top screen, but only if that screen sets Is Back Handler. Screens that must be answered (the loot panel, a quest that cannot be declined) take the back action and ignore it.

Closing. Deactivating pops the screen off its layer. When the menu layer is empty the game unpauses and gameplay input returns.

Pooling. CommonUI keeps a pushed screen and reuses the same instance next time. Anything that must be fresh per visit is reset every time the screen is shown, never once when it is built.

HUD plates (widgets whose parent class is XenoCoreHudPlateBase) are created once and stay. They slide and fade in when created, Set Shown fades them in or out without removing them, and Play Outro fades out and removes. A plate that must start hidden calls Begin Hidden from its own Construct. Set Shown Immediate, Set Show Delay, Collapse All Except, Begin Hidden and Skip Intro are all Blueprint-callable.

Plates built on XenoCoreHudPlateBase: player plate, target plate, Arts palette, battle notification, quest tracker, quest reward banner, level-up banner, autosave badge.

Celebration bursts. The quest reward banner and the level-up banner each have a Celebration, and the party join banner a Joined Effect: a UI Niagara burst (UserInterface/HUD/NS_XenoCore_QuestComplete, NS_XenoCore_LevelUp, NS_XenoCore_PartyJoin) played behind the card, with its own System, Size, Scale and Duration. Clear System for no burst. A paused game does not freeze a burst. Unreal's shot screenshot command does not capture these bursts at all, so check them by eye, or with a screenshot of the whole desktop.

Settings

Setting Where it lives What it does
Show Hide Duration, Show Hide Offset Any HUD plate › XenoCore › HUD The show/hide fade time, and an optional slide
Intro Duration, Outro Duration, Intro Offset Any HUD plate › XenoCore › HUD › Transition The entrance and exit
Plate positions W_XenoCore_HUDLayout Where each HUD element sits
Extra HUD widgets An Add Widgets action with a HUD.Slot.XenoCore.* Slot ID Adds your widget to a HUD slot without editing the layout
Screens and HUD widgets C++ opens Project Settings › Game › XenoCore UI Replace the save, quest log, quest offer, shop, trade, Arts and Skills screens, the interact prompt, loot panel, battle notification, dialogue box and number systems
Menu entries, button class and padding Project Settings › Game › XenoCore UI › Menu What the in-game menu lists, in what order, and with which button
Dialogue anchor, beam material Project Settings › Game › XenoCore Content Non-UI assets C++ spawns
Escape Menu Class W_XenoCore_HUDLayout What Escape and the pad's Select button open during play: W_XenoCore_MenuShell, the same menu as the menu button
Styles UserInterface/Foundation/ Text styles, button styles, tab button, font colour materials
Action rows and their glyphs UserInterface/Foundation/DT_XenoCore_Actions Named actions (Unequip, Delete, Sort...) that screens bind to show a button prompt
Item category icons UserInterface/Foundation/DA_XenoCore_ItemIcons Icons per item category

Style assets in UserInterface/Foundation/:

Asset Use
TextStyle_XenoCore_TitleEntry, TextStyle_XenoCore_TitleEntrySelected CommonUI text styles for the title screen's entries
ButtonStyle_XenoCore_Menu, ButtonStyle_XenoCore_TitleEntry CommonUI button styles
W_XenoCore_TabButton Tab button for tab lists. Use it instead of Lyra's W_LyraButtonTab
W_XenoCore_MenuButton, W_XenoCore_GlyphButton, W_XenoCore_ItemRow, W_XenoCore_StatBlock, W_XenoCore_StatRow, W_XenoCore_Rule Shared building blocks for screens

The text styles use Lyra's Orbitron font (/Game/UI/Foundation/Fonts/Orbitron).

The stat block (W_XenoCore_StatBlock) draws a list of stat rows. Show Lines and Show Header draw what you pass; Show Character and Show Item make the block fill itself from a party member or an equipped slot and redraw when equipment changes.

Wrapping lists. Menu lists wrap from bottom to top. Ordinary panels wrap through their navigation rules. A List View cannot, so call Wrap List View once from the screen's Construct.

The two settings pages

Anything C++ opens or spawns by itself (a world subsystem or a static function has no asset of its own to hold a class reference) is named on one of two pages in Edit › Project Settings › Game. Both are saved to Config/DefaultGame.ini, and every entry starts at the shipped asset, so nothing changes until you edit one.

XenoCore UI

Category Setting Must derive from Shipped
Screens Save Screen XenoCoreSaveScreenBase W_XenoCore_SaveScreen
Screens Quest Log Screen XenoCoreQuestLogBase W_XenoCore_QuestLog
Screens Quest Offer Screen XenoCoreQuestOfferBase HUD/W_XenoCore_QuestOffer
Screens Shop Screen XenoCoreShopScreenBase HUD/W_XenoCore_ShopScreen
Screens Trade Screen XenoCoreTradeScreenBase HUD/W_XenoCore_TradeScreen
Screens Arts Screen XenoCoreArtsScreenBase Arts/W_XenoCore_ArtsScreen
Screens Skills Screen XenoCoreSkillsScreenBase Skills/W_XenoCore_SkillsScreen
Screens Gem Popup XenoCoreGemPopupBase W_XenoCore_GemPopup
HUD Interact Prompt XenoCoreInteractPromptBase HUD/W_XenoCore_InteractPrompt
HUD Loot Popup XenoCoreLootPopupBase HUD/W_XenoCore_LootPopup
HUD Battle Notify XenoCoreBattleNotifyBase HUD/W_XenoCore_BattleNotify
HUD Dialogue Box should be XenoCoreDialogueBoxBase HUD/W_XenoCore_DialogueBox
HUD Damage Taken Numbers, Damage Dealt Numbers, Heal Numbers Niagara systems HUD/NS_XenoCore_DamageNumbers_Taken, HUD/NS_XenoCore_DamageNumbers, HUD/NS_XenoCore_HealNumbers
HUD Keyboard Key Icons Lyra's keyboard controller data Input/CommonInput_XenoCore_Keyboard
Items Item Rarity Table, Rarity Pip Size, gem socket icons, tint, size and spacing A XenoCoreItemRarityRow Data Table; textures Items/DT_XenoCore_ItemRarities, Items/T_XenoCore_GemSocketFilled, _Empty. See Set and style item rarity
Tutorials Tutorial Table, Tutorial Card, Tutorial Enemy Near Distance, Tutorial Enemy Status Radius A XenoCoreTutorialRow Data Table; XenoCoreTutorialCardBase Tutorials/DT_XenoCore_Tutorials; empty card = the native card. See Tutorial cards
Menu Menu Entries, Additional Menu Entry Sets Entries, and XenoCoreMenuEntrySet assets The thirteen entries above
Menu Menu Button Class, Menu Button Padding, Menu Group Gap A CommonUI button XenoCoreMenuEntryButton, 4 px below each, 20 px before a group

XenoCore Content

Category Setting Shipped
Dialogue Dialogue Anchor (must derive from XenoCoreDialogueAnchor) Characters/NPC/BP_XenoCore_DialogueAnchor
Feedback Beam Material (the heal and buff light shaft) Effects/Loot/M_XenoCore_LootBeam
Debug Reaction Break / Topple / Daze Effect (for the XenoCore.Reaction command) GE_XenoCore_Reaction_Break, _Topple, _Daze

The same page also holds Startup (starting roster and New Game map overrides, title music, fallback field and battle music), Dialogue Camera Rig, Battle (victory fanfare, the sounds an AI ally plays when hit), Voice, Items (the gem registry) and Critical Hit. Each is described on the page for its system.

The widget paths in the first table are under UserInterface/.

The fallback. If a setting is cleared, fails to load, or names an asset of the wrong class, XenoCore logs a warning and uses the shipped asset. A mistake costs the replacement, never the feature. The dialogue anchor falls back one step further, to the C++ anchor.

Fails silently in a packaged build: a replacement outside the cooked folders

A setting holds a soft reference, and the cooker does not follow soft references in project settings. The shipped assets are cooked because Config/DefaultGame.ini always cooks /XenoCore/UserInterface and /XenoCore/Characters/NPC, or because other cooked assets reference them. Your replacement works in the editor, and in a packaged build it is missing, so the game falls back to the shipped asset with nothing on screen to say so. Add your folder to Project Settings › Packaging › Additional Asset Directories to Cook (DirectoriesToAlwaysCook).

Silent traps

Fails silently: a plate that hides itself by collapsing never comes back

A Collapsed widget stops ticking, so it can never notice the condition that should show it again. HUD plates must hide with opacity: use Set Shown, never Set Visibility to Collapsed.

Fails silently: a popup without Is Back Handler closes the screen under it

Back falls through to the screen that asked the question, which closes and takes the popup with it. It looks exactly like "No" was pressed. Every popup must tick Is Back Handler.

Fails silently: text over a button swallows the pad's A

With the project's settings a pad's A is a simulated click at the centre of the focused widget. A text block or image drawn on top of a button, and hit-testable, takes that click instead. Set every widget layered over a button to Hit Test Invisible.

Fails silently: a Common Text Block with a style ignores its font

If a Style is set, it overrides Font and Color and Opacity. Clear Style before setting a font by hand, or set the font in a text style.

Enter and Space press the focused button

Config/DefaultGame.ini sets CommonUI's CommonButtonAcceptKeyHandling to TriggerClick, so the keyboard's accept keys click the focused button, just as the pad's A does. A pad A press is never handled twice: CommonUI's analog cursor takes it first, and only a press nothing handled reaches the button.

Hovering is not focus

A button's On Hovered does not fire for keyboard or pad navigation. Anything that acts on "the selected row" must track On Focus Received as well.

Extending it

  • Restyle: duplicate the styles you want into Content/MyGame/UI/ and point widgets at them. See Restyle the menus.
  • A new menu screen: make a Widget Blueprint whose parent is Common Activatable Widget (or a XenoCore screen base), set its input config to Menu, tick Is Back Handler, and add a Menu Entries row that names it. See Add a menu screen.
  • Replace a shipped screen or HUD widget: duplicate it, keep its parent class, and point the matching XenoCore UI setting at your copy. See Restyle the menus.
  • A yes/no popup: push it into the asking screen's own OverlayStack, never onto the menu layer (that would replace the screen). From Blueprint, the Ask Confirm node does the whole thing with W_XenoCore_ConfirmPopup and continues on On Yes or On No. When a popup closes, give focus back to the row that asked. See Add a confirm popup.
  • A new HUD element over gameplay: add it to a HUD.Slot.XenoCore.* slot with an Add Widgets action; give it the parent class XenoCoreHudPlateBase if it should fade in and out. See Add a HUD element.
  • A button prompt on a screen: add a row to your own action table and bind it; see Add an input action with a button prompt.

Never edit Lyra's UI assets; copy what you need.

For programmers: classes

Plates derive from UXenoCoreHudPlateBase; the stat block is UXenoCoreStatBlockBase; menu actions UXenoCoreMenuAction; the settings pages are UXenoCoreUISettings and UXenoCoreContentSettings. The game pauses through AXenoCorePlayerController whenever UI.Layer.Menu is non-empty. Widgets opened by systems with no asset of their own come from UXenoCoreBattleNotifySubsystem, UXenoCoreLootPopupSubsystem, UXenoCoreDamageNumberSubsystem and UXenoCoreInteractionSubsystem. From C++, a confirm popup is UXenoCoreConfirmPopupBase::AskWithAnswer.

Why it's built this way

For programmers
  • Lyra's look, not an imitation of a console RPG. XenoCore ships as a Lyra template, so a buyer expects an interface that belongs to Lyra. The layouts and information follow the genre; the chrome is Lyra's. XenoCore authors its own style assets in Lyra's idiom rather than pointing at Lyra's, apart from the font.
  • Opacity, not visibility. HUD plates have two opacity drivers, the entrance/exit and the show/hide fade. They are multiplied through one function, so neither can erase the other.
  • Pause by the layer, not by a screen. Pushing a sub-screen deactivates the shell, so a shell-scoped pause would resume the game under an open screen. "Is anything on the menu layer" is the question that matters.
  • Menu input filtering, not ability blocking. Stopping gameplay input at the mapping-context level stops every action at once, including ones added later.
  • Settings, not asset paths. A subsystem has no asset of its own to hold a class reference, so the widgets C++ opens used to be paths typed into the code, and swapping one meant editing the plugin. They are now project settings with a shipped default and a logged fallback, so a buyer swaps a screen without touching XenoCore, and a bad value cannot break the feature.
  • Data-driven menu. Eleven hand-wired buttons meant every new screen was an edit to a shipped asset. An entry list merged by Id lets a plugin add, reword or hide entries from its own content.
  • Rows built in C++. Lyra's list view drops null items without warning, so several XenoCore screens build their rows into a vertical box from C++, where what was built can be logged.

Next: Input and camera · Events you can listen to