top of page

Before leaving SCAD, I worked on an Online-Multiplayer Battle Arena game called Quacktown Smackdown. The game was produced entirely in Unreal Engine 4 and utilizes the Online Subsystem Steam API for Multiplayer Sessions. Being only one of two designated programmers on the team, I managed all of the UI operations and half of the game's Backend Systems. From December 2021 to June 2022, I was involved with the production of Quacktown Smackdown

EARLY PRODUCTION

Planning and Pre-Production:

In the Pre-Production phase, most of the art direction for the UI and HUD was led by our team’s lead character artist. My duty was to implement the designs she produced, in engine. During implementation, I added my own elements to the mix including UI animations, text materials, and a background visual that utilized the buoyancy component.

PREPROD_TitleScreenUI.png

To keep myself organized during the production of the game, I used Google Sheets due to its ensured mobility. In the document, I created formulas that allowed me to check off certain elements which would then affect other cells to make my tasks more readable for me.

PREPROD_HUDAndUISpreadsheet.png
PREPROD_ToDoListPersonalSchedule.png

In the shared spreadsheet in which our team would organize our Sprints, I created a Bug Sheet to keep track of bugs that we encountered during testing. This allowed for better management of what could hinder a Player’s gameplay experience. Since many of the team members were not familiar with spreadsheet management, I made the Bug Sheet easy to navigate by using drop down filters and custom formatting so that the only manual entry was the bug’s name and description.

GAME SYSTEMS

GAME TIMERS:

Overview:

​The usage of timer systems in Quacktown Smackdown created a uniform structure for how long a game match is run. In the game, there were two primary timers: The Countdown Timer and The Game’s Match Timer. The Countdown Timer runs a 3 Second Countdown before the commencement of a Game Match. During this countdown, all Player inputs are frozen so that there is no head start in gameplay. Once the Countdown Timer terminates, the Match Timer begins. The Match Timer controls the duration of a match in Quacktown Smackdown. A standard match was initially set to five minutes, but this has been adjusted multiple times over the course of production.

Functionality of the Match Timer:

I started implementation of the Timer systems from within the Game State Blueprint. The Game State allows replication of information to all clients while still existing at the Server level making it the best location for the Timer functions to proceed. The first timer that was created was the Match timer as it was a greater priority during production. I started with creating a small custom event that called a Set Timer by Function Name node. The node is set to a tick of 1 Second and Loops infinitely so that the Match Timer’s function handles its own cessation.

​In the actual function of the Match Timer, there are two integer variables that are manipulated: MatchSeconds and MatchMinutes. MatchSeconds has a default value of 0 and MatchMinutes has a default value of 5. When the function is called, MatchSeconds is immediately decremented by a value of 1. When this occurs, a check is made to see if the new value of MatchSeconds is equal to -1.

If False, a check is made to see if MatchSeconds is less than or equal to 3 AND if MatchMinutes is equal to 0. If this is True, a quack sound is made for all Players in the session to hear. This path will run five times during the last seconds of the match as an auditory warning for all Players that the game will be ending soon.

Returning back to the initial Branch node: upon True, MatchMinutes is then decremented by a value of 1. Continuing on with the True path, another check is made to see if the new value of MatchMinutes is equal to -1. If this is also True, that means that the Timer has finished. When this occurs, all widgets are removed from the screen, and a new Widget is added to the Viewport to display a Game Over message.

Functionality of the Countdown Timer:

After completion of the Match Timer, we decided that another timer was necessary to trigger the start of a game’s match. This timer we called the Countdown Timer as it performs a three second countdown to initiate the commencement of a match. Using similar functionality of the main Match Timer, the Countdown Timer starts from a custom event attached to a Set Timer by Function Name node.

The function – Funct_runCountdownTimer – runs when this event is called. In the function, two variables are affected to control the actions of the timer: an integer CountDownSeconds and a Boolean CountDownOver. CountDownSeconds has a default value of 3 to represent the countdown’s duration of three seconds. On every run of the function, CountDownSeconds decrements by 1. When it reaches 0, CountDownOver is then set to True (its default value is False). When CountDownOver is True, a call is made that triggers the main Match Timer.

MAP CHANGING:

Setting the Gameplay Map for a match occurs in the Widget Blueprint for the Lobby’s Host. In the Blueprint there is a string variable, mapLVL_ID. When either the Bathtub or Sandbox button is clicked, mapLVL_ID is then changed to the name of the level storing their locations.

