controllerface / bvge

Personal game dev experiments
0 stars 0 forks source link

Create Basic HUD Renderer #71

Closed controllerface closed 4 months ago

controllerface commented 5 months ago

I need to start displaying some information on the screen about the player, like health, inventory, etc. To do that, I will need a HUD renderer that can write out all of this data to the screen.

I want to think of the HUD as being split into a few key sections:

  1. player status - health, and any status effects that may be active
  2. inventory - the items the player has
  3. equipment - the equipment the player has on
  4. hotbar - the items that can be selected for in-game actions, like tools, guns, swords, etc.

... and probably a couple others as time goes on, but these should be good for a starting point.

The UI will need work on multiple screen sizes, with 1920 x 1080 being the low-end, 4K being the top end. As long as it is acceptable in this range it should be OK.

There may need to be some more complex HUD interactions later, that is not important for this story though, just some form of display is enough, with the ability to show/hide the larger "panels" like equipment and inventory.

controllerface commented 4 months ago

I finally made some progress on the precursor to a full HUD, which is text rendering. At the moment, I just have a simple message printed in the bottom corner of the screen, just to show it works, but I will go from here and build out the full UI.

I have used the basic tutorial from the learnopengl.com site, with a number of tweaks to make it work in Java, as well as fix some things about their implementation that I didn't like. However, it's still extremely inefficient, requiring a draw call for each letter of text. That is obviously not going to fly, so I will be changing that over to a more efficient mechanism.

For this part of the code, I am probably going to avoid using CL kernels and instead just push data into the GL buffers directly. I considered also keeping HUD data in CL buffers, but after stewing on it for a bit, I think that's actually more work both code-wise and from the CPU/GPU load perspective.

Now that there is an inventory system in place, the main data that needs to be displayed is already CPU side, so displaying it should just be a matter of rendering out the information that make up the HUD itself, from memory. This is of course easier said than done. There will be a number of things to get right, like scaling and docking, which I also want to make configurable. At the very least, I want to make the UI scale variable to accommodate different screen sizes. If I can, I'll also make the panels able to dock on different sides, or possible even placeable/draggable, but I am not going to focus on that if it's too much work.

controllerface commented 4 months ago

A little more progress on the text renderer, I am now using a 2D texture array instead of individual textures for each character, so there is only one texture bind per tick when things are rendered. I still need to set up batching so there's fewer draw calls. As it stands, it is still drawing once per character. Also, I need to just drop calls for single spaces, there' no real point actually drawing them since the space is an an empty texture.

Once I have these things done, the next step is going to be drawing multiple text strings in different locations. Right now, I can really only render one string of characters starting at some screen position. I will want to have several different strings drawn in multiple locations to build out an actual UI. I still haven't decided how to tackle layout of objects for the UI, but that I think is a follow-on from batching.

controllerface commented 4 months ago

I whipped up a very basic event system for the HUD class to use, it now gets informed when the window size changes. My initial attempt to properly snap text in response to the change isn't working that well, but its a start. I also created a TextContainer class and have employed it to allow drawing of multiple strings of text, in whatever location on screen I want, and it all uses the same buffer. I will probably have to expand this class or build a base class for other UI components, so i can push forward with "panels" of some kind, which will act as visual containers for both text objects as well as elements like a health bar and other core stuff like that.

I am still trying to wrap my head around the best way to deal with some kind of hotbar and/or crafting menu. Originally, I was going to have blocks work pretty much the same way as minecraft/terraria, where they are essentially items you place from your inventory. However, I am considering have "place bock of x" be a form of a crafting recipe you select from a list. Placing a block would require some amount of that particular mineral type and of course deduct that amount from your inventory when placed. This way I can have a system where the "yield" of breaking a block is not always enough to replace that same block.

All in all, happy with progress here. Another thing I forgot to mention is I have converted the drawing code over to using indirect calls like the ModelRenderer, and also have switched to using buffer mapping instead of using bufferSubData calls. This makes it a lot more efficient as only the data that is changed is actually written and it no longer requires "raw" arrays as a go-between from the CPU->GPU transfer. I may investigate using both of these techniques in the other renderers at some point.

controllerface commented 4 months ago

I have added an event now to notify when the player inventory changes and also provided the HUD renderer with the inventory object, and using this I now have a basic display of the player inventory on screen. For now, it is simply a text list of the objects collected that appears in the upper left corner of the screen. I will need to come up with some way to "fold up" the inventory so it takes up only some fixed portion of the screen and also some way to scroll it if it gets too large.

Another thing I may need to do is is build in some way to re-size or re-create the individual buffers used to store HUD data, so that it can grow as needed. Right now, I am just using the batch size which probably gives me way more space that I need, but it's actually quite wasteful. Even with an entire screen full of HUD elements, I probably won't use that much space, and let's say I did, if I added one extra text container with a single character in it, it would probably crash. This is not an immediate worry, but I will need to address this eventually.

I also need to determine how best to interact with the HUD using the mouse. Luckily, I don't need anything nearly as complex as the game physics system since I only have a single point and I also plan on using simple rectangles to implement all of the "hot spots" for interaction. If the number of rectangles is small enough, I probably can get away with just checking them all against the mouse position, but will experiment a bit once I actually have some things to click on.

So.. slow progress, but progress nonetheless. I want to make sure I take my time on this, not necessarily on the visual polish, but I really want the code to be as efficient as possible.

controllerface commented 4 months ago

Giving this some thought, I may go ahead and call this done for now. The other items mentioned like health/equipment etc. are not likely to be added for a while, but the basic HUD bits are in place now and I think I want to move on to block placement. I want to move more toward getting the simplest implementation of things in place and then moving to the next thing, as I am finding that is an easier path right now.

I did do some debugging to make sure everything works as I expect, and I after turning on global debugging for OpenGL, I spotted a few things and fixed them. Going forward, I'll be leaving that in place to help ensure I don't have hidden errors.