118 lines
5.7 KiB
Markdown
118 lines
5.7 KiB
Markdown
---
|
||
title: "Starting a colony sim, lessons I'm carrying forward"
|
||
description: "Kicking off a new Odin prototype and the grid-game patterns I already trust: cameras, tilemaps, and how to lay out entity data."
|
||
date: 2026-07-03
|
||
type: lesson
|
||
series: colony-sim-prototype
|
||
series_order: 1
|
||
languages: ["odin"]
|
||
tags: ["odin", "colony-sim", "game-dev"]
|
||
---
|
||
|
||
I'm starting a second prototype, a colony simulation, in [Odin](https://odin-lang.org/) and Raylib. It's early days and there's not much game there yet. This post isn't a feature tour. It's the stuff I already know works because I learned it building the [tower defense prototype](https://github.com/Codegirl-Games/tower-defense-prototype) first.
|
||
|
||
If you're starting a top-down grid game, these are the foundations I keep reaching for.
|
||
|
||
## The 2D camera is just coordinate math
|
||
|
||
A 2D camera doesn't need a library. It's two numbers and two functions:
|
||
|
||
- **Offset**: which world point sits at the center of the screen
|
||
- **Zoom**: how many world pixels map to one screen pixel
|
||
|
||
Convert world → screen:
|
||
|
||
```
|
||
screen = (world - offset) * zoom + screen_center
|
||
```
|
||
|
||
Convert screen → world (for mouse input):
|
||
|
||
```
|
||
world = (screen - screen_center) / zoom + offset
|
||
```
|
||
|
||
That's it. Panning moves the offset. The scroll wheel clamps zoom between sensible min/max values. Pan speed gets divided by zoom so movement feels consistent when you're zoomed in.
|
||
|
||
The lesson I keep re-learning: **every mouse click must go through `screen_to_world` before you do anything useful.** Selection, movement commands, building placement: all of it. Forget this once and your clicks drift when the camera moves.
|
||
|
||
## Tilemaps are flat arrays with helpers
|
||
|
||
A tilemap is a width, a height, and a flat buffer. Tile `(x, y)` lives at index `y * width + x`. Wrap access in two helpers and never think about the math again:
|
||
|
||
- `tile_index(map, x, y)`: buffer lookup
|
||
- `tile_in_bounds(map, x, y)`: guard every read and write
|
||
|
||
World position to tile coordinate is just `floor(world / tile_size)` on each axis. I used the same pattern in both prototypes. The colony sim added a comment in `tile_in_bounds`, *"learning from another game, this will become handy"*, because I skipped it early in the tower defense project and paid for it later.
|
||
|
||
Terrain type can start as a `u8` per tile. A switch or lookup table maps type → color. Don't over-engineer biomes on day one; get the grid drawing and the coordinate conversions right first.
|
||
|
||
When drawing, multiply tile size by zoom and cull tiles that fall off-screen. An 80×60 map is 4,800 rectangles, fine for a prototype, but the cull pass is free and keeps the pattern honest for bigger maps.
|
||
|
||
## Logical tiles vs visual position
|
||
|
||
Grid games have two positions whether you plan for it or not:
|
||
|
||
- **Logical position**: which tile the entity occupies (`Tile_Coord{3, 7}`)
|
||
- **Visual position**: where the sprite actually renders (smoothly interpolated between tile centers)
|
||
|
||
The colonist's grid cell updates one step at a time. The circle on screen lerps toward the next tile center each frame. Gameplay stays discrete; motion looks continuous. Mix these up and pathfinding, collision, and selection all get harder.
|
||
|
||
I didn't need this in the tower defense game: enemies moved in continuous world space along a path. Colony sims live on tiles. Separate the two early.
|
||
|
||
## Struct of arrays vs array of structs
|
||
|
||
Both layouts show up in my code. Neither is always wrong.
|
||
|
||
**Array of structs (AoS)**, what the tower defense prototype uses:
|
||
|
||
```odin
|
||
enemies: [MAX_ENEMIES]Enemy,
|
||
```
|
||
|
||
Each slot is a full `Enemy` struct: position, health, speed, active flag, all together. Natural to read: `enemy.health -= damage`. Good when you often touch most fields on one entity at once.
|
||
|
||
**Struct of arrays (SoA)**, what the colony sim uses:
|
||
|
||
```odin
|
||
Entity_World :: struct {
|
||
active: [MAX_ENTITIES]bool,
|
||
position: [MAX_ENTITIES]Tile_Coord,
|
||
move_state: [MAX_ENTITIES]Move_State,
|
||
visual_position: [MAX_ENTITIES]rl.Vector2,
|
||
// ...
|
||
}
|
||
```
|
||
|
||
Each field is a parallel array across all entities. Good when you update one system at a time (move every entity, then draw every entity) and when entities are sparse (lots of inactive slots in a fixed pool).
|
||
|
||
My rule of thumb so far:
|
||
|
||
| Reach for AoS when… | Reach for SoA when… |
|
||
|---|---|
|
||
| Entities are few and always accessed whole | You iterate one component across many entities |
|
||
| Struct fits in cache and you're touching most of it | Many slots are inactive (object pool) |
|
||
| Code clarity matters more than layout | Systems are split (movement, render, AI) |
|
||
|
||
Both prototypes use **fixed pools** with an `active` flag, no allocate/free per spawn. That pattern transferred directly from tower defense to colony sim regardless of AoS vs SoA.
|
||
|
||
## Entity handles, not raw indices
|
||
|
||
The colony sim returns `Entity`, a `distinct u32`, instead of passing array indices around. Internally it's `index + 1`, with `0` meaning invalid. Small thing, but it stops you from accidentally passing a tile coordinate or a mouse value where an entity ID goes.
|
||
|
||
## Update and draw stay separate
|
||
|
||
Both games follow the same loop shape:
|
||
|
||
1. Read input
|
||
2. Update simulation (`world_update`, `entity_update_movement`)
|
||
3. Draw (`tilemap_draw`, `entity_draw`)
|
||
|
||
Simulation code never calls draw functions. Draw code never changes game state. Obvious, but worth stating because it's the seam that keeps things readable as files multiply.
|
||
|
||
## What I'm not writing about yet
|
||
|
||
Pathfinding, job queues, resources, building: none of that exists in the repo yet. When there's a full week of work to show, I'll write a proper devlog. For now, the [repo](https://github.com/Codegirl-Games/colony-sim-prototype) is a camera, a tilemap, one colonist, and a right-click move command.
|
||
|
||
The game will come. These patterns are the part I'm confident in.
|