Once the Host is ready to start the game, they will click the Start Game button. Upon clicking that button, an event is called from the Lobby level’s Player Controller Blueprint. A series of actions that are required by the online-multiplayer systems are run to keep a check of which Players are connected and in the lobby. After this is complete, a console command is called that takes all accounted for Players in the session and spawns them into the level that is stored in mapLVL_ID.

GAME UI

OVERVIEW:

The implementation of all UI elements and Player HUD was done by myself during my time on Quacktown Smackdown. I programmed both the backend systems of the UI and the frontend using Unreal Engine’s UMG UI Designer.

LOBBY UI:

The Lobby of Quacktown Smackdown incorporates the usage of six different widgets: TitleScreen, MainMenu, CosmeticsMenu, HostLobbyScreen, and JoinedLobbyScreen. Much of the functionality remains the same for all Lobby UI Screens.

UI Manager:

To promote efficiency while navigating the Lobby, I created a “UI Manager” that keeps track of which screen the Player needs to navigate and has navigated to. This system also made navigation from a Back button easier as it retained the information about the last opened menu on the screen. I implemented this manager within the Player Controller Blueprint.

The UI Manager works by getting called from within the Widget Blueprint of a Lobby UI Screen. Each UI Widget is assigned a number and based off that number, that widget will be called by the UI Manager. When one of the Lobby UI Screens is initialized, a custom event – setReturn – is called to record the information about the window that was just opened by the Client in the event that the back button is used. In addition, another event – updateUIManager – is called to let the Manager know which widget needs to be opened once a button on the screen is clicked.

Title Screen:

The Title Screen’s design is very simple; all that is included in the UI is text, a video flythrough of the Bathtub map, and the Quacktown Smackdown logo. The video flythrough works by usage of a custom UI Material. A TextureSample node is plugged into the Main Color slot of the material and uses a Media Player object as the source of the video. The video is set to play automatically in the background and loops when complete.

In the Blueprint Editor for the Widget, on the Event Construct node, the flythrough video is called from an Open-Source node.

The “Press Any Key to Start” functionality works starting from the Player Controller. A Boolean variable – AnyKeyPressed – is set to False by default. Extending from an Any Key input node, upon pressed, another Boolean variable – IntroLock – is used as a condition. IntroLock is also False by Default. If it is False while a key is being Pressed, AnyKeyPressed becomes True; if it is False while a key being Released, AnyKeyPressed remains False.

The purpose of IntroLock is to create minor delay and to lock Client input after the first moment a key is pressed. This allows the intro animations to fully play through and creates a buffer while the next widget loads.

Upon being clicked, outro animations run, and the widget is removed from the parent to make way for the next UI Screen.

Main Menu:

Once the Title Screen UI is gone, the Main Menu UI becomes the primary UI for the Lobby. A Player/Client is unable to navigate back to the Title Screen unless they restart the game itself. For the ‘Find A Game’ scroll box on the Main Menu screen, a tab for any online game session will appear when a Player is connected via their Steam account.

To allow this to happen, a custom event – Find Games – runs indefinitely to refresh the sessions available for a client to join. When the event gets called, the first thing that happens is that it finds up to 100 available game sessions. Once that occurs, all previous session buttons are cleared from the screen to make room for the refreshed values. Immediately after, the found game sessions are added to the scroll box using a widget made specifically for that function. Each refresh runs on a tick of 1 second.

The Main Menu UI Screen has the ability to transfer a Client to the Cosmetics Menu, the Host’s Session Screen, and the Joined Players’ Session Screen.

Cosmetics Menu:

​The Cosmetics Menu's UI Screen design contains four buttons: three buttons to switch the cosmetics menu tabs and one back button that doubles as a confirmation of changes for the customization options. The panels sit towards the left-hand side of the screen to allow screen space for the live visualization of the customization on the Client’s character.

​The Cosmetics Menu uses multiple widgets. The widget for the primary screen contains the saving functionality that allows the Duck Customizations to work. The full Customization System was a collaboration between the other programmer and I and was implemented towards the end of production

When the UI Screen initializes, the Lobby’s camera is called by reference to moves its location closer to the sample duck by means of the Zoom Camera function. Immediately after, the UI animations proceed, and the cosmetics menu’s tab is set to the Hat Menu by default.

