File size: 6,533 Bytes
40c0886
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# Open-World Game Design

Designing an open world is fundamentally a trade-off between **density of
content** and **memory/CPU budget**. This document collects the patterns the
agent uses when generating the open-world city example.

## Procedural generation

### Grid + jitter

The simplest viable city layout is a grid of blocks where each block holds
one or more buildings whose position is jittered slightly to break the
symmetry:

```csharp
for (int x = 0; x < gridX; x++)
for (int z = 0; z < gridZ; z++)
{
    Vector3 block = origin + new Vector3(x * blockSpacing, 0f, z * blockSpacing);
    SpawnBuilding(block + Random.insideUnitSphere * 4f);
}
```

The generated `CityGenerator.cs` follows this pattern. For more variety,
swap the grid for:

* **Voronoi street networks** (Delaunay triangulation, then prune).
* **L-system road growth** (recursive rewrite rules -- great for organic
  cities).
* **Wave function collapse** -- tile-based, but expensive to author.

### Building composition

A building is rarely a single mesh. The generated `BuildingGenerator.cs`
composes three primitives:

1. A wall cube with a Standard material (concrete/glass color).
2. A slightly smaller cube with an emissive **window material** so windows
   glow at night.
3. A `BoxCollider` for collision.

For more variety:

* Vary the footprint (square vs rectangular).
* Add a flat roof cap with a different material.
* Add a parapet along the top.
* Randomly choose between glass-tower, brick-mid-rise, and concrete-low-rise
  palettes.

### Runtime vs baked

The agent generates everything **at runtime in `Start()`** because:

* The Unity project opens with an empty scene and the user immediately
  sees content after pressing Play.
* No need to ship `.unity` scenes with hundreds of GameObjects.
* Easy to randomize: just change the seed.

The downside is the first-frame hitch on large worlds. Mitigations:

* Spread generation across frames using a coroutine that yields after
  every N buildings.
* Use `Object.Instantiate` from a small set of prefabs instead of building
  primitives from scratch each time.
* Use `Graphics.DrawMeshInstanced` for buildings once you do not need
  per-building colliders.

## Level of Detail (LOD)

For an open world you must use LOD. Without it, the GPU processes every
window pane on every building no matter how far away it is.

| LOD | Screen size | Poly count                  |
|-----|-------------|-----------------------------|
| 0   | 60%+        | Full mesh with windows      |
| 1   | 25-60%      | Simplified mesh, no windows |
| 2   | 5-25%       | Single box per building     |
| Culled | <5%      | Not rendered at all         |

Add a `LODGroup` to each building and assign LOD meshes. For procedural
buildings you can build the LOD meshes in code by reducing the window
count and merging meshes.

## Streaming and chunking

For worlds larger than ~1km x 1km you must chunk:

* Divide the world into a grid of chunks (e.g. 200m x 200m).
* Load chunks near the player; unload chunks far away.
* Use `Addressables` or `SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive)`
  to load chunk scenes.

The generated example does NOT chunk -- it builds everything in one shot,
which is fine for an 8x8 block city. Chunking becomes mandatory around
16x16 blocks.

## Fog and far plane

Always set `RenderSettings.fog` for open worlds. It hides:

* Distant popping when LOD changes happen.
* The far clip plane edge.

A linear fog from 100m to 800m is a good default for a city.

## Day/Night cycle

The generated `DayNightCycle.cs` rotates a directional light around the X
axis to simulate the sun:

```csharp
float sunAngle = (_timeOfDay - 0.25f) * 360f; // 0.25 = noon
sun.transform.rotation = Quaternion.Euler(sunAngle, -30f, 0f);
```

* Keep the day length short (60-180 s) for demo purposes; 24 min real time
  per day for actual gameplay.
* Lerp ambient light and skybox color between day/night presets so the
  world does not suddenly flip black.
* Turn street lights on/off based on a sun-intensity threshold.

## Player traversal

Open worlds need fast traversal. Options:

| Method                | Speed (m/s) | Use case                              |
|-----------------------|-------------|---------------------------------------|
| Walking               | 3-5         | On-foot exploration                   |
| Sprinting             | 7-10        | Short bursts                          |
| Car (arcade)          | 15-30       | The generated `VehicleController.cs`  |
| Horse / mount         | 8-12        | Fantasy open world                    |
| Fast travel           | instant     | Pre-unlocked locations                |

The generated project ships both a `PlayerController` (on-foot) and a
`VehicleController` (car) so the user can switch between them.

## NPC traffic

For an open-world city, AI traffic should be **lightweight**:

* Use `NavMeshAgent` with a low `acceleration` for cars so they look
  smooth, not jerky.
* Pool the agents -- never `Instantiate` a new car every time one drives
  off-screen.
* Despawn agents when they are > 100m from the player.

The generated `NPCController.cs` covers pedestrians; for cars you would
extend it with lane-following logic.

## Save systems

Open worlds need persistent saves. The generated `SaveSystem.cs` writes a
single JSON file to `Application.persistentDataPath`:

```csharp
[Serializable]
public class GameSave
{
    public Vector3 playerPosition;
    public int score;
    public float timeOfDay;
    public List<string> completedQuests;
}
```

For larger worlds, split saves into:

* Player profile (small, frequent).
* World state (large, infrequent -- chunk ownership, building destruction,
  NPC schedules).

## Common pitfalls

* **Floating point precision:** Unity uses single-precision floats. Beyond
  ~5km from the origin, physics and rendering jitter. Use a **floating
  origin** that re-centres the world on the player every few hundred metres.
* **Shadow distance:** the default 150m shadow distance is too short for an
  open world. Raise to 300-500m for desktop; lower for mobile.
* **Lighting rebuild:** make sure all your static city geometry is marked
  **Navigation Static** AND **Lightmap Static** before baking. For runtime-
  generated cities you cannot bake -- rely on realtime lighting and the
  directional sun.
* **Collider spam:** 50 buildings x 6 faces each = 300 colliders. Mark
  small decoration meshes as `isTrigger = false` and use a single
  `BoxCollider` per building.