Part 22 - Fourth Polishing
April 11, 2019

The game at this point is getting more, let's say, presentable! Still we have a few really necessary things to be done! Although 3 of the last 4 parts were dedicated to polishing, we are going into even more. In this part we will finally include the "help window" sub menu. Then we add some confirmation dialogs, specially before quitting the game. Finally, we work on two minor details regarding the behavior of the game itself. First by adding a countdown before starting the game, so the player can get ready.

Help Menu

We have a multitude of options to create the help menu, from static images with basic instructions to a fully interactive system. For the first case to work, the images have to be very well done, by an artist. Since I'm not one, I have to go to another option. The other side of the "spectrum" is the fully interactive system. Doing so would probably require an entire tutorial part (if not two) just to finish it. Rather, we will use the widgets and the animation system provided by UMG. This will still require quite a bit of work to finish so let's get started.

First, we have to plan what we want to show in the help menu. Basically we want to display the commands and the objective. For the commands we have move left/right, order rotation up/down and "accelerate". As for the objective, we have to show the valid matches, which are horizontal, vertical and both diagonals. Since the loosing condition depends on the game mode we won't add this information in the help menu. That said, we will first try separating the information into two different pages, one for the movement and the other for the objective. If the layout works we keep that way, otherwise we tweak things and spread the information throughout more pages.

There is already one widget we have created to display the help menu, UI_Help, although it's empty. Let's fill it in! Begin be deleting the Canvas Panel and then adding the typical vertical box which, again, will allow us to "fill" the entire space allocated for this widget. Next add a border, set its Size property to Fill and copy the brush from any of the "background borders" of any of the other widgets we have created early. By doing so we ensure the readability of any text as well keep some consistency with the UI visual. Inside of this border add a Widget Switcher and rename it to HelpPageSwitcher. Now add two Vertical Box widgets into the switcher. The first one will be used to display the commands and the second one the objective. So, if you feel like, rename those boxes to better help identify those widgets. For reference, I have named them CommandsBox and ObjectiveBox, respectively.

Side Movement

Add one Editable Text (Multi-line) widget into the CommandsBox and set its Text property to something like Sideways movement of the piece can be controlled like this. Early in the tutorial, when designing the UIGM_Traditional I have explained the reason for this widget rather than a text widget. As a recap, we will probably need multiple lines of text and the Text widget gives some visual anomalies when the widget is first displayed on screen. We can avoid those with the editable text. However, we don't want the text to be editable, so we have to enable the Is Ready Only property. Increase the font size to 22 or so, because the default size makes the text very small.

Bellow this text add a vertical box and rename it to SideMove so we can easily identify it later when dealing with the animation (also I can reference it through the text). Set both vertical and horizontal alignment to center. Inside this box add 3 UI_BlockImage widgets. Change the Size property, under the Default category (that is, the variable we have created within the widget), to 64 so the displayed image is reasonable visible. Then, under the same property category, change the Type ID so one of the widgets is 0, the second is 1 and the last is 3. With that, each UI_BlockImage will show a different block from the selected theme.

Next, bellow the SideMove, we will display 4 icons, representing the button bindings for the horizontal movement. In this case, A, D, Left Arrow and Right Arrow. We will use an image for each one of the 4 buttons. For reference, the ones I have used:

Key Icons

Since those textures are meant to be used within the UI, after they are imported into the project we have to setup them accordingly, that is Mip Gen Settings = NoMipmaps, Texture Group = UI and Compression Settings = UserInterface2D (RGBA). The idea is to display the A and the Left arrow icons with one " or " text between then. Then an spacing and D or right arrow icons. To do that, add one horizontal box bellow the the SideMove box and set its horizontal alignment to Center. Into it add, in this order, Image, Text, Image, Spacer, Image, Text and Image. Change the Text property of both text widgets to " or " and set the vertical alignment to Center. Select the spacer and set its X = 64.

Now to the images. Rename them to something like imgIconA, imgIconLArrow, imgIconD and imgIconRArrow. Then assign the corresponding textures into them. Finally uncheck the Is Enabled property of all 4 image, which will make them look greyed out. Shortly we will manipulate this property through the UMG animation system to highlight the key press.

Now let's prepare the animation. Locate the Animations panel and click the green button + Animation. By doing so, we now have a new animation asset within the widget which we will be able to reference from blueprint a bit later. Rename this animation to Anim_SideMoveHelp.

Animations Panel