​The Zoom Camera event functions by setting the distance between the lobby camera and the sample Client duck to a relatively smaller distance. This is facilitated by the Vector Interpolation node. This node only causes the camera to translate in space, so another node is needed to affect its rotation. The rotation fine tunes the camera's POV and makes it more pleasing to the eye.

​To set the default tab on the Hats Menu, another custom event – Set Hats – is called. Set Hats changes the menu text and adjusts various slate brushes to “turn off” the other tabs in the cosmetics menu.

The same set of functions occur for both the Eyes and Skins tabs.

The customizations are saved for the Client once the Back Button is clicked. This works by using a Save Game Object to store the Client’s selected Hat, Eyes, and Skin. During the selection process of a customization item, variables in the OnlineGameInstance Blueprint are changed to reflect what the Client desires.

A Boolean variable – SaveGameSuccess – is set based off the return value of the “Save Game to Slot” node. If everything runs successfully and the return value is True, then the Back Button’s functionality can proceed without issue.

When the Back Button is finally clicked by the Client, the Save Game function runs as previously described. Using the SaveGameSuccess variable, a check is made to make sure what occurred during that event is valid. If True, then the lobby camera returns to its original position on the screen. The animations for the UI play in reverse as the screen is preparing to change back to the Main Menu. After the animations are complete, the widget is removed from parent and the Main Menu returns.

Here is the full Event Graph of the Customizations UI

Returning back to the selection process for the customization options, a separate UI Widget is used for the cosmetics options. There is a separate widget for the Hats, Eyes, and Skins that perform the same functions for each. For the purpose of this breakdown, I will focus on the Hats widget.

In the Designer, all of the buttons are placed in a Uniform Grid Panel nested within a Scroll box. Each individual button is used as a possible Hat selection for the Client. Images of the options are placed to preview the hats assigned to each button.

When the Client hovers over one of the Hat option buttons, a preview of that hat is toggled, and the Lobby Duck immediately begins to wear it. This happens due to a placeholder mesh variable that is set during the hover action and taking that placeholder and applying it to the actual hat static mesh. This does not affect the OnlineGameInstance variable for the hat.

When the Client is ready for selection, they will click the button associated with their desired hat and upon this click, that is when the variable in the OnlineGameInstance is changed to the same static mesh object.

During the click, another set of functions change the border slate brush of the selection box to display a black outline around it.

The Hat selection is also set when the Client’s mouse leaves the Widget.

Host Lobby Screen:

When a Client chooses to Host a game, they will be given the Host’s UI after being transferred to the Lobby Level. This Level is separate from that of the Main Menu. With this version of the UI, the Host has the ability to change the game’s map, start the match, and close the session. If the Host decides to start the game without adjusting the map selection, the Bathtub is toggled by default. Upon pressing “Start Game,” communication with the OnlineGameInstance (by means of the Player Controller and Game Mode Blueprints for the Lobby Level) confirms the number of instanced Players within the current session and uses the ‘server travel’ command to transfer the Clients and the Host to the gameplay level.

If the Host decides to close the lobby, all joined Clients must be carefully disconnected in a way that  does not sever their connection to the game. To make Client leave via a regular ‘Open Level’ node would cause their connection to be lost; one would have to restart the application in order to join/host a new game. The efforts to maintain the connection come from a function in the Lobby Level’s Game Mode Blueprint that utilizes the “Destroy Session” node. Once this is completed, the Main Menu Level is then opened. An options string is also used to ensure that the level does not open up to the Title Screen.

In the Designer, the UI is anchored to the left-hand side of the screen and retains the same animation style and theme of the other UI screens.

Joined Lobby Screen:

The Joined Lobby Screen has only two buttons: “Leave Lobby” and “Quack.” The Quack Button takes the form of a large rubber duck icon and sits in the center of the Joined Lobby UI Menu. The purpose of the Quack button is purely for entertainment while Clients wait in the lobby for the Host to start the match. When clicked, the Quack button sends a quack sound that is replicated for all joined Clients in the lobby to hear. The button can be spammed and does not have a cooldown.

When a Client wants to leave the lobby, a function similar to the one for the Host is called. The Client version of the function is called from the Player Controller Blueprint and uses the same Destroy Session node. It only accounts for the individual Client that wishes to leave.

IN-GAME UI:

Overview:

