Warning: long post ahead! My apologies in advance…
After quite a few months of working on a fairly big BP-based project, I’ve come to notice a few blueprinting patterns that really should be avoided, because they can potentially cause anything from minor annoyances to critical, project-threatening problems.
I thought it might be a good idea to share my findings here, so others can avoid these particular issues.
If you have bumped into something BP-related that simply turned out to be a really, really bad way of doing things for whatever reason, then by all means add your experiences to this thread!
UPDATE (October 24, 2014): THE INFORMATION BELOW RELATES TO VERSION 4.3 (that’s old by now)
Epic has made lots of changes related to linking and circular dependency checks et cetera since then, so these caveats may not apply in the current version (i.e. 4.5.1 as I’m writing this addendum). Further studies are needed! Test things yourself. If you have results that contradict or confirm the notes below, please post about it.
Pitfall: Custom BP-class arguments to BP interface functions
The scenario is this: You’ve got some Blueprint class, lets call it ThePawn_BP, and it’s a custom class based on the built-in pawn.h class. Then you have created an interface, TheInterface_BP, which declares a bunch of functions. One of these functions is called DoSomething, and it expects a ThePawn_BP object as one of its input arguments.
All fine so far. Now you implement this interface in one of your other classes, ItsATrap_BP, and you set up the DoSomething functionality there. It gets ThePawn_BP input object, and as the name suggests it does something with the pawn-derived object, and everything is lovely. You playtest and ItsATrap_BP::DoSomething actually does exactly what it’s supposed to do, whatever that is. Great.
You save your project, quit the editor and take a break for lunch.
Around 13:05 you crawl back to your workstation and open the project again. You press play and soon notice that ItsATrap_BP::DoSomething has stopped working, or at least it no longer does anything at all. You suspect someone renamed it to DoNothingAtAll, and start making plans to “interview” your neighbours about it, but first you open up ItsATrap_BP to have a look at the DoSomething implementation. It turns out that the wire between the input node’s ThePawn_BP object has disappeared, so it’s no longer connected to the rest of your nodes in that function. You reconnect it, playtest, and it works again.
The problem is that the wire in question will get disconnected every single time you save, exit and reload this project. I’m not saying that this is guaranteed to happen, but it’s fairly likely.
SUMMARY: Custom BP object arguments to BP interface function implementations are unreliable.
SOLUTION: Always use built-in classes as arguments to interface functions. Do not use custom BP arguments. In the example above, the solution is to make DoSomething take a Pawn or Actor argument instead, and then you cast to ThePawn_BP inside the function implementation before doing whatever it is you’re doing.
Death trap: Casting to custom BP-objects in MacroLibrary macros
The pitfall above is a “friendly” kind of problem, because it doesn’t disable your project in general, it just silently fails to do something, repeatedly, until you fix it, so it’s not a big deal. The death trap I’m about to describe now however, is quite a bit more nasty, because it can cause all sorts of erratic in-editor crashes or even prevent you from opening the project at all, forcing a roll-back to a previous revision and very likely a pending overconsumption of migraine medication.
Here’s the scenario: You have a custom player controller class, ThePC_BP, based on PlayerController.h as per usual. In this class you have a function called MakeStuffHappen, which makes stuff happen. It works fine.
In your level there’s a small zoo of different custom blueprint actors that sometimes need to call ThePC_BP::MakeStuffHappen for whatever reason. These may be stand-alone blueprints derived directly from Actor or Pawn, or maybe “second generation” child-blueprints derived from some custom base class that you have created.
At any rate, to call ThePC_BP::MakeStuffHappen, they need to call GetPlayerController, then cast the reference to ThePC_BP, and then they can call the function in question.
After a while you notice that this little chain of nodes shows up in lots of places. To clean things up, you decide to create a Blueprint MacroLibrary, in which you create a macro called GetThePC. It simply performs that little sequence of calls and returns a properly cast ThePC_BP link along with two execution pins, one for Success and one for Failed (in case the cast didn’t work for some reason).
You start placing this macro everywhere in your Blueprints where you need a link to ThePC_BP, and it seems to work fine, and your graphs are much cleaner and easier to read. Awesome.
Now here’s the kicker: at some point, maybe today, maybe next week, maybe when you place the thirteenth instance of that macro somewhere, maybe when you create a new child BP based on a BP that uses the macro - your project may suddenly fail to open. It will just crash on launch. Or, if you are lucky, nothing bad happens at all. If it goes south, a roll-back to a previous revision seems to be the only way to get back on track.
I have tried to create a minimal reproduction case for this particular problem, but so far it’s just completely random.
SUMMARY: Using a macro in a MacroLibrary to cast something to a custom BP class is potentially catastrophic.
SOLUTION: Just don’t do it. Come up with another way to do whatever it is that you are doing.
The common factor in both these problems seems to be related to the use of custom BP classes/types as opposed to built-in classes and Plain Old Data. Similar issues seem to arise when using BP FunctionLibraries, in that such functions appear to work fully reliably only when they are set up to perform operations on built-ins or PODs, while any use of custom classes (playercontroller, HUD, …) can cause various nasty effects.
Without knowing the exact procedures involved when blueprints are loaded, evaluated and compiled, I can only guess that the core issue boils down to circular or out-of-order dependencies. A macro tries to use a BP class it doesn’t yet know about, or a BP has some calls evaluated while the call target doesn’t yet exist…
It’s probably very easy to create these situations inadvertently while building blueprints on the fly, so to speak, without any well thought-out plan for the overall architecture. The nasty thing is when the problem doesn’t show up until the next time you reload the project.
When writing C++, we often make sure that, if necessary, class A knows about class B in advance, by employing things like forward declaration or just enforcing overall sanity simply by the order in which different classes and functions appear in the code file. In Blueprinting, there are no such mechanisms in place (as far as I know at least) and we mostly have no idea about in which particular order various classes are initialized when the project builds/loads. This can sometimes make it difficult to predict when a particular BP pattern is treading dangerously close to the abyss.
Anyway, some of these issues have appeared as reports on the AnswerHub, in one form or the other. I think the interface-related issue is sort of known, but I’m not entirely sure. The project-killing issue is less clear. I’ve seen a few reports that may or may not be related to the example I’ve given here, and the problem is further complicated by the fact that it’s extremely difficult to create a minimal case that reliably reproduces it. And without that, it’s very hard for the developers to fix.
And finally, if you had asked me about the project-killer scenario a week ago, I would have said it was a myth. It is so weird, you’ll just assume it’s some stupid user error that would obviously never ever happen to you… It is, however, more diabolical than that.