Clicking Anim_SideMoveHelp will "enable" the Timeline panel, allowing us to add/remove keyframes to a lot of the properties of the widgets. There are two ways to add tracks/keyframes into the timeline.

  1. Click the green + Track button and then selecting a widget we want to manipulate through the animation. Once the widget is added as a track, we can click the + button next to its name and select the property that we want to change. Once the property is added under the track, it's possible to click some small buttons next to it in order to manipulate the values and add/remove keyframes at the selected time.
  2. Click a tiny + button that is added after a property name and before the property's value. The icon looks like this: . Yes, it's small (and I didn't change its size). This option is very straightforward because it automatically adds the track and the property under it.

How you will proceed from here is totally up to you, although I will describe my steps. I found it easier to first add all the tracks/properties using option 2 and then a mix of the two options in order to manipulate the property values through the timeline. For the Anim_SideMoveHelp animation we will want to manipulate the translation of the SideMove vertical box and the Is Enabled property of the four images. That said, I have selected each of those widgets and clicked that tiny icon next to the mentioned properties, making sure the "current time" was at 0.00. This is how the timeline looks like after adding the tracks and the properties:

After an small delay, say 0.10 seconds, we enable the two buttons for the left movement. To do that, move the "playback bar" to the 0.10 position and then we can directly manipulate the Is Enabled property of the imgIconA and imgIconLArrow images. Clicking the corresponding checkbox within the track editor will automatically create a keyframe at the current time position which, again, should be 0.10. Doing so results in this:

With another small delay, say 0.05 seconds, we begin the translation of the SidMove widget. With the current translation (X = 0, Y = 0) we add a keyframe at the 0.15 time position. Then, at 0.30 time position we disable the image widgets again. Finally, at the 1.00 time position we set the SideMove translation to X = -60.0 and add the keyframe. The result of those edits looks like this:

Now we perform the "reverse" animation direction, that is, give an small delay to enable the "right buttons", and move the SideMove widget back to X = 0.0. The result of those ney keyframes looks like this:

Let's repeat the "movement to the right", so the translation of the SideMove widget stops at X = 60.0. Following the exact same logic we shortly enable the two "right buttons". The timeline will look like this:

Finally, we move to the left once again so the SideMove widget goes back to X = 0.0. Since 4.0 is our last keyframe, we have to add the corresponding keyframes for the properties of all the tracks, which results in this:

This timeline has been setup in a way that we can seamlessly play it in a loop. Still, we have to "tell" UE to actually play it! To do that, we open the graph editor of the widget and, from the Event Construct we call the Play Animation node. It requires an animation asset, which we do have, the Anim_SideMoveHelp and can be found under the Animations category in the variables list. It can be directly plugged into the In Animation input of the Play Animation node. We also want the animation to play in a loop, meaning we have to change the Num Loops to Play input. If we set this value to 0 then the animation will endlessly play. The graph looks like this:

In-game the animation looks like this:

Side Move Help Animation

Order Rotation

Now we add the UI (and animate it) to instruct how the block order can be changed in the player piece. We will follow very similar strategy of placing a text (editable text multi-line widget) like And you can change the block order within the piece like this, a vertical box to hold 3 UI_BlockImage widgets and a "row" containing 4 image icons plus spacer and the or texts. Since we will directly change properties from the blocks rather than their container, rename each of the UI_BlockImage widgets to something like block1, block2 and block3. The button icons can be named to imgIconS, imgIconDArrow, imgIconW and imgIconUArrow. All of the new widgets follow somewhat the same hierarchy of the previous ones and all of them go directly into the CommandsBox vertical box.

Then add a new animation called Anim_BlockOrderHelp. Once created, add the tracks into the timeline so we can manipulate the Is Enabled property of the four icon buttons and the TypeID of the UI_BlockImage widgets. But, wait, there is no button to add a keyframe for this property. The reason is because we have created this property and didn't enable the possibility to use it with Matinee or Sequencer. This is fairly easy to "fix". Open the UI_BlockImage widget and, in the graph editor, select the TypeID variable within the variable list. With that, changing its details we can enable the Expose to Cinematics property. Its tooltip reads as this:

Should this variable be exposed for Matinee or Sequencer to modify?

We indeed want that so once enabled, compile the widget and then we will be able to change this value throughout the timeline in the Anim_BlockOrderHelp animation. The idea is to show the "down rotation" three times and then the "up rotation" another three times. I will not describe the process to change the Is Enabled property mostly because it's the same we have done before, only this time we "enable/disable" each pair of icons 3 times. As for the TypeID property, follow the table bellow:

Keyframe Timeblock1 TypeIDblock2 TypeIDblock3 TypeID
0.00012
0.15201
1.15120
2.15012
3.15120
4.15201
5.15012
6.00012

The time line looks like this:

Sure enough, we have to play this animation through the blueprint graph:

And then, in-game the animation looks like this:

Block Order Animation

Fall Speed Increase

Lastly for the "commands page" we add another Editable Text (Multi-line) widget. This is solely to inform about the possibility of increasing the fall speed of the player piece. For reference, the Text value I have entered is Press spacebar to increase the fall speed of the piece. The overall look of the help page now looks like this:

For now ignore the fact that it seems there is plenty of empty space bellow the last information text. We will use it shortly when adding the buttons to change the help pages.

Objective

And speaking of help pages, now we can work on the second page, the one that will showcase the objective. Again, more specifically, make it clear about the possible combinations, horizontal, vertical and both diagonals. The plan is to have a text like "Your objective is to position the falling piece in order to match at least 3 consecutive blocks, like shown bellow". Then after that a few images giving examples of the possible match directions.

We already have the vertical box meant to be the second help page, named ObjectiveBox. That said, add one Editable Text (Multi-Line) just so the mentioned text can be entered. Setup it like the previous texts (bold, size 22 and read only).

Next comes the images. We want 4 "sets of images", one to display the horizontal match, the other for the vertical and the last two for the diagonals. We will create 9 images for each "set". Although the vertical example could be composed of only 3 images, we will use 9 just for visual consistency. Those images are meant to be displayed as grids of 3x3 and we will display two grids per "row". In order to help us with the layout we will use the Uniform Grid Panel widget, four of them to be more exact. So, in order to place "two grids per row", we add two horizontal box widgets (set each one to center horizontal alignment and padding to 32) into the ObjectiveBox and then two uniform grid panel widgets into each of those new boxes. Rename the grid panels to GridHorizontal, GridVertical, GridDiagonal1 and GridDiagonal2, respectively. Select GridVertical and GridDiagonal2 (the second grid of each "row") and set the left padding to 64. Careful here to not set the Slot Padding property that is under Child Layout category. We want to change the padding that is under the Slot (Horizontal Box Slot) category so the two grids have some space between them within the same "row". This padding will space out each of the grids.

Now add 9 UI Block Image widgets inside each one of the grid panel widgets. Yes, that's a lot of new widgets. Now, whenever you select one of those images there will be two new properties under the Slot category, Row and Column. Leave the first block image with the default values and then edit the rest so the widgets get spread throughout the grid. In order to make things simpler I have renamed all of those images, prefixing them with imgHoriz, imgVert, imgDiag1 and imgDiag2, respectively, and finishing with R[row]C[column], substituting the [row] and [column] with the proper indices.

As with the other block image widgets, set the Size property (the one under Default category) to 64. The TypeID is completely up to you, just make sure to create the correct matching runs within the corresponding grids. To make things clearer the following tables show the name of each block image as well as the corresponding Row, Column and suggested TypeID properties.

NameRowColumnTypeID
imgHorizimgVertimgDiag1imgDiag2
[prefix]R0C0000121
[prefix]R1C0101033
[prefix]R2C0202200
[prefix]R0C1013312
[prefix]R1C1111320
[prefix]R2C1210331
[prefix]R0C2022230
[prefix]R1C2121103
[prefix]R2C2223022

The idea with those values is to make the central row, central column and the diagonals have the matching sequences while the surrounding blocks seems random. The result looks like this:

Still, this static image may not seem very clear at first and will definitely require some attention. Let's add some "blinking" in order to highlight the matched blocks. That said, create a new animation and name it Anim_BlinkingHelp. We will manipulate the Visibility property of the relevant blocks throughout the timeline. Since this will be relatively simple we will perform this highlighting on all 12 blocks at the same time. In other words, add the Visibility track to the Anim_BlinkingHelp animation for each of the corresponding block images (imgHorizR1C0, imgHorizR1C1, imgHorizR1C2, imgVertR0C1, imgVertR1C1, imgVertR2C1, imgDiag1R0C0, imgDiag1R1C1, imgDiag1R2C2, imgDiag2R2C0, imgDiag2R1C1 and imgDiag2R0C2). By default, at time 0.00 there should be a keyframe set to Self Hit Test Invisible for each of the image blocks. Now move to the time 0.25 and set the property to Hidden, meaning that the widget will be hidden but will still take layout space, which is what we want. Finally, at time 0.50 go back to the Self Hit Test Invisible. The timeline looks like this:

The construct script must be updated in order to play this new animation:

And a preview, of just one of the grids:

Blinking Animation Preview

Page Change

Currently there is no way to change the help pages other than "hard coding" the active page within the widget switcher (which is what I have done in order to test the layout). We have already done layout and functionality to perform the exact same thing we want to do here. More specifically, the buttons used to select the desired game mode. This means, open the UI_ModeSelector widget and copy the border containing the previous and next buttons. After that, paste the contents after the border containing the widget switcher in the UI_Help. The result is that the vertical box should contain two border widgets. We don't need to keep track of the page indexing so delete the text widget that is in between the two buttons. To keep the next button in the correct position, set its Size property to Fill and then right horizontal alignment.

Now to the blueprint graph, handling the click event of the two buttons. In this case we will perform the logic a little bit differently, still with a "generic" algorithm so if we have to add any pages to the help menu, the buttons will still work without any additional change. On both cases we will use the modulus operation, which gives the rest of a division. When clicking the next button, we simply add one to the current active index (HelpPageSwitcher.GetActiveWidgetIndex) and then obtain the rest of the division by the total number of "pages" (%). By doing so, the overall result will "warp" once going past the last page. The previous button requires a little bit more explanation. The problem is that if we directly use the modulus operator the result will not work when the current active index is 0 because it will go to a negative number. So, before performing the modulus we add the number of pages to the result of active widget index - 1. The blueprint graph following this logic, both buttons:

Then the little "current page / number of pages" text in between the next and previous buttons. Create a new binding to the Text property, naming the created function GetPageLabel or something like that. As we have done in the other helper widgets, we will use the Format Text node in order to place the numeric values representing the index of the page and the total number of pages. Now, remember that we get a 0-based indexing and some people may be confused by that, so we add one to the result of the GetActiveWidgetIndex:

The Blocks - Theme

We now have a fully functional help screen, with some animations to help describe how to play and the goal of the game. Still, there is an small problem: If you change the theme, coming back to the help screen will not reflect that in the shown blocks, although the background image will be the correct one. The problem is that we have cached the game theme data within the UI_BlockImage widget and haven't done anything to update that.

The "problem" we face now is when will we update the cached value? Surely we could handle the Tick event and at every single frame update the theme, but that entirely defeats the purpose of caching the theme data. Better would be to have an event that we can handle and update only when necessary. Unfortunately there isn't an out of the box event that we could use so we will have to create that ourselves. Fortunately, it's very easy to do so. Every time we change the theme a function named SaveGameTheme, which is in the BP_GameInstance, is called. We can add a new node into the SaveGameTheme flow, which is call an event dispatcher. Then, from wherever we want to respond to the game theme change, we obtain the game instance object and bind to the dispatcher.

All that said, create a new event dispatcher in the BP_GameInstance class, calling it GameThemeChanged. After that, just add a call to this event after the theme change is saved into the save object:

Then, we have to update the UI_BlockImage to bind to this event and update the cached value.

And that's it. Changing the theme now will "propagate" the change into the UI_BlockImage widget and the help menu will correctly display the blocks that correspond to the selected theme.

Confirmation Dialogs

In applications it's a common procedure to ask the user if he/she really wants to exit when the quit button is clicked. There are also numerous situations where it would be very nice to have some kind of confirmation from the user. The confirmation dialog is often implemented as a modal dialog, however Unreal Engine does not have this kind of widgets. The usual work around this is to create some kind of overlay widget that consumes all user input without "bubbling" them into the other widgets and gets hidden when one of its buttons is pressed.

The overall idea here is to create some sort of dialog container that will be used to correctly position and size the dialogs within it. Each contained dialog will be, by default, set to collapsed, meaning it will not take any layout space and will be hidden. Once the dialog is needed, the blueprint graph will set it to visible and further interaction will be performed from within the dialog itself.

The Dialog Container

Because we want the dialogs to correctly occupy the window space allocated to the game itself, or in other words the same background image space, we will confine all dialogs inside overlay containers, one container per "main widget" (in our case, UI_MainMenu and UI_InGame). This overlay will be directly placed within the canvas panel of the main widgets and will be resized and repositioned according to the space occupied by the background image. So, first add one overlay widget directly into the canvas panel of the UI_MainMenu (later we will work on the pause menu too). Rename the overlay to something like DialogOverlay and set it to be a variable since we want to manipulate its size and position. Speaking of that, the size and position within the design doesn't matter (remember, we will procedurally change those).

And then, we have to set the overlay's size and position. We already have a function we use to perform this kind of operation on the widget switcher. This function is the Set Position and Size. Looking at the graph, all that is being done is taking the menu switcher as a canvas slot and directly setting the position and size. We can do the same with the overlay. The great thing is that we can use the exact same Set Position and Set Size nodes:

Now open the UI_InGame widget so we can add the same overlay to serve as dialog container. The function Set Layout we have create in this widget in order to correctly size and position things is a bit more busy! We could certainly incorporate the dialog sizing and positioning in there but it will only serve to increase the clutter even more. Rather, let's create a new function, SetDialogLayout, taking position and size as input, which are the exact same inputs we have used with the Set Position and Size in the UI_MainMenu widget. In other words, two input values of the type Vector 2D. The graph is almost the same:

Now we have to call this new function, right after we call the Build Widget and before the visibility of the widget itself. All of this occurs when the game window is resized and the visibility helps hide the resizing and repositioning operations (yes, those were already mentioned). The size input is Bottom Right - Top Left, as we have already done early. The graph becomes this:

Ask Before Quit - From Main Menu

The "major" problem that we have to solve here is not the widget layout/logic itself, but the when the dialog should be shown! Suppose we add code into the quit button so it displays the confirmation dialog instead and from the dialog itself we use the Quit Game node. This is a pretty straightforward way (and is somewhat how we will implement the system)! There is still one detail that must be taken in consideration, however. Alt+F4 or that little X button in the game window will completely bypass this dialog. Most games simply ignore this bypassing because the engine may not give an easy way of aborting the close process. And even when the possibility is present (which is true for Unreal Engine), the process is still not that simple. Yet, Alt+F4 is not a combination that the user will accidentally enter, meaning that it's not a problem. The X button is somewhat easy to mistakenly press. The good thing is that we can disable this button. To do that, Edit → Project Settings → Description and disable the Allow Close setting. All this does is disable that X button.

Just to picture the not so easy way to abort the close process, in Unreal Engine we have to bind a function to the event delegate WindowCloseRequestedDelegate, which is part of the UGameViewportClient class and is accessed by OnWindowCloseRequested(). If the event handler returns false then the close operation is cancelled. A modal dialog system is extremely convenient in this case because it halts the code execution meaning that we could directly return its "answer" in the event handler. Since we don't have a modal dialog system and must immediately return something, we end up needing a variable that is changed based on user input through the dialog. We have to default this value to false and then, after changing it to true when the user click "Yes, I'm sure I want to quit the game", "forcefully" call the Close() function again. Supposing this route is taken, we then have to properly design a system in order to display the confirmation dialog at the correct moment and "answer" to it, not to mention avoiding an endless loop of "show dialog → no clicked → show dialog...". There is one more detail that must be mentioned, which is a quote from the documentation written in the source code specifically regarding the mentioned delegate:

Delegate type used by UGameViewportClient when the top level window associated with the viewport has been requested to close. At this point, the viewport has not been closed and the operation may be canceled. This may not called from PIE, Editor Windows, on consoles, or before the game ends from other methods. This is only when the platform specific window is closed.

Return indicates whether or not the window may be closed.

Although those notes would not really be no factor when deciding if we should abort the window closing or not, it's important to know how this event works so we don't come to some unexpected behavior when working with it. As an example, if testing from PIE and we rely on this event to display the confirmation box, we will not get the dialog at all! To be perfectly honest, it's way too much trouble to try to intercept every single case of the game window being closed. Besides, the Alt+F4 directly closing the game provides a very fast way to exit in a hurry.

All that being said, disabling the X close button is entirely optional (personally I have disabled it). Ignoring the Alt+F4 should be considered a feature of allowing the user to quickly exit the game. So, let's create the dialog itself, through a helper widget named UI_ConfirmQuit. As usual, delete the canvas panel but this time don't directly add a vertical box. Instead, add a border widget and name it DarkenAll or something like that. The thing is, we will use this border to use the entire game window and make everything bellow it darker. So, set its brush to black with some transparency like alpha = 0.85 and set its Padding to 0.

Inside of the DarkenAll add yet another border named Wrapper, which will be used to "wrap" the contents and will somewhat represent the dialog itself. Select one of those borders wrapping contents in the UI_MainMenu and copy the brush settings, pasting into the Wrapper brush. Set the Slot Padding to 64 and the Content Padding to 0. Now we setup the alignment, which will provide two sections, Slot and Content. We want the dialog (the Wrapper that is) to be vertically centered and using the entire width (minus the padding, of course). To do that, Vertical Alignment = Center and Horizontal Alignment = Fill. Then we want whatever is inside the Wrapper (the contents) to be centered, both vertically and horizontally. Although changing the Content Vertical Alignment will not make any difference with the Slot Alignment settings, we set it to Center just for consistency. Of course, we change the Contents Horizontal Alignment to Center.

Inside the dialog (the Wrapper border) we want to display a text, maybe Do you really want to exit uColumns? and bellow it two buttons, yes and now. To do that, add a vertical box inside of the Wrapper box. Into the vertical box add one Editable Text (Multi-Line) widget, setting its font size to 22, checking the Is Read Only and setting the visibility to Self Hit Test Invisible.

Now we add one horizontal box to hold the buttons row then two UIC_TextButtons inside of it, renaming them to btYes and btNo. Set their label to correspond to the names. Set the padding to 24 on both buttons. At this point, their sizes are inconsistent, right? Wrap each one of them with a Size Box widget and override the Width setting to 120. Lastly we place the buttons centered within the dialog, by selecting the horizontal box and setting its horizontal alignment to Center.

From the preview it's possible the contents of the dialog are using a tiny space in the center of the wrapper border. That's because the vertical box is probably not set to fill the contents. So, select and it change both Vertical Alignment and Horizontal Alignment to Fill. After this, it should be clear that we need to add some spacing between the contents of the dialog and the wrapper border. To do so, add a padding to the vertical box itself, maybe 32.

Now let's add the functionality. All we have to do here is add blueprint graph to both buttons. The graph for each button is extremely easy and simple. For the btYes we have a single node, Quit Game. And for the btNo we simply hide the widget itself:

This dialog widget is done and now we have to use it. First open (if it's not already) the UI_MainMenu widget. Drag one UI_ConfirmQuit widget into the dialog overlay container we have created early and then set its visibility to Collapsed so it will be hidden and not use any layout space. Also, set both horizontal and vertical alignment to Fill, which will ensure the dialog correctly occupies the space we have calculated and set to it.

We have to update the Quit button's blueprint graph. Currently we are calling the Quit Game function node. This is now being done by the confirmation dialog itself which must be displayed when the btQuit is clicked. This is done by changing the visibility of the newly added UI_ConfirmQuit widget:

And then, this is how it looks like:

Now let's do something similar with the in game menu, UI_InGame widget. Drag the UI_ConfirmQuit widget into the dialog container, set its visibility to Collapsed and both alignment values to Fill. In the very same way we have updated the clicked event of the btQuit in the UI_MainMenu we do it here. The graph will be the exact same, so no need to add an image for it here.

Confirm Video Mode

Back in part 20, when we implemented the settings menu we have directly changed the video mode, despite the fact that I have warned that it's not the ideal. We have to incorporate some sort of a confirmation dialog in here. In this case, a timed dialog, that is, we set a countdown and if it reaches 0, the entire operation is aborted and the dialog is closed. Of course, we have to provide a button to abort the operation before the countdown finishes.

We have an small problem here, however. The UI_Settings is a helper widget that is used within other "main widgets". That means we can't use the exact same method we have used to create the "ask before quit" dialog because that strategy relies on the sizing and positioning inside the canvas of the parent widgets. The create dialog widget → add to viewport route brings the problem of not being able to correctly resize (and position) the dialog. In other words, we need means to access the dialog containers that we have added into the main widgets. To do that, we add a property into the UI_Settings that is meant to specify which dialog container should receive the confirmation dialog. Then, from within the main widgets, where we instance the settings widget, we can select that property and set the value to the corresponding overlay container. In the construct script of the UI_Settings check if the property is valid and, if so, we spawn a confirmation dialog and add it as a child of the container.

But then, why not make the confirmation dialog be a direct child of the container, within the main widgets? The reason for that is because the moment we want to display the dialog comes from an event that is handled by the UI_Settings. This specific event the the Apply button being clicked.

Ok, let's create the dialog helper widget, named UI_ConfirmVMode. The overall structure of the widget is almost the same we have done in the UI_ConfirmQuit, with the major difference being an extra text (a common one) widget between the editable text (multi-line) and the horizontal box holding the yes and no buttons. This text will be used to display the countdown value and it should be horizontally centered just like the buttons. For the functionality we want to confirm (and save) the video mode when the yes button is clicked. Conversely, when the time expires or the no button is clicked we want to revert the video mode change and hide the dialog. As for the message itself, maybe something like "Confirm new video mode?".

There are two different events that will require the exact same functionality of reverting the video mode. In order to avoid blueprint duplicated logic, we create a new custom event function named RevertVideoMode. Within it we first obtain the game user settings object that will be used to revert the video mode and then apply the resolution change. After that, set the widget visibility to Collapsed to hide the dialog and finally call the Reposition Game Window function to ensure we don't get any errors regarding the window position (remember we created this function early as part of the blueprint library).

Now, from the btYes clicked event, we want to confirm the video mode, save the settings, hide the dialog and then reposition the game window. Note the fact that we also have two common tasks. We will reuse the function nodes. The two buttons and the revert video mode graph:

Now we have to deal with the countdown. For that we will create a new float variable named Timer and another one that will be used as a reference, named Timeout. We will update the value of the Timer variable through the tick event, while its initial value will be given by the Timeout variable. We will also create an integer variable named DisplayTimer which will serve us double purpose. The first one should be obvious by its name, which will be the value we will display in the countdown text. The second is that we will use this variable to compare with 0 and, if so, we revert the video mode.

Anyway, from the tick event we add a Gate node that should start Closed. In other words, the graph after the gate will not, by default, be executed. After the gate we subtract In Delta Time from the Timer variable, while assigning the result into the DisplayTimer. We then check if the DisplayTimer is <= 0 and if true we revert the video mode and close the gate.

Of course we need means to open the gate, which will be done through a custom event function named StartTimer. Before opening the gate we first initialize the Timer variable, assigning Timeout into it. Then, we open the tick's gate. The graph looks like this:

Next we have to create a new binding to the Text property of the text widget we added do show the countdown. We simply plug the DisplayTimer variable into the output of the function, which should automatically crate a conversion node for us:

Lastly we set the default value of the Timeout variable, maybe to 15 which should give us 15 (ok, not exactly based on how we are updating the timer) seconds. Then, we set it to be Instance Editable = true and Expose on Spawn = True. By doing so, when we spawn this dialog we will be able to specify the countdown time.

Next we update the UI_Settings widget. As mentioned early, we need a property to be edited from the main widgets. So, add a new variable of the Overlay type (attention to not choose the Overlay Item type) named OverlayContainer and enable the Instance Editable, which will allow us to edit its value from the property editor within the main widgets. We will set this property from the UI_MainMenu shortly, but first let's finish working on the UI_Settings. From the construct script we will first check if the dialog container is valid and if so create the dialog. One thing to note is that the return value of the Create Widget node is promoted to variable, named dlgConfirmVideoMode, so we can then un-hide the dialog when necessary. Furthermore, the created widget is added into the container and the alignment is set to Fill, vertically and horizontally. After that we execute the old graph that is already in place. In order to help organization, we separate those two sections using a Sequence node. The result looks like this:

And then, we have to update the graph that is applying the video mode, within the Change Video Mode function (still in the UI_Settings widget). The first thing is that we have to change one of the nodes, more specifically the Apply Settings. The problem is that this node also saves the changes to the settings file, which we don't want just yet. Rather, we want to apply only the resolution changes, with the Apply Resolution Settings. Then, following the sequence, from the True branch we are currently confirming and saving the changes. Instead of doing that we want to display (un-hide) the confirmation dialog, which now controls the video mode confirm → save → hide dialog or revert change → hide dialog. Now, since we want to save the changes only if the input flag Save Changes is true and we only display the confirmation if the dialog object is valid, we can use the AND boolean operation and plug the result into the Branch node that is already in place. To display the dialog we just change its visibility to Visible. After that we have to open the countdown gate with the StartTimer:

Now we can open the UI_MainMenu, select the UI_Settings widget from within the hierarchy and then select the dialog overlay object in the property dropdown:

Trying to change the video mode now from the main menu will display the confirmation dialog:

Back to Main Menu Confirmation

Let's work on one last confirmation dialog, that will ask the player to confirm the back to main menu option from the pause menu. This dialog, however, will be directly created within the UI_InGame rather than being a helper widget. This choice is just to show how things can done in different ways. The helper widget route, however, is interesting because if you come to design a different HUD for an specific game mode it becomes possible to reuse the dialog, not to mention less "clutter" in the hierarchy. That said, add a border widget into the DialogContainer and set its slot alignment to Fill on both horizontal and vertical properties, the brush to a black color with alpha = 0.85 and its name to dlgConfirmBackToMain or something like that. Mark the border to be a variable because we will need to change its visibility through blueprint. Make sure the visibility is set to Collapsed.

And then, into that border (dlgConfirmBackToMain), the usual hierarchy we have used in the other dialog helper widgets (border vertical center, horizontal fill, 64 padding, vertical box, multi line text, horizontal box, yes and no buttons). Set the multi line text to something like Do you really want to go back to the main menu?. Rename the buttons to btConfirmMainYes and btConfirmMainNo, respectively. When the btConfirmMainYes button is clicked then we will use the Open Level node to transition into the main menu level. And if the btConfirmMainNo is pressed then we hide the dialog again. Of course we have to update the clicked event of the btMainMenu button. Instead of directly transitioning into the main menu level we open the dialog we just created, which is just setting the dlgConfirmBackToMain visibility to Visible. The graph for each of the buttons:

Start Countdown

When we transition from the main menu into the game, the action already takes place, with absolutely no time for the player to get ready. A good idea here is to add some sort of a countdown, maybe of 5 seconds before the action begins. If we add a new state into the state machine, then things will be really easy to manage. The idea is to transition from the GameInit into this new StartCountdown state rather than the Spawning. Once the specified time is elapsed then we can transition to the Spawning state, which will keep the game running in the state loop we have already seen working. Through the use of two custom events we will show and update a countdown widget. As usual, we will make the count time in a way that can be tweaked.

All that said, let's first declare the two events that we will use to help display and update the widget. The first event will be used to notify about the start countdown and it's when we will show the widget. We will give one argument that will be used as initial display value for the widget. The other event will be used in order to update the widget itself. It will be called at every state function update and will receive two arguments, the display value as well as a boolean indicating if the counting has been completed or not. We could easily go away with this flag and perform the test from blueprint, but using this argument will make the blueprint graph a little bit simpler.

GameModeInGame.h
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnStartCountdownDelegate, int32, InitialValue);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStartCountdownMultiDelegate, int32, InitialValue);

DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnUpdateStartCountdownDelegate, int32, DisplayValue, bool, Completed);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnUpdateStartCountdownMultiDelegate, int32, DisplayValue, bool, Completed);

In the private section of the class, next to the other broadcaster delegates we declare these new event broadcasters:

GameModeInGame.h
UPROPERTY()
FOnStartCountdownMultiDelegate mOnStartCountdown;

UPROPERTY()
FOnUpdateStartCountdownMultiDelegate mOnUpdateStartCountdown;

Of course we need public functions that allow us to add listeners to those two event delegates:

GameModeInGame.h
// Binds a new event function that will be called whenever the countdown to start the game begins
UFUNCTION(BlueprintCallable, Category = "Event Binding")
void ListenOnStartCountdown(FOnStartCountdownDelegate OnStartCountdown) { mOnStartCountdown.Add(OnStartCountdown); }

// Binds a new event function that will be called whenever the countdown time is updated
UFUNCTION(BlueprintCallable, Category = "Event Binding")
void ListenOnUpdateStartCountdown(FOnUpdateStartCountdownDelegate OnUpdateStartCountdown) { mOnUpdateStartCountdown.Add(OnUpdateStartCountdown); }

Then we need the "upkeep" variables. We need one that will hold the initial countdown time, which should be also accessible from the property editor. Then a variable to keep the current countdown time.

