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 Array
Branch
Delay
Do N
Do Once
For-Each Loop
Gate
Multi-Gate
Event BeginPlay
Sequence
You 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!