Game Development

Your First Ebitengine Game in 7 Steps: The Ultimate Guide 2025

Ready to build your first game with Go? Our ultimate 2025 guide walks you through creating an Ebitengine game in 7 simple steps, from setup to launch.

A

Alex Ivanov

Senior Go developer and indie game creator passionate about simple, powerful game engines.

7 min read5 views

Introduction: Why Ebitengine in 2025?

Welcome to the world of game development with Go! If you're a Go programmer looking to dive into creating games, or a developer from another language intrigued by Go's simplicity and performance, you've come to the right place. In 2025, the landscape of indie game development is more vibrant than ever, and choosing the right engine is crucial. Enter Ebitengine (formerly Ebiten), a dead-simple, open-source 2D game engine for Go.

Why Ebitengine? It embraces the Go philosophy: it's minimal, explicit, and incredibly productive. It doesn't bog you down with a heavy editor or a complex component system. Instead, it provides a clean API that lets you build games from the ground up, giving you full control. Its performance is stellar, and its cross-compilation capabilities are a dream come true—build for Windows, macOS, Linux, WebAssembly, and even mobile from a single codebase with ease. This guide will take you from zero to a running game in seven straightforward steps.

Step 1: Setting Up Your Go Environment

Before we can build a game, we need the tools. The foundation of any Go project is the Go toolchain itself. If you already have Go installed (version 1.20 or later), you can skip to the next step.

If not, head over to the official Go downloads page and grab the installer for your operating system. The installation is typically a breeze. Once installed, open your terminal or command prompt and verify the installation by typing:

go version

You should see an output like go version go1.22.5 windows/amd64. This confirms that Go is ready to go!

Step 2: Installing Ebitengine

Ebitengine is a Go library, which means we install it like any other Go package. There's no separate SDK to download. However, Ebitengine does rely on system libraries for graphics (OpenGL/DirectX/Metal) and audio. Most systems have these, but some Linux distributions might require you to install them manually.

For Debian/Ubuntu, you might need:

sudo apt-get install libgl1-mesa-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libxxf86vm-dev

While you don't need to install the package globally, we'll let Go's module system handle it in the next step. For now, your environment is prepped.

Step 3: Creating Your Project Structure

A clean project structure is key to a manageable codebase. Let's create a simple one. Open your terminal, navigate to your development folder, and create a new directory for your game.

mkdir my-first-ebitengine-game
cd my-first-ebitengine-game

Now, we need to initialize a Go module. This tracks your project's dependencies. Run:

go mod init mygame

You can replace mygame with your own module path, like github.com/your-username/mygame. This command creates a go.mod file. Finally, create your main Go file:

touch main.go

Your project structure should now look like this:

my-first-ebitengine-game/
├── go.mod
└── main.go

Simple, clean, and ready for code.

Step 4: The Core: The Game Loop

At the heart of every game is the game loop. It's a continuous cycle that processes input, updates the game's state, and draws the results to the screen. Ebitengine simplifies this by asking you to implement the ebiten.Game interface, which has three main methods.

The Update() Function: Game Logic

The Update() function is called 60 times per second by default (this is the Ticks Per Second or TPS). This is where all your game logic lives. Have a player that needs to move? Check for input here. Does an enemy need to follow a path? Update its position here. Is it time for a bullet to disappear? Check its lifetime here.

The Draw() Function: Rendering

The Draw(screen *ebiten.Image) function is where you render everything. It's called every time your display refreshes (this is Frames Per Second or FPS). You're given a special image, screen, which represents the game window. Anything you draw onto this image will appear in the window. Importantly, you should not put game logic in the Draw function; keep it purely for rendering.

The Layout() Function: Sizing

The Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) function tells Ebitengine the logical size of your game. This is powerful because it separates your game's resolution from the window's actual size. For example, you can design your game for a 320x240 resolution, and Ebitengine will automatically scale it up to fit a larger window, preserving your pixel-perfect art style.

Step 5: Writing Your First Code - A Movable Character

Let's put theory into practice. Open main.go and paste in the following code. This creates a basic game struct and sets up an empty window.

package main

import (
	"log"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"image/color"
)

// Game implements ebiten.Game interface.
type Game struct{
    playerX float64
    playerY float64
}

// Update proceeds the game state. Update is called every tick (1/60 [s] by default).
func (g *Game) Update() error {
	// We'll add input handling here in the next step
	return nil
}

