Building Heads Up Display for Your Game How to Add Text to Ebitengine Games

An important part of game development is adding a HUD (Heads Up Display). This helps keep your players informed throughout the game. What are some things that you may want in your HUD? Current Level Score Collectibles status Buffs/Nerfs Other things that are specific to your game If you’ve not been following along with the posts, I’ve been teaching a game development class at a local library. We’ve been building a clone of Sink Sub.


Apr. 26, 2024 637 words ebitengine ·

An important part of game development is adding a HUD (Heads Up Display). This helps keep your players informed throughout the game.

What are some things that you may want in your HUD?

If you’ve not been following along with the posts, I’ve been teaching a game development class at a local library. We’ve been building a clone of Sink Sub. In this game we need to keep track of score and display it to the user. If you want to see the game play as it is now, April 2024, you can check it out here.

Pseudo Code

Before we dig into the actual code, lets take a look at the algorithm for getting our text ready for display.

Let’s look at an API first

When needing to load a font face, we’ll be using an API described in the ebitengine text/v2 docs. There is a method defined here, NewGoTextFaceSource. This method takes an io.Reader and returns a *text.GoTextFaceSource and an error.

Currently, I’m just using a font provided by the ebitengine example source, fonts.MPlus1pRegular_ttf. This variable is a slice of bytes (loaded from the embed command). This is the pulled into a bytes.NewReader, which takes a slice of bytes, loads them in memory, and prepares it as a reader to be sent to functions that require an io.Reader.

Once we call the, NewGoTextFaceSource, we’ll get a reference to a *text.GoTextFaceSource, which can be used for creating many *text.GoTextFace.

I’ve introduced something else new

There are a couple of ways that you can create strings, especially with formatting. You could use fmt.Sprintf

In our case here, we want to have a more nicely formatted string. Thinking of showing a score, we want to show separators for easier groking of data.

To do that we’re going to be using a message.Printer. This lets us set a printer that is language aware, and format our numbers and other strings accordingly.

Side note

If you’re accustomed to English like I am, you might not know, but other locations and other languages will format their numbers differently.

Choosing a printer will help us do this.

A look at the code

package main

import (
	"bytes"
	"image/color"
	"log"
	"math/rand/v2"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
	"github.com/hajimehoshi/ebiten/v2/text/v2"
	"golang.org/x/text/language"
	"golang.org/x/text/message"
)

// The font source we'll use
var mplusFaceSource *text.GoTextFaceSource

func main() {

	// Loading the font face source with the data from the font
	ff, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
	if err != nil {
		log.Fatal("error loading font", err)
	}

	mplusFaceSource = ff

	ebiten.SetWindowSize(1280, 720)
	ebiten.SetWindowTitle("Font Demo")

	// Creating our game with a new printer, using English
	if err := ebiten.RunGame(&Game{printer: message.NewPrinter(language.English)}); err != nil {
		log.Fatal(err)
	}
}

type Game struct {
	score   int64
	printer *message.Printer
}

func (g *Game) Update() error {

	// Randomly updating a score
	num := rand.Int32N(26)
	if num%5 == 0 {
		g.score += 10
	}

	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {

	// Writing hello game to the screen
	op := &text.DrawOptions{}
	op.GeoM.Translate(25, 25)
	op.ColorScale.ScaleWithColor(color.White)
	text.Draw(screen, "Hello, Game!", &text.GoTextFace{Source: mplusFaceSource, Size: 24}, op)

	// Writing a score to the screen
	op = &text.DrawOptions{}
	op.GeoM.Translate(25, 50)
	op.ColorScale.ScaleWithColor(color.White)
	score := g.printer.Sprintf("Score: %d", g.score)
	text.Draw(screen, score, &text.GoTextFace{Source: mplusFaceSource, Size: 18}, op)
}

func (g *Game) Layout(ow, oh int) (int, int) {
	return ow, oh
}

A basic look

Like all things in the blog so far, this is a basic look at what can be done with adding text to your games. This should get you started on figuring out how you can use it for your specific game.