Letter slots¶
SetText operates on specialised TextMesh that are configured to be rendered. They are pooled in an array field called letterpool
and the game allocates 500 of them on boot.
Such a TextMesh is refered to as a letter slot. Letter slots are the building blocks of SetText's rendering. Every piece of text rendered from SetText is done with letter slots. Since the game enforces a limit of 500, it means that at any given moment, at most 500 letter slots may be in use by SetText.
What a letter slot represents depends on the rendering system. In regular letter rendering, each letter slot is a single character while in single letter rendering, each letter slot is a complete line. The main purpose of the latter is to use the least amount of letter slots whily having much less, but sufficient compatibility with the various visual effects the former rendering method provides. Using less letter slots has many performance benefits since less TextMesh and font Materials are in use.
Since 500 characters isn't a whole lot and it would be easy to run out of letter slots, SetText makes sure to periodically free the letter slots it no longer needs to render. Doing this allows to use the same letter slots that were used previously after they were freed. So effectively, the 500 limit only applies for a single moment: there cannot be more than 500 letter slots rendered on screen at the same time, but reusing letter slots that were freed is fine.
NOTE: Although there are 500 slots available, the first one is always going to be used by the game at all time from boot due to FontSet, more details in a section below.
Allocating a letter slot¶
Allocating a letter slot involves the method NewLetter:
private static TextMesh NewLetter()
private static TextMesh NewLetter(string id)
It creates a GameObject whose name is letterX
where X
is the value of id
with a TextMesh that is setup as a SetText letter slot and is then returned. The parameterless version of the method is UNUSED, but remains functional and it calls the other overload with an id
value of empty string.
This is primarily called as part of LoadEssentials when setting up the initial 500 letter slots in the letterpool
, but it's also possible GetEmptyLetter (more details in a section below) calls it if it finds out that a letter slot is null.
A letter slot is a TextMesh that is setup with the following characteristics which are optimised for SetText usage:
- Childed to the MainCam (where MainManager resides) with a local z position of 10.0 on layer 5 (
UI
) - richText is disabled
- fontStyle of Normal
- anchor set to LowerLeft
- MeshRenderer's shadowCastingMode set to Off
- MeshRenderer's allowOcclusionWhenDynamic disabled
What this means is that under normal cirumstances, all letter slots are configured like this on boot and if for some reasons, one becomes null, it will eventually get recreated by this method when obtaining a free slot.
Obtaining a free letter slot¶
As for how to obtain a free slot, this is where the method GetEmptyLetter comes in:
public static TextMesh GetEmptyLetter()
It finds and allocate if needed a free letter slot from letterpool
and return it or return null if no letter slots is available. If the letter slot is null, it is allocated using NewLetter with its letterpool
index ToString as the id before being returned. For any non null letter slots, only those with an text of empty string are considered free by this method.
Reserving a free letter slot¶
After obtained a free letter slot, it needs to be reserved by SetText. This is done by SetLetter:
public static void SetLetter(ref TextMesh letter, int fontid, string text, Transform parent, Vector3 pos, Color color, int sortorder, Vector3 size)
It configures the letter slot letter
with the following:
- SetFont(
letter
,fontid
) called (more details in a section below) - Childed to
parent
- Local position of
pos
- text of
text
- anchor of LowerLeft
- color and MeshRenderer's material.color of
color
- angles of Vector3.zero
- scale of
size
- MeshRenderer's sortingOrder of
sortorder
Since the text isn't empty, it effectively reserves the letter slot. GetEmptyLetter will not be able to return it until it is free.
Freeing a reserved letter slot¶
In order for SetText to free a letter slot, the method DisableLetter needs to be called on it:
private static void DisableLetter(TextMesh input)
It either destroys input
if it doesn't have a Letter
tag (meaning it's not an active letter slot) or disable the letter slot if it does have a Letter
tag such that it is considered free again. The disablement process goes as follow:
- Set
input
.text to empty string - If
input
has a FontEffects, it is destroyed input
gets childed to the MainManager instanceinput
.tag is reset toUntagged
It's the prefered method for SetText to disable letter slots.
It's possible to disable all letter slots childed to a GameObject using a method with the same name:
private static void DisableLetter(Transform input)
private static void DisableLetter(Transform input, bool destroy)
It performs the following on input
:
- Destroys all children GameObject with a ButtonSprite component
- Call DisableLetter on all children's TextMesh
- If
destroy
is true,input
gets destroyed
The overload without a destroy
parameter has its value default to true.
There's an even more useful helper method to batch free everything childed to a GameObject: DestroyText.
public static void DestroyText(Transform parent)
public static void DestroyText(Transform parent, bool destroy)
It Calls DisableLetter on every child of parent
with a tag of Text
where DisableLetter has a destroy parameter of destroy
if a value is sent (otherwise, the overload used is DisableLetter(Transform) which behaves as if true was sent).
It's mainly useful for destroying large amount of UI text that were rendered with SetText in non dialogue mode.
Configuring a letter slot font¶
While SetLetter already allows to configure the font, it's possible to do it manually and to change it after the fact by calling SetFont:
public static void SetFont(TextMesh letter, int fontid)
It configure letter
such that it uses the font specified by fontid
including setting the proper material (stored in fontmat
) and the font shader (fontmat[0].shader
) on the MeshRenderer (all fontmat
uses the same 3D text shader). More precisely, the font used will be fonts[x]
where x
is FontID(fontid
) and it will also dictate the fontmat
to use in the same manner
Here's the signature of FontID:
public static int FontID(int id)
Returns id
mapped to a font id with the following logic:
- Any negative numbers maps to their absolute value + 1
- 2 (UNUSED) maps to 1 (
D3Streetism
) - Any other numbers of 0 or above are unchanged
This mapping is documented in the fonttype documentation.
Calculating letter slots spacing¶
A big part of SetText is the ability to automatically break lines such that they render on the textbox without overflowing. Doing this requires to know the rendered space a letter slot takes on the screen in regular letter rendering.
This is what the method GetLetterOffset gives:
public static float GetLetterOffset(char letter, int fontid, float size)
It returns the amount of space letter
takes horizontally when rendered by SetText in regular letter rendering using a font with id fontid
and a size of size
. More precisely, this is calculated the following way:
- If
letter
is, the result is 0.3 *
size
- If
letter
is not contained in FontID(fontid
), the result is 0.3 *size
- If nether of the above cases applies, the result is (the CharacterFont's advance * 0.75 - 2.0) * (
size
/ the fontSize of the Font)
There's also a specialised version for single letter rendering when a backbox command is involved to calculate its position and scale:
private static float GetLetterOffset(TextMesh letter, float size)
public static float GetLetterOffset(char letter, int fontid, float size)
It returns (letter
.bounds.extents.x * 2.0 - 2.0) * size
. If letter
is null, 0.3 * size
is returned instead.
Even more useful for OrganizeLines though is the method GetTextLength:
public static float GetTextLenght(string text, int fontid)
It returns the sum of all GetLetterOffset values of each char in text
using a fontid of fontid
and a size of 1.0. Each in
text
is counted as 0.3 without calling GetLetterOffset.
FontSet¶
On LoadEverything, there is a method that gets called towards its end that has an impact on letter slots allocation: FontSet.
private void FontSet()
This method calls SetText in non dialogue mode in such a way that all letters contained in each fonts and whose characters are in any letterPromptHelp
will be rendered under the game's normal viewport. This is a caching optimisation to force Unity to preload most letters and fonts materials used for future text rendering which avoids the possibility of stutters when loading any of these assets.
The SetText call is done with a position of (0.0, 0.0, -1.0), parent of MainCamera
and a string of |
single|
followed by a list of font commands with all valid indexes each being superceded with every letters of every letterPromptHelp
.
Because this is done on boot with no explicit destruction later by the game, it remains reserved. It effectively means that while there are 500 letter slots available, only 499 are usable by the game under normal gameplay at all time.
TempLetter¶
There's an UNUSED (and broken) coroutine related to letter slots: TempLetter.
public static IEnumerator TempLetters(string text, int[] spacebreaks, bool rainbow, Vector3 posLeave0ToCenter, float showtime)
This is UNUSED and left in a broken state, but it can technically be called. It is NOT recommended to call this coroutine.
Temporarily renders text
using newly allocated letter slots for showtime
amount of seconds with a rainbow font effect if rainbow
is true. The spacebreaks
and posLeave0ToCenter
parameters don't do anything and their values should always be null and Vector3.zero respectively.
This coroutine is broken because the letter slots used aren't configured properly (wrong positioning, wrong font and wrong font sizes). This results in the text not rendering properly with the default Unity font.