Loading and ticking¶
Designers and programmers · 10 min read · No code
Who this is for: anyone adding content who wants it to stay cheap. The first half (which references to make soft, and when to tick) is for designers working in Blueprints and Details panels; the warning boxes marked For programmers are for whoever writes C++.
Two things decide most of what XenoCore costs at run time: what an asset drags into memory when it loads, and what runs every frame. A property that points straight at another asset (a hard reference) loads that asset, and everything it points at, whenever its owner loads. A Blueprint with a live Event Tick runs a Blueprint call every frame for every copy in the world. The idea that makes the rest make sense: load an asset when it is needed, not when something that might need it loads; and run code when something changes, not every frame in case it did.
Hard and soft references¶
A soft reference stores the asset's path only. In the Details panel it looks and works exactly like a hard one: the same picker, the same thumbnail. The difference is that nothing loads until code asks for it.
Hard is right when the owner cannot do its job without the asset the moment it is used:
- a character's meshes, anim Blueprint and the montages it always plays;
- its Pawn Data and ability sets;
- the widget classes a screen builds on open.
Soft is right when the asset is optional, situational, one of many, or large and needed later:
- drop pools, shop and trade stock, quest targets, learnable Arts, rare effects;
- a default that a Blueprint child is expected to override (fonts, glyph widgets, textures).
These XenoCore properties are soft. Existing data converted on its own; you set them the same way as before.
| Property | Where | Loads when |
|---|---|---|
| Enemy Target | Quest objective | Never by the quest. Kills are matched against enemies already in the world. See Quests |
| Art | Art learnset rows (DT_XenoCore_ArtLearnset_*) |
The character learns it: at spawn or level-up. See Arts |
| Item Def | Drop table entries | A chest is opened, and only the items rolled. See Loot |
| Item (offers, overtrade bonus) | Shop and trade tables | The shop or trade screen opens. See Shops and trade |
| Death Effect | Enemy | The enemy is first provoked, so it is ready by the time the body vanishes. See Enemies and AI |
| Glyph Class, Action Table, Font Family | Tactics panel (XenoCoreTacticsPanelBase) |
The panel is first built. See Party tactics |
| Font Family, Sun Texture, Moon Texture | Change Time screen (XenoCoreChangeTimeScreen) |
The screen is first built. See Time of day and weather |
Blueprint graphs that read these fields
A graph reading one of the properties above now gets a soft reference. Load it with Load
Class Asset Blocking / Load Asset Blocking at a pause (below), or Async Load Class
Asset / Async Load Asset if it can wait. (For programmers: in C++, the shop and trade rows
have LoadItem().)
Where to load a soft reference¶
- At a natural pause: a menu opening, a chest opening, a level-up, the moment an enemy is provoked. The player expects a beat there, and one load is invisible.
- Never per frame or per hit. Loading from a tick or a damage path is a path lookup every call at best, and a disk read at worst.
- Cache what you load. Keep the loaded pointer (or the async handle) while it is in use, so the second call is free and garbage collection does not unload it mid-fight.
The XenoCore Content and XenoCore UI project settings already work this way: each setting is resolved the first time it is read and then kept. Editing a setting clears the cache.
Fails silently in the editor: caching an asset on a settings object
For programmers. In a cooked game, settings objects created at startup sit in a memory pool the
garbage collector never walks, so an asset cached on one is not kept alive by it. The editor never
shows this. A Development build halts on the title screen ("breaking Disregard for GC
assumptions"); a Shipping build frees the asset under the pointer. XenoCore's own caches go
through XenoCoreSettingsCache::KeepAlive; do the same in any settings class you add, and test
a packaged build.
Ticking¶
- Prefer events and timers. React to a gameplay message, a delegate or a timer. Most "is it time yet?" checks run perfectly well every 0.2 to 0.5 s.
- Throttle what must poll. XenoCore's own polls run on intervals: the enemy's label decides
whether to tick every Label Tick Check Interval (
0.4s), and the environment director refreshes brightness, fog and sky colour at most every 0.1 s. - Turn tick off when idle. A widget or component with nothing to do should stop ticking and start again when something changes. Enemy health bars and labels do this.
- No per-frame searches. Get Component by Class, Get All Actors of Class and similar iterators belong in Begin Play or behind a timer, with the result cached.
Fails silently: an Event Tick that only calls its Parent
A Blueprint whose Event Tick (or Event Blueprint Update Animation) is wired only to Parent: Tick does nothing extra, but it still makes every copy run a Blueprint call each frame. Nothing warns. Delete the node; the parent's tick still runs without it. A greyed-out Event Tick placed by default and left unconnected costs nothing.
Measuring¶
What an asset drags in. Right-click an asset in the Content Browser and choose Size Map: it shows everything the asset loads with it, by size. Reference Viewer (same menu) shows which property holds each reference. Compare a whole play session rather than single assets: one asset's cost overstates the saving when the map or the party loads the same content anyway.
Which Blueprints tick. Open a Blueprint's Event Graph and look for a connected Event Tick; see the warning above.
For real numbers, in Play In Editor or a packaged build, open the console:
| Command | Shows |
|---|---|
stat game |
Time spent in game-thread tick, per category |
stat unit |
Frame, game, draw and GPU time |
dumpticks |
Every registered tick function, enabled or not |
memreport -full |
Memory by class and asset, written to Saved/Profiling/MemReports |
Unreal Insights (run with -trace=default) shows which ticks and loads cost what, frame by
frame.
Looked at and kept¶
These came up and were deliberately left alone, so there is no need to look again:
| Candidate | Why it stays |
|---|---|
| Body Meshes on the party base classes | The template pawn already loads both Mannequin bodies, so a soft reference saves nothing |
| Member Class on party member definitions | Every starting member spawns at New Game, and the party and progression screens read benched members' class defaults |
| Lyra's fonts | Already set to Lazy Load |
| Gaur Plain's map size | The fix is converting the map to World Partition, not trimming references. See Build a world-partition map |