UI that is found during gameplay includes the Timers, Leaderboard, Pause Menu, and the Quack Menu. The Timer is anchored to the top of the screen and is set to a size that makes it easy to see without getting in the way of Players’ sights.

The text for the Timer is taken from the Game State and due to the function binding, it is live updated to reflect the accurate amount of time left within the match.

The Leaderboard uses two different widgets to relay information. The main Leaderboard UI is only a scroll box on a border and the Leaderboard tabs use text bindings and are added as children to the main UI.

The purpose of the Quack Menu is to allow Players to quack at one another and supply audio cues via distinct types of quack sound effects. The overall design included four quack options. I used the image from the Lobby quack button to be consistent. I had to figure out the general design and layout for it myself. I reused assets from the other UI screens as there were not any graphic elements made specifically for it.

When a Player clicks one of the quacks, it plays the sound effect from their location and does not have a cooldown for usage.

The sound effect is called from a function that organizes many of the sounds used within the gameplay level.

Overhead Healthbar System:

The Overhead Healthbar System works by using a Widget Component attached to the Player Pawn. This component is then initialized using the associated widget class.

The Design for the OHBs is simple. The progress bar depletes the lower a Player’s health is. When health is restored, the progress bar is augmented.

The Healthbars are also not visible behind any walls to keep a Player stealth when navigating the map. The method behind how this works is explained in the Emote System breakdown.

UI ANIMATION:

The Animations for the UI were done by me to keep the UI screens engaging for the Player. Each Animation is done in the designer of a UI widget and varies only slightly between each of the many UI screens used in the game.

QTSD Title Screen
Timer Initialization
Player HUD Animations
Main Menu Initialization
Game Over Animation
Cosmetics Menu Initialization

UI TEXT MATERIAL:

For the text material, I used the GeneratedOffsetBands node to give motion to what would have been an otherwise flat color. The sine nodes allow for the even loop that occurs in the material and by clamping the values, prevents the texture from going too dark or too light in terms of display. The clamps also prevent the bands from completely disappearing when they fade. The multiply nodes assist with providing color and chose orange due to it being my favorite color.

PLAYER HUD

OVERVIEW:

The Player HUD evolved several times throughout production up until even the last week of classes. Many of the changes come from new ideas for implementation or complete redesign of the Player HUD itself. The HUD reflects the necessary information about a Player’s abilities, health, ammo, kill count, and death count in an easy to view manner that does not interfere with their ability to play the game efficiently.

HUD FUNCTIONALITY:

The Design of the Player HUD extends from concepts created by the lead character artist on our team. I took these concepts and implemented them as an Unreal widget. Most of the elements on the screen are manipulated through functions they are bound to allow for the crucial live updates needed in the game.

Much of the bound information is taken directly from the Player State or the Player Pawn Blueprints as the HUD is more so a container to visualize this information in an efficient manner. When the HUD is initialized, it goes through a series of widget animations to add to the playful appeal of the game. In addition, indicators that should be off by default are set to be so and references to the various blueprints are made so that casting would not be repetitive for the function binding.

As mentioned, the information is pulled directly from the other Blueprints and for some, required enumerators to set the data effectively.

HUD_Function_AbilityIcon
HUD_Function_AbilityName
HUD_Function_Ammo
HUD_Function_DeathCount
HUD_Function_DeathIndicator
HUD_Function_Health
HUD_Function_KillCount
HUD_Function_SetCrosshair
HUD_Function_SniperScope
HUD_Function_WeaponIcon

MULTIPLAYER EMOTE SYSTEM:

Blueprinting the Emote System was most definitely one of the more challenging parts of my work on Quacktown Smackdown. There is not much information about how to do such a feat in Unreal, so it was left to me to engineer both the backend and the frontend.

Emote System (UI):

When beginning production of the Emote System, I started with creating the UI that would be displayed when a Player goes to select an emote. By doing this, I sharpened my idea of how to approach coding the backend. I decided on having the UI be toggled by holding down the [E] key. As the Player holds down [E], the UI shows up on a Player’s HUD and collapses once the [E] key is released. This was created as an InputAction called Interact in the Project Preferences.

I also thought that if a Player is mid-action trying to navigate the battle-arena – keeping their best interest in mind – having the UI assume control of their mouse to click-and-select an emote would not be convenient. I decided that to select an emote, a Player’s mouse must hover over their desired emote so that when the [E] key is released, that is the emote that appears above their head. Thus, the action of opening the Emote Window & selecting an emote is swift and requires no extra keys or clicks beyond the initial holding of the [E] key. Another bonus to this is that a Player is still able to make a kill while toggling the Emote Window.

