HUDs and Actors and Fonts and Such

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!

Moderator: GZDoom Developers

Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

Please bear in mind that the people helping you do not automatically know how much you know. You may be asked to upload your project file to look at. Don't be afraid to ask questions about what things mean, but also please be patient with the people trying to help you. (And helpers, please be patient with the person you're trying to help!)
User avatar
Kinsie
Posts: 7296
Joined: Fri Oct 22, 2004 9:22 am
Discord: Find Me...
Twitch ID: thekinsie
Location: MAP33

HUDs and Actors and Fonts and Such

Post 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.
User avatar
Player701
 
 
Posts: 1392
Joined: Wed May 13, 2009 3:15 am
Discord: Player701#8214
Operating System: Windows 10/8.1/8/201x 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: nVidia with Vulkan support
Location: Russia

Re: HUDs and Actors and Fonts and Such

Post 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.
User avatar
Kinsie
Posts: 7296
Joined: Fri Oct 22, 2004 9:22 am
Discord: Find Me...
Twitch ID: thekinsie
Location: MAP33

Re: HUDs and Actors and Fonts and Such

Post 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?
User avatar
Player701
 
 
Posts: 1392
Joined: Wed May 13, 2009 3:15 am
Discord: Player701#8214
Operating System: Windows 10/8.1/8/201x 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: nVidia with Vulkan support
Location: Russia

Re: HUDs and Actors and Fonts and Such

Post 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?
User avatar
Kinsie
Posts: 7296
Joined: Fri Oct 22, 2004 9:22 am
Discord: Find Me...
Twitch ID: thekinsie
Location: MAP33

Re: HUDs and Actors and Fonts and Such

Post 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.
You do not have the required permissions to view the files attached to this post.
User avatar
Player701
 
 
Posts: 1392
Joined: Wed May 13, 2009 3:15 am
Discord: Player701#8214
Operating System: Windows 10/8.1/8/201x 64-bit
OS Test Version: No (Using Stable Public Version)
Graphics Processor: nVidia with Vulkan support
Location: Russia

Re: HUDs and Actors and Fonts and Such

Post 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.
User avatar
Kinsie
Posts: 7296
Joined: Fri Oct 22, 2004 9:22 am
Discord: Find Me...
Twitch ID: thekinsie
Location: MAP33

Re: HUDs and Actors and Fonts and Such

Post by Kinsie »

Holy crap, it works! Fantastically, even! Thanks so much for delving into this weird font esoterica for me, I greatly appreciate it!

Return to “Scripting”