↓
iaen.es Devlog Leer en español

Gotic · 3 min read

Brumacosta, my game's first village: a whole town generated in code with Godot 4

Six houses, a market, a windmill, a campfire and villagers with daily routines, all generated in code. This is how Brumacosta was born, and how it became the template for every other town in Gotic.

#towns#procedural#world#godot#mods

Brumacosta, my game's first village: a whole town generated in code with Godot 4

When you leave the beach in Gotic, there's a trail. About 240 m later it reaches Brumacosta: six houses with solid roofs (you can land on them with the broom), a market, a well, a windmill with turning sails, a campfire and five villagers. Walk under the arch and a message announces that you've discovered Brumacosta. Ivo, the lookout, tells you what the island has to offer. Lía, the herbalist at the green stall, has errands for you if her mod is enabled.

Brumacosta's main street at 8:07, with Ivo by the market and Lía at the far end
The same street by day. At night, with the day-night mod, windows and street lamps light up

How it's built

Brumacosta started out with its own code, in the round where I wanted the island to feel inhabited. Later I asked for more towns, "as complete as the one we already have". To do that I moved its construction into game/village.gd, which builds a whole town from a dictionary: houses with collision, market, well, campfire, fences, arch, villagers and one landmark. To make sure the village hadn't changed, I compared screenshots from before and after the move. They differ by 6 and 374 pixels out of 921,600, and only because the villagers walk.

Since then the aldea mod only keeps what belongs to it: the named villagers, their dialogue and the trail from the beach.

village = Village.new({"name": "BRUMACOSTA", "center": Vector2(center.x, center.z)})
add_child(village)
village.build()
village.attach(self)
houses = village.houses
villagers = village.villagers
sails = village.sails
world.terrain.set_exclusion(module_id, center, village.radius())
marker("aldea", center, WorldArt.GOLD, "Brumacosta · aldea")
WorldArt.path(self, Island.spawn_point() + Vector3(0, 0, -8), center, 3.0)

set_exclusion clears an area of the terrain, removing trees and rocks without touching the heightmap. Later I added some life. The unnamed villagers go from door to door, to the well, the stalls and the fire, following lanes within ±3 m of the street, and stop for a while at each one. The named ones stay put because their conversations are anchored to a spot. Three hens near the arch scatter if you run past, and a cat sits on a doorstep waiting to be petted.

What went wrong

  • Half the village underwater. I assumed that heading inland from the beach meant dry land, but the procedural coast has inlets and the first location sat across one. The town now uses an anchor point on dry ground, and the tests check all four corners of every house and that the trail from the beach never crosses water.
  • Hens inside a house. They used to peck around the campfire and ended up walking through a wall. A test that simulates two and a half minutes of routines caught it, since nobody may leave the clearing, step in water or enter a house. I moved them next to the arch.
  • A frozen town behind the title menu. Towns only animate near the player. With a 220 m limit Brumacosta stood still behind the title screen, because the spawn point is 240 m away. I raised the limit to 300 m.
  • An overflowing quest log. With one village, a permanent "BRUMACOSTA · 224 m" line was a good guide. With four towns it meant four lines in a five-line log. Now, once you've visited it, Brumacosta only writes there when you're within 60 m.

Still unverified

The village round finished with 429 passing checks, and the PC package booted with its five mods, but the Android APK failed to export. There are no FPS figures from a phone. This street screenshot cost 546 draw calls and about 213,000 triangles on PC. The clucking and purring are generated in code and their levels were measured, but I haven't actually listened to them yet.

More from Gotic →