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.
Alex Ivanov
Senior Go developer and indie game creator passionate about simple, powerful game engines.
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:
- Run for testing: The
go run
command compiles and runs your program without creating a permanent executable. It's perfect for quick iteration. - Build for distribution: The
go build
command compiles your code into a single, standalone executable file.
go run .
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?
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.