Architecture overview¶
Programmer · 20 min read · No code
XenoCore is one Game Feature plugin that sits on top of Epic's Lyra sample. Lyra supplies the framework: game mode, Experiences, the ability system plumbing, CommonUI menus and the character base classes. XenoCore adds everything RPG by subclassing Lyra's classes and adding components to Lyra's actors, never by editing Lyra. The one idea that makes the rest make sense: party-wide state lives on the player controller, per-character state lives on each pawn, and presentation lives in subsystems. Every system page in this section is one row of that split.
If you are new to the Lyra vocabulary, read the Glossary entries for Experience, Game Feature plugin and Pawn data first.
Designers: what you need from this page
This page is written for programmers. As a designer you need three things from it: the ownership map (which asset holds which setting), the rule that your changes go in your own copies, and the extension points. Everything else is background.
How it fits together¶
The plugin is Plugins/GameFeatures/XenoCore. It has two C++ modules and one content root:
| Part | What it is |
|---|---|
Source/XenoCoreRuntime |
All gameplay C++. Loaded in game and editor. |
Source/XenoCoreEditor |
Editor-only tools (animation-layer, camera, map and Niagara helpers). Never ships in a packaged game. |
Content/ |
Every XenoCore asset. Appears in the Content Browser as XenoCore Content. |
Config/Tags/XenoCoreTags.ini |
Gameplay tags that only data uses. Tags that C++ names are declared in XenoCoreGameplayTags.h. |
The plugin starts in the Registered state (XenoCore.uplugin, BuiltInInitialFeatureState) and
is switched on by an Experience, exactly like Lyra's own ShooterCore.
The Game Feature Data asset¶
Content/XenoCore (the asset named after the plugin) is the plugin's Game Feature Data. It runs
while the feature is active and holds three actions:
| Action | What it adds |
|---|---|
| Add Components | On every LyraPlayerController: BP_XenoCore_Party (the party), XenoCoreQuestComponent (quests) and XenoCorePartyGaugeComponent (the Party Gauge). On every Controller: BP_XenoCore_Progression (levels, EXP, gold) and LyraInventoryManagerComponent (the shared bag). On every PlayerController, client side only: LyraIndicatorManagerComponent (world indicators). |
| Add Input Context Mapping | IMC_Default (Lyra's) at priority 1 and IMC_XenoCore (XenoCore's inputs) at priority 2. |
| Add Gameplay Cue Path | Registers the plugin's GameplayCues folder (the action's default path, /GameplayCues, relative to the plugin), so GCN_XenoCore_Hit, GCN_XenoCore_Swing and GCN_XenoCore_HealNumber can be found. |
It also tells the Asset Manager to scan /XenoCore/Experiences, /XenoCore/Maps and
/XenoCore/System/Playlists, which is how the plugin's Experiences become discoverable without you
editing the host project's config.
Fails silently: a new cue folder is never searched
Only the plugin's GameplayCues folder is registered. A Gameplay Cue Notify you save anywhere
else in XenoCore Content (for example under Battle/GameplayCues) is never found: the cue fires,
nothing plays, nothing is logged. Keep cue notifies in GameplayCues, or add a second path to
the action.
The Experiences¶
| Experience | Used by | What it does |
|---|---|---|
Experiences/B_XenoCore_Experience |
The sample world and L_XenoCore_Gym |
Sets Default Pawn Data to Game/PawnData_XenoCore_Player; adds the LAS_XenoCore_HUD action set (the HUD layout W_XenoCore_HUDLayout and the number-pop component); grants AbilitySet_XenoCore_Interact to LyraPlayerState (whose ability system XenoCore does not use, so this grant does nothing; the party's Leader Only Ability Set is the grant that works); adds B_XenoCore_TeamSetup (a team creation component) to LyraGameState; enables the XenoCore Game Feature. |
Experiences/B_XenoCore_FrontEnd_Experience |
L_XenoCore_FrontEnd |
Adds BP_XenoCore_TitleState to the game state, which pushes the boot screens and the title screen. |
The ownership map¶
This is the table to come back to. When you want to know where something is stored, who changes it and who is told about it, find its row.
| State | Lives on | Class (Blueprint) | Why there |
|---|---|---|---|
| Roster, active party, leader slot | Player controller | UXenoCorePartyComponent (Characters/Party/BP_XenoCore_Party) |
Party-wide, and must survive switching which character you drive. |
| Levels, EXP, Art Points, learned arts, palettes, skill progress, gold | Player controller | UXenoCoreProgressionComponent (Game/BP_XenoCore_Progression) |
Keyed by member definition, so a benched character with no body still has a level. |
| The bag (all items) | Player controller | ULyraInventoryManagerComponent |
Party-wide; Lyra's own item-cost code looks for it on the controller. |
| Quest records | Player controller | UXenoCoreQuestComponent |
Party-wide and save-shaped. |
| Party Gauge | Player controller | UXenoCorePartyGaugeComponent |
Party-wide battle state; not saved. See Party Gauge, downed and revive. |
| Menu pause, ability-input pump | Player controller | AXenoCorePlayerController |
Pumps the pawn's ability system, not the player state's. |
| Ability system, attributes, active effects | Each pawn | ULyraAbilitySystemComponent on AXenoCoreCombatantCharacter (party) or AXenoCoreEnemyCharacter (enemies, a separate branch from Lyra's ALyraCharacterWithAbilities) |
Each character is its own identity; switching leader never moves stats. |
| Health and death, revive | Each pawn | UXenoCoreHealthComponent |
Swapped in for Lyra's health component on every combatant. |
| Worn equipment | Each pawn | UXenoCoreEquipmentComponent |
Equipment spawns meshes, which are per body. |
| Input binding and camera for the driven body | Each playable pawn | UXenoCoreHeroComponent |
Binds the pawn's own ability system; re-binds input when a body is taken back. |
| Party AI behaviour | Each companion's controller | AXenoCorePartyMemberController + StateTree ST_XenoCore_PartyMember |
Carries the team; behaviour is data. |
| Enemy AI behaviour | Each enemy's controller | AXenoCoreEnemyController + an enemy StateTree |
Carries the team, aggro and leash. |
| Current target | The target-mode ability instance | UXenoCoreGameplayAbility_TargetMode (GA_XenoCore_TargetMode) |
One instance per pawn, so the lock and its target share one lifetime. |
| Dialogue box and line sequencing | World | UXenoCoreDialogueSubsystem |
One conversation at a time, driven from the NPC's graph. |
| "What am I standing next to" and the A prompt | World | UXenoCoreInteractionSubsystem |
One owner for one button and one screen corner. |
| Damage-taken and heal numbers | World | UXenoCoreDamageNumberSubsystem |
Listens to Lyra's damage message; draws what Lyra does not. |
| Battle text (MISS, CRITICAL HIT!, BREAK...) | World | UXenoCoreBattleNotifySubsystem |
Can annotate any actor, widget or not. |
| Loot popup | World | UXenoCoreLootPopupSubsystem |
Reacts to a chest being opened. |
| Party join/leave banner | World | UXenoCorePartyNoticeSubsystem |
Waits until the game is unpaused before announcing. |
| "The battle was won", fanfare and victory emotes | World | UXenoCoreBattleVictorySubsystem |
The one place that tells a win from a retreat. |
| Battle voice lines and banter | World | UXenoCoreBattleVoiceSubsystem |
One speaker at a time across the party. See Battle voices. |
| Map markers | World | UXenoCoreMapMarkerSubsystem |
What the minimap draws. See Minimap. |
| Holding the loading screen until the world is ready | World | UXenoCoreWorldReadySubsystem |
Lifts only once bodies are posed and shaders have settled. |
| Save slots, load order, autosave | Game instance | UXenoCoreSaveSubsystem |
Must outlive a map change. |
| Time of day, weather, tutorial cards seen | Game instance | UXenoCoreTimeOfDaySubsystem, UXenoCoreWeatherSubsystem, UXenoCoreTutorialSubsystem |
Survive travel; each is a save participant. See Time of day and weather. |
| Your own saved state | Wherever it lives, as a save participant | IXenoCoreSaveParticipant, UXenoCoreSaveParticipantComponent |
Stored in the save under its own key; XenoCore never needs to know what it is. See Make a system save. |
| Which widgets and assets C++ opens | Project settings | UXenoCoreUISettings, UXenoCoreContentSettings |
A subsystem has no asset of its own to point at a widget. See UI shell and HUD. |
| Title screen | Game state (front end only) | UXenoCoreTitleStateComponent (UserInterface/BP_XenoCore_TitleState) |
Lyra's own front-end shape. |
Single-player by design
Several resolvers answer "the party" by taking player controller 0. That assumption is written
down in one function per system (UXenoCorePartyComponent::GetPartyComponent,
UXenoCoreProgressionComponent::GetPartyProgression, UXenoCoreInventoryStatics::GetPartyInventory).
XenoCore does not support multiplayer or split-screen.
One resolver per question¶
Every "where is X?" question has exactly one function that answers it, and everything else calls that function. Use these from Blueprint rather than searching for components yourself:
| Question | Ask |
|---|---|
| Where is the party? | Get Party Component |
| Where are levels, EXP and gold? | Get Party Progression |
| Where is the bag? | Get Party Inventory |
| What is this pawn locked onto? | Get Current Target (UXenoCoreTargetingStatics) |
Fails silently: asking a companion's own controller
A companion is possessed by an AI controller that has none of the party components. A graph
that does Get Controller → Get Component by Class on a companion gets nothing and carries on
with empty data. Always use the resolvers above.
Lifecycle¶
- The map loads its Experience. The Experience enables the XenoCore Game Feature, whose Game Feature Data adds the controller components and input contexts.
- Lyra's game mode spawns the bootstrap pawn from Default Pawn Data (
B_XenoCore_Player). Its hero component binds its ability system and grants its ability sets. UXenoCorePartyComponent::BeginPlayeither restores a save or adds each Starting Roster member, then spawns bodies for the active members around the player and hands the leader's body to the player. See Party.- Each spawned body initialises its own ability system, grants its pawn data's ability sets and applies its statline at its level. See Characters.
- Subsystems register their message listeners at world begin play and react from then on.
Change signals are messages¶
Lyra's own inventory and equipment change messages are sent only from replication callbacks, so in a single-player game they never fire. XenoCore broadcasts its own on the Gameplay Message Subsystem. Each has exactly one broadcaster, and every change goes through that broadcaster. The one safety net is the inventory watcher, which announces bag changes made around the statics.
| Channel | Sent by | Carries |
|---|---|---|
XenoCore.Party.Message.Changed |
Party component | "Something changed" plus the active count. Re-read the component. |
XenoCore.Progression.Message.Changed |
Progression component | Levelled count and the EXP just awarded. |
XenoCore.Progression.Message.LevelUp |
Progression component | Every member who levelled, with before and after levels. |
XenoCore.Inventory.Message.Changed |
UXenoCoreInventoryStatics (and UXenoCoreInventoryWatchSubsystem for changes made around it) |
Bag changed. |
XenoCore.Equipment.Message.SlotChanged |
Equipment component | A slot changed. |
XenoCore.Targeting.Message.TargetChanged |
Target-mode ability | Old and new target. |
XenoCore.Battle.Message.Notification |
Battle notify subsystem | Text, colour and target for a battle line. |
XenoCore.Enemy.Message.Defeated |
Enemy character | An enemy died. |
XenoCore.Battle.Message.Won |
Battle victory subsystem | A battle was won (not fled). |
XenoCore.Party.Message.TacticChanged |
Party component | A party tactic changed. |
XenoCore.Loot.Message.ChestOpened |
Treasure chest | A chest was opened. |
XenoCore.Quest.Message.Changed, .TurnedIn |
Quest component | Quest state; turn-in carries the payout. |
The full list and payloads are in Events you can listen to.
Changing the bag directly
Every bag change goes through UXenoCoreInventoryStatics. A change made by calling Lyra's
inventory component yourself is caught by UXenoCoreInventoryWatchSubsystem, which sends
XenoCore.Inventory.Message.Changed for it a frame later, so menus stay current. It cannot undo
one, though: Lyra's Remove Item Instance will remove an item a character is wearing, and the
save then loses it. See Items and inventory.
Extending it¶
The rule: never edit Lyra. Subclass it, add a component, or add a row of data. Lyra's files are the host; if you change them, a Lyra update overwrites your work, and XenoCore's behaviour may change under you.
- To change a XenoCore system's numbers, edit the Blueprint subclass that holds them
(
BP_XenoCore_Party,BP_XenoCore_Progression,BP_XenoCoreDamageExecution) or, better, make a child Blueprint of it inContent/MyGame/and point the Game Feature Data at your child. - To change behaviour in C++, subclass the XenoCore class and override its virtuals. See Subclass a mechanic.
- To add a new system, give it the same shape: party-wide state on a controller component with a static resolver, per-character state on the pawn's ability system, presentation in a world subsystem that listens to a message.
Every public class and struct in XenoCoreRuntime is exported (XENOCORERUNTIME_API), so a C++
subclass in your own module links, including the characters, both AI controllers, the stat set,
the damage execution and the equipment component. Only some of Lyra's classes are not
exported; see Subclass a mechanic.
Extension points¶
These are the places built for your additions. Each needs no edit to a XenoCore asset or source file.
| To add | Use | Recipe |
|---|---|---|
| A menu entry, or a different menu button | Menu Entries or a XenoCoreMenuEntrySet in Project Settings › Game › XenoCore UI |
Add a menu screen |
| A replacement for a screen or HUD widget C++ opens | The matching XenoCore UI or XenoCore Content setting | Restyle the menus |
| A widget on the HUD | An Add Widgets action on a HUD.Slot.XenoCore.* slot |
Add a HUD element |
| Something the player presses A on | A Blueprint child of AXenoCoreInteractableActor |
Add an interactable object |
| Saved state for your own system | A XenoCore Save Participant component, or the interface |
Make a system save |
| A yes/no question in a screen | The Ask Confirm node | Add a confirmation popup |
| An input action for every playable character | One row in Game/AbilitySet_XenoCore_Input |
Add an input action with a prompt |
| An equipment stat | One modifier row on Inventory/GE_XenoCore_EquipmentStats |
Add an equipment stat |
| New behaviour in C++ | A subclass in your own module | Subclass a mechanic |
XenoCore needs no edits to Lyra
Where XenoCore needs Lyra content to behave differently, it ships its own copy instead of
changing Lyra's: the damage-number cue is GameplayCues/GCNL_XenoCore_DamageTaken (fired by
GE_XenoCore_Damage; Lyra's GCNL_Character_DamageTaken is untouched), and keyboard key
icons are added at startup from Project Settings › Game › XenoCore UI › HUD › Keyboard Key Icons.
A fresh download of Lyra therefore needs nothing re-applied.
Why it's built this way¶
- Pawn-hosted ability systems. Lyra keeps a player's ability system on the player state, which
would mean every party member shares one identity or needs its own player state. XenoCore uses
Lyra's own
ALyraCharacterWithAbilitiesshape (the one Epic's damageable test dummy uses), so a character's stats, arts and effects travel with its body. Switching leader is only possession. - Controller components for the party. The same place Lyra's quick bar and item-cost code look. It survives pawn changes and is reachable from anything with a world.
- Subsystems for presentation. A notification is about the battle, not a pawn, so it has no business being a component on every character.
- Own messages. Lyra's change messages rely on replication. XenoCore's fire in standalone play, and each has one sender, so a listener never has to guess which of several paths changed something.