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
| Technology | Purpose |
|---|---|
| Godot 4.6 | Game client + dedicated server |
| ENet (UDP) | Multiplayer protocol |
| Supabase Auth | Anonymous player authentication |
| Supabase Edge Functions | Lobby API + GameFlow server allocation |
| Supabase Realtime | Push server connection details to all clients |
| Agones SDK | Game server lifecycle (health, ready, shutdown) |
Prerequisites
- Godot 4.6 (standard, not .NET)
- Supabase CLI + Docker
- A GameFlow account with a game and fleet configured
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:
- In Godot, go to Project → Export
- Select the Linux Server preset (already configured in
export_presets.cfg) - 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
- Go to the GameFlow Dashboard → your game
- Upload
build/game.pckas a new build - Copy your Game ID
- Update
GAMEFLOW_GAME_IDinsupabase/functions/.env.local
Step 9: Run a Match
- Start Edge Functions (
supabase functions serve ...) - Press Play in Godot — enter a username and click Play Online
- Create a lobby — share the 6-digit code with other players
- All players mark Ready → the lobby owner clicks Start
- The Edge Function allocates a GameFlow server → Realtime notifies all clients → game begins
Re-export and re-upload
game.pckevery 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;