A Hat in Time

A Hat in Time

Not enough ratings
Scripting A Hat in Time: HUD Modding
By SamiSha
In this guide, you will be introduced to HUD modding a UI based way to code and create an interface that helps the player.

A lot of mods capitalize on UI for many reasons, but importantly to represent certain information to the player or modify content.

This guide will provide basic level understanding of how the HUD works, basic introduction to functions and best practices.
   
Award
Favorite
Favorited
Unfavorite
Introduction
It is important to note that this guide requires you having a basic understanding of code, if you don't know essentials, you may struggle a lot, if you still have any questions the A Hat in Time discord is highly recommended for any questions, especially for scripting, or feel free to ask me down below.

Also this guide was made to help everyone get an a "somewhat" introduction to A Hat in Time essential scripting.

https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2289914449

In this guide we will go over all the basics and including how they run and work.

What can we use the HUD for?
HUDs can be used for anything of your liking and depends on why you are developing them, either to present information like specified, showcase content and even make it read inputs of the player. It all depends on what you are going for, this guide will try to explain this stuff as much as possible, but enough to get the hang of everything.
Notable Modding Examples
This section showcases mods that utilize HUD in many different ways to achieve different stuff, you can use any of these to get a reference idea how it was done but please don't explicitly 1:1 copy, this is here for educational purposes.

Acceleration Badge uses an O-Meter HUD, using rotation of the arrow to showcase current state
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1977737842

Typer Shark, a typing game that runs very intensively on the HUD side, making a HUD based game, also uses "text size" checks to properly put the chars to look like a word
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2863763530

Mustache Girl: Down with the Mafia, uses an ability bar to showcase "time piece fuel"
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2777272305

Relic Trials, HUD system with relic animation, combo ""meter"" and score system
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2639296483

Information Overlay exposes a lot of variables found in the game, used by speedrunners but helps with understanding number showcasing fundamentals and more
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1820331852
Explaining the HUD
HUDs are used to present information that can help the player, their most common use in a lot of games is to showcase health/mana bar, currency, time and creating more complex things like main menu and settings page. They function by exposing variables and values from other files, for example let's back track to the mention of "health bar", health bar takes in consideration the player's "Health" and "HealthMax" found in Hat_Player.

The class known as Hat_HUD essentially holds the entirety of the HUD process and pretty much handles everything inside, an instance of the Hat_HUD can be found in the Player's controller class, for quick access to it, simple write the following code (requires the file extending from an Actor.uc at any point).
Hat_HUD(Hat_PlayerController(GetALocalPlayerController()).MyHUD);
Fortunately we usually never utilize it outside of these cases:
1) You want to call "OpenHUD".
2) You want to call "CloseHUD".
3) Getting a certain HUD, whether to see if they have it active or to manipulate the its variables from somewhere else.
4) Rest is more advanced stuff (will not be discussed here).

There's not much to discuss other than essentially you are applying additional HUDs onto the Hat_HUD class, this is what the next section will explain.

HUD Elements
UE3 uses a different system for HUDs in comparison that is more complex, fortunately the A Hat in Time developers designed a system specifically made to simplify the process in the form of "HUD Elements", which is what we are going to work on.

Things like the Pon counter, Healthbar, Timepiece counter, conversation bubble and more all extend Hat_HUDElement, with some having more feautres through Hat_HUDElementFancy and Hat_HUDMenu (which both extend from Hat_HUDElement), we will discuss these classes more in details later in this guide, but for now it is important for you to understand that features that HUDs are made through creating "elements" that are added into HUD system which they do all the work that is needed to render, calculate and showcase the information you want to show to the player.

Opening, Closing, Getting HUD Elements
To do this you first need to acquire the PlayerController which contains myHUD which has all the HUD that gets plastered with HUD Elements.

The easiest way to acquire the player is by calling
GetALocalPlayerController().myHUD;
If you aren't extending from an actor then you can use this in any script to get the PlayerController.
class'WorldInfo'.static.GetWorldInfo().GetALocalPlayerController().myHUD;
Next we need to cast it to Hat_HUD by putting it around parentheses.
Hat_HUD(GetALocalPlayerController().myHUD);
And we can finally call "OpenHUD()", "CloseHUD()" and "GetHUD()" to add, remove and get a HUD Element.

