Skip to main content

Godot + Supabase

Geometry Survival is a multiplayer Vampire Survivors-like game that demonstrates how to build a complete online game with Godot and Supabase, hosted on GameFlow. Players choose a geometric shape and survive waves of enemies using a rock-paper-scissors kill mechanic.

Repository: geometryvival

Architecture

Game Client (Godot)

│ HTTP — anonymous auth, lobby CRUD
│ WebSocket — Supabase Realtime (lobby updates, match start)

Supabase Edge Functions + PostgreSQL

│ POST /fleets/{gameId}/allocate

GameFlow → Dedicated Game Server (Godot headless)

When the lobby owner starts a match, a Supabase Edge Function calls the GameFlow API to allocate a dedicated server. The server's IP and port are written back to the database. Supabase Realtime notifies all clients, who then connect directly to the game server via ENet (UDP).

Stack

TechnologyPurpose
Godot 4.6Game client + dedicated server
ENet (UDP)Multiplayer protocol
Supabase AuthAnonymous player authentication
Supabase Edge FunctionsLobby API + GameFlow server allocation
Supabase RealtimePush server connection details to all clients
Agones SDKGame server lifecycle (health, ready, shutdown)

Prerequisites

Step 1: Clone the Repository

git clone https://github.com/GameFlowGG/geometryvival
cd geometryvival

Step 2: Start Supabase Locally

supabase start

This starts a local Supabase stack via Docker and applies the database migration automatically. Once ready, it prints your local credentials:

API URL:   http://127.0.0.1:54321
anon key: eyJhbGci...

Step 3: Configure the Supabase Addon

Edit addons/supabase/.env with your local credentials:

[supabase/config]

supabaseUrl="http://127.0.0.1:54321"
supabaseKey="<your local anon key>"

Step 4: Configure GameFlow Credentials

Create supabase/functions/.env.local:

GAMEFLOW_GAME_ID=your-game-id
GAMEFLOW_API_KEY=your-api-key

Get these from the GameFlow Dashboard → your game → Settings.

Step 5: Start Edge Functions

supabase functions serve --env-file supabase/functions/.env.local

Leave this running in a terminal. Functions hot-reload on file changes.

Step 6: Open the Project in Godot

Open project.godot in Godot 4.6. Press Play to run the game as a client (the login screen appears).

Step 7: Export the Dedicated Server

To deploy to GameFlow, export the game as a Linux Server PCK:

  1. In Godot, go to Project → Export
  2. Select the Linux Server preset (already configured in export_presets.cfg)
  3. Click Export PCK/Zip → save as build/game.pck

The export template must be installed first: Editor → Manage Export Templates → Download.

Step 8: Upload to GameFlow

  1. Go to the GameFlow Dashboard → your game
  2. Upload build/game.pck as a new build
  3. Copy your Game ID
  4. Update GAMEFLOW_GAME_ID in supabase/functions/.env.local

Step 9: Run a Match

  1. Start Edge Functions (supabase functions serve ...)
  2. Press Play in Godot — enter a username and click Play Online
  3. Create a lobby — share the 6-digit code with other players
  4. All players mark Ready → the lobby owner clicks Start
  5. The Edge Function allocates a GameFlow server → Realtime notifies all clients → game begins

Re-export and re-upload game.pck every time you change server-side code.

Deploying to Production

Supabase

# Link to your remote project
supabase link --project-ref <your-project-ref>

# Push the database schema
supabase db push

# Deploy all Edge Functions
supabase functions deploy

# Set GameFlow secrets
supabase secrets set GAMEFLOW_GAME_ID=your-game-id
supabase secrets set GAMEFLOW_API_KEY=your-api-key

Then update addons/supabase/.env with your production Supabase URL and anon key (found in Dashboard → Settings → API):

[supabase/config]

supabaseUrl="https://<your-project-ref>.supabase.co"
supabaseKey="<your anon key>"

Rebuild and redistribute the game client.

Project Structure

geometryvival/
├── scripts/
│ ├── game_constants.gd # Shared: shape names, colors, kill rule
│ ├── network_manager.gd # Autoload: ENet host/join
│ ├── supabase_manager.gd # Autoload: auth, lobbies, Realtime
│ ├── game.gd # Game loop: spawning, collisions, difficulty
│ ├── player.gd # Movement, shape cycling, damage
│ └── enemy.gd # AI: moves toward nearest player
├── supabase/
│ ├── migrations/
│ │ └── 20260326000001_initial.sql # Full DB schema
│ └── functions/
│ ├── auth-anonymous/ # Create account after anonymous sign-in
│ ├── lobbies-list/ # List public lobbies (with stale cleanup)
│ ├── lobbies-create/ # Create a lobby
│ ├── lobbies-join/ # Join by ID or invite code
│ ├── lobbies-ready/ # Toggle ready state
│ ├── lobbies-leave/ # Leave lobby (auto-deletes if empty)
│ └── lobbies-start/ # Validate ready → allocate via GameFlow → Realtime
└── addons/
├── supabase/ # godot-engine/supabase addon
└── agones/ # Agones SDK addon

Gameplay

  • WASD / Arrow keys — move
  • SPACE — cycle shape (Circle → Square → Triangle)
  • ESC — surrender (press twice) or exit if already dead

Kill rule: Circle kills Square → Square kills Triangle → Triangle kills Circle.

Enemies spawn from screen edges and increase in speed over time. The match ends when all players are dead.

Troubleshooting

"GAMEFLOW_GAME_ID and GAMEFLOW_API_KEY must be set"

The Edge Function is not picking up your .env.local. Make sure you are running:

supabase functions serve --env-file supabase/functions/.env.local

Players can't move

Each player node is named after its peer ID. The authority is set in _enter_tree() in player.gd — make sure you haven't moved this to _ready(), as that is too late for MultiplayerSynchronizer to initialize correctly.

Supabase Realtime not triggering

Confirm that both tables are added to the Realtime publication in your migration:

ALTER PUBLICATION supabase_realtime ADD TABLE public.lobbies;
ALTER PUBLICATION supabase_realtime ADD TABLE public.lobby_players;