HUD¶
The game can present UI elements showing some game informations in a HUD fashion. They are expressed in 5 hud elements with matching hudfont elements to present their text with a DynamicFont. The first 4 hud elements also have a backing hudpsrites that changes color depending on the new values on RefreshHUD. Here's an overview of what each of the hud elements shows:
hud[0]:playerdata[0]'s HP / max HPhud[1]:playerdata[1]'s HP / max HPhud[2]:playerdata[2]'s HP / max HPhud[3]: The party's TP / max TPhud[4]: Berry count
There's also the discoverymessage HUD element which is handled completely separately. It represents a HUD notification that only pops temporarilly when discoveryhud cooldown hasn't expired yet. This typically happens when a new librarystuff entry was unlocked.
RefreshHUD¶
RefreshHUD is the heart of the HUD system that manages all the hud elements and their related data. It's a method called on LateUpdate as long as playerdata isn't empty (a map isn't being loaded) and basicload is true (LoadEverything ran once). It has the following signature:
private static void RefreshHUD()
The method has 3 main logic (the first one that applies is done, they are mutually exclusive):
- (Re)create all
hudelements if it's null or empty - The first 4
hudelements updates and are shown if instance.hudcooldownhasn't expired yet by calling ShowHUD(true) which is detailed in a section below - Hides the HUD if neither of the above applies by calling ShowHUD(false) which is detailed in a section below
No matter what however, the following tasks always happen at the end of the method:
- If the instance.
showmoneycooldown hasn't expired yet (thehud[4]berry count HUD element should be shown) and the message lock is released, instance.showmoneydecreased byframestep - If the instance.
discoveryhudcooldown hasn't expired yet (the instance.discoverymessageHUD element should be shown), it decreases by instance.framestep - All of the
hud[4]berry count HUD element updates happens as detailed below regardless even if the HUD was recreated and even if it's hidden
HUD Creation and Recreation¶
The HUD is meant to be automatically recreated once it's destroyed. This is ensured by RefreshHUD.
Creation¶
If the HUD isn't created yet or it was destroyed, RefreshHUD will do the following:
- Calls CreateHUD (detailed below)
- Reset
tphpandptmhpto new arrays with the same length asplayerdata(these are last values observed by RefreshHUD for use in values updates, this is detailed in a section below) hud[4]berry count HUD has its local x position set to thehud[3]TP HUD's local x position so they are aligned horizontally with each other
CreateHUD is what does most of the HUD (re)creation. Here is the signature:
private static void CreateHUD()
More precisely, here's what this method does:
- Calls ApplyBadges
- Calls ApplyStatBonus
- Reset
hud,huspritesandhudfontto new arrays (length is 5 except forhudspriteswhich is 4) - Creates a new GameObject named
HUDchilded to theGUICamerawith no angles or local position hudvalueis reset to {6.5, 7.5, 8.5, 9.5, -6.5, -7.0}- Each
hudis created via a call to NewHUDElement where the parent is theHUDGameObject, the hudid is the matchinghudindex and the position depends on the specifichud:- 0: (-7.0, 8.5)
- 1: (-3.5, 8.5)
- 2: (0.0, 8.5)
- 3: (7.0, 8.5)
- 4: (
hud[3]x position, 0.0 -hud[3]y position)
NewHUDElement is the method that creates each hud, here's the signature:
private static void NewHUDElement(int hudid, Vector2 position, Transform parent)
This is the method that ends up assigning hud[hudid] to a newly created GameObject with a local position of position (the z is 10.0) childed to parent (normally, always the GUICamera).
Here's the properties of the GameObject assigned:
- Name of
hudXwhereXishudid - Childed to
parent - Local position of (
position.x,position.y, 10.0) - No angles
- One child being a new GameObject named
basespriteXwhereXishudidthat has a SpriteRenderer. It has the following properties:- Layer 5 (
UI) - sprite of
guisprites[4](the HUD background sprite) - sortingOrder of 10
- Scale of (0.55, 0.65, 1.0)
- No angles
- A child being a new GameObject named
facespritethat has the following properties:- Layer 5 (
UI) - Local position of (-2.3, 0.0, 0.0)
- Scale of 80%
- sortingOrder of 12
- Layer 5 (
- Layer 5 (
From there, each hudid gets different properties. For any below 3 (any playerdata HP):
basesprite's color set tocharcolor[instance.playerdata[hudid].animid]facesprite's sprite set toguisprites[instance.playerdata[hudid].animid + 5](player party member HUD icon)hudsprites[hudid]set to thebasesprite's SpriteRenderer- NewUIObject called to create a new object named
hpiconchilded to thefacespritewith local position (0.45, -0.6, 0.0) and a scale of 50% usingguisprites[24](HP icon) with a sortingOrder of 13 - If
hudidrefers to aplayerdatathat exists,hudfont[hudid]is SetUp with an inital text (see the table below for the formatting and values) with the following parameters:- Without centralize
- With noKerning
- Frequency of 2.0
- Fonttype of 2 (the unused mapping that redirects to
D3Streetism) - Sortorder of 11
- Fontsize of 1.75x
- Childed to the
basesprite - Position of (-0.9, -0.6, 0.0)
- Color of Color.white
- Small of (3.0, 0.85)
- The
dropshadowis set to true
For a hudid of 3 (TP):
basesprite's color set tomenucolor[3]facesprite's sprite set toguisprites[28](TP icon)hudsprites[3]set to thebasesprite's SpriteRendererhudfont[3]is SetUp with an inital text (see the table below for the formatting and values) with the following parameters:- Without centralize
- With noKerning
- Frequency of 2.0
- Fonttype of 2 (the unused mapping that redirects to
D3Streetism) - Sortorder of 11
- Fontsize of 1.75x
- Childed to the
basesprite - Position of (-0.9, -0.6, 0.0)
- Color of Color.white
- Small of (3.0, 0.85)
- The
dropshadowis set to true
For a hudid of 4 (Berry count):
basesprite's color set tomenucolor[0]facesprite's sprite set toguisprites[29](Berry icon)hudfont[3]is SetUp with an inital text (see the table below for the formatting and values) with the following parameters:- Without centralize
- With noKerning
- Frequency of 2.0
- Fonttype of 2 (the unused mapping that redirects to
D3Streetism) - Sortorder of 11
- Fontsize of 1.75x
- Childed to the
basesprite - Position of (-0.75, -0.6, 0.0)
- Color of Color.white
- The
dropshadowis set to true
Destruction¶
The HUD can be destroyed temporarilly with a call to RebuildHUD:
public static void RebuildHUD()
It does the following:
- Calls ApplyBadges
- Calls ApplyStatBonus
- Destroys
hud[0]'s parent ifhud[0]exists - Reset
hudto null
The effects are temporary because as soon as RefreshHUD gets called, it will recreate the HUD with the process mentioned above, but it has the effect of forcing a reconstruction and thus an update of the HUD.
This method only gets called on event 22 (loading a save file) and on a ChangeParty. It's meant to reflect drastic HUD changes only as if only values updates are needed, ForceHUD or RefreshHUDValues are better suited (they are detailed in a section below).
HUD Updates¶
When the hud updates via RefreshHUD, a bunch of different tasks and values are involved. As a reminder, the first 4 hud elements are only updated if instance.hudcooldown hasn't expired yet while hud[4] (berry count) is always updated when RefreshHUD is called.
They are organised like the following (the first 3 hud elements will be refered to as hud[0-2] because they are all similar, but they refer to different associated values indexed by the same hud index):
| Transform and DynamicFont | Text format | Text values | Last values | Value updates | hudsprites updates |
Sounds |
|---|---|---|---|---|---|---|
hud[0-2], hudfont[0-2] |
X/Y |
|
|
|
hudsprites[0-2].color changes depending on playerdata[0-2].hp:
|
If playerdata[0-2].hpt increases as a result of the incremental update while still not being equal to playerdata[0-2].hp and sounds[5] isn't plauying anything, PlaySound is call to play TP on sounds[5] with 1.0 volume and a pitch of 1.15 + random number between -0.1 and 0.1 |
hud[3], hudfont[3] |
X/Y |
|
|
|
hudsprites[3].color (if the sprite exists) is updated to a lerp from the existing color to instance.menucolors[3] with a factor of 0.05 * framestep |
If instance.tpt increases as a result of the incremental update while still not being equal to instance.tp and sounds[5] isn't plauying anything, PlaySound is call to play TP on sounds[5] with 1.0 volume and a pitch of 1.0 + random number between -0.1 and 0.1 |
hud[4], hudfont[4] |
X |
X: instance.moneyt padded left to 3 digits |
X: tempmoneh |
X: instance.moneyt incrementally updates towards instance.money, but the text only updates if last value is different |
None (hudsprites[4] doesn't exist) |
Before instance.moneyt updates, If it's not being equal to instance.money while instance.showmoney is above 0.0 and sounds[4] isn't plauying anything, PlaySound is call to play Money on sounds[4] with 1.0 volume and a pitch of 0.7 (1.0 instead if instance.money is higher than instance.moneyt) + random number between -0.1 and 0.1 |
To note, an incremental value update means the backing HUD value only changes by 1 towards its target on each call while a direct update sets the value directly.
Additionally, a HUD update also causes the following to happen:
- If the game isn't
inbattle, instance.hudcooldowndecreases byframestep(so the HUD is never hidden during battle) - ShowHUD(true) is called to reveal the hud (this is detailed in a section below)
- If instance.
hudcooldownexpired, it is set to -100.0
Manually force direct updates¶
There's a way to force all HUD values to update immediately to their target values. This is done with the RefreshHUDValues method:
public static void RefreshHUDValues()
It the following to update the displayed HUD values to their backing value:
- Set all
playerdata'shptto theirhp - Set
tpttotp - Set
moneyttomoney
It's only called on event 22 (save loading), during StartBattle and on PauseMenu.PrepareExit, but it is meant to be callable externally to force the next RefreshHUD to have an updated HUD without any incremental value updates (everything will stabilise by the end of the next refresh).
Reveal and hiding the HUD¶
RefreshHUD also controls if it should reveal or show the HUD depending on instance.hudcooldown. This is managed by the ShowHUD method:
public static void ShowHUD(bool show)
The HUD is shown if show is true, hidden if it's false.
This method will first determine if the HUD should be shown when it wasn't before. It happens if show is true while the hudvisible is false. If that happens, ForceHUD is called. hudvisible is updated to the show value either way.
ForceHUD is a method with the following signature:
private static void ForceHUD()
It sets all tphp, ptmhp, tmtp values and tempmoneh to -1.
This essentially forces a call to RefreshHUD on the next LateUpdate because it will find that everything the HUD should display has changed.
After that, what happens in ShowHUD is each hud local y position gets lerped from the existing one to a number that depends on the hud index and its matching hudvalue with a factor of TieFramerate(0.2). The target of the lerp uses the following formula:
A * (hudvalue + (hudvalue - X) * (hudvalue - Y))
Here are the variables values:
A: 1.0 except forhud[4]where it's -1.0X: 6.5 ifshowis false, 4.25 if it's true. If it'shud[4], it's the other way aroundY: 4.25 ifshowis false, 6.5 if it's true. If it'shud[4], it's the other way around
Also, this formula is computed after lerping the hudvalue from the existing one to X + the hud index with a factor of TieFramerate(0.15).
discoverymessage¶
There's a HUD notification object that is separared from all the rest of the hud system and that object is discoverymessage.
To show it, discoveryhud needs to be set to the amount of frames that it should be shown. This cooldown is updated by RefreshHUD where if it hasn't expired yet, it is decreased by framestep.
Setting the value will impact a method call in LateUpdate that happens right after RefreshHUD: RefreshDiscovery. Here is its signature:
private void RefreshDiscovery()
The method updates the local position of the discoverymessage UI element according to discoveryhud. If discoveryhud is higher than 0.0, the local position is lerped from the existing one to (-8.0, -3.0, 0.0) with a factor of TieFramerate(0.1) which will smoothly reveal discoverymessage. Otherwise, the same lerp happens, but towards (-8.0, -6.0, 0.0) instead which will hide discoverymessage.
On the first time this is called, discoverymessage will be null which will cause this method to create it from scratch (any subsequent calls reuses the same object). It is set to a NewUIObject with name Discovery childed to the GUICamera with a pos of (-8.0, -6.0, 0.0). It has one child GameObject that is instantiated from the Prefabs/Objects/logbookicon prefab with a local position of (0.0, -1.0, 10.0).
From there, there's 3 SetText calls all done with the following parameters:
- fonttype: 1 (
D3Streetism) - linebreak: null
- dialogue: false
- tridimensional: false
- position: (1.2, -1.0, 10.0)
- No camoffset
- size: Vector3.one
- parent:
discoverymessage - caller: null
This means that there's going to be 3 more children to discoverymessage, one for each of these SetText call for a total of 4 children. They corresponds to indicate what updated in the librarystuff. Here's what they say in order:
|single||color,4||dropshadow,1,-1|followed bymenutext[102](Logbook updated)|single||color,4||dropshadow,1,-1|followed bymenutext[154](Quest complete)|single||color,4||dropshadow,1,-1|followed bymenutext[155](Achievement unlocked)
The last 2 of these are initially disabled upon creation.
The way this is supposed to work is UpdateJournal will change the enablement of these 3 children such that only the relevant one is enabled and the other 2 disabled.