GameModeInGame.h
// How many seconds to wait right before the action actually begins
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Settings", meta = (DisplayName = "Countdown Initial Time", AllowPrivateAccess = true))
int32 mInitialCountdown;

float mCurrentCountdown;

One last thing to be done before moving to the definition file is to add the new state function declaration:

GameModeInGame.h
StateFunctionProxy StateStartCountdown(float Seconds);

Now let's initialize the initial countdown with a default value, 5 seconds:

GameModeInGame.cpp
AGameModeInGame::AGameModeInGame()
{
   ...
   mInitialCountdown = 5;
}

Then we have to update the StateGameInit() state function. Right after the CustomGameInit() call we set the current countdown, broadcast the mOnStartCountdown event and then transition into the new state rather than the Spawning State. We could postpone the event broadcast to the next state however that would also force us to perform a check to see if we have to fire the event or not.

GameModeInGame.cpp
AGameModeInGame::StateFunctionProxy AGameModeInGame::StateGameInit(float Seconds)
{
   ...
   // Perform custom game initialization
   CustomGameInit(Seconds);

   // Set initial start countdown time
   mCurrentCountdown = mInitialCountdown;

   // Broadcast start countdown event
   mOnStartCountdown.Broadcast(mInitialCountdown);

   // Transition into the StartCountdown state
   return &AGameModeInGame::StateStartCountdown;
}