Opening, Closing and Getting HUD Example:
function Example() { local PlayerController pc; local Hat_HUD plyrHUD; pc = GetALocalPlayerController(); // Uncomment and use this instead if you aren't extending "Actor" at any point // pc = class'WorldInfo'.static.GetWorldInfo().GetALocalPlayerController(); plyrHUD = Hat_HUD(pc.myHUD); // Opens HUD plyrHUD.OpenHUD(class'Hat_HUDElementHealth'); // Closes a HUD plyrHUD.CloseHUD(class'Hat_HUDElementHealth'); // Return the HUD Element, if you want to manipulate content, make sure to cast the return result to change anything inside! plyrHUD.GetHUD(class'Hat_HUDElementHealth'); // Example to manipulate content found in the HUD Element Hat_HUDElementHealth(plyrHUD.GetHUD(class'Hat_HUDElementHealth')).MaxHealth = 9; }

Canvas
Before we dive into the basics you first need to learn how the HUD works.

Like painters in real life use a canvas to capture the world, so is HUDs do, Canvas represents the drawing board that we will be using to draw text, textures, materials, particles and much more!

It is important to note that there's only 1 canvas at a time, this canvas is passed through all the HUD Elements in the system which these HUD Elements draw their information they have into it, once this is over and the game goes to the next frame, the game will request a new canvas into the system and subsequently destroy the previous canvas and everything that was drawn into it, this behavior is done continuously and can even be calculated based on your current FPS, this means if the game is 60 FPS per second, then 60 canvases are made and drawn upon per second.

Canvas can be altered and modified directly from the Video settings under "Resolution", "Maximum Frame Rate" and "VSync" (also effects frames rate), playing an important role in how the UI in size and animation on the Canvas.

Canvas is the frame we draw upon all the UI content based on the coordinates and the functions that are designed to draw upon it, subsequently achieving the UI effects we want to go for.

IMPORTANT
Canvas is only "functional" through the Render(HUD H) function that is found in all Hat_HUDElement classes, and can be directly accessed by casting H.Canvas, any other function other than Render(HUD H) can still be accessed but the variables are not initialized (e.g., H.Canvas.ClipX will return 0 not 1920 (if 1080p resolution)!!!).
.png]
Screen Position
IMPORTANT: This entire section uses in game resolution "1920x1080" for this demonstration!

Remember that HUDs like the health bar is at the top left and the time piece counter at the bottom left? Well this is because in the rendering process they are using a specific position on the screen.

A Canvas has a size, and the size specifically will differentiate and adjust based on the game's resolution and subsequently altering how the position will be represented on the screen, if the position are larger or smaller than the limits of the size of 0/0 to Max-Width/Max-Height, then your position point is off player screen and subsequently rendering the content outside (or partially outside) on the screen.

Let's represent left/right position as X, and up/down position as Y, in the case of 1920x1080 X and Y can represent the following values:

X for Width which is from 0 to 1920.
Y for Height which is from 0 to 1080.

Anything outside these limits the content is being rendering off screen and will cause it to look like it was cut off from a certain position.

This should help in giving a representation of how screen cords look like on an actual screen.

