City district

Build a city district from road tiles and buildings on the lots of each block, with sidewalks, a lane graph for traffic, colliders, named zones and a guarded edge of the world.

Source: module-kits:kits/world_district/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs world-districtMCP tool: gripforge_docs { "topic": "world-district" }

A city district (world.district) #

Readable offline: gripforge docs world-district, or the MCP tool gripforge_docs (topic world-district).

What it is for #

world.district builds the world of an open-world city game. The district is a square grid of cells by cells tiles of tile metres. Every avenueEvery row and column is a road; the cells between roads form blocks. Each road cell takes the tile that matches its neighbours (straight, four-way, T or curve) and is turned into place. Each block gets a sidewalk ring and lots along its streets, filled from the buildings you bind, facing the street. Interior lots stay open (courtyards) or are filled.

It provides the ground and building colliders (world.ground), the road tile centres, the sidewalk nodes and the lane graph (world.roads), and the named zones (world.zones). Vehicle and character kits stand on it, traffic and pedestrian kits follow its lanes and sidewalks. Without bound assets it still plays, with flat slabs and procedural boxes.

Turn it on #

gripforge_gamekit_install   { project_id: "gkp_…", id: "world.district" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "world.district",
                              config: { "cells": 17, "avenueEvery": 4, "seed": 3, "buildingHeight": [8, 30] } }

The kit requires game.engine, physics.rigidbody and physics.port. Bind the four road tiles and any number of buildings to the asset slots below.

First example #

<!-- test: kits/game_engine/host/tests/world_district.rs::the_layout_is_a_function_of_config_and_seed --> With cells 13, tile 8 and avenueEvery 4 and the same seed, the layout is always the same; another seed changes the lots. The district is 104 metres wide with four road lines. It has 3 by 3 blocks of 3 by 3 lots (81 lots, one interior lot per block left open), 36 sidewalk pieces and the three default zones.

Settings and defaults #

SettingTypeDefaultMeaning
layouttiles or blockstilesroad tiles, or the grid of square blocks (below)
cellsinteger 5 to 9713tiles per side (an odd number keeps a central block)
tilenumber 4 to 168tile edge in metres; road tiles and lots share it
avenueEveryinteger 2 to 84a road every N cells; blocks are N-1 cells wide
buildingHeight[min, max][6, 18]metres, for the procedural boxes when no building is bound
seedinteger1deterministic layout seed
sidewalksbooleantruea sidewalk ring per block
courtyardsbooleantrueleave the interior lots of a block open
profilegrid or coastal_citygridweb: regional land use, parks, coast dressing, neighbourhood zones
appearanceasset or realisticassetweb: shared street surfaces, markings, sky, street furniture and skyline
streetscapeobjectnoneweb: seed, buildingDistance, vegetationDistance, decorDistance, weathering, wetness (all six)
streamEntitiesbooleanfalseweb: with world.streaming, materialise nearby instances and colliders only
edgeobjectsee belowthe edge of the world

The blocks layout (layout set to blocks) builds a blocks by blocks grid of square blocks instead:

SettingDefaultMeaning
blocks3blocks per side (1 to 8)
blockSize40block edge in metres (20 to 120)
roadWidth8road width in metres (4 to 20)

It uses buildingHeight, seed and sidewalks too, draws box buildings, keeps the default zones and spawn, and is built on the web and Rust hosts; the engine wrappers build the tiles. The tile settings do not apply to it.

The edge of the world, edge: apron (default 40) is the ground in metres that goes on beyond the streets, barrier (rail, wall or none, default rail) holds vehicles and characters on the edge, height (default 1.2) is its height in metres, and recoverBelow (default 12) is how far under the ground, or beyond the apron, an actor can be before it is put back on the nearest road.

Asset slots #

SlotManyWithout a binding
road_straight, road_intersection, road_t, road_curvenoflat slab
district_buildingyesprocedural boxes
district_tree, district_park_tree, district_skylineno, yes, noprocedural, in the realistic appearance
lot_fillnoflat lot
city_blockyesblocks layout: procedural boxes
ground_texture, skyboxnoflat grey, gradient sky

Tiles are square, tile metres wide, origin at the centre, +Y up. The straight tile runs along Z, the T keeps its straight along Z with the branch towards +X, the curve links -Z to +X; the kit applies the yaw. Buildings have their origin at the ground centre and their front on -Z; they are fitted to the lot and turned towards their street.

Services, events, variables #

  • world.district (service): layout() answers { cells, tile, extent, profile, areas, tiles, sidewalks, sidewalkNodes, lots, blocks, lanes, zones, spawn }.
  • world.roads (service): nearestSidewalk(pos), randomWalk(from, rng) and lanes(), the lanes as { from, to, width }.
  • world.ground (service): static ground and building colliders, with groundHeight(x, z).
  • world.ready (event): { blocks, extent, bounds, zones, tiles, buildings }, once colliders and zones exist; spawn-time kits may place entities. It is sent again when a different district is rebuilt.
  • world.recovered (event): { entity, cause, x, y, z }, an actor lost under the ground (fell) or beyond the apron (outside) was put back.
  • world.district.area (variable): the neighbourhood the player stands in (coastal profile); empty in the grid profile.
  • zones (collection): { id, shape (sphere or box), center, radius or size, tags }. The defaults are z_parking, z_downtown (tag police) and z_garage; a zone with a default id replaces it.

Hooks and modding #

  • district.build { scene, cells, tile, avenueEvery, seed, profile, sidewalks, courtyards }: tiles layout, asked before a build (at the start, when a scene asks for another district, when a load rebuilds it). A tap may change seed, profile, sidewalks and courtyards; the grid is read only. Not cancellable.
  • city_block.build { scene, blocks, blockSize, roadWidth, seed, sidewalks }: the same for the blocks layout. A tap may change blocks, seed and sidewalks.
  • world.recover { entity, cause, x, y, z }: asked before an actor is put back. A tap may change x, y, z (its feet); cancelled, the game handles it.
{ "id": "harbour_town", "hook": "district.build", "match": { "scene": "harbour" }, "set": { "profile": "coastal_city" } }
{ "id": "back_to_garage", "hook": "world.recover", "match": { "cause": "outside" }, "set": { "x": 0, "y": 0, "z": 12 } }

A scene whose world names world.district plays the district of its own world.config over the kit config, and rebuilds when it differs. A mod may override the layout and look settings (all but streamEntities) and add, patch or remove zones.

Engines #

The tiles layout is built by the web player and the native player, and by the Godot, Unity and Unreal wrappers with the same layout, roads, lanes and zones. The realistic appearance, the coastal profile, the streetscape and entity streaming are web only; the engines keep the asset look and the eager layout. Hooks run through the Rust kit logic.

When it does not do what you expect #

  • Flat slabs and grey boxes: no road tile or building is bound to its slot.
  • The streetscape or the realistic look is missing in an engine: those settings are web only.
  • streamEntities has no effect: world.streaming is not installed.
  • A car drove out of the map and came back: the edge safety net, see edge and the world.recover hook.
  • The tile settings are ignored: layout is blocks.

Limits #

One district per scene, built from a regular grid. It does not simulate traffic or pedestrians; it gives them lanes and sidewalks.

Versions #

Kit 1.10.0. 1.9.0 added the edge of the world, 1.10.0 the blocks layout and the city_block.build hook; district.build came with 1.7.0. The services world.ground, world.roads and world.zones are at 1.0.0.