How to make an UI for a 3D game?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By 20xxdd20

What I have to add? There are specific uses for like MarginContainer and CanvasLayer, there is a rule under where should a crosshair go? The whole UI is confusing.

Can you state a specific question?

sash-rc | 2021-08-12 12:59

If I want to make the player UI (crosshair, health bar, cooldowns), under what type of Control it should be? (Control node) Like is there a rule or scheme to follow when making UI?

20xxdd20 | 2021-08-12 13:18

If you’re doing static UIs (99% of all games), the process is actually no different to making a UI for a 2D game. Controls and all 2D nodes are automatically rendered above the 3D viewport, so just create the menu using any Nodes you need.

It will just work. Try it out!

Yuminous | 2021-08-12 23:22

:bust_in_silhouette: Reply From: a0kami

TL;DR:

  • Use a TextureRect for crosshair
  • Range such as ProgressBar/TextureProgress for health bars
  • RichTextLabel for digit cooldowns
  • (orTextureProgress for radial icon progression)

#UI requirements:
You probably are the only one to know which design suits best your project and your workflow.
What I mean by that is UI is not just a generic interface, it’s part of the gameplay and should fit the artistic direction you’re aiming toward.
That said godot built a nice abstraction with convenient classes for UI quick prototyping but also more complex and in-depth interfaces by instancing UI scenes.

Pretty much all UI nodes inherit from Control which works a lot like Node2D.
There is no “main” UI node as all nodes share the same capabilities thanks to the inheritance. Rather, your visual UI nodes will be shaped, controlled, contained via parent helper UI nodes.

For example in my projects, to import UI in the main scene, I instantiate a separate specific UI scene which is based on a generic Control node.
This allow to list all related UI visuals as children and to have a “root-level” script which controls the initialisations and can be used as a proxy in case the different UI parts ever need to interact with each other. But this is not required and could be confusing depending on your workflow/code logic.
An other possibility is having each game element have its own UI elements, for example enemies in a 2D shooter could have their own health bars above their sprites.

Nodes

The main visuals element are summarized here and are basically:

  • Text (Label, RichTextLabel, LineEdit, TextEdit): different way to display text
  • Buttons (LinkButton, ColorPickerButton, CheckBox/ButtonGroup, CheckButton, OptionButton, MenuButton): different ways to handle user input
  • Tabs/TabsContainer
  • Sliders/Bars (Range) : everything related to ranges whether it is to display or set a value/progression
  • TextureRect: Probably the most interesting one, a way to display a 2D image along with NineRectPatch to easily display a scalable image (by tiling the centre but preserving the corners).

For further fine-tuned control such as placement, those can be instantiated as children of a Container. There are many specific containers but to name a few,

  • HSplitContainer/VSplitContainer will offload the calculation to equally place (or stack) respectively horizontally and vertically your nodes.
  • ScrollContainer will offload the scrolling logic for your scrollable elements such as lists.
  • ViewportContainer will allow you to display a sub-view, a texture, shader, camera, its own 3D space, you name it.
  • And the MarginContainer you’re naming is meant to add a margin (spacing toward the exterior of the element) to all children, instead of having to manually add a margin to all your children.

Finally the CanvasLayer you mentioned is used in case you don’t want to have all the 2D related elements to draw on the same “layer”, given the rendering order, some elements could be overwritten/superimposed. For example It allows you to draw a 2D background on the farthest plane, draw the 2D player over it on another layer above it, then draw the UI on yet another layer above the last, and yet a new layer above it for special effects, and so on…

Crosshair

Where you want to instantiate your crosshair is up to you. If it’s closely linked to other UI then maybe a generic Control node as a parent which holds the rest of the UI element makes sense. Or maybe it can be part of your player Node if you plan to have the crosshair react to player input or gameplay logic.

But I can only see 2 convenient solutions to draw your crosshair:

  • TextureRect: loading a image of a crosshair to which you can scale (make it bigger when player moves and have less accuracy or smaller when the player stops and crouch and have better accuracy), you can also change its tint/color depending on what you’re aiming at (by overriding the Material of CanvasItem within the TextureRect with a custom shader).
    There is a crosshair in the godot 3D example demo. Here, it is used to aim where we shoot but also as a 2D origin point from which to shoot a ray projected in 3D space and spawn a projectile (same script, line 140).
    When you aim, the visibility is controlled via an Animation. The TextureRect “visibility” properties are controlled via curves for the given key in the Animation node when either “far” or “shoot” animation is played .
  • Draw it yourself. Whether for Node2D or Control, the Nodes inheriting from CanvasItem let you use a whole set of 2D drawing primitives:
  • draw_line(): draws a line of specified color and width from point A to point B
  • draw_rect(): draws a rectangle at point A of size (x,y) with specified color, filled or not, etc…
  • draw_circle(): draws a circle of origin point C and radius R with specified color, filled or not, etc…
  • draw_ etc…
    You could then manually draw the 4 characteristic rectangles that compose a regular popular crosshair of any size/color you want, solid color or fill color different from border color…

Health Bars

It depends how you want those to look like. Is there text next to a bar ? Is there a numerical value ? Is only filled/empty icons ? Is it a regular colored bar or should it be a texture ?

In your first version you could probably have a ProgressBar, a gauge filled based on a value. You need to somehow connect the health value change in the gameplay to this UI element.
But you can make more detailed version with an image using a TextureProgress.

But basically you can do pretty much any crazy look combining textures and shaders, for example, you could make a spiral or round texture with decreasing alpha along the spiral, and add an alpha cut-off in the shader based on a uniform which represent the health value. (Think Kingdom Hearts style rounded health bar.)

Cooldown

Again, how does the cool down look like ?

  • Is it a simply a filling bar ?

  • ProgressBar/TextureProgress

  • Is it a digit countdown ?

  • Label/RichTextLabel which text value is constantly updated

  • Is it a skill icon ?

  • TextureRect as a background “disabled” icon and either another TextureRect or a TextureProgress with the FILL_CLOCKWISE flag set, on top for a “enabled” icon.

Nice answer!

Yuminous | 2021-08-13 02:54