One thing that's not entirely obvious when starting with Unreal (well, to me, at least) is the difference between hard and soft object pointers.
When I started feeling my way around the engine, I followed the scaffold used by Epic in Shooter Game (a predominantly C++ FPS). Roughly: base class everything in C++, add actor components and set them up in the constructor, and use structs to group properties & tweakables. Then, derive a blueprint if and when you need it.
I still, basically, work this way today.
Obviously I knew of weak pointers as a C++ thang and skimmed the UE4 docs about the templated types, but I was using them sparingly in UE code and basically never in Blueprints... Not at first, anyway.
So the non-obvious bit about hard references, at least until you start digging into performance, is they often cause whatever they're pointing at to be loaded. A simplistic example: I spawn in Actor A, and it points to Actor B (for some reason), which has a load of large Audio and Particle effects in it; well, Actor B and its large bits are gonna get loaded, as well... Maybe not too awful at runtime, but it can cause a problem if your Game Instance has a hard pointer to something which has a hard pointer to something... and ends up pulling in half the game during the initial load (TRC fail), or while -- ahem -- you're streaming levels...
On the face of it, it's relatively easy to avoid, but there's more subtlety to it. For example, casting in a blueprint also acts as a hard-object reference, so you can accidentally create a chain while casting in an "unrelated" Blueprint. On the plus side, cos casting's a little tricky to avoid; if you've been making base classes in C++, like wot I have, then you can cast to the C++ class "for free", because the C++ module is already loaded.
Anyway. As the preamble's given away, I've been reviewing the project and looking at my hard-object references. Why? To get rid of a spike while streaming levels that's been visible on my PC and, more importantly, on the Steam Deck.
There's a convenient tool in the editor called Size Map. Right-click on any asset, go to Size Map and look on in joy (or horror) at what else the asset pulls in with it.
Yup, all those particle effects and audio cues I was spawning? Hard-object pointers, in a struct, in C++.
Holy shit-balls, the Game Instance is pulling in the whole UI, and the Dev Menu alone is pulling in 400 MB of Zone Controller because it's casting to a Blueprint whatthehellisthisuttermadness... You get the idea.
So I've gone through the bulk of the project, checking Size Map, digging through the reference viewer, and cleaning up.
It wasn't awful! I'd been good for the most part. But occasionally I'd find something utterly crazy, like all the foley samples I'd attached to the player that were 192khz... Or the Dev Menu pulling in half the overworld because of a solitary button that was casting to a level blueprint.
Result? Loading times on the Deck are improved, and the editor loads a little faster, but I'm stuck with a 3 frame overrun during streaming, which for the life of me, I can't get rid of. I can see it in insights, but I can't work out how to drill down and find out what's being added to the World and causing the problem. Hey Ho. I'm sure that'll bug me forever.
Anyway, it's something to bear in mind, as it's an easy trap to fall into. In fact, next time I teach UE I'll make a long detour and try and explain this whole thing.
There's some good info in this video, if you want to go down the rabbit hole: