Skip to main content

Quickstart Guide

This guide will help you get a GRPG server and client running on your local machine.

Prerequisites

Before you begin, ensure you have:
  • Go 1.24+ installed (download here)
  • Git for cloning the repository
  • SQLite3 development libraries (for player database)
  • Basic terminal/command line knowledge
On Ubuntu/Debian: sudo apt-get install libsqlite3-dev
On macOS: SQLite3 is included by default

Installation

1

Clone the repository

git clone https://github.com/grian32/grpg.git
cd grpg
2

Install dependencies

GRPG uses Go modules with local replacements for the data-go package. Navigate to each component and install dependencies:
# Install server dependencies
cd server-go
go mod download
cd ..

# Install client dependencies
cd client-go
go mod download
cd ..
The data-go package is shared between components and uses Go module replacement:
replace grpg/data-go => ../data-go
3

Prepare game assets

GRPG requires binary asset files (maps, textures, NPCs, etc.). You’ll need to either:
  • Download pre-built assets (if available from the project)
  • Build assets using the data-packer tool (see Data Packer)
Create an assets directory structure:
mkdir -p ../grpg-assets/assets
mkdir -p ../grpg-assets/maps
4

Start the server

The server runs on port 4422 by default and creates a SQLite database for player persistence:
cd server-go
go run main.go assets.go
You should see output like:
Listening on 127.0.0.1:4422
The server automatically runs database migrations on startup. The first run creates players.db with the initial schema.
5

Launch the client

In a new terminal window:
cd client-go
go run main.go
This opens the game client window (1152x960 default resolution) with a login screen.
6

Connect and play

  1. Enter a username in the login screen
  2. Click connect (or press Enter)
  3. The server will create a new player or load your existing save
  4. You’ll spawn at position (0, 0) in the game world
Controls:
  • Arrow keys: Move player
  • Space: Interact with objects/NPCs
  • I: Toggle inventory
  • Tab: Switch UI panels

Understanding the Server

Let’s look at what happens when the server starts:
server-go/main.go
func main() {
    // Open SQLite database for player persistence
    db, err := sql.Open("sqlite3", "./players.db")
    if err != nil {
        log.Fatal("Failed to connect to DB: ", err)
    }
    defer db.Close()

    g.Database = db

    // Run database migrations
    driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
    m, err := migrate.NewWithDatabaseInstance("file://db/migrations", "sqlite3", driver)
    if err := m.Up(); err != nil && err != migrate.ErrNoChange {
        log.Fatal("Failed to migrate: ", err)
    }

    // Start TCP listener on port 4422
    listener, err := net.Listen("tcp", ":4422")
    if err != nil {
        log.Fatal("Failed to start: ", err)
    }

    // Load game assets (objects, NPCs, maps)
    objs, err := LoadObjs(assetsDirectory + "assets/objs.grpgobj")
    npcs, err := LoadNpcs(assetsDirectory + "assets/npcs.grpgnpc")
    LoadMaps(assetsDirectory+"maps/", g, objs)

    // Initialize script manager for content
    scriptManager = scripts.NewScriptManager(g, npcs)

    // Start game loop
    packets := make(chan ChanPacket, 1000)
    go cycle(packets)

    // Accept client connections
    log.Println("Listening on 127.0.0.1:4422")
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Printf("Failed to accept connection: %v\n", err)
            continue
        }
        go handleClient(conn, g, packets)
    }
}
Key components:
  • Database: SQLite with automatic migrations from db/migrations/
  • Network: TCP listener on port 4422
  • Assets: Binary files loaded from grpg-assets/
  • Game Loop: 60ms tick cycle processing packets and NPC movements

Understanding the Client

The client connects and renders using Ebiten:
client-go/main.go
func main() {
    // Configure Ebiten window
    ebiten.SetWindowSize(int(g.ScreenWidth), int(g.ScreenHeight))
    ebiten.SetWindowTitle("GRPG Client")
    ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
    ebiten.SetTPS(60)  // 60 ticks per second

    // Start with login screen
    g.SceneManager.SwitchTo(&game.LoginScreen{Game: g})

    // Connect to server
    defer g.Conn.Close()

    // Start packet processing goroutine
    serverPackets := make(chan network.ChanPacket, 100)
    go network.ReadServerPackets(g.Conn, serverPackets)

    // Run game
    ebGame := &GameWrapper{
        gsm:     g.SceneManager,
        packets: serverPackets,
        game:    g,
    }
    if err := ebiten.RunGame(ebGame); err != nil {
        log.Fatal(err)
    }
}

Using the Data Packer

The data-packer tool converts human-readable manifests into optimized binary formats:
cd data-packer

# Pack textures (PNG → GRPGTEX with JPEG XL)
go run main.go tex -m manifest.gcfg -o textures.grpgtex

# Pack tiles
go run main.go tile -m tiles.gcfg -o tiles.grpgtile -t textures.grpgtex

# Pack objects
go run main.go obj -m objs.gcfg -o objs.grpgobj -t textures.grpgtex

# Pack NPCs
go run main.go npc -m npcs.gcfg -o npcs.grpgnpc -t textures.grpgtex

# Pack items
go run main.go item -m items.gcfg -o items.grpgitem -t textures.grpgtex
The data-packer uses GCFG manifest format. Manifest structure should match the binary format fields 1:1, with the only transformation being PNG → JPEG XL for textures.

Creating Your First Interactive Object

Let’s create a simple berry bush that players can interact with:
1

Create the content script

Create a new file server-go/content/my_berry_bush.go:
package content

import (
    "server/scripts"
    "server/shared"
)

func init() {
    scripts.OnObjInteract(scripts.BERRY_BUSH, func(ctx *scripts.ObjInteractCtx) {
        // Check if bush has berries (state 0 = has berries)
        if ctx.GetObjState() == 0 {
            // Deplete the bush
            ctx.SetObjState(1)
            
            // Give player berries
            ctx.PlayerInvAdd(scripts.BERRIES)
            
            // Award XP
            ctx.PlayerAddXp(shared.Foraging, 100)

            // Regenerate after 100 ticks (~6 seconds)
            ctx.AddTimer(100, func() {
                ctx.SetObjState(0)
            })
        }
    })
}
2

Import the content package

The content package is automatically imported in server-go/main.go:
import (
    _ "server/content"  // Runs all init() functions
    // ... other imports
)
3

Restart the server

cd server-go
go run main.go assets.go
Your berry bush is now interactive! Walk up to it and press Space to collect berries.

Creating Your First NPC

Here’s how to spawn an NPC with dialogue:
server-go/content/my_npc.go
package content

import "server/scripts"

func init() {
    // Spawn NPC at position (3, 3) with wander range of 2 tiles
    scripts.SpawnNpc(scripts.TEST, 3, 3, 2)

    // Define dialogue interaction
    scripts.OnTalkNpc(scripts.TEST, func(ctx *scripts.NpcTalkCtx) {
        ctx.ClearDialogueQueue()

        ctx.TalkPlayer("Hello, test")
        ctx.TalkNpc("...")
        ctx.TalkPlayer("See you!")

        ctx.StartDialogue()
    })
}
The NPC will:
  • Spawn at coordinates (3, 3)
  • Wander up to 2 tiles in any direction
  • Respond with a 3-line dialogue when talked to

Database Schema

GRPG uses SQLite with migration support. The initial schema:
db/migrations/000001_initial.up.sql
CREATE TABLE players (
    player_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    x INTEGER NOT NULL,
    y INTEGER NOT NULL
);
Additional migrations add inventory, skills, and XP tracking.

Network Protocol

GRPG uses a custom binary protocol with GBuf for efficient serialization:
data-go/gbuf/gbuf.go
// Writing data
buf := gbuf.NewEmptyGBuf()
buf.WriteUint16(playerID)
buf.WriteString("Hello")
buf.WriteBool(true)

// Reading data
buf := gbuf.NewGBuf(bytes)
playerID, _ := buf.ReadUint16()
message, _ := buf.ReadString()
isActive, _ := buf.ReadBool()
All data is big-endian encoded for network transmission.

Troubleshooting

Ensure SQLite3 development libraries are installed:
  • Ubuntu/Debian: sudo apt-get install libsqlite3-dev
  • macOS: Included by default
  • Windows: Install from sqlite.org
  • Verify the server is running and shows “Listening on 127.0.0.1:4422”
  • Check that port 4422 isn’t blocked by a firewall
  • Ensure both client and server are running on the same machine
The server expects assets at ../../grpg-assets/ relative to server-go/. You can:
  • Change assetsDirectory in server-go/main.go
  • Create the directory structure and use the data-packer to build assets
Ensure you’re in the correct directory. The go.mod uses local replacement:
replace grpg/data-go => ../data-go
This requires the data-go directory to exist at the same level as server-go and client-go.

Next Steps

Architecture Deep Dive

Learn about GRPG’s networking, tick system, and binary formats

Content Scripting

Master content scripting for creating NPCs, objects, and interactions

Map Editor Guide

Build custom worlds with the chunk-based map editor

API Reference

Explore the complete API documentation

Build docs developers (and LLMs) love