// Draw draws the game screen. Draw is called every frame (typically 1/60[s] for 60Hz display).
func (g *Game) Draw(screen *ebiten.Image) {
    // Draw a white rectangle to represent our player
    ebitenutil.DrawRect(screen, g.playerX, g.playerY, 16, 16, color.White)
}

// Layout takes the outside size (e.g., the window size) and returns the (logical) screen size.
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return 640, 480
}

func main() {
	g := &Game{
        playerX: 320, // Start in the middle of the screen
        playerY: 240,
    }
	ebiten.SetWindowSize(640, 480)
	ebiten.SetWindowTitle("My First Ebitengine Game")
	if err := ebiten.RunGame(g); err != nil {
		log.Fatal(err)
	}
}

When you first run this, Go will automatically download Ebitengine and its dependencies because we listed it in our import block. This code defines a Game struct holding our player's X/Y coordinates. The Draw function renders a 16x16 white rectangle at these coordinates. The main function initializes the game, sets the window size and title, and starts the game loop with ebiten.RunGame(g).

Step 6: Adding Player Control

A static square is boring. Let's make it move! Ebitengine provides simple functions for checking keyboard input. We'll modify our Update function to check for arrow key presses and adjust the player's coordinates.

Replace your empty Update function with this:

func (g *Game) Update() error {
    playerSpeed := 4.0

    if ebiten.IsKeyPressed(ebiten.KeyArrowUp) {
        g.playerY -= playerSpeed
    }
    if ebiten.IsKeyPressed(ebiten.KeyArrowDown) {
        g.playerY += playerSpeed
    }
    if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) {
        g.playerX -= playerSpeed
    }
    if ebiten.IsKeyPressed(ebiten.KeyArrowRight) {
        g.playerX += playerSpeed
    }

	return nil
}

This code is checked 60 times per second. If an arrow key is being held down, it adjusts the playerX or playerY value. Now our white square is a controllable character!

Step 7: Building and Running Your Game

You're ready to launch! In your terminal, at the root of your project directory, you have two options:

  1. Run for testing: The go run command compiles and runs your program without creating a permanent executable. It's perfect for quick iteration.
  2. go run .
  3. Build for distribution: The go build command compiles your code into a single, standalone executable file.
  4. go build .

After running go build, you'll find an executable file (e.g., mygame.exe on Windows or mygame on macOS/Linux) in your directory. You can send this single file to your friends, and they can play your game without installing Go or any other dependencies. This is one of Go's most powerful features for game development.

Ebitengine vs. The Competition

How does Ebitengine stack up against other popular choices for 2D game development in Go?

Go 2D Game Engine Comparison 2025
Feature Ebitengine Raylib-go Pixel
Core Philosophy Minimalist, pure Go API C-bindings to Raylib, feature-rich Modern, OpenGL-focused API
Ease of Use Very high, simple to start High, but requires CGO Moderate, more boilerplate
Dependencies Pure Go (except for OS drivers) Requires Raylib C library Pure Go (except for OS drivers)
Cross-Compilation Excellent, no CGO headaches Complex due to CGO Good, but can have platform issues
Platforms Desktop, Web, Mobile Desktop, Web (with effort) Desktop
Community/Activity Very active development Active (tied to Raylib) Less active development

What's Next on Your Game Dev Journey?

Congratulations! You've built and run your first Ebitengine game. This is just the beginning. Here are some ideas for your next steps:

  • Sprites: Replace the white square with a character image. Use ebitenutil.NewImageFromFile("path/to/your/sprite.png") to load images.
  • Sound: Add background music or sound effects. Ebitengine has built-in packages for loading and playing audio files.
  • Game State: Implement a simple state machine for a main menu, a pause screen, and a game-over screen.
  • Explore the Examples: The official Ebitengine repository has a wealth of examples covering everything from animation to shaders.

Conclusion

In just seven steps, you've gone from an empty folder to a playable, cross-platform game. You've seen firsthand the power and simplicity of Ebitengine. By providing a minimal, Go-idiomatic API, it gets out of your way and lets you focus on what matters most: building your game. Whether you're creating a small personal project or the next indie hit, Ebitengine provides a solid, performant, and enjoyable foundation for your 2D game development adventure in 2025 and beyond.