If we want to set it in the middle, we should simply pass half the maximum of width and height, which in this case 1920 / 2 = 960 for X and 1080 / 2 = 540 for Y.
function bool Render(HUD H) { local float x, y; if(!super.Render(H)) return false; x = 960.0f; // 50% of Width y = 540.0f; // 50% of Height DrawBorderedText(H.Canvas, "Hello everyone!", x, y, 1.0f); return true; }
But there's a problem, this code doesn't really center unless the screen resolution is specifically 1920x1080 in the player's setting, this will be problematic when it comes to other resolutions which in that case, Canvas provides 2 useful variables that gives the maximum width and height, which are frequently used in a lot of HUD Elements.

So if we want to always center something into our screen then we use, H.Canvas.ClipX and H.Canvas.ClipY, both variables return 1920 and 1080 respectively which represents the max size.

So to always accurately set our content in the right spots, on all resolutions we need to look at position as a percentage, but how do we pass 50%?

For that, we need to look at the Screen Resolution in the form of a clamped value of 0 - 1 which serves a similar representation to a value between 0% to 100%, which results with the following:

Axis
0 (Min)
1 (Max)
x
0
1920
y
0
1080

In this way, we can accurately set our content in the right spots all by taking the Maximum Width/Height and multiplying it by a value between 0 and 1.

So to go back to the previous example, we simply replace the explicit position value of 960 and 540 with H.Canvas.ClipX and H.Canvas.ClipY respectively, and then multiple both "0.5f", subsequently setting the position to be 50% on both axis and causing the content to be in the center!

The code below will center the text on all resolutions and changing the resolution during this HUD open will automatically adjust to it correctly.
function bool Render(HUD H) { local float x, y; if(!super.Render(H)) return false; x = H.Canvas.ClipX * 0.5f; // 50% of Width y = H.Canvas.ClipY * 0.5f; // 50% of Height DrawBorderedText(H.Canvas, "Hello everyone!", x, y, 1.0f); return true; }
Rendering with Render(HUD H) Function
Playing as the most important function in HUDElements, Render() handles all drawings that you apply on the Canvas, its also the only location you can "Draw" content and nowhere else, this is because Canvas is only defined during Render.

Remember: You change the CURRENT STATE, not EVERYTHING!
When we alter the canvas's attributes we do not change previously drawn content, therefore we can customize the look of other content without changing everything else, if you want to have text with different colors or different fonts being drawn/used, it is possible!

H.Canvas.Font = [FONT LINK FROM CONTENT PACKAGE]
Used to change the applied font when calling text drawing functions.
H.Canvas.SetDrawColor();
Takes 4 parameters of R G B A (from 0 to 255) to apply a certain color for drawing, H.Canvas.SetDrawColor(255, 255, 255, 255) = White, H.Canvas.SetDrawColor(255, 0, 0, 255) = Red and so on.
See this color picker to find the color you want: https://rgbacolorpicker.com/

IMPORTANT: SetDrawColor while colors text to that color, it can behave as an "Color Channel" in which case, the ENTIRE canvas is changed to that color channel, so if you want to say make it normal again it is wise to cast the color back to white upon using it for a function call, this way you keep things cleaner.

Also mandatory to have one at the beginning of render regardless to not overlap with other HUD Elements!


Render Template:
function bool Render(HUD H) { local float Scale; if(!Super.Render(H)) return false; // Helps keep size clean if screen is bigger/smaller Scale = FMin(H.Canvas.ClipX, H.Canvas.ClipY) / 1080.0f; // A font to use for text, this defaults to Cursed Casual H.Canvas.Font = class'Hat_FontInfo'.static.GetDefaultFont("abcdefghijkmnlopqrstuvwxyzABCDEFGHIJKMNLOPQRSTUVWXYZ"); // 8 Bit RGBA channels, can be used to color text by inputting the RGB of the color, H.Canvas.SetDrawColor(255, 255, 255, 255); DrawBorderedText(H.Canvas, "Hello world!", H.Canvas.ClipX * 0.5f, H.Canvas.ClipY * 0.5f, Scale * 0.6, true, TextAlign_Center); // Draw it in Red H.Canvas.SetDrawColor(255, 0, 0, 255); DrawBorderedText(H.Canvas, "Hello world! but in Red!", H.Canvas.ClipX * 0.5f, H.Canvas.ClipY * 0.6f, Scale * 0.6, true, TextAlign_Center); // Returns back to white H.Canvas.SetDrawColor(255, 255, 255, 255); DrawBorderedText(H.Canvas, "Hello world!", H.Canvas.ClipX * 0.5f, H.Canvas.ClipY * 0.7f, Scale * 0.6, true, TextAlign_Center); return true; }
Rendering Text
DrawBorderedText(Canvas InCanvas, string S, float X, float Y, float Size, optional bool Shadow, optional TextAlign Align, optional float ShadowAlpha = 0.5, float BorderWidth = 4.0, optional Color BorderColor, optional float VerticalSize = -1, optional float BorderQuality = 1);

To adjust the font, make sure to call this first before the DrawBorderedText function:
H.Canvas.Font = class'Hat_FontInfo'.static.GetDefaultFont("abcdefghijkmnlopqrstuvwxyzABCDEFGHIJKMNLOPQRSTUVWXYZ");

This will avoid defaulting to the Unreal 3 engine font which is extremely small and instead use the A Hat in Time Cursed Casual font, if you want you can also simply use a custom by doing the following:
H.Canvas.Font = [LINK TO THE FONT FROM THE CONTENT PACKAGE]
Tick(HUD H, float d)
Render(HUD H) which is a function found in Hat_HUDElement which like the name implies is used for rendering content on the player's screen, applying a list of rules for the Render to draw everything on the functions you've called inside, Render runs every frame and this is important as we move on to understanding the Canvas.

Tick(HUD H, float d) which is also found in Hat_HUDElement, is similar to the general function found under classes through Unreal 3 Actors, but also this time it passes the HUD into the function, however, H bi-partially defined, meaning a lot of stuff like the Canvas which plays an important role is only accessible in the Render() function, but irrelevant outside of it.

When to use Tick() and Render()?
Tick function should be used to calculate and do math and Render should otherwise pass those "results" you've done with a variable, this should keep content clean, its also important that unlike Render, Tick has delta which is important for a lot of content such as timers, animations and much more, and essentially it works similar to any Tick function found anywhere else in the Unreal 3 framework.

H is as a parameter is still not useless as you can directly access the HUD and all its features, the player and controller from it via PlayerOwner and much more, its just "Canvas" is not defined.

So in summary:

Tick(HUD H, float d)
Render(HUD H)
Called every frame
Called every frame
H.Canvas is "None"
H.Canvas is defined
Passes "delta" per frame
Draws per frame
Used mainly for math
Used mainly for rendering
Acquiring the Player or Controller
In HUD Elements all functions passes the "HUD" essentially holds the Hat_HUDElements and even the player who owns this HUD.

To get the PlayerController:
H.PlayerOwner; // Returns the PlayerController
To get the player, simply add "Pawn" and cover it with Hat_Player():
Hat_Player(H.PlayerOwner.Pawn); // Returns the Hat_Player
Playing Sounds and Audios
HUD Elements has two predefined Sound Playing functions, the only thing it requires is two parameters to fill the gap, the first being the HUD to get the PlayerOwner from it the other is the SoundCue you want to play.

PlaySound is simple and will play the sound without direct modification.
PlayOwnerSound is an AudioComponent and allows for additional modification such as the "volume" and "pitch".

PlaySound(HUD H, Soundcue c); PlayOwnerSound(HUD H, SoundCue c, optional float volume = 1, optional float pitch = 1);

Important: The AudioComponent type does not "return" the result, meaning functions such as "Stop()" cannot be casted, resulting in limitations, if you want to use features like this or directly modify the pitch through time instead of one time and so much more, then I would heavily recommend doing the following:

// Put this at the top of the class var transient AudioComponent myAudioComponent; // Creates an AudioComponent using the player as the location // Bools from left to right -> bPlay, bStopWhenOwnerDestroyed, bUseLocation, bAttachToSelf myAudioComponent= H.PlayerOwner.Pawn.CreateAudioComponent(mySound, true, true, true, H.PlayerOwner.Pawn.Location, true); // Useful functions to use // For more stuff, see the AudioComponent.uc class for function calls. myAudioComponent.Play(); myAudioComponent.Stop(); myAudioComponent.SetPaused(true); myAudioComponent.SetPaused(false); myAudioComponent.PitchMultiplier = newPitchValue; // Min is 0.5f, Max is 2.0f myAudioComponent.VolumeMultiplier = newVolumeValue; // Min is 0.5f, Max is 2.0f
Rendering Materials and Textures
Like rendering text and other stuff to the canvas, they follow the same rules, each render call is rendered above everything else, and will persist until the next frame call.

Also all are rendered from the center point unless you use a different Draw function. For this tutorial we are using DrawCenterMat, there's many function calls that changes the rotation center point, for whatever reason, say like you are making an O-Meter and you have this arrow that needs to rotate, in that case, DrawBottomCenterMat() allows you rotate it from the bottom center to simulate that effect.

In all of these functions, the size is separated for both X and Y, it uses the X and Y position you are setting, and it will take all those pixels, with half of each going from the center.

Example: The size for X is 100, and Y is 50, then from the center it takes 25 above and 25 below for Y Size, and 50 left and 50 right for X Size.

In majority of cases, both X and Y should have the same size due all textures and materials uses a UV Coordinates of a 1:1 Square, so any different size will cause stretching, unless built into mind with the content you are going to use.

Textures
Textures are very simple, call this:

DrawCenter(H, PosX, PosY, SizeX, SizeY, YourTextureHere);

That's it, remember that SetDrawColor manipulates the alpha channels of the content, so that's about as much as you can get creative with textures, they are very simple.

Materials
All materials require "Used with Gamma Correction" toggled while also set in the Blend Mode to Masked and Lighting Mode to Unlit, reason because in Screens there's no light and relies on gamma for its render, everything does not require shading, Masked materials enable to do some masking if you want to make transparent render around the material itself.

For opacity reasons, you do not need to mess with it in the material itself a lot, use H.Canvas.SetDrawColor(255, 255, 255, alpha); where alpha goes from 255 to 0, where 255 is fully visible and 0 no longer visible, adjust this to half (128) and it has 50% opacity.

Preparing the Material
To render a material you need to create an instance of the material on run time using the class MaterialInstanceTimeVarying.

Create 2 variables:

var MaterialInterface YourMaterial; var MaterialInstanceTimeVarying YourMaterialInstance;

And then in OnOpenHUD(HUD H, optional string command) that was discussed above, add the following:

YourMaterialInstance= new Class'MaterialInstanceTimeVarying'; instYourMaterialInstanceance.SetParent(YourMaterial);

And then when you want to render that material you instantiated, just call DrawCenter.

DrawCenter(H, PosX, PosY, SizeX, SizeY, YourMaterialInstance);

And there we go! You have successfully rendered a material to the screen, the best part about materials is that you can add Parameters to it and adjust it DIRECTLY from the HUD itself, say you want to swap a texture? Change the color inside it? or send a value between 0 to 1? You can do all of that, simple call YourMaterialInstance.ONE_OF_THE_FUNCTIONS_DOWN_BELOW();

.png]

In "ParameterName", should be the name of the Parameter set in the Material.

.png]

So to set OnClick here I need to call:

YourMaterialInstance.SetScalarParameterValue('OnClick', 1.0f);


Anywhere in the code, whatever the reason you are setting the value for that Parameter relies on your material, which requires a whole different section, this guide is getting too long, for more info to learn about Materials, see this UE3 page:

https://docs.unrealengine.com/udk/Three/MaterialsCompendium.html#Parameters

Materials are really awesome when crafted and utilized correctly and can be used for many different functionalities that could be beneficial for your mod, I use it mainly to create resource bars and buttons that can click and hover effects, but they are far more powerful for more complex features.
Fancy Elements
Also knows as Hat_HUDElementFancy

A more "fancier" element that allows for more features, that mostly try to automate content that will be commonly visible especially in a casual environment none-timed environment, their biggest feature is that they fade in and out depending on the specified states such as the following:
  • Moving
  • Idling
  • In first person
  • Dead
  • Missing health

All of these above varies for each HUD and by default ALL Fancy classes are affected by player velocity, this is why they move up/down and even shake aggressively near explosions.

This happens with the healthbar, pons and time piece counters and much more, other than that, they also "react" to your movements, shake and much more showing as if the HUD moves with your mobility.

As the title implies, its all there to make them look fancy.

Creating Elements
The entire system of this HUD is meant to utilize to specifically build elements every frame, which then later on depleted after rendering the content.

They are meant to simplify the process but also allow for more complex stuff outside of using "text", this is seen with pons for example, using the AddElement for icons and AddNumberElement(); for number showcase (used for Time Pieces and Pons amount and pretty much quests like shard collecting and treasure pons).
var array<HUDElementFancy> Elements;

When to use?
Despite everything said, Fancy Elements are mostly used to showcase fancy elements that requires an entire built system to be designed, train rush timer and everything mentioned does that, however there's also common usage for it, and its mostly over showing statistics that are important for the level in a fancy way.

Especially if you have an entire set up of textures that is provided for the numbers AND the icons!

In-game Examples
Pons and Time Pieces are more casual so they aren't visible all the time, and instead will appear when their own content was collected at any point, other than idling in your place.

Whilst Health does also showcase during idle state, they also show up when you are 2 HP and below to remind the player that they are low health.

And then theire's train rush timer which is always shown regardless of state to remind of the current time left.

Mod Examples
Death Defiance showcases the amount of lives (if set in the config to be 2+ lives).
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2079252662
Death Counter showcase deaths similar to the Deathwish ones OUTSIDE of a Deathwish.
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2083978547
Counts all kills you've done from the moment you install this mod, but showcases an icon to showcase you the exact number to the right of your current health.
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2253217576
Menu Elements
A HUD Element used for Menus which enables mouse functionality, when activating a HUD Element that extends this class, the mouse will appear on screen, and subsequently interact with the environment, this happens in OnOpenHUD() call, so it is wise to NOT override the function and instead extend it via "Super.OnOpenHUD(H, command);".

Unfortunately this HUD requires a full fledged guide to explain ins and outs, especially with getting a mouse interacting with the HUD itself, this page will stay as WIP but if you want my honest recommendation check out Hat_HUDMenu(s) found in the source code, they already established the functionality and are a great way to learn them and recycle them for your own personal usage.