engo


Tutorial 4
HUD

In this tutorial, we will create a Heads-Up display, or HUD, that can provide information to the player about things like how much money they have at the moment, details about the currently selected city, etc.

Recap

Remember what we did in the last tutorial?
We added the KeyboardScroller, EdgeScroller and MouseZoomer to allow camera movements.

Final Code

The final code for tutorial 4 is available here at GitHub.

What makes HUD so special?

You might ask, what makes an HUD different from, say, the Cities we added in the previous tutorial? Good question! Once we get a map size bigger than the game window, we’ll be able to scroll around our map. Some components we don’t want to move when we move the camera. The HUD shader option makes it so these components do not move as you pan around the game world.

Creating the HUD

Let’s get started creating the HUD entities. We’ll create a 200x200 light gray, partially transparent HUD at the bottom left corner of the screen.

Space component

Getting the dimensions for the SpaceComponent is easy, but we need a way to properly position our HUD in the bottom left corner. Luckily engo has six useful functions to deal with the size of the screen:

Each of these functions also have the corresponding Height variation.

Because we are building a HUD, we are not interested in how large our in-game map is. We are only interested in how big the window itself is. We shall be using the WindowHeight(), and subtracting the height of our HUD (in this case 200).

type HUD struct {
	ecs.BasicEntity
	common.RenderComponent
	common.SpaceComponent
}
hud := HUD{BasicEntity: ecs.NewBasic()}
hud.SpaceComponent = common.SpaceComponent{
      Position: engo.Point{0, engo.WindowHeight() - 200},
      Width:    200,
      Height:   200,
}

RenderComponent

Now we know where to draw it, let’s talk about what we’re going to draw. We could be creating a 200x200 image filled with a white background. This is usually the best way to go, since you can easily add additional effects in your image editing environment. Luckily, engo does not rely on files alone to provide the textures. The common package has a function called common.NewTextureSingle(img common.Image). This allows you to create a texture by providing a common.Image.

Not image.Image

Note that this is not the same Image as in image.Image. common.Image requires three methods: Data() interface{}, Width() int and Height() int. In order to use image.Image, we need to convert it using common.NewImageObject(img).

As noted, we are indeed going to use our own image.Image, and then cast it in order to transform it to a Texture.

It is a bit verbose at the moment, so we’re not going in too much depth into this:

hudImage := image.NewUniform(color.RGBA{205, 205, 205, 255})
hudNRGBA := common.ImageToNRGBA(hudImage, 200, 200)
hudImageObj := common.NewImageObject(hudNRGBA)
hudTexture := common.NewTextureSingle(hudImageObj)

hud.RenderComponent = common.RenderComponent{
  Drawable: hudTexture,
  Scale:    engo.Point{1, 1},
  Repeat:   common.Repeat,
}

But if we were to add this RenderComponent to our entity, and add the entity to our world, we would notice that this is no HUD yet. As we predicted, it moves around as the camera moves. This is because we haven’t set the correct Shader yet. We also set the Z-index of our UI to a higher number, so it’s always on top of anything else on the screen.

Shader

A shader roughly is a program which is being executed on the (hundreds / thousands of) GPU cores, in order to graphically render your texture. Engo has two you can use without much effort: common.DefaultShader (which, surprisingly, is the default), and the common.HUDShader.

We shall be using the HUDShader now:

hud.RenderComponent.SetShader(common.HUDShader)
hud.RenderComponent.SetZIndex(1)

// And finally add it to the world:
for _, system := range world.Systems() {
  switch sys := system.(type) {
    case *common.RenderSystem:
      sys.Add(&hud.BasicEntity, &hud.RenderComponent, &hud.SpaceComponent)
  }
}

That’s it! If we put these lines of code in our Setup() function, our game will have a 200x200px HUD with gray-ish background, which does not move around as we move around with our camera. You may have noticed that a 200x200px HUD is a bit large for a 400x400ox window. You may want to change the window dimensions to something a bit bigger, 800x800px for instance. If you have any cool thoughts on how to handle resizing / different resolutions in an orderly fashion, please tell us by creating an issue at GitHub!

Next time we’re going to render the background map using a tmx file and adjust our CameraBounds based on it!