3. Getting Started with Ebitengine

3. Getting Started with Ebitengine

Watch the associated video on YouTube or MakerTube

Watch on YouTube

Introduction #

In this tutorial we will add the Ebitengine game engine as a dependency and use it to create a graphical application.

The code for this tutorial is available here. Please consider donating if you find this tutorial helpful.

The Ebitengine Game Engine #

Ebitengine is a game engine created in 2013 by Hajime Hoshi.

It provides APIs which greatly simplify the process of developing graphical applications using Go.

Ebitengine supports:

  • Drawing 2D graphics
  • Handling user input
  • Playing audio

Reasons why I prefer to use Ebitengine to develop games include:

Create a Go Module #

Follow the steps outlined in Getting Started with Go to create a Go module.

If you have completed the previous tutorial, you may use your existing Go module for this tutorial.

Add Ebitengine as a Dependency #

Open a terminal window and cd to the directory of your Go module, then execute the following command:

go get github.com/hajimehoshi/ebiten/v2

This will add some lines to go.mod, such as:

require (
	github.com/hajimehoshi/ebiten/v2 v2.8.8
)

In the above example, our module now depends on version 2.8.8 of Ebitengine.

Your module may depend on a newer version of Ebitengine.

Game Interface #

Ebitengine defines the following Game interface:

type Game interface {
	Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int)
	Update() error
	Draw(screen *Image)
}

Layout #

Layout is called nearly every frame to determine the size of the application.

The current size of the application window is provided as outsideWidth and outsideHeight.

When the application is running in fullscreen mode, the size of the screen is provided.

The return value of Layout is the size of the drawable screen area, or canvas size.

If the window is smaller or larger than the canvas, the application is scaled while maintaining the correct aspect ratio.

Update #

Update is called every tick to update the game state.

The rate at which Update is called is determined by the tick rate.

The default tick rate is 60 ticks per second.

The tick rate is controlled by calling SetTPS.

Draw #

Draw is called every frame to draw the game onto the screen.

Layout will always be called at least once before Draw is called for the first time.

The default frame rate is unlimited, because the game runs without vertical synchronization enabled.

The frame rate is controlled by calling SetVsyncEnabled. When a value of true is provided, the frame rate is limited to the screen’s refresh rate. Enabling vsync also prevents visual artifacts from appearing on the screen.

Implementing the Game Interface #

To create a game using Ebitengine, we must implement the Game interface.

To do this, we create a struct which implements the three required methods.

Create a file named game.go and write the following:

package main

This defines our package name as main. All packages which produce an executable, meaning they have a main function, must be named main.

When we create a package which other packages may import, such as a library, we will name the package based on the name of our library.

import (
	"image/color"

	"github.com/hajimehoshi/ebiten/v2"
)

Import the image/color and ebiten packages.

The ebiten package import path ends in v2 to differentiate it from the earlier v1 version of Ebitengine.

This package versioning scheme enables us to import different version of packages at the same time.

We will only be importing and using the v2 version of the Ebitengine package.

type game struct {
	backgroundColor color.RGBA
}

Define a struct named game with one field named backgroundColor.

The backgroundColor field has the type color.RGBA.

color.RGBA is a type which represents a color by specifying a red, green, blue and alpha value ranging from 0 to 255.

func newGame() *game {
	return &game{
		backgroundColor: color.RGBA{0, 0, 255, 255},
	}
}

Define a constructor function which creates and initializes a new game.

The default value of a color.RGBA is a color with red, green, blue and alpha set to zero.

Because the default alpha value is zero, the default value of color.RGBA is a completely transparent color.

When initialing the game, we specify a blue value of 255 to produce a completely blue color.

We also specify an alpha value of to 255 to produce a fully opaque (non-transparent) color.

// Layout returns the size of the application. If the window is smaller or larger,
// the application is scaled while maintaining the correct aspect ratio.
func (g *game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return 480, 270
}

Define a method named Layout, which is called with the current window size, and return the size of the canvas.

In this application, we specify a canvas size of 480x270. The canvas will be scaled to fit the application window size.

// Update updates the game state.
func (g *game) Update() error {
	return nil
}

Define a method named Update, which updates the game state.

We don’t have any game state to update yet, so this method simply returns nil.

// Draw draws the game onto the screen.
func (g *game) Draw(screen *ebiten.Image) {
	// Draw background.
	screen.Fill(g.backgroundColor)
}

Define a method named Draw, which draws the game onto the screen.

The canvas is provided as screen, which we call Fill on. Fill replaces all pixels in an image with the specified color.

main.go #

Create (or replace) a file named main.go with the following code:

package main

import (
	"log"

	"github.com/hajimehoshi/ebiten/v2"
)

func main() {
	ebiten.SetWindowTitle("Getting Started with Ebitengine - Trevors-Tutorials.com #3")
	ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)

	err := ebiten.RunGame(newGame())
	if err != nil {
		log.Fatal(err)
	}
}

We define our package name, just as we did in game.go.

We import the log package, which provides useful logging functions.

We define a main function, where our application’s execution will begin.

We call a few of Ebitengine’s functions to set the window title and to enable resizing the window.

And finally, we initialize a new game and pass it to Ebitengine’s RunGame function.

RunGame will block (wait to return) until an error occurs or the user requests to exit the application.

RunGame will return an error value or nil when no error occurred.

Running Our Application #

As we learned in the last tutorial, we can execute go run in the directory of our Go module to run our application.

We can also execute go build to compile our application into an executable file, which we can then distribute to others.

Once you have confirmed that your application builds and runs, try changing the background color to a different value.


Stay tuned for the next tutorial, Creating Your First Game with Ebitengine.

Please consider donating if you found this tutorial helpful.