Step 3: Add a Player Sprite

Load a sprite sheet and draw a character on top of the tile world.

What you will learn

  • How to generate a character sprite sheet programmatically
  • Loading a sprite sheet with LoadSpriteSheet
  • Drawing a sprite at a world position with DrawWorldSprite

The code

At this step your main.go looks like this:

//go:generate sh -c "cd assets-src && go run gen_assets.go"
//go:generate sh -c "mkdir -p assets && go run github.com/drpaneas/gosprite64/cmd/mk2dsheet -in assets-src/character.png -out assets/character.sheet -tile-width 16 -tile-height 16 && go run github.com/drpaneas/gosprite64/cmd/mk2dsheet -in assets-src/tiles.png -out assets/tiles.sheet -tile-width 8 -tile-height 8 && go run github.com/drpaneas/gosprite64/cmd/mk2dmap -in assets-src/level.json -out assets/level.map && go run github.com/drpaneas/gosprite64/cmd/mk2dbundle -sheet assets/tiles.sheet -map assets/level.map -out assets/level.bundle"

package main

import (
	"github.com/drpaneas/gosprite64"
)

type Game struct {
	scene   *gosprite64.Scene
	camera  *gosprite64.Camera
	charSS  *gosprite64.SpriteSheet
	playerX float32
	playerY float32
}

func (g *Game) Init() {
	bundle, err := gosprite64.OpenBundle("assets/level.bundle")
	if err != nil {
		panic(err)
	}

	scene, err := gosprite64.LoadScene(bundle)
	if err != nil {
		panic(err)
	}
	g.scene = scene
	g.camera = &gosprite64.Camera{Width: 288, Height: 216}

	charSheet, err := gosprite64.LoadSpriteSheet("assets/character.sheet")
	if err != nil {
		panic(err)
	}
	g.charSS = charSheet

	g.playerX = 144
	g.playerY = 108
}

func (g *Game) Update() {}

func (g *Game) Draw() {
	gosprite64.ClearScreen()
	g.scene.Draw(g.camera)
	gosprite64.DrawWorldSprite(g.charSS, 0, g.playerX, g.playerY, g.camera)
}

func main() {
	gosprite64.Run(&Game{})
}

How it works

Generating the character sprite

The gen_assets.go file (run by the first go:generate line) creates a 64x16 PNG containing four 16x16 frames of a simple character. Each frame is a colored stick figure - head, body, and legs drawn with basic rectangles.

The pipeline tool mk2dsheet slices this image into a sprite sheet with 4 frames of 16x16 pixels each.

Loading the sprite sheet

charSheet, err := gosprite64.LoadSpriteSheet("assets/character.sheet")

LoadSpriteSheet reads the compiled .sheet file from the embedded cartridge filesystem. It returns a *SpriteSheet that knows the frame count and dimensions of each frame.

Drawing the sprite

gosprite64.DrawWorldSprite(g.charSS, 0, g.playerX, g.playerY, g.camera)
ParameterMeaning
g.charSSWhich sprite sheet to draw from
0Frame index (first frame)
g.playerX, g.playerYWorld position
g.cameraCamera (converts world coords to screen coords)

The sprite is drawn at its world position, offset by the camera. Since we placed the player at 144, 108 (center of the 288x216 screen) and the camera is at 0, 0 - the sprite appears in the center.

Screen vs World coordinates

DrawSprite uses screen coordinates (top-left of the display is 0,0). DrawWorldSprite uses world coordinates and subtracts the camera position automatically. Use world coordinates when your game world is larger than the screen.

Build and run

go generate ./examples/platformer
GOENV=n64.env go1.24.5-embedded build -o examples/platformer/game.elf ./examples/platformer

Build and run to see the tile world with a small character sprite sitting motionless in the center of the screen. The character does not move yet - we will add animation and input in the next steps.