ryzerrr commited on
Commit
40c0886
·
verified ·
1 Parent(s): 91c0815

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +254 -0
  2. examples/generate_example.py +78 -0
  3. examples/open_world_city/Assets/Scenes/MainScene.unity +101 -0
  4. examples/open_world_city/Assets/Scenes/MainScene.unity.meta +11 -0
  5. examples/open_world_city/Assets/Scripts/AudioManager.cs +67 -0
  6. examples/open_world_city/Assets/Scripts/AudioManager.cs.meta +11 -0
  7. examples/open_world_city/Assets/Scripts/BuildingGenerator.cs +68 -0
  8. examples/open_world_city/Assets/Scripts/BuildingGenerator.cs.meta +11 -0
  9. examples/open_world_city/Assets/Scripts/CameraController.cs +13 -0
  10. examples/open_world_city/Assets/Scripts/CameraController.cs.meta +11 -0
  11. examples/open_world_city/Assets/Scripts/CityGenerator.cs +110 -0
  12. examples/open_world_city/Assets/Scripts/CityGenerator.cs.meta +11 -0
  13. examples/open_world_city/Assets/Scripts/DayNightCycle.cs +69 -0
  14. examples/open_world_city/Assets/Scripts/DayNightCycle.cs.meta +11 -0
  15. examples/open_world_city/Assets/Scripts/GameManager.cs +116 -0
  16. examples/open_world_city/Assets/Scripts/GameManager.cs.meta +11 -0
  17. examples/open_world_city/Assets/Scripts/HealthSystem.cs +82 -0
  18. examples/open_world_city/Assets/Scripts/HealthSystem.cs.meta +11 -0
  19. examples/open_world_city/Assets/Scripts/NPCController.cs +124 -0
  20. examples/open_world_city/Assets/Scripts/NPCController.cs.meta +11 -0
  21. examples/open_world_city/Assets/Scripts/PickupSystem.cs +63 -0
  22. examples/open_world_city/Assets/Scripts/PickupSystem.cs.meta +11 -0
  23. examples/open_world_city/Assets/Scripts/PlayerController.cs +109 -0
  24. examples/open_world_city/Assets/Scripts/PlayerController.cs.meta +11 -0
  25. examples/open_world_city/Assets/Scripts/RoadNetwork.cs +75 -0
  26. examples/open_world_city/Assets/Scripts/RoadNetwork.cs.meta +11 -0
  27. examples/open_world_city/Assets/Scripts/SaveSystem.cs +66 -0
  28. examples/open_world_city/Assets/Scripts/SaveSystem.cs.meta +11 -0
  29. examples/open_world_city/Assets/Scripts/ThirdPersonCamera.cs +85 -0
  30. examples/open_world_city/Assets/Scripts/ThirdPersonCamera.cs.meta +11 -0
  31. examples/open_world_city/Assets/Scripts/UIManager.cs +59 -0
  32. examples/open_world_city/Assets/Scripts/UIManager.cs.meta +11 -0
  33. examples/open_world_city/Assets/Scripts/UnityAgent.Scripts.asmdef +11 -0
  34. examples/open_world_city/Assets/Scripts/UnityAgent.Scripts.asmdef.meta +11 -0
  35. examples/open_world_city/Assets/Scripts/VehicleController.cs +136 -0
  36. examples/open_world_city/Assets/Scripts/VehicleController.cs.meta +11 -0
  37. examples/open_world_city/Packages/manifest.json +23 -0
  38. examples/open_world_city/ProjectSettings/ProjectSettings.asset +10 -0
  39. examples/open_world_city/ProjectSettings/ProjectVersion.txt +1 -0
  40. examples/open_world_city/ProjectSettings/TagManager.asset +15 -0
  41. pyproject.toml +23 -0
  42. unity_agent/__init__.py +32 -0
  43. unity_agent/__main__.py +103 -0
  44. unity_agent/config.py +90 -0
  45. unity_agent/knowledge/README.md +33 -0
  46. unity_agent/knowledge/__init__.py +41 -0
  47. unity_agent/knowledge/entries/open_world_design.md +185 -0
  48. unity_agent/knowledge/entries/unity_audio.md +120 -0
  49. unity_agent/knowledge/entries/unity_fundamentals.md +99 -0
  50. unity_agent/knowledge/entries/unity_input_system.md +154 -0