When designing the UI for the Emote system, I only had the thirteen emote images illustrated by our lead character artist. I improvised a way to display the thirteen images in a non-obstructive manner so that a Player could still be aware of what is happening during gameplay while the Emote Window is open.

As with the HUD, I created an Animation sequence for the Opening and Closing of the Emote UI. The Emotes fly into view of the screen with a slight jump upon Opening and upon Closing, the same animation is played in reverse.

Emote System (Functionality):

Before getting into the main code, I created an Enumerator Blueprint to store each possible emote.

This Enum manages the switches between the emotes a Player can activate. When InputAction Interact is Pressed, the Emote a Player’s mouse last hovered over will be selected. This was done by creating buttons for each of the individual emotes, toggling certain mouse events, and changing the Input Mode to Game and UI, after the Widget was created.

The mouse toggle changes are referenced from the PlayerController. To expedite these processes, I created two events in the PlayerController: theTrioTrue and theTrioFalse. theTrioTrue enables the mouse cursor, click events, and mouse over events whereas theTrioFalse disables these.

For each button, I used the OnHovered Node and connected it to my custom event called sendEmoteToPlayer which has an input option for current emote. sendEmoteToPlayer then sends the current selection to the Pawn Blueprint.

In the Pawn Blueprint, a series of custom events manage the replication of the selected emote. The first event (NR_setEmo) sets an ENUM variable defined in the Pawn Blueprint (currentEmote) equal to the passed value received from the Widget Blueprint. Replication isn’t needed for this to occur as it just establishes the information stored in the Pawn Blueprint’s ENUM variable, hence the name “NR” – No Replication.

This completes the set of actions that occur when InputAction Interact is Pressed (While [E] is being held down).

When InputAction Interact is Released, the first thing that occurs is the closing sequence for the Emote Window UI. This event simply runs the animation sequence that is played upon Construction in reverse. The Delay node ensures the animation sequence completes before the Widget is removed.

EMOTE_EmoteSysUIWidget_closeEmoteMenu.png

Following this, the Input Mode is set to Game Only and theTrioFalse is called to reinstate the mouse states for gameplay. The last thing that happens during the Released state is a call to an event, SRV_DisplayEmote, which takes a single input: currentEmote (defined in NR_setEmo). SRV_DisplayEmote replicates from the Server level and essentially says “Hey! This specific client wants to send an Emote!” During this event, currentEmote is used to switch which case is true. Each case creates a SlateBrush based off the value of currentEmote and sends this information to a custom event called MUL_displayEmotes.

To actually display the Emote, there is a component on the Pawn called Widget_emotePopup. This component by default is set to Screen space to ensure that the Emote image can display properly no matter the angle it is being viewed in. This component also utilizes another Widget called EmoteSysDisplay_UI.

EmoteSysDisplay_UI is a simple Widget that has a single image anchored to the center of the frame. This image is what gets displayed above a Player’s head when an Emote is selected in-game.

When MUL_displayEmotes gets called by the Server, the Server sends it (Widget_emotePopup) the proper SlateBrush it needs to adopt to display the correct emote image. Next a cast is made to EmoteSysDisplay_UI because EmoteSysDisplay_UI is the widget class that is instanced by Widget_emotePopup. From here, the SlateBrush of the single image in EmoteSysDisplay_UI is set to that which was defined by the case selected in the SRV_DisplayEmote event. An animation sequence is run to display the new image; it stays on screen for a set duration of time using a Delay node before the closing animation sequence runs and the SlateBrush is reset to nothing.

EMOTE_MulticastDisplayEmotes.png

To make sure the Emote is not seen behind walls or other objects, I created an event that handles visibility called widgetWallVisibilityToggle. In short, widgetWallVisibilityToggle executes a line-trace from a Player’s location in the world to objects that obstruct the Player’s view by usage of the Player Camera Manager. If the line trace collides with any mesh, the LineTraceByChannel node returns True. If there is no collision, it returns False. When True, a Widget Component’s visibility is set to Collapsed; when False, the visibility is set to Visible.

UI_INGAME_WidgetWallVisibilityToggle.png
bottom of page