Cross-platform gamedev in WSL

Jun 21, 2026

You can use Ubuntu on WSL1 to develop, build, and test a game for Windows, Linux, and the browser.

WSL has a component (WSLg) that includes a Wayland compositor (Weston) and a XWayland server. This means that it can be used to run many Linux-native graphical apps.

I’ll be using the default Linux distro for WSL — Ubuntu — in this example, and the Ebitengine game engine for Go.

Set up the project

Ensure that Ubuntu on WSL is installed.

Start an Ubuntu session and install Go, which is easy with the snap:

sudo snap install go --classic

Make a Go project:

mkdir game && cd game
go mod init game

Write the code

Use a text editor pre-installed on Ubuntu to edit the game code:

vim main.go
# nano main.go

Paste this simple example, which bounces a ball around the window and changes the ball colour based on the build target:

package main

import (
	"fmt"
	"image/color"
	"log"
	"runtime"

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

const (
	screenW    = 480
	screenH    = 360
	ballRadius = 12
	textPadX   = 8
	textPadY   = 8
)

var label = fmt.Sprintf("This is a %s build", runtime.GOOS)

func ballColor() color.Color {
	switch runtime.GOOS {
	case "linux":
		return color.RGBA{R: 255, G: 0, B: 0, A: 255}
	case "windows":
		return color.RGBA{R: 0, G: 0, B: 255, A: 255}
	case "js":
		return color.RGBA{R: 0, G: 255, B: 0, A: 255}
	default:
		return color.White
	}
}

type Game struct {
	x, y   float64
	vx, vy float64
}

func (g *Game) Update() error {

	g.x += g.vx
	g.y += g.vy

	if g.x-ballRadius <= 0 || g.x+ballRadius >= screenW {
		g.vx = -g.vx
	} else if g.y-ballRadius <= 0 || g.y+ballRadius >= screenH {
		g.vy = -g.vy
	}

	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
	screen.Fill(color.Black)
	ebitenutil.DebugPrintAt(screen, label, textPadX, textPadY)
	vector.DrawFilledCircle(screen, float32(g.x), float32(g.y), ballRadius, ballColor(), true)
}

func (g *Game) Layout(_, _ int) (int, int) {
	return screenW, screenH
}

func main() {
	ebiten.SetWindowSize(screenW, screenH)
	ebiten.SetWindowTitle("Crossplatform builds in WSL")
	log.Fatal(ebiten.RunGame(&Game{x: 40, y: 40, vx: 4, vy: 2.5}))
}

Make sure to resolve the Go dependencies with go mod tidy.

Build cross-platform

You can build for Windows right away:

GOOS=windows GOARCH=amd64 go build .

To build for Linux, first install dependencies that are required to compile the game for X11:

sudo apt update

sudo apt install \
        libx11-dev \
        libxrandr-dev \
        libxcursor-dev \
        libxinerama-dev \
        libxi-dev \
        libxxf86vm-dev \
        libgl1-mesa-dev \
        xorg-dev

Then build for Linux:

GOOS=linux GOARCH=amd64 go build .

To build for WASM, run:

GOOS=js GOARCH=wasm go build -o game.wasm .

Next, copy wasm_exec.js to the current directory so that the WebAssembly binary can be executed:

# note: exact location depends on your Go version
cp $(go env GOROOT)/misc/wasm/wasm_exec.js .

Finally, create two html files, one that includes the game (game.html) and the other that embeds the game in an iframe (index.html).

Game:

<!DOCTYPE html>
<script src="wasm_exec.js"></script>
<script>
  const go = new Go();
  WebAssembly.instantiateStreaming(fetch("game.wasm"), go.importObject).then(r => go.run(r.instance));
</script>

Embedder:

<!DOCTYPE html>
<style>
  body { margin: 0; background: #555; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; }
</style>
<iframe src="game.html" width="480" height="360" frameborder="0"></iframe>

Run the builds

You now have three builds of your game for different platforms.

In one Ubuntu session, run the Windows game:

./game.exe

In another, run the Linux game:

./game

Finally, to run the game in a browser, create a simple Python server to serve to localhost:

python3 -m http.server -b localhost

Then ctrl+left click on the link that appears in the terminal (http://127.0.0.1:8000/).

In the GIF below, you can see all builds running from WSL in my Windows machine. The ball is a different colour depending on the platform:

  • Red: Linux
  • Blue: Windows
  • Green: web

Multiple builds all running on Windows

Summary of conveniences

  • WSL: quick setup of a Linux development environment on Windows
  • Ubuntu: editors and Python are pre-installed, Go is just a snap install away
  • Go: easy cross compilation to multiple platforms
  • Ebitengine: no complex setup, just import the engine and start coding
1

Full disclosure: I work on the team who develops and maintains Ubuntu on WSL.

Subscribe Edit
https://edibotopic.com/blog/feed.xml