This guide presents a Game Pauser blueprint mod for Hogwarts Legacy and describes how it works.
Beginners should read the previous tutorials before this one. They can be found here:
I had intended to published this mod on Nexus… but then I discovered that there already is one! Oops!
Oh well, it will still provide some useful tips on creating Blueprint mods so here goes…
If you haven't read the first article in this series (Blueprint Example 101 - Hello World) please do so now. The 2nd & 3rd would be wise too.
In particular, I'm going to assume that you:
The first step is always to plan the mod:
In this case the answers to those questions are:
Easypeasy.
How do we pause the game? A little googling revealed:
An interesting YouTube Video
Some judicious use of findstr on the CXXHeaders then confirmed:
So there's a Function called SetGamePaused that returns a Boolean (presumably success or failure) and takes a Boolean input called bPaused. So when bPaused is TRUE that will presumably pause the game and when it's FALSE that will presumably un-pause it. The fact that this Function is in Engine.hpp tells us that the Function is provided by Unreal Engine and isn't part of the game-specific stuff added by the HL game devs. Hopefully the game devs didn't do anything to break it!
I'm assuming you've had enough of creating your own copies of my Blueprints now, so I'll just describe what the Blueprint is doing. You can download the umap file using the link below. Remember to rename it to MyGamePauser.umap (noting the uppercase letters) and put it in PhoenixUProj\Content\CustomContent\.
Download the Level Blueprint from here.
First we'll review the Event Graph and then work our way down the list of Functions on the left:
As usual the Event Graph is quite simple. That won't always be the case though. As we discussed in Tutorial #103, anything that involves user actions has to be processed via Events. So if/when we start doing that the Event Graph will fill up pretty quick. But this one just contains:
Event BeginPlay which triggers when the mod is loaded, and just calls our MyInitialize Function.Keyboard Input Event called Pause which triggers when the Pause button is pressed. This just calls a Function which I've called MyGamePauser. Note (see the big red arrow on the right) that you must tick the Execute When Paused tick-box otherwise this button will be ignored during pause and you'll never be able to un-pause!Delay node which does nothing. We'll come back to that. But to obtain a multi-line comment like this use SHIFT-ENTER for newlines.You will notice, however, that there's a MyDisplayMessage node which is greyed-out. That's a node which I have disabled. Disabling nodes is a really useful (but largely unknown) feature. Rather than remove a node which you don't want included in your mod right now (and have to reconnect all the other nodes to compensate) you can simply disable it. But to do so you need to set up some hotkeys. Proceed as follows. Select Edit > Editor Preferences:
This will bring up a new window, in which you should search for nodes:
Straight away this has revealed some super-useful information! There are keyboard shortcuts for some commonly-used nodes!
Get an ArrayBranchDelayDo NDo OnceFor-Each LoopGateMulti-GateEvent BeginPlaySequenceYou can change these key bindings if you like (or you can add new ones by editing UE4HL\Engine\Config\BaseEditorPerProjectUserSettings.ini).
But right now we're interested in the section lower down called Graph Editor. The bindings will all be blank initially, but change them as shown below:
Now if you select a node and hit those hotkeys:
Alt-# will make the node Disabled.Ctrl-# will make the node Development Only.Shift-# will make the node Enabled again.Pretty cool eh? One thing to note, however… The Print String node is marked Development Only by default. Using Shift-# will change that to Enabled… However, the compiler is hard-coded to ignore it, so it will still be stripped out of Development Builds. Annoying!
Some parts of this are the same (as in Tutorials 101 & 103) but some things have changed:
Notes:
Sequence node. Without the Sequence node this would be one long chain of nodes, which would be unreadably small if I tried to squeeze it onto one screenshot. Instead I've split it up into three chains, but IT IS VERY IMPORTANT TO NOTE that the Sequence node DOES NOT WAIT for one chain to complete before it begins executing the next one. You should treat a Sequence node as if it were executing all chains simultaneously!Variables UIManager and PhoenixHUD are set, like before.Variable UIBPErrorMessage is also set. This was previously done inside the DisplayMessage Function but it only needs to run once so this is a better place for it.Initialized Variable is set, as before.Duration input (which defaults to 2 seconds). How is this possible when the HermesDisplayErrorMessage inside DisplayMessage didn't have a Duration input? We will see… For now, please note that I have changed the Function name to MyDisplayMessage. I'll be adopting this convention from now on - all my Function, Event and Macro names will start with My. I think this is helpful because a) it makes it obvious what's Custom and what's from the Game, and b) it makes searching for my stuff super easy!MyDisplayMessage to always be Enabled because I think it's helpful to tell the user that the mod has actually loaded. That way, if it doesn't work, they know that it's not because it failed to load!So I've made some changes to Darkstar's DisplayMessage Function. This is largely because I discovered that there's a DisplayErrorMessage Function that does the exact same thing as HermesDisplayErrorMessage, but it allows you to specify how long the message stays on-screen. It's even defined in the same game file so I didn't have to create a new dummy asset for it.
Notes:
Sequence node to keep things compact, but I SHOULD NOT HAVE DONE THIS! Please make it a single chain in your own version.DisplayTime then set the Text Block to “not translatable” (the tick-box is unticked). This is so the text won't appear in [brackets].Duration is 2.0 seconds. However, if I accidentally set it to zero or a negative number then no message will appear. I have therefore put some logic in the top branch to set it to 2.0 if the input value is less than or equal to zero.Rather than have the following appear twice inside MyDisplayMessage I stripped it out as a separate Function:
I think this is unchanged, except that I've re-drawn it in my style. (Apologies to Darkstar if my style horrifies them!)
If you google how to toggle something on & off in Unreal Engine you'll find lots of websites and Youtube videos that tell you to use a Flip-Flop node. That's fine when you're in control of all the code, but when you're using the Flip-Flop to call a game Function which might fail (for reasons unknown) you can't be sure that the Flip-Flop will remain aligned with the effect you're trying to produce. In this case, the Flip-Flop might think we're paused, but the game is not paused. We have to be careful about that sort of thing and plan for it. In this case I went for the following instead of a Flip-Flop. It's more complicated, but more robust:
Sequence node but that is a mistake. Please make this one long chain in your version!Function makes no assumptions about whether the game is paused or un-paused.PhoenixGameSettings. (This should be moved to MyInitialize really.)PhoenixGameSettings. I found the SetSoundMuted Function in PhoenixGameSettings but there's no Function to fetch PhoenixGameSettings, so I checked the parent UserGameSettings, and found a Function called Get GameUserSettings that could get one of those. Since there's probably only one of these I Cast it to PhoenixGameSettings in the hope that it would work and it did. (Remember - all Children of a Parent Class also count as members of that Parent Class. For example, Goblin Assassins are all members of the Goblin Parent Class.)Select node for this. Select nodes are great for choosing between things based on a Variable's value.And that's it. That's the whole mod.
Here's how it looks in-game:
It may surprise you to learn that this mod did not work straight out of the box. For several hours I was able to pause but I could never un-pause. So I designed a version of the MyGamePauser Function which would use a While Loop to keep retrying (up to 100 times) until the state successfully changed.
But the problem turned out to be elsewhere. In the end I figured out that the problem was a delay node I'd added (to allow me to read some debug info before it was over-written by later debug info). When the game was paused this delay node never completed, so the game effectively hung. That's why there's an orphan delay node on the Event Graph with a comment saying “DO NOT USE delay nodes when the game is paused. The delay will never end.” Anyway, the MyGamePauserWithRetries Function is shown below. It's not used in this mod but it might be useful (to me or someone else) in future. I have used a Sequence node but that is a mistake. Please make this one long chain in your version!