Finally we work on the StartCountdown state. The main task here is to update the value of the mCurrentCountdown variable and check if the desired amount of seconds has been elapsed in order to build the "flag" used as second argument of the update event. Note that we have declared the event to receive an integer value which is also called DisplayValue. The idea is to convert the internal floating point value into an integer that can be directly used by the countdown widget. We will build the completed flag based on the value of the display value. The usual conversion from float to integer is just to truncate the floating point value. With this path, after the first update we immediately change from 5 to 4 as display value, assuming we keep the 5 seconds in the property editor. There is more, if you take a look at how we have calculated the timeout in the video confirmation dialog, we are actually counting one second less than that specified in the variable. Then one could argue about directly using the floating point value to obtain the completed flag. Well, this will give the amount of seconds desired but also we get the 0 to be displayed for approximately 1 second before handling the action to the player.

Basically, we want to calculate the specified amount of seconds (5 by default) and displaying a number that is always bigger than 0. We will indeed obtain the completed flag based on the display value (if <= 0 then completed = true). But the display value has to be "shifted" by one. After updating the internal counting variable mCurrentCountdown we can either:

  1. Directly assign into the integer value (truncate) and then add one to the result; or
  2. Assign the ceil of the result into the integer value.

Although the option 1 seems a bit easier to understand, it comes with a hidden caveat! Depending on how we update the countdown variable, adding 1 to the "truncate result" will give us the consequence of never reaching 0! The thing is, once we subtract delta seconds from the countdown, we don't what it to go bellow 0, so we somewhat "clamp" the value to 0. From this it should be clear why we never reach 0, right? However, if we use the ceil path, then the amount of seconds will be counted as expected and since ceil(0) = 0 we will end the counting! Of course, we could add a bit more checks but then, we end up with some more code than if we just use the ceil method. Thus, in this project we go with option 2:

GameModeInGame.cpp
AGameModeInGame::StateFunctionProxy AGameModeInGame::StateStartCountdown(float Seconds)
{
   mCurrentCountdown -= Seconds;
   if (mCurrentCountdown < 0.0f)
   {
      mCurrentCountdown = 0.0f;
   }

   const int32 display_value = FMath::CeilToInt(mCurrentCountdown);
   const bool completed = (display_value == 0);
   mOnUpdateStartCountdown.Broadcast(display_value, completed);

   if (completed)
      return &AGameModeInGame::StateSpawning;

   return &AGameModeInGame::StateStartCountdown;
}

Countdown Widget

By default we now have a 5 seconds delay before spawning the first player piece and actually starting the game but because there is no widget indicating what is going on we may think the game is frozen. Let's make a helper widget, UI_StartCountdown that will utilize the dialog container of the UI_InGame main widget. This is probably the simplest widget in the project, at least in terms of hierarchy. All we need is a border and a text widget centered on it. Set the border's brush color to black with alpha = 0.5. Renamed the text to something like txtDisplayValue, set it to variable since we will need to manually update its value and then increase the size of the text to something like 46. In its blueprint graph we have to bind an event listener to the game mode's OnUpdateCountdown when the widget is constructed. The event handler should update the txtDisplayValue and if the countdown is completed we have to hide itself:

Now, in the UI_InGame we use the hierarchy tree to help with the next step. We want to add one UI_StartCountdown widget into the DialogContainer overlay, however the ideal here is to keep it before the dlgConfirmBackToMain border so we can still see this confirmation widget if we need to work on it, something like this:

UI In Game Hierarchy

Remember to set the countdown widget visibility to Collapsed and its slot alignment to Fill on both directions.

Finally, we have to add means to display the countdown widget. In the construction script graph we already have several event bindings in there. Let's append the one for the On Start Countdown, which should just set the visibility of the countdown widget to Visible as well as setting the initial display value. In the image bellow display only part of the blueprint graph coming from the Event Construct of the UI_InGame:

Fixing the Music Start

Now that we have a nice countdown, with the widget displaying it, giving the player some seconds to get ready, there is one small detail we need to fix. The music has to start playing when the countdown finishes and not when it's counting down! The fix is somewhat simple but since we are controlling audio playback from each of the custom game mode classes, we will have to edit both of them.

Let's begin with the BP_GMTraditional. In there, from the Event CustomGameInit we don't want to start the audio playback anymore. Setting the Playback Index variable to 0 and Percent Offset = 0 is still desireable, though. In other words, remove the Set Integer Parameter and Play nodes from this execution path. Anyway, we didn't explicitly create an event telling when the countdown has finished, but we can still use the On Update Countdown event and rely on the Completed boolean flag. Now, in this blueprint class we are already binding an event, OnGameOver, that is being used to control audio playback, more specifically to stop it. After this event binding we add our new one to start the playback:

For the BP_GMTimed we do almost the same thing. In the Event CustomGameInit we set the TimeCount variable and that is it, moving out the rest of the graph into the OnUpdateCountdown event handler:

On both cases, if the audio playback begins while the countdown is displayed, then select the Audio component and uncheck the Auto Activate under the Activation category so the component doesn't begin its playback without our blueprint code explicitly asking to.

Preventing Countdown When Counting Down

In the timed game mode, when the initial 5 seconds countdown is counting, the actual game timer is also counting! We have to prevent that! Luckily the fix is somewhat easy. We create a gate node between the Tick event and the Set TimeCount. We then create two new custom event functions, one named StartTimer and the other StopTimer, which will open and close the gate, respectively:

We have to close the gate when the game is over because if the button to play again is clicked, we have to prevent the timer to be updated. And obviously we have to open the gate when the start countdown finishes:


We have done quite a bit in this part, in terms of polishing. We are still not done yet with those, which will work with in the next part, where we also finally see the packaging process that is provided by Unreal Engine.

Introduction
Previous123456789101112131415161718192021
22
23Next