Page 1 of 1

HUDs and Actors and Fonts and Such

Posted: Sun Jan 30, 2022 10:59 pm
by Kinsie
Yo,

I'm currently messing around with a HUD that can be customized using actor properties in the player class, to avoid turning the HUD into a bloated mess of switch cases or if/else statements for per-class HUD customization. Currently it works fairly well for stuff in the Draw/DrawMainBar function like changing the STBAR graphic or the Y height of the numbers, but it's fallen apart when it comes to fonts.

One of the properties in the PlayerClass is a string called SBarFont. The draw method grabs the player's SBarFont string, tries to find a font based on it, and creates it if it exists. At least, in theory.

Code: Select all

	protected void DrawMainBar (double TicFrac)
	{
		let plr = Mix_PlayerPawn(CPlayer.mo);
		Font plrfnt = Font.FindFont(plr.SBarFont);
		
		if (!plrfnt)
		{
			Console.Printf(String.Format("\cdMix_Error: Font ''%s'' not found! Resorting to BigFont...", plr.SBarFont));
			playerfont = HUDFont.Create("BIGFONT");		
		} else {
			playerfont = HUDFont.Create(plrfnt);
		}       
Problem is, for some reason it returns null unless the font has previously been loaded in the Init() function. Moving this code to Init() as it stands results in a "VM execution aborted: tried to read from address zero" fatal error, which I'm assuming (without knowing exactly how the engine does it) is done because Init() runs before actors etc. like the player are loaded into the map.

So! Here lies the question. How would I grab the player's playerclass's properties in the Init() function?

Alternatively, if that's not possible: Currently I'm using the workaround of loading all the custom fonts manually in the Init() function so that they exist for the Draw() function...

Code: Select all

	override void Init()
	{
		Super.Init();
		SetSize(32, 320, 200);

		// Create the font used for the fullscreen HUD
		Font fnt = "HUDFONT_DOOM";
		mHUDFont = HUDFont.Create(fnt, fnt.GetCharWidth("0"), Mono_CellLeft, 1, 1);
		fnt = "INDEXFONT_DOOM";
		mIndexFont = HUDFont.Create(fnt, fnt.GetCharWidth("0"), Mono_CellLeft);
		mAmountFont = HUDFont.Create("INDEXFONT");
		
		diparms = InventoryBarState.Create();
		
		// i need to define the fonts in init for them to load for reasons.
		// i can't access the player class in init because nothing's spawned yet.
		// i'm hoping to work around this in the future, but for now consider
		// this bullshit precaching or summat
		font fart;
		fart = "bigfont_consoledoom";
		fart = "hudnums_wolf";   
	}
...but naturally, this'll fall apart as more player classes are added, especially if they're added by people who aren't me. Would there be a way within the confines of the Init() function to iterate through all the classes in the playerclasses array, grab their SBarFont property string and load their font as a sort of precaching state/workaround? I've tried to do this myself, but haven't figured out a way that works.

Re: HUDs and Actors and Fonts and Such

Posted: Mon Jan 31, 2022 4:26 am
by Player701
First of all:
Kinsie wrote:Problem is, for some reason it returns null unless the font has previously been loaded in the Init() function.
This is probably a bug and should be reported, especially if you can construct a small test sample to reproduce the issue.
Kinsie wrote:Moving this code to Init() as it stands results in a "VM execution aborted: tried to read from address zero" fatal error, which I'm assuming (without knowing exactly how the engine does it) is done because Init() runs before actors etc. like the player are loaded into the map.
Correct. CPlayer is null at this time, so it's no surprise your code triggers a VM abort.
Kinsie wrote:So! Here lies the question. How would I grab the player's playerclass's properties in the Init() function?
Theoretically, you can do this via the global PlayerClasses array. Each playerclass has a Type, and you can use GetDefaultByType on it and access everything from there.

However, I'm not sure this is the proper way to implement this kind of functionality. The catch here is that the value of CPlayer can change at any time between draw calls (e.g. by pressing F12 during a coop game), and this behavior is out of your control. If you cache the fonts during Init, you will have to monitor the changes yourself. And if you create new HUDFont instances every time your draw your HUD, you put unnecessary strain on the GC.

A better solution would be to cache the HUDFont instance(s) within your player pawn actors. This is allowed because any such fields/methods can simply be marked ui, and your HUD code can access them directly.

Code: Select all

class MyPlayerPawn : DoomPlayer
{
    private ui string sbarFontName;
    private transient ui HUDFont sbarFont;

    property SBarFont : sbarFontName;

    Default
    {
        MyPlayerPawn.SBarFont "BIGFONT";
    }

    ui HUDFont GetSBarFont()
    {
        if (sbarFont == null)
        {
            sbarFont = HUDFont.Create(Font.FindFont(sbarFontName));
        }
        
        return sbarFont;
    }
}
NB: you will probably have to check whether Font.FindFont returned null here, and fall back to some default font in this case. In your HUD code, you initialize a fallback HUDFont instance only once (in Init); when you draw, check if your player is of type MyPlayerPawn (or any of its descendants) and call GetSBarFont, otherwise use the fallback instance. This way, you do not create new font instances every time you have to draw your HUD, nor do you have to monitor whether the value of CPlayer has changed.
Kinsie wrote:Would there be a way within the confines of the Init() function to iterate through all the classes in the playerclasses array, grab their SBarFont property string and load their font as a sort of precaching state/workaround? I've tried to do this myself, but haven't figured out a way that works.
Sure, like I mentioned above, you can use the player classes array:

Code: Select all

for (uint i = 0; i < PlayerClasses.Size(); i++)
{
    let cls = PlayerClasses[i].Type;

    if (cls is 'MyPlayerPawn')
    {
        MyPlayerPawn(GetDefaultByType(cls)).GetSbarFont();
    }
}
Note that this is still a work-around, and if FindFont starts returning null for legitimate fonts after some point in time, this is most likely unexpected behavior and should be reported as a bug.

Re: HUDs and Actors and Fonts and Such

Posted: Mon Jan 31, 2022 8:52 am
by Kinsie
Hmm. I think I managed to get your class-based font caching implemented, but I'm not sure I'm doing it the right way. All I know is, if the font hasn't already been explicitly loaded in Init(), I get a "tried to read address zero" error without my paltry error checking ever factoring into it.
Spoiler:
The for loop at the end doesn't seem to make much difference, I guess the font needs to be explicitly loaded in Init()?

Any theories on how I've screwed this up?

Re: HUDs and Actors and Fonts and Such

Posted: Mon Jan 31, 2022 9:10 am
by Player701
Kinsie wrote:The for loop at the end doesn't seem to make much difference, I guess the font needs to be explicitly loaded in Init()?
You need to put the loop into your Init code if you want to explicitly load all fonts automatically. It definitely does work for me with a custom font defined via [wiki]FONTDEFS[/wiki]. In GetSBarFont, you may also want to check if Font.FindFont returns null and fall back to some default font, in case one of the player classes has an invalid font defined for it. Note that HUDFont.Create will not trigger a VM abort if it gets passed a null font.

I also could not reproduce the issue with FindFont returning null unless the font has been previously loaded in Init. If you're using some other font (not a FONTDEFS-based one), could you please send it to me (perhaps via PM) so that I could test it myself?

Re: HUDs and Actors and Fonts and Such

Posted: Mon Jan 31, 2022 8:57 pm
by Kinsie
Player701 wrote:If you're using some other font (not a FONTDEFS-based one), could you please send it to me (perhaps via PM) so that I could test it myself?
I'm using Unicode-format fonts. These ones, specifically.

Re: HUDs and Actors and Fonts and Such

Posted: Tue Feb 01, 2022 12:14 am
by Player701
Thank you very much, I've managed to reproduce the issue. It actually seems to be intended behavior: Font.FindFont only searches for fonts that have already been initialized. If you don't know whether the font has already been loaded, use Font.GetFont instead. It calls FindFont internally, and if it fails (returns null), it actually loads the font and returns it. So FindFont returning null is not a bug.

TL;DR: Replace FindFont with GetFont, and the for loop from my previous post is also not needed.

Re: HUDs and Actors and Fonts and Such

Posted: Tue Feb 01, 2022 2:08 am
by Kinsie
Holy crap, it works! Fantastically, even! Thanks so much for delving into this weird font esoterica for me, I greatly appreciate it!