README.md ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Unity Agent
2
+
3
+ A Python package that generates complete, playable **Unity 2022.3 LTS**
4
+ projects on disk. The agent exposes a registry of code-generation tools
5
+ that emit C# scripts (MonoBehaviours), `.unity` scene files, project
6
+ configuration (manifest, ProjectSettings, .meta) and a full open-world
7
+ city example game. The output is a real Unity project you can open in
8
+ Unity Hub and press Play.
9
+
10
+ ## What it does
11
+
12
+ * Writes complete, compilable **C#** MonoBehaviours (player, vehicle,
13
+ camera, AI, weapons, inventory, quests, save system, ...).
14
+ * Scaffolds a Unity project structure (`Assets/`, `Packages/manifest.json`,
15
+ `ProjectSettings/`, `.meta` files) exactly the way Unity expects.
16
+ * Provides a **system prompt** that teaches an LLM to act like a senior
17
+ Unity engineer and to call the right tool in the right order.
18
+ * Ships a **knowledge base** of focused markdown references
19
+ (physics, navigation, input, UI, audio, rendering, open-world design).
20
+ * Runs **static QA** against generated projects to catch unbalanced
21
+ braces, missing meta files, missing manifest, etc.
22
+ * Comes with a fully working **open-world city example** generated
23
+ end-to-end from the tool layer.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ git clone <this-repo> unity-agent
29
+ cd unity-agent
30
+ pip install -e . # optional; the package also works with PYTHONPATH=.
31
+ ```
32
+
33
+ The package has **no third-party Python dependencies** -- it only uses the
34
+ standard library. Python 3.9+ is required.
35
+
36
+ ## Quick start
37
+
38
+ ### Generate the bundled example
39
+
40
+ ```bash
41
+ # Generate the full open-world city example into examples/open_world_city/
42
+ python -m unity_agent generate --game open_world_city --preset open_world_city --output-dir examples
43
+
44
+ # Run static QA on it
45
+ python -m unity_agent qa examples/open_world_city
46
+ ```
47
+
48
+ Open `examples/open_world_city/` in Unity Hub (2022.3 LTS or newer) and
49
+ press Play.
50
+
51
+ ### Use the package from Python
52
+
53
+ ```python
54
+ from unity_agent.config import Settings
55
+ from unity_agent.transport.unity_transport import UnityTransport
56
+ from unity_agent.tools.unity_tools import (
57
+ SetupUnityProjectTool,
58
+ CreatePlayerControllerTool,
59
+ CreateProceduralCityTool,
60
+ WriteSceneFileTool,
61
+ )
62
+
63
+ settings = Settings(output_dir="my_games")
64
+ transport = UnityTransport(settings)
65
+
66
+ SetupUnityProjectTool(settings, transport).run(project_name="MyGame")
67
+ CreatePlayerControllerTool(settings, transport).run(project_name="MyGame")
68
+ CreateProceduralCityTool(settings, transport).run(project_name="MyGame")
69
+ WriteSceneFileTool(settings, transport).run(project_name="MyGame", scene_name="MainScene")
70
+
71
+ print(transport.summary())
72
+ ```
73
+
74
+ ### CLI
75
+
76
+ ```bash
77
+ python -m unity_agent --help
78
+
79
+ # Commands
80
+ python -m unity_agent list-tools
81
+ python -m unity_agent setup --project-name MyGame --output-dir my_games
82
+ python -m unity_agent generate --game MyGame --preset open_world_city
83
+ python -m unity_agent generate --game MyFPS --preset fps_arena
84
+ python -m unity_agent generate --game Minimal --preset minimal
85
+ python -m unity_agent qa path/to/MyGame
86
+ ```
87
+
88
+ ## Project layout
89
+
90
+ ```
91
+ unity-agent/
92
+ unity_agent/
93
+ __init__.py # Package entry point
94
+ __main__.py # CLI: `python -m unity_agent ...`
95
+ config.py # Settings (Unity path, LLM keys, output dirs)
96
+ tests.py # Self-test suite (run with `python -m unity_agent.tests`)
97
+ transport/
98
+ __init__.py
99
+ unity_transport.py # Writes .cs / .unity / .meta / config files
100
+ tools/
101
+ __init__.py
102
+ base.py # ToolBase + ToolRegistry + @register_tool
103
+ unity_tools.py # 24 tools (see below)
104
+ orchestrator/
105
+ __init__.py
106
+ prompts.py # SYSTEM_PROMPT + build_user_prompt + tool_catalog
107
+ knowledge/
108
+ README.md
109
+ __init__.py # load_entry / list_entries / load_all
110
+ entries/
111
+ unity_fundamentals.md
112
+ unity_physics.md
113
+ unity_rendering.md
114
+ unity_navigation.md
115
+ unity_input_system.md
116
+ unity_ui.md
117
+ unity_audio.md
118
+ open_world_design.md
119
+ qa/
120
+ __init__.py # ProjectQA + Issue + QAReport + CLI
121
+ examples/
122
+ generate_example.py # Regenerates examples/open_world_city/
123
+ open_world_city/ # A complete generated Unity project
124
+ Assets/Scripts/*.cs
125
+ Assets/Scenes/MainScene.unity
126
+ Packages/manifest.json
127
+ ProjectSettings/*
128
+ README.md
129
+ pyproject.toml
130
+ README.md
131
+ ```
132
+
133
+ ## Tools (24 total)
134
+
135
+ | Tool | Output |
136
+ |-------------------------------|--------------------------------------------------------------|
137
+ | `setup_unity_project` | Project scaffold (manifest, ProjectSettings, asmdef) |
138
+ | `create_player_controller` | PlayerController.cs (WASD + jump + physics) |
139
+ | `create_vehicle_controller` | VehicleController.cs (arcade car physics) |
140
+ | `create_procedural_city` | CityGenerator.cs + BuildingGenerator.cs (50+ buildings) |
141
+ | `create_third_person_camera` | ThirdPersonCamera.cs (smooth follow + collision) |
142
+ | `create_fps_controller` | FPSController.cs (mouse-look + WASD + jump) |
143
+ | `create_day_night_cycle` | DayNightCycle.cs (sun rotation + sky/ambient lerp) |
144
+ | `create_ai_npc` | NPCController.cs (NavMesh wander/chase FSM) |
145
+ | `create_pickup_system` | PickupSystem.cs + Pickup.cs (score + collectibles) |
146
+ | `create_health_system` | HealthSystem.cs + HealthBar.cs |
147
+ | `create_weapon_system` | WeaponSystem.cs (raycast hitscan + ammo + reload) |
148
+ | `create_audio_manager` | AudioManager.cs (SFX + music singleton) |
149
+ | `create_ui_manager` | UIManager.cs (TMP HUD: score / health / messages) |
150
+ | `create_terrain_generator` | TerrainGenerator.cs (Perlin heightmap + splat) |
151
+ | `create_water_shader` | WaterMaterial.cs (animated standard-material water) |
152
+ | `create_particle_effects` | ParticleSpawner.cs (bursts + explosion helper) |
153
+ | `create_save_system` | SaveSystem.cs (JSON save/load to persistentDataPath) |
154
+ | `create_inventory_system` | InventorySystem.cs (stacks + capacity + events) |
155
+ | `create_quest_system` | QuestSystem.cs (state machine + progress + completion) |
156
+ | `create_building_generator` | BuildingGenerator.cs (walls + emissive windows + collider) |
157
+ | `create_road_network` | RoadNetwork.cs (grid layout + intersections) |
158
+ | `write_csharp_script` | Any C# script to Assets/Scripts/ |
159
+ | `write_scene_file` | .unity scene file to Assets/Scenes/ |
160
+ | `generate_complete_game` | Full game in one call (presets: open_world_city, fps_arena, minimal) |
161
+
162
+ ## The system prompt
163
+
164
+ `unity_agent.orchestrator.prompts.SYSTEM_PROMPT` is a long, prescriptive
165
+ prompt that teaches the LLM to:
166
+
167
+ * Think like a senior Unity engineer (lifecycle, physics step, idiomatic
168
+ C#).
169
+ * Know the tool catalog and the recommended workflow (scaffold ->
170
+ components -> bootstrap script -> scene).
171
+ * Always emit code that compiles on the first try in Unity 2022.3 LTS.
172
+ * Prefer procedural content (runtime `Start()` generation) over
173
+ inspector-wired prefabs so the example "just plays" on open.
174
+ * Use the `UnityAgent` namespace and the `UnityAgent.Scripts` asmdef.
175
+
176
+ Use it like this from your own LLM driver:
177
+
178
+ ```python
179
+ from unity_agent.orchestrator import SYSTEM_PROMPT, build_user_prompt
180
+
181
+ prompt = build_user_prompt("Build me an open-world city game I can drive around in")
182
+ # Send SYSTEM_PROMPT + prompt to your LLM, then dispatch tool calls.
183
+ ```
184
+
185
+ ## Knowledge base
186
+
187
+ Eight focused markdown references in `unity_agent/knowledge/entries/`:
188
+
189
+ 1. `unity_fundamentals.md` -- MonoBehaviour lifecycle, GameObjects,
190
+ components, coroutines, inspector best practices.
191
+ 2. `unity_physics.md` -- Rigidbody, colliders, raycasting, ForceModes,
192
+ physics vs frame step.
193
+ 3. `unity_rendering.md` -- Materials, Standard shader, lighting, post-
194
+ processing, LOD, fog.
195
+ 4. `unity_navigation.md` -- NavMesh, NavMeshAgent, AI state machines,
196
+ NavMeshObstacle, off-mesh links.
197
+ 5. `unity_input_system.md` -- Legacy Input Manager, mouse-look recipes,
198
+ new Input System, touch.
199
+ 6. `unity_ui.md` -- Canvas, anchoring, HUD pattern, TMP, performance.
200
+ 7. `unity_audio.md` -- AudioSource, 3D audio, AudioMixer, AudioManager
201
+ singleton.
202
+ 8. `open_world_design.md` -- Procedural generation, LOD, streaming,
203
+ day/night cycle, save systems.
204
+
205
+ Load them programmatically:
206
+
207
+ ```python
208
+ from unity_agent.knowledge import list_entries, load_entry, load_all
209
+
210
+ for slug in list_entries():
211
+ print(slug, len(load_entry(slug)))
212
+ ```
213
+
214
+ ## QA
215
+
216
+ `unity_agent.qa.ProjectQA` runs static checks against a generated project:
217
+
218
+ * Required directories exist (`Assets/`, `Assets/Scripts/`, `Packages/`,
219
+ `ProjectSettings/`).
220
+ * Required files exist (`Packages/manifest.json`,
221
+ `ProjectSettings/ProjectVersion.txt`,
222
+ `ProjectSettings/ProjectSettings.asset`).
223
+ * Every `.cs` file has balanced braces and at least one type declaration.
224
+ * Every `.cs` and `.unity` file has a sibling `.meta`.
225
+ * Scene files start with the `%YAML` header.
226
+ * `manifest.json` parses as JSON and has a `dependencies` object.
227
+
228
+ ```python
229
+ from unity_agent.qa import ProjectQA
230
+ report = ProjectQA("examples/open_world_city").run()
231
+ print(report.summary())
232
+ print("PASS" if report.passed else "FAIL")
233
+ ```
234
+
235
+ ## Tests
236
+
237
+ ```bash
238
+ python -m unity_agent.tests
239
+ ```
240
+
241
+ Verifies the tool registry has 24 entries, the project scaffolder writes
242
+ the expected files, generated C# has balanced braces, the full game
243
+ generator produces 12+ scripts, and QA passes on a freshly generated
244
+ project.
245
+
246
+ ## Requirements
247
+
248
+ * Python 3.9+ (no third-party dependencies).
249
+ * Unity Hub + Unity 2022.3 LTS (or newer) to actually open and play the
250
+ generated projects. The Python layer works without Unity installed.
251
+
252
+ ## License
253
+
254
+ MIT.
examples/generate_example.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Regenerate the open_world_city example using the unity_agent package.
2
+
3
+ Usage::
4
+
5
+ python examples/generate_example.py
6
+
7
+ This re-creates ``examples/open_world_city/`` from scratch using the same
8
+ tool chain the orchestrator would use.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ # Make the package importable when run directly.
17
+ ROOT = Path(__file__).resolve().parent.parent
18
+ sys.path.insert(0, str(ROOT))
19
+
20
+ from unity_agent.config import Settings
21
+ from unity_agent.transport.unity_transport import UnityTransport
22
+ from unity_agent.tools.unity_tools import (
23
+ GenerateCompleteGameTool,
24
+ WriteCSharpScriptTool,
25
+ )
26
+ from unity_agent.qa import ProjectQA
27
+
28
+
29
+ CAMERA_CONTROLLER_CS = """\
30
+ using UnityEngine;
31
+
32
+ namespace UnityAgent
33
+ {
34
+ /// <summary>
35
+ /// Convenience alias for <see cref="ThirdPersonCamera"/>. Lets designers
36
+ /// attach a component named CameraController if they prefer that name.
37
+ /// </summary>
38
+ public class CameraController : ThirdPersonCamera
39
+ {
40
+ // All behaviour is inherited from ThirdPersonCamera.
41
+ }
42
+ }
43
+ """
44
+
45
+
46
+ def main() -> int:
47
+ examples_dir = ROOT / "examples"
48
+ settings = Settings(output_dir=str(examples_dir), product_name="open_world_city")
49
+ transport = UnityTransport(settings)
50
+ transport.open_project("open_world_city")
51
+
52
+ # Clear the destination so we always start fresh.
53
+ import shutil
54
+ if (examples_dir / "open_world_city").exists():
55
+ shutil.rmtree(examples_dir / "open_world_city")
56
+ transport.open_project("open_world_city")
57
+
58
+ result = GenerateCompleteGameTool(settings, transport).run(
59
+ game_name="open_world_city", preset="open_world_city",
60
+ project_name="open_world_city",
61
+ )
62
+ print(result.summary)
63
+
64
+ # Add the CameraController alias the user spec asks for.
65
+ cam_result = WriteCSharpScriptTool(settings, transport).run(
66
+ filename="CameraController.cs", code=CAMERA_CONTROLLER_CS,
67
+ project_name="open_world_city",
68
+ )
69
+ print(cam_result.summary)
70
+
71
+ # QA the result.
72
+ report = ProjectQA(examples_dir / "open_world_city").run()
73
+ print(report.summary())
74
+ return 0 if report.passed else 1
75
+
76
+
77
+ if __name__ == "__main__":
78
+ sys.exit(main())
examples/open_world_city/Assets/Scenes/MainScene.unity ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ %YAML 1.1
2
+ %TAG !u! tag:unity3d.com,2011:
3
+ --- !u!29 &1
4
+ OcclusionCullingSettings:
5
+ m_ObjectHideFlags: 0
6
+ serializedVersion: 2
7
+ m_OcclusionBakeSettings:
8
+ smallestOccluder: 5
9
+ smallestHole: 0.25
10
+ backfaceThreshold: 100
11
+ m_SceneGUID: 00000000000000000000000000000000
12
+ m_OcclusionCullingData: {fileID: 0}
13
+ --- !u!104 &2
14
+ RenderSettings:
15
+ m_ObjectHideFlags: 0
16
+ serializedVersion: 9
17
+ m_Fog: 0
18
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
19
+ m_FogMode: 3
20
+ m_FogDensity: 0.01
21
+ m_LinearFogStart: 0
22
+ m_LinearFogEnd: 300
23
+ m_AmbientSkyColor: {r: 0.4, g: 0.4, b: 0.4, a: 1}
24
+ m_AmbientEquatorColor: {r: 0.2, g: 0.2, b: 0.2, a: 1}
25
+ m_AmbientGroundColor: {r: 0.1, g: 0.1, b: 0.1, a: 1}
26
+ m_AmbientIntensity: 1
27
+ m_AmbientMode: 0
28
+ m_SubtractiveShadowColor: {r: 0.2, g: 0.2, b: 0.2, a: 1}
29
+ m_SkyboxMaterial: {fileID: 0}
30
+ m_HaloStrength: 0.5
31
+ m_FlareStrength: 1
32
+ m_FlareFadeSpeed: 3
33
+ m_HaloTexture: {fileID: 0}
34
+ m_SpotCookie: {fileID: 0}
35
+ m_DefaultReflectionMode: 0
36
+ m_DefaultReflectionResolution: 128
37
+ m_ReflectionBounces: 1
38
+ m_ReflectionIntensity: 1
39
+ m_CustomReflection: {fileID: 0}
40
+ m_Sun: {fileID: 0}
41
+ m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
42
+ m_UseRadianceAmbientProbe: 0
43
+ --- !u!157 &3
44
+ LightmapSettings:
45
+ m_ObjectHideFlags: 0
46
+ serializedVersion: 12
47
+ m_GIWorkflowMode: 0
48
+ m_EnableBakedLightmaps: 1
49
+ m_EnableRealtimeLightmaps: 0
50
+ --- !u!196 &4
51
+ NavMeshSettings:
52
+ m_ObjectHideFlags: 0
53
+ serializedVersion: 2
54
+ m_AutoGenerate: 0
55
+ --- !u!1 &100
56
+ GameObject:
57
+ m_ObjectHideFlags: 0
58
+ m_Name: Main Camera
59
+ m_IsActive: 1
60
+ m_Component:
61
+ - component: {fileID: 101}
62
+ - component: {fileID: 102}
63
+ m_Layer: 0
64
+ --- !u!4 &101
65
+ Transform:
66
+ m_GameObject: {fileID: 100}
67
+ m_LocalPosition: {x: 0, y: 5, z: -10}
68
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
69
+ m_LocalScale: {x: 1, y: 1, z: 1}
70
+ --- !u!20 &102
71
+ Camera:
72
+ m_GameObject: {fileID: 100}
73
+ m_ClearFlags: 1
74
+ m_BackGroundColor: {r: 0.4, g: 0.6, b: 0.9, a: 1}
75
+ m_Orthographic: 0
76
+ m_FieldOfView: 60
77
+ m_NearClipPlane: 0.3
78
+ m_FarClipPlane: 1000
79
+ --- !u!1 &200
80
+ GameObject:
81
+ m_ObjectHideFlags: 0
82
+ m_Name: Directional Light
83
+ m_IsActive: 1
84
+ m_Component:
85
+ - component: {fileID: 201}
86
+ - component: {fileID: 202}
87
+ m_Layer: 0
88
+ --- !u!4 &201
89
+ Transform:
90
+ m_GameObject: {fileID: 200}
91
+ m_LocalRotation: {x: 0.4, y: -0.3, z: 0.2, w: 0.8}
92
+ m_LocalPosition: {x: 0, y: 50, z: 0}
93
+ m_LocalScale: {x: 1, y: 1, z: 1}
94
+ --- !u!108 &202
95
+ Light:
96
+ m_GameObject: {fileID: 200}
97
+ m_Type: 1
98
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
99
+ m_Intensity: 1
100
+ m_Shadows:
101
+ m_Type: 2
examples/open_world_city/Assets/Scenes/MainScene.unity.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 8a32e127d322fab3b2c0affc00e372cd
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/AudioManager.cs ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Singleton audio manager that exposes named one-shot SFX and music
7
+ /// channels. Used by other systems to play clips without having to manage
8
+ /// their own AudioSource components.
9
+ /// </summary>
10
+ public class AudioManager : MonoBehaviour
11
+ {
12
+ public static AudioManager Instance { get; private set; }
13
+
14
+ [Header("Mixers")]
15
+ public AudioSource sfxSource;
16
+ public AudioSource musicSource;
17
+
18
+ [Range(0f, 1f)] public float masterVolume = 1f;
19
+ [Range(0f, 1f)] public float sfxVolume = 0.8f;
20
+ [Range(0f, 1f)] public float musicVolume = 0.5f;
21
+
22
+ private void Awake()
23
+ {
24
+ if (Instance != null && Instance != this) { Destroy(gameObject); return; }
25
+ Instance = this;
26
+ DontDestroyOnLoad(gameObject);
27
+
28
+ if (sfxSource == null)
29
+ {
30
+ sfxSource = gameObject.AddComponent<AudioSource>();
31
+ sfxSource.playOnAwake = false;
32
+ }
33
+ if (musicSource == null)
34
+ {
35
+ musicSource = gameObject.AddComponent<AudioSource>();
36
+ musicSource.loop = true;
37
+ musicSource.playOnAwake = false;
38
+ }
39
+ ApplyVolumes();
40
+ }
41
+
42
+ public void PlaySfx(AudioClip clip, float volumeScale = 1f)
43
+ {
44
+ if (clip != null && sfxSource != null)
45
+ sfxSource.PlayOneShot(clip, volumeScale * sfxVolume * masterVolume);
46
+ }
47
+
48
+ public void PlayMusic(AudioClip clip)
49
+ {
50
+ if (clip == null || musicSource == null) return;
51
+ musicSource.clip = clip;
52
+ musicSource.volume = musicVolume * masterVolume;
53
+ musicSource.Play();
54
+ }
55
+
56
+ public void StopMusic()
57
+ {
58
+ if (musicSource != null) musicSource.Stop();
59
+ }
60
+
61
+ public void ApplyVolumes()
62
+ {
63
+ if (musicSource != null) musicSource.volume = musicVolume * masterVolume;
64
+ AudioListener.volume = masterVolume;
65
+ }
66
+ }
67
+ }
examples/open_world_city/Assets/Scripts/AudioManager.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 803745e8c679b595410a0a143b1abc48
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/BuildingGenerator.cs ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Procedural building generator: builds a box building mesh with
7
+ /// emissive windows and adds a BoxCollider for collision.
8
+ /// </summary>
9
+ public static class BuildingGenerator
10
+ {
11
+ public static GameObject Generate(string name, Vector3 position, Vector3 size,
12
+ Material wallMaterial, Material windowMaterial)
13
+ {
14
+ GameObject go = new GameObject(name);
15
+ go.transform.position = position;
16
+
17
+ // Walls
18
+ GameObject walls = GameObject.CreatePrimitive(PrimitiveType.Cube);
19
+ walls.name = "Walls";
20
+ walls.transform.SetParent(go.transform, false);
21
+ walls.transform.localScale = size;
22
+ walls.transform.localPosition = Vector3.up * size.y * 0.5f;
23
+ if (wallMaterial != null) walls.GetComponent<MeshRenderer>().sharedMaterial = wallMaterial;
24
+
25
+ // Windows: a smaller cube with the window material, slightly offset on each side.
26
+ if (windowMaterial != null)
27
+ {
28
+ GameObject windows = GameObject.CreatePrimitive(PrimitiveType.Cube);
29
+ windows.name = "Windows";
30
+ windows.transform.SetParent(go.transform, false);
31
+ windows.transform.localScale = size * 0.99f;
32
+ windows.transform.localPosition = Vector3.up * size.y * 0.5f;
33
+ var mr = windows.GetComponent<MeshRenderer>();
34
+ mr.sharedMaterial = windowMaterial;
35
+ }
36
+
37
+ // Collider (the cube already has one, but ensure it's a BoxCollider).
38
+ var box = go.GetComponent<BoxCollider>();
39
+ if (box == null) box = go.AddComponent<BoxCollider>();
40
+ box.size = size;
41
+ box.center = Vector3.up * size.y * 0.5f;
42
+
43
+ return go;
44
+ }
45
+
46
+ /// <summary>
47
+ /// Build an emissive window material procedurally so the agent does
48
+ /// not depend on any asset on disk.
49
+ /// </summary>
50
+ public static Material MakeWindowMaterial(Color glow)
51
+ {
52
+ var m = new Material(Shader.Find("Standard"));
53
+ m.name = "WindowMat";
54
+ m.color = Color.black;
55
+ m.SetColor("_EmissionColor", glow);
56
+ m.EnableKeyword("_EMISSION");
57
+ return m;
58
+ }
59
+
60
+ public static Material MakeWallMaterial(Color col)
61
+ {
62
+ var m = new Material(Shader.Find("Standard"));
63
+ m.name = "WallMat";
64
+ m.color = col;
65
+ return m;
66
+ }
67
+ }
68
+ }
examples/open_world_city/Assets/Scripts/BuildingGenerator.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 720373d252ebafade9462c2ee8876f1c
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/CameraController.cs ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Convenience alias for <see cref="ThirdPersonCamera"/>. Lets designers
7
+ /// attach a component named CameraController if they prefer that name.
8
+ /// </summary>
9
+ public class CameraController : ThirdPersonCamera
10
+ {
11
+ // All behaviour is inherited from ThirdPersonCamera.
12
+ }
13
+ }
examples/open_world_city/Assets/Scripts/CameraController.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 34509a07673ea2c5921f70e933984b60
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/CityGenerator.cs ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Procedural city generator: spawns a grid of buildings of varying
7
+ /// heights with emissive windows, street lights and parked vehicles.
8
+ /// Designed to be paired with <see cref="RoadNetwork"/>.
9
+ /// </summary>
10
+ public class CityGenerator : MonoBehaviour
11
+ {
12
+ [Header("Grid")]
13
+ public int gridX = 8;
14
+ public int gridZ = 8;
15
+ public float blockSpacing = 30f;
16
+
17
+ [Header("Buildings")]
18
+ public int buildingsPerBlock = 1;
19
+ public float minBuildingHeight = 8f;
20
+ public float maxBuildingHeight = 40f;
21
+ public float buildingFootprint = 8f;
22
+ public Color wallColor = new Color(0.4f, 0.4f, 0.42f);
23
+ public Color windowColor = new Color(1.0f, 0.85f, 0.4f);
24
+
25
+ [Header("Street Lights")]
26
+ public bool spawnStreetLights = true;
27
+ public float lightSpacing = 15f;
28
+ public Color lightColor = new Color(1.0f, 0.85f, 0.5f);
29
+ public float lightIntensity = 1.5f;
30
+ public float lightRange = 15f;
31
+
32
+ private readonly System.Collections.Generic.List<Light> _lights = new System.Collections.Generic.List<Light>();
33
+
34
+ private void Start()
35
+ {
36
+ Generate();
37
+ }
38
+
39
+ public void Generate()
40
+ {
41
+ Material wallMat = BuildingGenerator.MakeWallMaterial(wallColor);
42
+ Material winMat = BuildingGenerator.MakeWindowMaterial(windowColor);
43
+
44
+ float totalX = (gridX - 1) * blockSpacing;
45
+ float totalZ = (gridZ - 1) * blockSpacing;
46
+ Vector3 origin = transform.position - new Vector3(totalX * 0.5f, 0f, totalZ * 0.5f);
47
+
48
+ for (int x = 0; x < gridX; x++)
49
+ {
50
+ for (int z = 0; z < gridZ; z++)
51
+ {
52
+ Vector3 block = new Vector3(origin.x + x * blockSpacing, 0f, origin.z + z * blockSpacing);
53
+ SpawnBlock(block, wallMat, winMat);
54
+ }
55
+ }
56
+
57
+ if (spawnStreetLights) SpawnStreetLights(origin, totalX, totalZ);
58
+ }
59
+
60
+ private void SpawnBlock(Vector3 center, Material wallMat, Material winMat)
61
+ {
62
+ for (int i = 0; i < buildingsPerBlock; i++)
63
+ {
64
+ float h = Random.Range(minBuildingHeight, maxBuildingHeight);
65
+ Vector3 size = new Vector3(buildingFootprint, h, buildingFootprint);
66
+ Vector3 pos = center + new Vector3(
67
+ Random.Range(-4f, 4f), 0f, Random.Range(-4f, 4f));
68
+ BuildingGenerator.Generate($"Building_{center.x}_{center.z}_{i}", pos, size, wallMat, winMat);
69
+ }
70
+ }
71
+
72
+ private void SpawnStreetLights(Vector3 origin, float totalX, float totalZ)
73
+ {
74
+ Material poleMat = BuildingGenerator.MakeWallMaterial(new Color(0.1f, 0.1f, 0.1f));
75
+
76
+ for (float x = origin.x; x <= origin.x + totalX + 0.1f; x += lightSpacing)
77
+ {
78
+ for (float z = origin.z; z <= origin.z + totalZ + 0.1f; z += lightSpacing)
79
+ {
80
+ SpawnStreetLight(new Vector3(x, 0f, z), poleMat);
81
+ }
82
+ }
83
+ }
84
+
85
+ private void SpawnStreetLight(Vector3 pos, Material poleMat)
86
+ {
87
+ GameObject pole = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
88
+ pole.name = "StreetLight";
89
+ pole.transform.SetParent(transform, false);
90
+ pole.transform.position = pos + Vector3.up * 4f;
91
+ pole.transform.localScale = new Vector3(0.2f, 4f, 0.2f);
92
+ pole.GetComponent<MeshRenderer>().sharedMaterial = poleMat;
93
+
94
+ GameObject bulb = GameObject.CreatePrimitive(PrimitiveType.Sphere);
95
+ bulb.transform.SetParent(pole.transform, false);
96
+ bulb.transform.localPosition = Vector3.up * 0.9f;
97
+ bulb.transform.localScale = Vector3.one * 0.4f;
98
+ bulb.GetComponent<MeshRenderer>().sharedMaterial = BuildingGenerator.MakeWindowMaterial(lightColor);
99
+
100
+ Light l = bulb.AddComponent<Light>();
101
+ l.type = LightType.Point;
102
+ l.color = lightColor;
103
+ l.intensity = lightIntensity;
104
+ l.range = lightRange;
105
+ _lights.Add(l);
106
+ }
107
+
108
+ public Light[] StreetLights => _lights.ToArray();
109
+ }
110
+ }
examples/open_world_city/Assets/Scripts/CityGenerator.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: a1590f0f60a74829eaa91c784094575b
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/DayNightCycle.cs ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Rotates a directional light around the world to simulate a day/night
7
+ /// cycle. Also lerps between day and night sky/ambient colors and turns
8
+ /// street lights on after sunset.
9
+ /// </summary>
10
+ public class DayNightCycle : MonoBehaviour
11
+ {
12
+ [Header("Sun")]
13
+ public Light sun;
14
+ public float dayLengthSeconds = 120f;
15
+
16
+ [Header("Colors")]
17
+ public Color daySky = new Color(0.5f, 0.7f, 1.0f);
18
+ public Color nightSky = new Color(0.03f, 0.04f, 0.08f);
19
+ public Color dayAmbient = new Color(0.6f, 0.6f, 0.6f);
20
+ public Color nightAmbient = new Color(0.12f, 0.13f, 0.18f);
21
+
22
+ [Header("Lights")]
23
+ public Light[] streetLights;
24
+ public float streetLightThreshold = 0.2f;
25
+
26
+ private float _timeOfDay; // 0..1
27
+
28
+ private void Start()
29
+ {
30
+ if (sun == null) sun = GetComponent<Light>();
31
+ RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
32
+ }
33
+
34
+ private void Update()
35
+ {
36
+ _timeOfDay = (_timeOfDay + Time.deltaTime / dayLengthSeconds) % 1f;
37
+
38
+ // Sun angle: at timeOfDay=0.25 sun is at noon.
39
+ float sunAngle = (_timeOfDay - 0.25f) * 360f;
40
+ if (sun != null)
41
+ {
42
+ sun.transform.rotation = Quaternion.Euler(sunAngle, -30f, 0f);
43
+ float dayFactor = Mathf.Clamp01(Mathf.Cos((_timeOfDay - 0.25f) * Mathf.PI * 2f) * 0.5f + 0.5f);
44
+ sun.intensity = dayFactor * 1.2f;
45
+ }
46
+
47
+ float t = Mathf.Clamp01(Mathf.Cos((_timeOfDay - 0.25f) * Mathf.PI * 2f) * 0.5f + 0.5f);
48
+ RenderSettings.ambientLight = Color.Lerp(nightAmbient, dayAmbient, t);
49
+ Camera mainCam = Camera.main;
50
+ if (mainCam != null)
51
+ {
52
+ mainCam.backgroundColor = Color.Lerp(nightSky, daySky, t);
53
+ }
54
+
55
+ UpdateStreetLights(t);
56
+ }
57
+
58
+ private void UpdateStreetLights(float dayFactor)
59
+ {
60
+ if (streetLights == null) return;
61
+ bool on = dayFactor < streetLightThreshold;
62
+ foreach (var l in streetLights)
63
+ if (l != null) l.enabled = on;
64
+ }
65
+
66
+ /// <summary>Current time of day in 0..1 range (0=midnight).</summary>
67
+ public float TimeOfDay => _timeOfDay;
68
+ }
69
+ }
examples/open_world_city/Assets/Scripts/DayNightCycle.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 06ebd9e278d5fc98279370f9128b86f9
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/GameManager.cs ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Bootstrap that wires up the generated systems on scene load. The
7
+ /// scene only needs an empty GameObject with this component attached.
8
+ /// </summary>
9
+ public class GameManager : MonoBehaviour
10
+ {
11
+ [Header("Player")]
12
+ public GameObject playerPrefab;
13
+ public Vector3 spawnPosition = new Vector3(0f, 2f, 0f);
14
+
15
+ [Header("City")]
16
+ public int cityGridX = 8;
17
+ public int cityGridZ = 8;
18
+
19
+ private void Start()
20
+ {
21
+ SpawnPlayer();
22
+ SpawnCity();
23
+ SpawnLights();
24
+ SetupCamera();
25
+ SetupAudio();
26
+ SetupUI();
27
+ }
28
+
29
+ private void SpawnPlayer()
30
+ {
31
+ GameObject player = playerPrefab != null
32
+ ? Instantiate(playerPrefab, spawnPosition, Quaternion.identity)
33
+ : CreatePrimitivePlayer();
34
+ player.tag = "Player";
35
+ player.name = "Player";
36
+
37
+ var pc = player.GetComponent<PlayerController>();
38
+ if (pc == null) pc = player.AddComponent<PlayerController>();
39
+ var rb = player.GetComponent<Rigidbody>();
40
+ if (rb == null) rb = player.AddComponent<Rigidbody>();
41
+ var col = player.GetComponent<CapsuleCollider>();
42
+ if (col == null) col = player.AddComponent<CapsuleCollider>();
43
+ col.height = 2f; col.radius = 0.4f; col.center = new Vector3(0f, 1f, 0f);
44
+ rb.constraints = RigidbodyConstraints.FreezeRotation;
45
+ }
46
+
47
+ private GameObject CreatePrimitivePlayer()
48
+ {
49
+ GameObject go = GameObject.CreatePrimitive(PrimitiveType.Capsule);
50
+ go.transform.position = spawnPosition;
51
+ return go;
52
+ }
53
+
54
+ private void SpawnCity()
55
+ {
56
+ GameObject city = new GameObject("City");
57
+ var gen = city.AddComponent<CityGenerator>();
58
+ gen.gridX = cityGridX;
59
+ gen.gridZ = cityGridZ;
60
+
61
+ GameObject roads = new GameObject("Roads");
62
+ roads.transform.SetParent(city.transform, false);
63
+ var net = roads.AddComponent<RoadNetwork>();
64
+ net.gridSize = Mathf.Max(cityGridX, cityGridZ);
65
+ }
66
+
67
+ private void SpawnLights()
68
+ {
69
+ GameObject sunGo = new GameObject("Sun");
70
+ var sun = sunGo.AddComponent<Light>();
71
+ sun.type = LightType.Directional;
72
+ sun.intensity = 1.2f;
73
+ sun.shadows = LightShadows.Soft;
74
+
75
+ var dnc = sunGo.AddComponent<DayNightCycle>();
76
+ dnc.sun = sun;
77
+ // Bind street lights from the city generator after one frame.
78
+ StartCoroutine(BindStreetLights(dnc));
79
+ }
80
+
81
+ private System.Collections.IEnumerator BindStreetLights(DayNightCycle dnc)
82
+ {
83
+ yield return null; // allow CityGenerator.Start() to run
84
+ var cg = FindObjectOfType<CityGenerator>();
85
+ if (cg != null) dnc.streetLights = cg.StreetLights;
86
+ }
87
+
88
+ private void SetupCamera()
89
+ {
90
+ Camera cam = Camera.main;
91
+ if (cam == null)
92
+ {
93
+ var go = new GameObject("Main Camera");
94
+ cam = go.AddComponent<Camera>();
95
+ go.tag = "MainCamera";
96
+ go.AddComponent<AudioListener>();
97
+ }
98
+ var tpc = cam.GetComponent<ThirdPersonCamera>();
99
+ if (tpc == null) tpc = cam.gameObject.AddComponent<ThirdPersonCamera>();
100
+ var player = GameObject.FindGameObjectWithTag("Player");
101
+ tpc.target = player != null ? player.transform : null;
102
+ }
103
+
104
+ private void SetupAudio()
105
+ {
106
+ var go = new GameObject("AudioManager");
107
+ go.AddComponent<AudioManager>();
108
+ }
109
+
110
+ private void SetupUI()
111
+ {
112
+ var go = new GameObject("UIManager");
113
+ go.AddComponent<UIManager>();
114
+ }
115
+ }
116
+ }
examples/open_world_city/Assets/Scripts/GameManager.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 146f64a8241e4314114edb414ce6d721
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/HealthSystem.cs ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+ using UnityEngine.Events;
3
+
4
+ namespace UnityAgent
5
+ {
6
+ /// <summary>
7
+ /// Damageable entity with health regen and death event. Pairs with the
8
+ /// optional <see cref="HealthBar"/> UI component.
9
+ /// </summary>
10
+ public class HealthSystem : MonoBehaviour
11
+ {
12
+ [Header("Health")]
13
+ public float maxHealth = 100f;
14
+ public float currentHealth { get; private set; }
15
+ public float regenPerSecond = 1f;
16
+ public float regenDelay = 5f;
17
+
18
+ [Header("Events")]
19
+ public UnityEvent onDeath;
20
+ public UnityEvent<float> onDamaged;
21
+
22
+ private float _lastDamageTime;
23
+ private bool _dead;
24
+
25
+ public float Normalised => currentHealth / maxHealth;
26
+
27
+ private void Awake()
28
+ {
29
+ currentHealth = maxHealth;
30
+ }
31
+
32
+ private void Update()
33
+ {
34
+ if (_dead) return;
35
+ if (Time.time - _lastDamageTime > regenDelay && currentHealth < maxHealth)
36
+ {
37
+ currentHealth = Mathf.Min(maxHealth, currentHealth + regenPerSecond * Time.deltaTime);
38
+ }
39
+ }
40
+
41
+ public void TakeDamage(float amount)
42
+ {
43
+ if (_dead) return;
44
+ currentHealth = Mathf.Max(0f, currentHealth - amount);
45
+ _lastDamageTime = Time.time;
46
+ onDamaged?.Invoke(amount);
47
+ if (currentHealth <= 0f) Die();
48
+ }
49
+
50
+ public void Heal(float amount)
51
+ {
52
+ if (_dead) return;
53
+ currentHealth = Mathf.Min(maxHealth, currentHealth + amount);
54
+ }
55
+
56
+ private void Die()
57
+ {
58
+ _dead = true;
59
+ onDeath?.Invoke();
60
+ Destroy(gameObject, 0.5f);
61
+ }
62
+ }
63
+
64
+ /// <summary>
65
+ /// Simple health bar that reads a <see cref="HealthSystem"/> and updates
66
+ /// a child <c>Image.fillAmount</c>.
67
+ /// </summary>
68
+ public class HealthBar : MonoBehaviour
69
+ {
70
+ public HealthSystem source;
71
+ public UnityEngine.UI.Image fillImage;
72
+ public bool hideWhenFull = true;
73
+
74
+ private void Update()
75
+ {
76
+ if (source == null || fillImage == null) return;
77
+ float n = source.Normalised;
78
+ fillImage.fillAmount = n;
79
+ if (hideWhenFull) fillImage.transform.parent.gameObject.SetActive(n < 0.999f);
80
+ }
81
+ }
82
+ }
examples/open_world_city/Assets/Scripts/HealthSystem.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 3e2b705888cccf6861f62175541c5a1d
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/NPCController.cs ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+ using UnityEngine.AI;
3
+
4
+ namespace UnityAgent
5
+ {
6
+ /// <summary>
7
+ /// AI NPC that wanders between random points and chases the player when
8
+ /// they enter the aggro radius. Uses Unity's NavMeshAgent.
9
+ /// </summary>
10
+ [RequireComponent(typeof(NavMeshAgent))]
11
+ public class NPCController : MonoBehaviour
12
+ {
13
+ public enum State { Idle, Wander, Chase }
14
+
15
+ [Header("References")]
16
+ public Transform player;
17
+
18
+ [Header("Behaviour")]
19
+ public State initialState = State.Wander;
20
+ public float wanderRadius = 20f;
21
+ public float aggroRange = 12f;
22
+ public float loseSightRange = 20f;
23
+ public float idleDuration = 2f;
24
+ public float wanderSpeed = 2.5f;
25
+ public float chaseSpeed = 5f;
26
+
27
+ [Header("Debug")]
28
+ public bool drawGizmos = true;
29
+
30
+ private NavMeshAgent _agent;
31
+ private State _state;
32
+ private float _stateTimer;
33
+ private Vector3 _startPos;
34
+
35
+ private void Awake()
36
+ {
37
+ _agent = GetComponent<NavMeshAgent>();
38
+ _agent.speed = wanderSpeed;
39
+ }
40
+
41
+ private void Start()
42
+ {
43
+ _state = initialState;
44
+ _startPos = transform.position;
45
+ if (player == null && GameObject.FindGameObjectWithTag("Player") != null)
46
+ player = GameObject.FindGameObjectWithTag("Player").transform;
47
+ }
48
+
49
+ private void Update()
50
+ {
51
+ switch (_state)
52
+ {
53
+ case State.Idle: TickIdle(); break;
54
+ case State.Wander: TickWander(); break;
55
+ case State.Chase: TickChase(); break;
56
+ }
57
+ }
58
+
59
+ private void TickIdle()
60
+ {
61
+ _stateTimer -= Time.deltaTime;
62
+ if (PlayerInRange(aggroRange)) { EnterChase(); return; }
63
+ if (_stateTimer <= 0f) EnterWander();
64
+ }
65
+
66
+ private void TickWander()
67
+ {
68
+ if (PlayerInRange(aggroRange)) { EnterChase(); return; }
69
+ if (_agent.remainingDistance <= _agent.stoppingDistance && !_agent.pathPending)
70
+ EnterIdle();
71
+ }
72
+
73
+ private void TickChase()
74
+ {
75
+ if (player == null) { EnterWander(); return; }
76
+ _agent.SetDestination(player.position);
77
+
78
+ if (!PlayerInRange(loseSightRange)) EnterWander();
79
+ }
80
+
81
+ private void EnterIdle()
82
+ {
83
+ _state = State.Idle;
84
+ _stateTimer = idleDuration;
85
+ _agent.isStopped = true;
86
+ }
87
+
88
+ private void EnterWander()
89
+ {
90
+ _state = State.Wander;
91
+ _agent.speed = wanderSpeed;
92
+ _agent.isStopped = false;
93
+ Vector3 dir = Random.insideUnitSphere * wanderRadius;
94
+ dir.y = 0f;
95
+ Vector3 target = _startPos + dir;
96
+ if (NavMesh.SamplePosition(target, out NavMeshHit hit, wanderRadius, NavMesh.AllAreas))
97
+ _agent.SetDestination(hit.position);
98
+ }
99
+
100
+ private void EnterChase()
101
+ {
102
+ _state = State.Chase;
103
+ _agent.speed = chaseSpeed;
104
+ _agent.isStopped = false;
105
+ }
106
+
107
+ private bool PlayerInRange(float range)
108
+ {
109
+ if (player == null) return false;
110
+ return Vector3.Distance(transform.position, player.position) <= range;
111
+ }
112
+
113
+ private void OnDrawGizmosSelected()
114
+ {
115
+ if (!drawGizmos) return;
116
+ Gizmos.color = Color.yellow;
117
+ Gizmos.DrawWireSphere(transform.position, aggroRange);
118
+ Gizmos.color = Color.red;
119
+ Gizmos.DrawWireSphere(transform.position, loseSightRange);
120
+ Gizmos.color = Color.green;
121
+ Gizmos.DrawWireSphere(_startPos == Vector3.zero ? transform.position : _startPos, wanderRadius);
122
+ }
123
+ }
124
+ }
examples/open_world_city/Assets/Scripts/NPCController.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: bdb23444532d812096a8a0bf7c0f341f
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/PickupSystem.cs ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+ using UnityEngine.Events;
3
+
4
+ namespace UnityAgent
5
+ {
6
+ /// <summary>
7
+ /// Collectible pickup. Triggers an event when collected and adds to the
8
+ /// global PickupSystem score.
9
+ /// </summary>
10
+ [RequireComponent(typeof(Collider))]
11
+ public class Pickup : MonoBehaviour
12
+ {
13
+ public int value = 10;
14
+ public AudioClip collectClip;
15
+ public UnityEvent onCollected;
16
+
17
+ private void Reset()
18
+ {
19
+ GetComponent<Collider>().isTrigger = true;
20
+ }
21
+
22
+ private void OnTriggerEnter(Collider other)
23
+ {
24
+ if (!other.CompareTag("Player")) return;
25
+ PickupSystem.Instance?.Collect(value);
26
+ onCollected?.Invoke();
27
+ if (collectClip != null && PickupSystem.Instance != null)
28
+ PickupSystem.Instance.PlayPickupSound(collectClip);
29
+ Destroy(gameObject);
30
+ }
31
+ }
32
+
33
+ /// <summary>
34
+ /// Singleton that tracks the player's score and plays pickup sounds.
35
+ /// </summary>
36
+ public class PickupSystem : MonoBehaviour
37
+ {
38
+ public static PickupSystem Instance { get; private set; }
39
+
40
+ public int score { get; private set; }
41
+ public AudioSource audioSource;
42
+
43
+ public System.Action<int> onScoreChanged;
44
+
45
+ private void Awake()
46
+ {
47
+ if (Instance != null && Instance != this) { Destroy(gameObject); return; }
48
+ Instance = this;
49
+ if (audioSource == null) audioSource = gameObject.AddComponent<AudioSource>();
50
+ }
51
+
52
+ public void Collect(int amount)
53
+ {
54
+ score += amount;
55
+ onScoreChanged?.Invoke(score);
56
+ }
57
+
58
+ public void PlayPickupSound(AudioClip clip)
59
+ {
60
+ if (audioSource != null && clip != null) audioSource.PlayOneShot(clip);
61
+ }
62
+ }
63
+ }
examples/open_world_city/Assets/Scripts/PickupSystem.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 08af630d5809345e6854281a491a6740
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/PlayerController.cs ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Third-person character controller with WASD movement, jump and a
7
+ /// rigidbody-based physics response. Designed to be paired with the
8
+ /// ThirdPersonCamera component.
9
+ /// </summary>
10
+ [RequireComponent(typeof(Rigidbody))]
11
+ [RequireComponent(typeof(CapsuleCollider))]
12
+ public class PlayerController : MonoBehaviour
13
+ {
14
+ [Header("Movement")]
15
+ public float moveSpeed = 6f;
16
+ public float sprintMultiplier = 1.6f;
17
+ public float rotationSmooth = 12f;
18
+ public float jumpForce = 7.5f;
19
+
20
+ [Header("Ground Check")]
21
+ public float groundCheckDistance = 0.2f;
22
+ public LayerMask groundMask = ~0;
23
+
24
+ [Header("Camera")]
25
+ public Transform cameraTransform;
26
+
27
+ private Rigidbody _rb;
28
+ private CapsuleCollider _col;
29
+ private Vector2 _moveInput;
30
+ private bool _jumpRequested;
31
+ private bool _isGrounded;
32
+ private float _targetRotation;
33
+
34
+ private void Awake()
35
+ {
36
+ _rb = GetComponent<Rigidbody>();
37
+ _col = GetComponent<CapsuleCollider>();
38
+ _rb.freezeRotation = true;
39
+ _rb.interpolation = RigidbodyInterpolation.Interpolate;
40
+ if (cameraTransform == null && Camera.main != null)
41
+ cameraTransform = Camera.main.transform;
42
+ }
43
+
44
+ private void Update()
45
+ {
46
+ _moveInput.x = Input.GetAxisRaw("Horizontal");
47
+ _moveInput.y = Input.GetAxisRaw("Vertical");
48
+ _moveInput = Vector2.ClampMagnitude(_moveInput, 1f);
49
+
50
+ if (Input.GetButtonDown("Jump") && _isGrounded)
51
+ _jumpRequested = true;
52
+ }
53
+
54
+ private void FixedUpdate()
55
+ {
56
+ GroundCheck();
57
+ Move();
58
+ if (_jumpRequested)
59
+ {
60
+ _rb.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
61
+ _jumpRequested = false;
62
+ }
63
+ }
64
+
65
+ private void GroundCheck()
66
+ {
67
+ Vector3 origin = transform.position + Vector3.up * (_col.height * 0.5f - _col.radius);
68
+ _isGrounded = Physics.SphereCast(
69
+ origin, _col.radius * 0.9f, Vector3.down,
70
+ out _, groundCheckDistance, groundMask, QueryTriggerInteraction.Ignore);
71
+ }
72
+
73
+ private void Move()
74
+ {
75
+ float speed = moveSpeed;
76
+ if (Input.GetKey(KeyCode.LeftShift)) speed *= sprintMultiplier;
77
+
78
+ if (_moveInput.sqrMagnitude < 0.01f)
79
+ {
80
+ // Apply horizontal damping so the character stops cleanly.
81
+ Vector3 v = _rb.velocity;
82
+ v.x *= 0.85f;
83
+ v.z *= 0.85f;
84
+ _rb.velocity = v;
85
+ return;
86
+ }
87
+
88
+ Vector3 camForward = cameraTransform ? cameraTransform.forward : transform.forward;
89
+ Vector3 camRight = cameraTransform ? cameraTransform.right : transform.right;
90
+ camForward.y = 0f; camForward.Normalize();
91
+ camRight.y = 0f; camRight.Normalize();
92
+
93
+ Vector3 desired = (camForward * _moveInput.y + camRight * _moveInput.x) * speed;
94
+ Vector3 velocity = _rb.velocity;
95
+ velocity.x = desired.x;
96
+ velocity.z = desired.z;
97
+ _rb.velocity = velocity;
98
+
99
+ if (desired.sqrMagnitude > 0.01f)
100
+ {
101
+ _targetRotation = Mathf.Atan2(desired.x, desired.z) * Mathf.Rad2Deg;
102
+ float smoothed = Mathf.LerpAngle(
103
+ transform.rotation.eulerAngles.y, _targetRotation,
104
+ Time.fixedDeltaTime * rotationSmooth);
105
+ _rb.MoveRotation(Quaternion.Euler(0f, smoothed, 0f));
106
+ }
107
+ }
108
+ }
109
+ }
examples/open_world_city/Assets/Scripts/PlayerController.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: c6566cf7b83ff91f376a7d09d43b5fd3
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/RoadNetwork.cs ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Collections.Generic;
2
+ using UnityEngine;
3
+
4
+ namespace UnityAgent
5
+ {
6
+ /// <summary>
7
+ /// Lays out a grid road network at runtime: builds thin box meshes for
8
+ /// roads and an intersection marker at every grid crossing.
9
+ /// </summary>
10
+ public class RoadNetwork : MonoBehaviour
11
+ {
12
+ [Header("Grid")]
13
+ public int gridSize = 5;
14
+ public float blockSpacing = 30f;
15
+ public float roadWidth = 6f;
16
+ public float roadThickness = 0.1f;
17
+
18
+ [Header("Materials")]
19
+ public Material roadMaterial;
20
+ public Material intersectionMaterial;
21
+
22
+ private readonly List<GameObject> _roads = new List<GameObject>();
23
+
24
+ private void Start()
25
+ {
26
+ Build();
27
+ }
28
+
29
+ public void Build()
30
+ {
31
+ foreach (var r in _roads) if (r != null) Destroy(r);
32
+ _roads.Clear();
33
+
34
+ float total = (gridSize - 1) * blockSpacing;
35
+ Vector3 origin = transform.position - new Vector3(total * 0.5f, 0f, total * 0.5f);
36
+
37
+ for (int i = 0; i < gridSize; i++)
38
+ {
39
+ // Horizontal road
40
+ _roads.Add(SpawnRoad(
41
+ new Vector3(origin.x + total * 0.5f, 0f, origin.z + i * blockSpacing),
42
+ new Vector3(total + roadWidth, roadThickness, roadWidth),
43
+ roadMaterial, $"Road_H_{i}"));
44
+
45
+ // Vertical road
46
+ _roads.Add(SpawnRoad(
47
+ new Vector3(origin.x + i * blockSpacing, 0f, origin.z + total * 0.5f),
48
+ new Vector3(roadWidth, roadThickness, total + roadWidth),
49
+ roadMaterial, $"Road_V_{i}"));
50
+ }
51
+
52
+ // Intersections.
53
+ for (int x = 0; x < gridSize; x++)
54
+ {
55
+ for (int z = 0; z < gridSize; z++)
56
+ {
57
+ Vector3 p = new Vector3(origin.x + x * blockSpacing, 0f, origin.z + z * blockSpacing);
58
+ _roads.Add(SpawnRoad(p, new Vector3(roadWidth * 1.2f, roadThickness * 1.5f, roadWidth * 1.2f),
59
+ intersectionMaterial, $"Intersection_{x}_{z}"));
60
+ }
61
+ }
62
+ }
63
+
64
+ private GameObject SpawnRoad(Vector3 pos, Vector3 scale, Material mat, string name)
65
+ {
66
+ GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
67
+ go.name = name;
68
+ go.transform.SetParent(transform, false);
69
+ go.transform.position = pos + transform.position;
70
+ go.transform.localScale = scale;
71
+ if (mat != null) go.GetComponent<MeshRenderer>().sharedMaterial = mat;
72
+ return go;
73
+ }
74
+ }
75
+ }
examples/open_world_city/Assets/Scripts/RoadNetwork.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 4afc638bbfb2ca81ab494f215750fc85
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/SaveSystem.cs ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.IO;
3
+ using UnityEngine;
4
+
5
+ namespace UnityAgent
6
+ {
7
+ /// <summary>
8
+ /// JSON save system using <see cref="JsonUtility"/>. Stores a single
9
+ /// save slot per profile in <c>Application.persistentDataPath</c>.
10
+ /// </summary>
11
+ public class SaveSystem : MonoBehaviour
12
+ {
13
+ public static SaveSystem Instance { get; private set; }
14
+
15
+ public string profileName = "default";
16
+
17
+ private void Awake()
18
+ {
19
+ if (Instance != null && Instance != this) { Destroy(gameObject); return; }
20
+ Instance = this;
21
+ DontDestroyOnLoad(gameObject);
22
+ }
23
+
24
+ private string SavePath => Path.Combine(Application.persistentDataPath, profileName + ".save");
25
+
26
+ public void Save<T>(T data) where T : class
27
+ {
28
+ try
29
+ {
30
+ string json = JsonUtility.ToJson(new Wrapper<T> { payload = data }, true);
31
+ File.WriteAllText(SavePath, json);
32
+ Debug.Log($"[SaveSystem] Saved to {SavePath}");
33
+ }
34
+ catch (Exception e)
35
+ {
36
+ Debug.LogError($"[SaveSystem] Save failed: {e}");
37
+ }
38
+ }
39
+
40
+ public T Load<T>() where T : class
41
+ {
42
+ if (!File.Exists(SavePath)) return null;
43
+ try
44
+ {
45
+ string json = File.ReadAllText(SavePath);
46
+ var w = JsonUtility.FromJson<Wrapper<T>>(json);
47
+ return w?.payload;
48
+ }
49
+ catch (Exception e)
50
+ {
51
+ Debug.LogError($"[SaveSystem] Load failed: {e}");
52
+ return null;
53
+ }
54
+ }
55
+
56
+ public bool HasSave => File.Exists(SavePath);
57
+
58
+ public void Delete()
59
+ {
60
+ if (File.Exists(SavePath)) File.Delete(SavePath);
61
+ }
62
+
63
+ [Serializable]
64
+ private class Wrapper<T> { public T payload; }
65
+ }
66
+ }
examples/open_world_city/Assets/Scripts/SaveSystem.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 713fe181501c9d094612732235d78b10
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/ThirdPersonCamera.cs ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Smooth third-person camera with collision handling. Attach to a Camera
7
+ /// GameObject and assign the target transform in the inspector.
8
+ /// </summary>
9
+ public class ThirdPersonCamera : MonoBehaviour
10
+ {
11
+ [Header("Target")]
12
+ public Transform target;
13
+
14
+ [Header("Positioning")]
15
+ public Vector3 pivotOffset = new Vector3(0f, 1.5f, 0f);
16
+ public float distance = 5f;
17
+ public float minDistance = 1.5f;
18
+ public float maxDistance = 8f;
19
+ public float height = 1.2f;
20
+
21
+ [Header("Smoothing")]
22
+ public float positionSmooth = 10f;
23
+ public float rotationSmooth = 10f;
24
+
25
+ [Header("Collision")]
26
+ public LayerMask collisionMask = ~0;
27
+ public float collisionRadius = 0.3f;
28
+
29
+ [Header("Input")]
30
+ public bool useMouseOrbit = true;
31
+ public float mouseSensitivity = 3f;
32
+ public float minPitch = -30f;
33
+ public float maxPitch = 75f;
34
+
35
+ private float _yaw;
36
+ private float _pitch = 15f;
37
+ private float _currentDistance;
38
+
39
+ private void Start()
40
+ {
41
+ _currentDistance = distance;
42
+ if (target != null)
43
+ {
44
+ Vector3 e = transform.eulerAngles;
45
+ _yaw = e.y; _pitch = e.x;
46
+ }
47
+ Cursor.lockState = CursorLockMode.Locked;
48
+ }
49
+
50
+ private void LateUpdate()
51
+ {
52
+ if (target == null) return;
53
+
54
+ if (useMouseOrbit)
55
+ {
56
+ _yaw += Input.GetAxis("Mouse X") * mouseSensitivity;
57
+ _pitch -= Input.GetAxis("Mouse Y") * mouseSensitivity;
58
+ _pitch = Mathf.Clamp(_pitch, minPitch, maxPitch);
59
+ }
60
+
61
+ Quaternion rotation = Quaternion.Euler(_pitch, _yaw, 0f);
62
+ Vector3 pivot = target.position + pivotOffset;
63
+
64
+ // Desired camera position.
65
+ Vector3 desired = pivot + rotation * new Vector3(0f, height, -distance);
66
+
67
+ // Collision: shrink the distance if there's an obstacle.
68
+ if (Physics.SphereCast(
69
+ pivot, collisionRadius, (desired - pivot).normalized,
70
+ out RaycastHit hit, distance, collisionMask, QueryTriggerInteraction.Ignore))
71
+ {
72
+ _currentDistance = Mathf.Clamp(hit.distance, minDistance, distance);
73
+ }
74
+ else
75
+ {
76
+ _currentDistance = Mathf.Lerp(_currentDistance, distance, Time.deltaTime * 4f);
77
+ }
78
+
79
+ Vector3 finalPos = pivot + rotation * new Vector3(0f, height, -_currentDistance);
80
+ transform.position = Vector3.Lerp(transform.position, finalPos, positionSmooth * Time.deltaTime);
81
+ transform.rotation = Quaternion.Slerp(
82
+ transform.rotation, rotation, rotationSmooth * Time.deltaTime);
83
+ }
84
+ }
85
+ }
examples/open_world_city/Assets/Scripts/ThirdPersonCamera.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: d76bc6d6a55fca62d80d72f6cff57325
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/UIManager.cs ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+ using UnityEngine.UI;
3
+ using TMPro;
4
+
5
+ namespace UnityAgent
6
+ {
7
+ /// <summary>
8
+ /// Lightweight HUD manager using Unity UI + TextMeshPro. Exposes methods
9
+ /// for other systems to update score / health / messages.
10
+ /// </summary>
11
+ public class UIManager : MonoBehaviour
12
+ {
13
+ public static UIManager Instance { get; private set; }
14
+
15
+ [Header("HUD")]
16
+ public TMP_Text scoreText;
17
+ public TMP_Text healthText;
18
+ public TMP_Text messageText;
19
+ public Image healthBarFill;
20
+
21
+ [Header("Message")]
22
+ public float messageDuration = 2f;
23
+
24
+ private float _messageTimer;
25
+
26
+ private void Awake()
27
+ {
28
+ if (Instance != null && Instance != this) { Destroy(gameObject); return; }
29
+ Instance = this;
30
+ SetMessage(string.Empty);
31
+ }
32
+
33
+ private void Update()
34
+ {
35
+ if (_messageTimer > 0f)
36
+ {
37
+ _messageTimer -= Time.deltaTime;
38
+ if (_messageTimer <= 0f && messageText != null) messageText.text = string.Empty;
39
+ }
40
+ }
41
+
42
+ public void SetScore(int score)
43
+ {
44
+ if (scoreText != null) scoreText.text = $"Score: {score}";
45
+ }
46
+
47
+ public void SetHealth(float current, float max)
48
+ {
49
+ if (healthText != null) healthText.text = $"HP: {Mathf.CeilToInt(current)} / {Mathf.CeilToInt(max)}";
50
+ if (healthBarFill != null) healthBarFill.fillAmount = Mathf.Clamp01(current / max);
51
+ }
52
+
53
+ public void SetMessage(string msg)
54
+ {
55
+ if (messageText != null) messageText.text = msg;
56
+ _messageTimer = string.IsNullOrEmpty(msg) ? 0f : messageDuration;
57
+ }
58
+ }
59
+ }
examples/open_world_city/Assets/Scripts/UIManager.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: dcbdcd3761369cee5079e3e57f514884
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/UnityAgent.Scripts.asmdef ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "UnityAgent.Scripts",
3
+ "rootNamespace": "UnityAgent",
4
+ "references": [],
5
+ "includePlatforms": [],
6
+ "excludePlatforms": [],
7
+ "allowUnsafeCode": false,
8
+ "autoReferenced": true,
9
+ "defineConstraints": [],
10
+ "versionDefines": []
11
+ }
examples/open_world_city/Assets/Scripts/UnityAgent.Scripts.asmdef.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 51397beb9916b964e08c142ac4dd2500
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Assets/Scripts/VehicleController.cs ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using UnityEngine;
2
+
3
+ namespace UnityAgent
4
+ {
5
+ /// <summary>
6
+ /// Arcade car physics: motor torque, steering, braking and a simple
7
+ /// four-wheel raycast suspension. Place this on a root GameObject with
8
+ /// a Rigidbody and four wheel transforms.
9
+ /// </summary>
10
+ [RequireComponent(typeof(Rigidbody))]
11
+ public class VehicleController : MonoBehaviour
12
+ {
13
+ [System.Serializable]
14
+ public class Wheel
15
+ {
16
+ public Transform transform;
17
+ public bool isSteering;
18
+ public bool isPowered;
19
+ public bool isBraking;
20
+ [HideInInspector] public bool isGrounded;
21
+ [HideInInspector] public float suspensionOffset;
22
+ }
23
+
24
+ [Header("Wheels")]
25
+ public Wheel[] wheels = new Wheel[4];
26
+
27
+ [Header("Engine")]
28
+ public float motorTorque = 1500f;
29
+ public float brakeTorque = 3000f;
30
+ public float maxSteerAngle = 30f;
31
+ public float maxSpeedKph = 180f;
32
+
33
+ [Header("Suspension")]
34
+ public float suspensionRestLength = 0.5f;
35
+ public float suspensionSpring = 35000f;
36
+ public float suspensionDamper = 4500f;
37
+ public float wheelRadius = 0.35f;
38
+ public LayerMask wheelMask = ~0;
39
+
40
+ [Header("Aerodynamics")]
41
+ public float downforce = 100f;
42
+ public float centerOfMassOffset = -0.5f;
43
+
44
+ private Rigidbody _rb;
45
+ private float _throttle;
46
+ private float _steer;
47
+ private float _brake;
48
+
49
+ private void Awake()
50
+ {
51
+ _rb = GetComponent<Rigidbody>();
52
+ _rb.centerOfMass += Vector3.up * centerOfMassOffset;
53
+ _rb.interpolation = RigidbodyInterpolation.Interpolate;
54
+ }
55
+
56
+ private void Update()
57
+ {
58
+ _throttle = Input.GetAxis("Vertical");
59
+ _steer = Input.GetAxis("Horizontal");
60
+ _brake = Input.GetKey(KeyCode.Space) ? 1f : 0f;
61
+ }
62
+
63
+ private void FixedUpdate()
64
+ {
65
+ UpdateWheels();
66
+ ApplySuspension();
67
+ ApplyDrive();
68
+ ApplyDownforce();
69
+ }
70
+
71
+ private void UpdateWheels()
72
+ {
73
+ foreach (var w in wheels)
74
+ {
75
+ if (w.transform == null) continue;
76
+ Vector3 origin = w.transform.position;
77
+ bool hit = Physics.Raycast(
78
+ origin, -transform.up, out RaycastHit r,
79
+ suspensionRestLength + wheelRadius, wheelMask,
80
+ QueryTriggerInteraction.Ignore);
81
+ w.isGrounded = hit;
82
+ w.suspensionOffset = hit ? r.distance - wheelRadius : suspensionRestLength;
83
+ }
84
+ }
85
+
86
+ private void ApplySuspension()
87
+ {
88
+ foreach (var w in wheels)
89
+ {
90
+ if (!w.isGrounded) continue;
91
+ float force = (suspensionRestLength - w.suspensionOffset) * suspensionSpring;
92
+ Vector3 velocity = _rb.GetPointVelocity(w.transform.position);
93
+ float upwardVel = Vector3.Dot(transform.up, velocity);
94
+ force -= upwardVel * suspensionDamper;
95
+ _rb.AddForceAtPosition(transform.up * Mathf.Max(0f, force), w.transform.position);
96
+ }
97
+ }
98
+
99
+ private void ApplyDrive()
100
+ {
101
+ float kph = _rb.velocity.magnitude * 3.6f;
102
+ float speedFactor = Mathf.Clamp01(1f - kph / maxSpeedKph);
103
+
104
+ foreach (var w in wheels)
105
+ {
106
+ if (!w.isGrounded) continue;
107
+ if (w.isPowered)
108
+ {
109
+ Vector3 force = transform.forward * (_throttle * motorTorque * speedFactor);
110
+ _rb.AddForceAtPosition(force, w.transform.position, ForceMode.Force);
111
+ }
112
+ if (w.isBraking && _brake > 0.01f)
113
+ {
114
+ Vector3 brakeForce = -transform.forward * (_brake * brakeTorque);
115
+ _rb.AddForceAtPosition(brakeForce, w.transform.position, ForceMode.Force);
116
+ }
117
+ if (w.isSteering)
118
+ {
119
+ // Rotate the visual wheel and apply a small yaw torque.
120
+ w.transform.localRotation = Quaternion.Euler(0f, _steer * maxSteerAngle, 0f);
121
+ }
122
+ }
123
+ // Steering yaw torque.
124
+ if (Mathf.Abs(_steer) > 0.01f)
125
+ {
126
+ float turn = _steer * maxSteerAngle * 0.02f;
127
+ _rb.AddTorque(transform.up * turn * (_rb.velocity.magnitude + 1f), ForceMode.Force);
128
+ }
129
+ }
130
+
131
+ private void ApplyDownforce()
132
+ {
133
+ _rb.AddForce(-transform.up * downforce * _rb.velocity.magnitude);
134
+ }
135
+ }
136
+ }
examples/open_world_city/Assets/Scripts/VehicleController.cs.meta ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fileFormatVersion: 2
2
+ guid: 67179e275140460130f6cafb48739957
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
examples/open_world_city/Packages/manifest.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dependencies": {
3
+ "com.unity.ai.navigation": "1.1.5",
4
+ "com.unity.cinemachine": "2.9.7",
5
+ "com.unity.collab-proxy": "2.2.0",
6
+ "com.unity.ide.rider": "3.0.27",
7
+ "com.unity.ide.visualstudio": "2.0.22",
8
+ "com.unity.inputsystem": "1.7.0",
9
+ "com.unity.postprocessing": "3.4.0",
10
+ "com.unity.textmeshpro": "3.0.6",
11
+ "com.unity.ugui": "1.0.0",
12
+ "com.unity.modules.ai": "1.0.0",
13
+ "com.unity.modules.animation": "1.0.0",
14
+ "com.unity.modules.audio": "1.0.0",
15
+ "com.unity.modules.imageconversion": "1.0.0",
16
+ "com.unity.modules.particlesystem": "1.0.0",
17
+ "com.unity.modules.physics": "1.0.0",
18
+ "com.unity.modules.physics2d": "1.0.0",
19
+ "com.unity.modules.terrain": "1.0.0",
20
+ "com.unity.modules.terrainphysics": "1.0.0",
21
+ "com.unity.modules.ui": "1.0.0"
22
+ }
23
+ }
examples/open_world_city/ProjectSettings/ProjectSettings.asset ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ %YAML 1.1
2
+ %TAG !u! tag:unity3d.com,2011:
3
+ --- !u!129 &1
4
+ PlayerSettings:
5
+ m_ObjectHideFlags: 0
6
+ serializedVersion: 26
7
+ companyName: UnityAgent
8
+ productName: open_world_city
9
+ defaultCursor: {instanceID: 0}
10
+ m_Version: 1
examples/open_world_city/ProjectSettings/ProjectVersion.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ m_EditorVersion: 2022.3.20f1
examples/open_world_city/ProjectSettings/TagManager.asset ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ %YAML 1.1
2
+ %TAG !u! tag:unity3d.com,2011:
3
+ --- !u!78 &1
4
+ TagManager:
5
+ serializedVersion: 2
6
+ tags:
7
+ - Player
8
+ - Enemy
9
+ - Pickup
10
+ layers:
11
+ - Default
12
+ - TransparentFX
13
+ - Ignore Raycast
14
+ - Water
15
+ - UI
pyproject.toml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "unity-agent"
7
+ version = "0.1.0"
8
+ description = "A Python agent that generates complete Unity game projects (C#, .unity scenes, project config) you can open and play in any Unity 2022+ installation."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Unity Agent" }]
13
+ keywords = ["unity", "unity3d", "game-development", "code-generation", "agent"]
14
+
15
+ [project.scripts]
16
+ unity-agent = "unity_agent.__main__:main"
17
+
18
+ [tool.setuptools.packages.find]
19
+ where = ["."]
20
+ include = ["unity_agent*"]
21
+
22
+ [tool.setuptools.package-data]
23
+ "unity_agent.knowledge" = ["entries/*.md", "README.md"]
unity_agent/__init__.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unity Agent - A Python package that generates complete Unity game projects.
3
+
4
+ The agent uses a collection of code-generation tools to emit compilable C#
5
+ scripts, Unity scene (.unity) files, project configuration (.meta, ProjectSettings,
6
+ Packages/manifest.json) and an open-world example game.
7
+
8
+ Quick start
9
+ -----------
10
+ >>> from unity_agent.config import Settings
11
+ >>> from unity_agent.transport.unity_transport import UnityTransport
12
+ >>> from unity_agent.tools.unity_tools import GenerateCompleteGameTool
13
+ >>> settings = Settings(output_dir="my_game")
14
+ >>> transport = UnityTransport(settings)
15
+ >>> GenerateCompleteGameTool(settings, transport).run(game_name="OpenWorldCity")
16
+ """
17
+
18
+ from unity_agent.config import Settings
19
+ from unity_agent.transport.unity_transport import UnityTransport
20
+ from unity_agent.tools.base import ToolBase, ToolRegistry
21
+ from unity_agent.orchestrator.prompts import SYSTEM_PROMPT, build_user_prompt
22
+
23
+ __all__ = [
24
+ "Settings",
25
+ "UnityTransport",
26
+ "ToolBase",
27
+ "ToolRegistry",
28
+ "SYSTEM_PROMPT",
29
+ "build_user_prompt",
30
+ ]
31
+
32
+ __version__ = "0.1.0"
unity_agent/__main__.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Command-line entry point for the unity_agent package.
2
+
3
+ Usage::
4
+
5
+ python -m unity_agent generate --game OpenWorldCity --preset open_world_city
6
+ python -m unity_agent list-tools
7
+ python -m unity_agent qa examples/open_world_city
8
+
9
+ Run ``python -m unity_agent --help`` for the full list of commands.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ from unity_agent.config import Settings
19
+ from unity_agent.transport.unity_transport import UnityTransport
20
+ from unity_agent.tools.base import get_registry
21
+ from unity_agent.tools.unity_tools import GenerateCompleteGameTool, SetupUnityProjectTool
22
+
23
+
24
+ def _cmd_list_tools(args: argparse.Namespace) -> int:
25
+ registry = get_registry()
26
+ for name in registry.all_names():
27
+ cls = registry.get(name)
28
+ print(f" {name:35s} {cls.description}")
29
+ return 0
30
+
31
+
32
+ def _cmd_generate(args: argparse.Namespace) -> int:
33
+ settings = Settings(
34
+ output_dir=args.output_dir,
35
+ product_name=args.game,
36
+ )
37
+ transport = UnityTransport(settings)
38
+ tool = GenerateCompleteGameTool(settings, transport)
39
+ result = tool.run(game_name=args.game, preset=args.preset, project_name=args.game)
40
+ print(result.summary)
41
+ for f in result.files:
42
+ print(f" {f}")
43
+ print()
44
+ print(transport.summary())
45
+ return 0 if result.ok else 1
46
+
47
+
48
+ def _cmd_setup(args: argparse.Namespace) -> int:
49
+ settings = Settings(output_dir=args.output_dir, product_name=args.project_name)
50
+ transport = UnityTransport(settings)
51
+ tool = SetupUnityProjectTool(settings, transport)
52
+ result = tool.run(project_name=args.project_name)
53
+ print(result.summary)
54
+ for f in result.files:
55
+ print(f" {f}")
56
+ return 0 if result.ok else 1
57
+
58
+
59
+ def _cmd_qa(args: argparse.Namespace) -> int:
60
+ from unity_agent.qa import ProjectQA
61
+ qa = ProjectQA(args.project_root)
62
+ report = qa.run()
63
+ print(report.summary())
64
+ for issue in report.issues:
65
+ print(f" [{issue.severity.upper():7}] {issue.category:8} {issue.message} {issue.path}")
66
+ return 0 if report.passed else 1
67
+
68
+
69
+ def build_parser() -> argparse.ArgumentParser:
70
+ p = argparse.ArgumentParser(prog="unity_agent", description="Unity game agent CLI.")
71
+ sub = p.add_subparsers(dest="cmd", required=True)
72
+
73
+ p_list = sub.add_parser("list-tools", help="List every registered tool.")
74
+ p_list.set_defaults(func=_cmd_list_tools)
75
+
76
+ p_gen = sub.add_parser("generate", help="Generate a complete game.")
77
+ p_gen.add_argument("--game", default="OpenWorldCity", help="Game / project name.")
78
+ p_gen.add_argument("--preset", default="open_world_city",
79
+ choices=["open_world_city", "fps_arena", "minimal"])
80
+ p_gen.add_argument("--output-dir", default="unity_output",
81
+ help="Where to write the project (default: unity_output).")
82
+ p_gen.set_defaults(func=_cmd_generate)
83
+
84
+ p_setup = sub.add_parser("setup", help="Scaffold an empty Unity project.")
85
+ p_setup.add_argument("--project-name", default="GeneratedGame")
86
+ p_setup.add_argument("--output-dir", default="unity_output")
87
+ p_setup.set_defaults(func=_cmd_setup)
88
+
89
+ p_qa = sub.add_parser("qa", help="Run QA checks on a generated project.")
90
+ p_qa.add_argument("project_root", help="Path to the project root.")
91
+ p_qa.set_defaults(func=_cmd_qa)
92
+
93
+ return p
94
+
95
+
96
+ def main(argv: list[str] | None = None) -> int:
97
+ parser = build_parser()
98
+ args = parser.parse_args(argv)
99
+ return args.func(args)
100
+
101
+
102
+ if __name__ == "__main__":
103
+ sys.exit(main())
unity_agent/config.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Global configuration for the Unity agent.
2
+
3
+ The :class:`Settings` dataclass is the single source of truth for runtime
4
+ parameters such as the Unity installation path, optional LLM API keys, the
5
+ default output directory and project metadata used when scaffolding a new
6
+ Unity project.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from dataclasses import dataclass, field, asdict
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+
17
+ DEFAULT_OUTPUT_DIR = "unity_output"
18
+ DEFAULT_UNITY_VERSION = "2022.3.20f1"
19
+
20
+ # Default Unity project metadata. These values are written into
21
+ # ProjectSettings/ProjectSettings.asset so the editor knows what to display
22
+ # in the project wizard and on the splash screen.
23
+ DEFAULT_COMPANY_NAME = "UnityAgent"
24
+ DEFAULT_PRODUCT_NAME = "GeneratedGame"
25
+
26
+
27
+ @dataclass
28
+ class Settings:
29
+ """Runtime configuration shared by every tool.
30
+
31
+ Parameters
32
+ ----------
33
+ unity_path:
34
+ Optional path to the Unity executable (``Unity.exe`` / ``Unity``).
35
+ When provided, the agent can optionally invoke Unity in batch mode
36
+ to validate that generated projects compile.
37
+ unity_version:
38
+ Target Unity version written into ``ProjectSettings.asset``.
39
+ output_dir:
40
+ Root directory where generated projects are written. Can be either a
41
+ relative path (resolved against the current working directory) or an
42
+ absolute path.
43
+ company_name / product_name:
44
+ Metadata written into the project settings.
45
+ openai_api_key / anthropic_api_key / zai_api_key:
46
+ Optional LLM provider keys. Only required if the orchestrator is
47
+ wired up to call an LLM directly.
48
+ overwrite:
49
+ If ``True`` existing files are replaced; otherwise a ``FileExistsError``
50
+ is raised when a destination file already exists.
51
+ """
52
+
53
+ unity_path: Optional[str] = None
54
+ unity_version: str = DEFAULT_UNITY_VERSION
55
+ output_dir: str = DEFAULT_OUTPUT_DIR
56
+ company_name: str = DEFAULT_COMPANY_NAME
57
+ product_name: str = DEFAULT_PRODUCT_NAME
58
+
59
+ openai_api_key: Optional[str] = field(default_factory=lambda: os.environ.get("OPENAI_API_KEY"))
60
+ anthropic_api_key: Optional[str] = field(default_factory=lambda: os.environ.get("ANTHROPIC_API_KEY"))
61
+ zai_api_key: Optional[str] = field(default_factory=lambda: os.environ.get("ZAI_API_KEY"))
62
+
63
+ overwrite: bool = True
64
+ auto_create_dirs: bool = True
65
+
66
+ # ------------------------------------------------------------------ #
67
+ # Convenience helpers
68
+ # ------------------------------------------------------------------ #
69
+ @property
70
+ def output_path(self) -> Path:
71
+ p = Path(self.output_dir)
72
+ return p if p.is_absolute() else (Path.cwd() / p)
73
+
74
+ def project_path(self, project_name: str) -> Path:
75
+ """Return the absolute path to a named project inside ``output_dir``."""
76
+ return self.output_path / project_name
77
+
78
+ def to_dict(self) -> dict:
79
+ return asdict(self)
80
+
81
+ @classmethod
82
+ def from_env(cls) -> "Settings":
83
+ """Build a :class:`Settings` instance populated from environment vars."""
84
+ return cls(
85
+ unity_path=os.environ.get("UNITY_PATH"),
86
+ unity_version=os.environ.get("UNITY_VERSION", DEFAULT_UNITY_VERSION),
87
+ output_dir=os.environ.get("UNITY_AGENT_OUTPUT_DIR", DEFAULT_OUTPUT_DIR),
88
+ company_name=os.environ.get("UNITY_COMPANY_NAME", DEFAULT_COMPANY_NAME),
89
+ product_name=os.environ.get("UNITY_PRODUCT_NAME", DEFAULT_PRODUCT_NAME),
90
+ )
unity_agent/knowledge/README.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Unity Agent Knowledge Base
2
+
3
+ This directory contains markdown knowledge entries the agent consults when
4
+ generating Unity projects. Each file is a short, focused reference on one
5
+ area of Unity development.
6
+
7
+ ## Entries
8
+
9
+ | File | Topic |
10
+ |----------------------------|------------------------------------------------------------|
11
+ | `unity_fundamentals.md` | MonoBehaviour lifecycle, GameObjects, components, coroutines |
12
+ | `unity_physics.md` | Rigidbody, colliders, raycasting, physics vs frame step |
13
+ | `unity_rendering.md` | Materials, shaders, lighting, post-processing, LOD, fog |
14
+ | `unity_navigation.md` | NavMesh, NavMeshAgent, AI state machines, obstacles |
15
+ | `unity_input_system.md` | Legacy Input Manager, mouse-look, the new Input System |
16
+ | `unity_ui.md` | Canvas, HUD, health bars, TextMeshPro, layout |
17
+ | `unity_audio.md` | AudioSource, 3D audio, AudioMixer, AudioManager pattern |
18
+ | `open_world_design.md` | Procedural generation, LOD, streaming, day/night, save |
19
+
20
+ ## How the agent uses these
21
+
22
+ The orchestrator can embed the contents of these files as context for the
23
+ LLM. The system prompt instructs the LLM to "consult the knowledge entries
24
+ for [topic] when generating code that touches [topic]."
25
+
26
+ For example, when generating a third-person controller the LLM should pull
27
+ `unity_fundamentals.md` (for the lifecycle) and `unity_input_system.md`
28
+ (for the mouse-look recipe).
29
+
30
+ ## Adding entries
31
+
32
+ Create a new `.md` file in this directory and update the table above. The
33
+ file name (without `.md`) becomes the entry's slug.
unity_agent/knowledge/__init__.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Knowledge base loader.
2
+
3
+ Provides a simple API for loading the markdown knowledge entries that ship
4
+ with the package. Entries are stored as ``.md`` files in
5
+ ``unity_agent/knowledge/entries/``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from importlib import resources
11
+ from pathlib import Path
12
+ from typing import Dict, List, Optional
13
+
14
+
15
+ def entries_dir() -> Path:
16
+ """Return the absolute path to the knowledge entries directory."""
17
+ return Path(__file__).resolve().parent / "entries"
18
+
19
+
20
+ def list_entries() -> List[str]:
21
+ """Return a list of knowledge entry slugs (file names without .md)."""
22
+ d = entries_dir()
23
+ if not d.exists():
24
+ return []
25
+ return sorted(p.stem for p in d.glob("*.md"))
26
+
27
+
28
+ def load_entry(slug: str) -> Optional[str]:
29
+ """Return the markdown body of a knowledge entry, or None if missing."""
30
+ p = entries_dir() / f"{slug}.md"
31
+ if not p.exists():
32
+ return None
33
+ return p.read_text(encoding="utf-8")
34
+
35
+
36
+ def load_all() -> Dict[str, str]:
37
+ """Return a dict mapping slug -> markdown body for every entry."""
38
+ return {slug: load_entry(slug) for slug in list_entries()}
39
+
40
+
41
+ __all__ = ["entries_dir", "list_entries", "load_entry", "load_all"]
unity_agent/knowledge/entries/open_world_design.md ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Open-World Game Design
2
+
3
+ Designing an open world is fundamentally a trade-off between **density of
4
+ content** and **memory/CPU budget**. This document collects the patterns the
5
+ agent uses when generating the open-world city example.
6
+
7
+ ## Procedural generation
8
+
9
+ ### Grid + jitter
10
+
11
+ The simplest viable city layout is a grid of blocks where each block holds
12
+ one or more buildings whose position is jittered slightly to break the
13
+ symmetry:
14
+
15
+ ```csharp
16
+ for (int x = 0; x < gridX; x++)
17
+ for (int z = 0; z < gridZ; z++)
18
+ {
19
+ Vector3 block = origin + new Vector3(x * blockSpacing, 0f, z * blockSpacing);
20
+ SpawnBuilding(block + Random.insideUnitSphere * 4f);
21
+ }
22
+ ```
23
+
24
+ The generated `CityGenerator.cs` follows this pattern. For more variety,
25
+ swap the grid for:
26
+
27
+ * **Voronoi street networks** (Delaunay triangulation, then prune).
28
+ * **L-system road growth** (recursive rewrite rules -- great for organic
29
+ cities).
30
+ * **Wave function collapse** -- tile-based, but expensive to author.
31
+
32
+ ### Building composition
33
+
34
+ A building is rarely a single mesh. The generated `BuildingGenerator.cs`
35
+ composes three primitives:
36
+
37
+ 1. A wall cube with a Standard material (concrete/glass color).
38
+ 2. A slightly smaller cube with an emissive **window material** so windows
39
+ glow at night.
40
+ 3. A `BoxCollider` for collision.
41
+
42
+ For more variety:
43
+
44
+ * Vary the footprint (square vs rectangular).
45
+ * Add a flat roof cap with a different material.
46
+ * Add a parapet along the top.
47
+ * Randomly choose between glass-tower, brick-mid-rise, and concrete-low-rise
48
+ palettes.
49
+
50
+ ### Runtime vs baked
51
+
52
+ The agent generates everything **at runtime in `Start()`** because:
53
+
54
+ * The Unity project opens with an empty scene and the user immediately
55
+ sees content after pressing Play.
56
+ * No need to ship `.unity` scenes with hundreds of GameObjects.
57
+ * Easy to randomize: just change the seed.
58
+
59
+ The downside is the first-frame hitch on large worlds. Mitigations:
60
+
61
+ * Spread generation across frames using a coroutine that yields after
62
+ every N buildings.
63
+ * Use `Object.Instantiate` from a small set of prefabs instead of building
64
+ primitives from scratch each time.
65
+ * Use `Graphics.DrawMeshInstanced` for buildings once you do not need
66
+ per-building colliders.
67
+
68
+ ## Level of Detail (LOD)
69
+
70
+ For an open world you must use LOD. Without it, the GPU processes every
71
+ window pane on every building no matter how far away it is.
72
+
73
+ | LOD | Screen size | Poly count |
74
+ |-----|-------------|-----------------------------|
75
+ | 0 | 60%+ | Full mesh with windows |
76
+ | 1 | 25-60% | Simplified mesh, no windows |
77
+ | 2 | 5-25% | Single box per building |
78
+ | Culled | <5% | Not rendered at all |
79
+
80
+ Add a `LODGroup` to each building and assign LOD meshes. For procedural
81
+ buildings you can build the LOD meshes in code by reducing the window
82
+ count and merging meshes.
83
+
84
+ ## Streaming and chunking
85
+
86
+ For worlds larger than ~1km x 1km you must chunk:
87
+
88
+ * Divide the world into a grid of chunks (e.g. 200m x 200m).
89
+ * Load chunks near the player; unload chunks far away.
90
+ * Use `Addressables` or `SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive)`
91
+ to load chunk scenes.
92
+
93
+ The generated example does NOT chunk -- it builds everything in one shot,
94
+ which is fine for an 8x8 block city. Chunking becomes mandatory around
95
+ 16x16 blocks.
96
+
97
+ ## Fog and far plane
98
+
99
+ Always set `RenderSettings.fog` for open worlds. It hides:
100
+
101
+ * Distant popping when LOD changes happen.
102
+ * The far clip plane edge.
103
+
104
+ A linear fog from 100m to 800m is a good default for a city.
105
+
106
+ ## Day/Night cycle
107
+
108
+ The generated `DayNightCycle.cs` rotates a directional light around the X
109
+ axis to simulate the sun:
110
+
111
+ ```csharp
112
+ float sunAngle = (_timeOfDay - 0.25f) * 360f; // 0.25 = noon
113
+ sun.transform.rotation = Quaternion.Euler(sunAngle, -30f, 0f);
114
+ ```
115
+
116
+ * Keep the day length short (60-180 s) for demo purposes; 24 min real time
117
+ per day for actual gameplay.
118
+ * Lerp ambient light and skybox color between day/night presets so the
119
+ world does not suddenly flip black.
120
+ * Turn street lights on/off based on a sun-intensity threshold.
121
+
122
+ ## Player traversal
123
+
124
+ Open worlds need fast traversal. Options:
125
+
126
+ | Method | Speed (m/s) | Use case |
127
+ |-----------------------|-------------|---------------------------------------|
128
+ | Walking | 3-5 | On-foot exploration |
129
+ | Sprinting | 7-10 | Short bursts |
130
+ | Car (arcade) | 15-30 | The generated `VehicleController.cs` |
131
+ | Horse / mount | 8-12 | Fantasy open world |
132
+ | Fast travel | instant | Pre-unlocked locations |
133
+
134
+ The generated project ships both a `PlayerController` (on-foot) and a
135
+ `VehicleController` (car) so the user can switch between them.
136
+
137
+ ## NPC traffic
138
+
139
+ For an open-world city, AI traffic should be **lightweight**:
140
+
141
+ * Use `NavMeshAgent` with a low `acceleration` for cars so they look
142
+ smooth, not jerky.
143
+ * Pool the agents -- never `Instantiate` a new car every time one drives
144
+ off-screen.
145
+ * Despawn agents when they are > 100m from the player.
146
+
147
+ The generated `NPCController.cs` covers pedestrians; for cars you would
148
+ extend it with lane-following logic.
149
+
150
+ ## Save systems
151
+
152
+ Open worlds need persistent saves. The generated `SaveSystem.cs` writes a
153
+ single JSON file to `Application.persistentDataPath`:
154
+
155
+ ```csharp
156
+ [Serializable]
157
+ public class GameSave
158
+ {
159
+ public Vector3 playerPosition;
160
+ public int score;
161
+ public float timeOfDay;
162
+ public List<string> completedQuests;
163
+ }
164
+ ```
165
+
166
+ For larger worlds, split saves into:
167
+
168
+ * Player profile (small, frequent).
169
+ * World state (large, infrequent -- chunk ownership, building destruction,
170
+ NPC schedules).
171
+
172
+ ## Common pitfalls
173
+
174
+ * **Floating point precision:** Unity uses single-precision floats. Beyond
175
+ ~5km from the origin, physics and rendering jitter. Use a **floating
176
+ origin** that re-centres the world on the player every few hundred metres.
177
+ * **Shadow distance:** the default 150m shadow distance is too short for an
178
+ open world. Raise to 300-500m for desktop; lower for mobile.
179
+ * **Lighting rebuild:** make sure all your static city geometry is marked
180
+ **Navigation Static** AND **Lightmap Static** before baking. For runtime-
181
+ generated cities you cannot bake -- rely on realtime lighting and the
182
+ directional sun.
183
+ * **Collider spam:** 50 buildings x 6 faces each = 300 colliders. Mark
184
+ small decoration meshes as `isTrigger = false` and use a single
185
+ `BoxCollider` per building.
unity_agent/knowledge/entries/unity_audio.md ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Unity Audio
2
+
3
+ Unity plays audio through `AudioSource` components that reference an
4
+ `AudioClip` asset. A single `AudioListener` in the scene is required
5
+ (usually on the Main Camera).
6
+
7
+ ## AudioClip
8
+
9
+ An `AudioClip` is the audio data. Supported formats: WAV (uncompressed,
10
+ fastest), MP3/Ogg Vorbis (compressed, smaller). Unity imports them and
11
+ lets you choose compression per-platform in the import settings.
12
+
13
+ ## AudioSource
14
+
15
+ ```csharp
16
+ var src = gameObject.AddComponent<AudioSource>();
17
+ src.clip = jumpClip;
18
+ src.volume = 0.7f;
19
+ src.pitch = 1.0f;
20
+ src.loop = false;
21
+ src.playOnAwake = false;
22
+ src.spatialBlend = 0.0f; // 0=2D, 1=3D
23
+ src.Play();
24
+ ```
25
+
26
+ Important properties:
27
+
28
+ | Property | Meaning |
29
+ |-----------------|-----------------------------------------------------------|
30
+ | `clip` | The AudioClip to play |
31
+ | `volume` | 0..1 |
32
+ | `pitch` | Default 1; >1 = higher pitch, <1 = lower |
33
+ | `loop` | Repeat when the clip ends |
34
+ | `playOnAwake` | Play automatically when the AudioSource is enabled |
35
+ | `spatialBlend` | 0=fully 2D, 1=fully 3D (position-based) |
36
+ | `minDistance` | Within this distance the sound is at full volume (3D) |
37
+ | `maxDistance` | Beyond this distance the sound is inaudible (3D) |
38
+ | `rolloffMode` | Logarithmic, Linear, or custom curve (3D falloff) |
39
+
40
+ ## 3D audio
41
+
42
+ For positional sounds (footsteps, gunfire, engines), set
43
+ `spatialBlend = 1.0f`. The volume then depends on the distance between the
44
+ AudioSource and the AudioListener.
45
+
46
+ ```csharp
47
+ src.spatialBlend = 1.0f;
48
+ src.rolloffMode = AudioRolloffMode.Logarithmic;
49
+ src.minDistance = 1f;
50
+ src.maxDistance = 50f;
51
+ ```
52
+
53
+ > Default `maxDistance` is 500 -- way too large for most gameplay. Lower
54
+ > it to 30-50 for SFX.
55
+
56
+ ## One-shot SFX
57
+
58
+ For short sound effects (pickups, gunshots, impacts) use `PlayOneShot`:
59
+
60
+ ```csharp
61
+ audioSource.PlayOneShot(clip, volumeScale);
62
+ ```
63
+
64
+ `PlayOneShot` can layer multiple clips on the same AudioSource without
65
+ cutting each other off.
66
+
67
+ ## AudioMixer
68
+
69
+ For volume control and grouping, create an **AudioMixer** asset
70
+ (`Assets > Create > Audio Mixer`). Expose parameters and control them at
71
+ runtime:
72
+
73
+ ```csharp
74
+ [SerializeField] private AudioMixer mixer;
75
+
76
+ public void SetMasterVolume(float dB) => mixer.SetFloat("MasterVolume", dB);
77
+ public void SetMusicVolume(float dB) => mixer.SetFloat("MusicVolume", dB);
78
+ public void SetSfxVolume(float dB) => mixer.SetFloat("SfxVolume", dB);
79
+ ```
80
+
81
+ Convert linear 0..1 to dB:
82
+
83
+ ```csharp
84
+ public static float LinearToDB(float linear) =>
85
+ linear <= 0.0001f ? -80f : Mathf.Log10(linear) * 20f;
86
+ ```
87
+
88
+ ## The AudioManager pattern
89
+
90
+ The generated `AudioManager.cs` is a singleton with two channels
91
+ (`sfxSource`, `musicSource`). It exposes:
92
+
93
+ ```csharp
94
+ AudioManager.Instance.PlaySfx(pickupClip, 1.0f);
95
+ AudioManager.Instance.PlayMusic(menuMusic);
96
+ AudioManager.Instance.StopMusic();
97
+ AudioManager.Instance.ApplyVolumes();
98
+ ```
99
+
100
+ `DontDestroyOnLoad` keeps the manager alive across scene loads so music
101
+ does not restart.
102
+
103
+ ## AudioListener
104
+
105
+ * Exactly one `AudioListener` per scene. The Main Camera has one by
106
+ default; remove it if you add another.
107
+ * The listener's transform defines "where the player's ears are" for 3D
108
+ audio panning.
109
+
110
+ ## Performance
111
+
112
+ * Pool AudioSources for sounds that play many times per second (machine
113
+ guns, rain). Reusing one AudioSource and calling `PlayOneShot` is fine
114
+ up to ~20 simultaneous clips.
115
+ * Use compressed formats (Vorbis) for music; uncompressed (WAV) for
116
+ frequent short SFX.
117
+ * Set `AudioClip.preloadAudioData = true` for short clips and `false` for
118
+ music that should stream from disk.
119
+ * Lower `maxDistance` aggressively; large falloff radii cost CPU on 3D
120
+ sounds.
unity_agent/knowledge/entries/unity_fundamentals.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Unity Fundamentals
2
+
3
+ ## MonoBehaviour Lifecycle
4
+
5
+ Every gameplay script in Unity derives from `MonoBehaviour`. The engine calls
6
+ a fixed sequence of messages; understanding the order is the difference
7
+ between code that runs smoothly and code that stutters or races.
8
+
9
+ ```
10
+ Awake() -> OnEnable() -> Start()
11
+ |
12
+ v
13
+ FixedUpdate() (physics, fixed timestep) --+
14
+ |
15
+ Update() (input, gameplay) --+
16
+ |
17
+ LateUpdate() (camera follow, post-sim) --+
18
+ |
19
+ OnDisable() -> OnDestroy()
20
+ ```
21
+
22
+ | Message | When it runs | Typical use |
23
+ |----------------|-----------------------------------------------|-----------------------------------------------|
24
+ | `Awake` | Once, when the script instance is loaded | Cache components, set up singletons |
25
+ | `OnEnable` | Every time the component is enabled | Subscribe to events |
26
+ | `Start` | Once, before the first Update | Cross-object references (other objects' Awake have run) |
27
+ | `FixedUpdate` | Fixed timestep (default 0.02 s = 50 Hz) | Rigidbody physics forces |
28
+ | `Update` | Every rendered frame | Input polling, gameplay logic |
29
+ | `LateUpdate` | After all Update calls have finished | Camera follow, post-processing |
30
+ | `OnDisable` | When the component is disabled | Unsubscribe from events |
31
+ | `OnDestroy` | When the GameObject is destroyed | Cleanup native resources |
32
+
33
+ > **Gotcha:** `Camera.main` searches the scene by tag every call in 2022.3.
34
+ > Cache it in `Awake` if you call it more than once per frame.
35
+
36
+ ## GameObjects and Components
37
+
38
+ A `GameObject` is an empty container. Behaviour is added through
39
+ **components**:
40
+
41
+ ```csharp
42
+ var go = new GameObject("Enemy");
43
+ go.transform.position = Vector3.zero;
44
+ go.AddComponent<Rigidbody>();
45
+ go.AddComponent<CapsuleCollider>();
46
+ var ai = go.AddComponent<NPCController>();
47
+ ai.player = GameObject.FindGameObjectWithTag("Player").transform;
48
+ ```
49
+
50
+ * Every GameObject always has a `Transform` (or `RectTransform` under a
51
+ Canvas). You cannot remove it.
52
+ * Use `GetComponent<T>()` / `GetComponentInChildren<T>()` /
53
+ `GetComponentInParent<T>()` to reach related components. Cache the result
54
+ in `Awake` instead of calling it every frame.
55
+ * `RequireComponent(typeof(T))` makes Unity auto-add the required component
56
+ when the script is added in the inspector.
57
+
58
+ ## The Component pattern in practice
59
+
60
+ ```csharp
61
+ [RequireComponent(typeof(Rigidbody))]
62
+ public class PlayerController : MonoBehaviour
63
+ {
64
+ private Rigidbody _rb;
65
+
66
+ private void Awake()
67
+ {
68
+ _rb = GetComponent<Rigidbody>(); // safe: RequireComponent guarantees it
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## Coroutines
74
+
75
+ Coroutines let you write asynchronous-looking code that runs across multiple
76
+ frames:
77
+
78
+ ```csharp
79
+ private IEnumerator RespawnAfterDelay(float seconds)
80
+ {
81
+ yield return new WaitForSeconds(seconds);
82
+ transform.position = _spawnPoint;
83
+ }
84
+
85
+ private void OnDeath() => StartCoroutine(RespawnAfterDelay(2f));
86
+ ```
87
+
88
+ * `yield return null;` waits one frame.
89
+ * `yield return new WaitForFixedUpdate();` waits until the next physics step.
90
+ * `yield return new WaitUntil(() => _isGrounded);` waits for a condition.
91
+
92
+ ## Inspector best practices
93
+
94
+ * Use `[Header]`, `[Tooltip]`, `[Range]`, `[SerializeField]` to keep the
95
+ inspector tidy.
96
+ * Prefer `private` + `[SerializeField]` over `public` for fields that should
97
+ not be touched by other scripts but should be editable in the inspector.
98
+ * Use `[System.Serializable]` on structs/classes to make them expandable in
99
+ the inspector.
unity_agent/knowledge/entries/unity_input_system.md ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Unity Input System
2
+
3
+ Unity offers two input systems:
4
+
5
+ 1. **Input Manager (legacy)** -- `Input.GetAxis`, `Input.GetKeyDown`. The
6
+ default in 2022.3 LTS. Used by every generated script in this project.
7
+ 2. **Input System package** -- the new event-driven API. Available via the
8
+ `com.unity.inputsystem` package.
9
+
10
+ ## Input Manager (legacy)
11
+
12
+ ### Axes
13
+
14
+ The legacy system reads virtual axes defined in
15
+ `Edit > Project Settings > Input Manager`. The defaults include:
16
+
17
+ | Axis name | Default keys |
18
+ |---------------|---------------------------------|
19
+ | `Horizontal` | A/D, Arrow Left/Right |
20
+ | `Vertical` | W/S, Arrow Up/Down |
21
+ | `Mouse X` | Mouse delta X |
22
+ | `Mouse Y` | Mouse delta Y |
23
+ | `Jump` | Space |
24
+ | `Fire1` | Left Ctrl / Mouse 0 |
25
+ | `Fire2` | Right Alt / Mouse 1 |
26
+ | `Fire3` | Left Shift / Mouse 2 |
27
+
28
+ ### Reading axes
29
+
30
+ ```csharp
31
+ float x = Input.GetAxisRaw("Horizontal"); // -1, 0, +1 (no smoothing)
32
+ float y = Input.GetAxis("Vertical"); // smoothed, -1..+1
33
+ Vector2 move = new Vector2(x, y);
34
+ move = Vector2.ClampMagnitude(move, 1f); // prevent diagonal speed boost
35
+ ```
36
+
37
+ * Use `GetAxisRaw` for responsive arcade movement; `GetAxis` for smooth
38
+ camera-style axes.
39
+ * Always clamp the magnitude of a 2-axis vector -- otherwise diagonal
40
+ movement is ~1.41x faster than cardinal.
41
+
42
+ ### Keys and buttons
43
+
44
+ ```csharp
45
+ if (Input.GetKeyDown(KeyCode.Space)) Jump();
46
+ if (Input.GetKey(KeyCode.LeftShift)) Sprint();
47
+ if (Input.GetKeyUp(KeyCode.E)) Interact();
48
+ if (Input.GetMouseButtonDown(0)) Fire();
49
+ if (Input.GetMouseButton(0)) FireContinuous();
50
+ ```
51
+
52
+ | Method | Fires when |
53
+ |-----------------------|-------------------------------------------|
54
+ | `GetKey` | Every frame the key is held |
55
+ | `GetKeyDown` | Only on the frame the key was pressed |
56
+ | `GetKeyUp` | Only on the frame the key was released |
57
+ | `GetMouseButton` | Every frame the button is held |
58
+ | `GetMouseButtonDown` | Only on the frame the button went down |
59
+
60
+ ### Mouse position and delta
61
+
62
+ ```csharp
63
+ Vector3 mousePos = Input.mousePosition; // screen-space pixels
64
+ float dx = Input.GetAxis("Mouse X"); // smoothed delta
65
+ float rawDx = Input.GetAxisRaw("Mouse X");
66
+ ```
67
+
68
+ To lock the cursor for first-person look:
69
+
70
+ ```csharp
71
+ Cursor.lockState = CursorLockMode.Locked; // hide + center
72
+ Cursor.visible = false;
73
+ ```
74
+
75
+ ## Mouse-look recipe (first-person)
76
+
77
+ ```csharp
78
+ private float _pitch;
79
+
80
+ private void Update()
81
+ {
82
+ float mx = Input.GetAxisRaw("Mouse X") * sensitivity;
83
+ float my = Input.GetAxisRaw("Mouse Y") * sensitivity;
84
+
85
+ transform.Rotate(Vector3.up * mx);
86
+ _pitch = Mathf.Clamp(_pitch - my, -85f, 85f);
87
+ cameraTransform.localRotation = Quaternion.Euler(_pitch, 0f, 0f);
88
+ }
89
+ ```
90
+
91
+ ## Mouse-look recipe (third-person orbit)
92
+
93
+ For a third-person camera we accumulate a **yaw** and **pitch** and build a
94
+ rotation each frame:
95
+
96
+ ```csharp
97
+ _yaw += Input.GetAxis("Mouse X") * sensitivity;
98
+ _pitch -= Input.GetAxis("Mouse Y") * sensitivity;
99
+ _pitch = Mathf.Clamp(_pitch, -30f, 75f);
100
+ Quaternion rotation = Quaternion.Euler(_pitch, _yaw, 0f);
101
+ Vector3 position = pivot + rotation * new Vector3(0f, height, -distance);
102
+ ```
103
+
104
+ The `ThirdPersonCamera.cs` generator follows exactly this pattern.
105
+
106
+ ## Input System package (new)
107
+
108
+ When the project has the Input System package active, the legacy `Input`
109
+ class will throw or return zeros unless `Project Settings > Player > Active Input Handling = Both`.
110
+
111
+ The new API is event-based:
112
+
113
+ ```csharp
114
+ [SerializeField] private InputActionAsset actions;
115
+ private InputAction _moveAction;
116
+
117
+ private void OnEnable()
118
+ {
119
+ _moveAction = actions.FindAction("Move");
120
+ _moveAction.performed += OnMove;
121
+ _moveAction.Enable();
122
+ }
123
+
124
+ private void OnMove(InputAction.CallbackContext ctx) => _moveInput = ctx.ReadValue<Vector2>();
125
+ private void OnDisable() => _moveAction.Disable();
126
+ ```
127
+
128
+ For the generated project we keep Active Input Handling on **Both** so the
129
+ legacy `Input` calls in the generated scripts keep working.
130
+
131
+ ## Touch and mobile
132
+
133
+ For mobile, read `Input.touches` (legacy) or the new Input System's
134
+ `Pointer`/`Touchscreen`:
135
+
136
+ ```csharp
137
+ foreach (Touch t in Input.touches)
138
+ {
139
+ if (t.phase == TouchPhase.Began) HandleTap(t.position);
140
+ }
141
+ ```
142
+
143
+ The generated scripts are desktop-first; you would add a virtual joystick
144
+ overlay for mobile ports.
145
+
146
+ ## Common pitfalls
147
+
148
+ * Reading `Input` in `FixedUpdate` -- the input system updates once per
149
+ frame, but `FixedUpdate` may run 0..N times per frame, so you will drop
150
+ or duplicate inputs.
151
+ * Using `Input.GetAxis` for physics forces -- the smoothed value can cause
152
+ floaty controls. Prefer `GetAxisRaw` and apply your own smoothing.
153
+ * Forgetting to lock the cursor in an FPS leads to the camera spinning
154
+ when the cursor leaves the game window.