"""
unity_tools.py
==============
Complete tool registry for the Unity Agent — a code-generation assistant that
can scaffold a GTA 6-level open-world game inside Unity 2022+.
Every tool in this module is a self-contained Python class that, when its
``run()`` method is invoked, emits production-quality, compilable C# Unity
scripts as a string. The agent orchestrator loops over the requested tools,
collects the generated source, and writes the files into a Unity project
scaffold (see :class:`SetupUnityProjectTool` and
:class:`GenerateCompleteGameTool`).
The registry covers every major gameplay system required by a modern open
world title:
* Locomotion & cameras (player, FPS, third-person)
* Vehicles & driving physics
* Procedural world generation (city, terrain, roads, buildings, water)
* Combat (weapons, health, ragdolls, explosions)
* AI (NPCs, pedestrians, traffic, police/wanted)
* Systems (economy, quests, missions, factions, skills, crafting, loot)
* Presentation (UI, minimap, radio, phone, cutscenes, photo mode)
* Persistence (save system, inventory, garages, properties)
* Atmosphere (day/night, weather, particles, audio, CCTV)
* Networking (multiplayer netcode)
All generated C# targets Unity 2022.3 LTS APIs and uses idiomatic patterns:
``[SerializeField]`` private fields, ``[Header]`` grouping, ``Awake``/``Start``/
``Update``/``FixedUpdate``/``LateUpdate`` lifecycle hooks, ``Rigidbody`` physics,
``NavMeshAgent`` navigation, the legacy ``Input`` class with optional new Input
System hooks, ``Canvas``-based UI, ``AudioSource`` 3D audio, and
``ParticleSystem`` effects.
The module exposes :data:`TOOL_REGISTRY` (a list of instantiated tools) and
the convenience function :func:`get_tool` for lookup by name.
"""
from __future__ import annotations
import json
import os
import textwrap
from typing import Any, Dict, List, Optional
# ---------------------------------------------------------------------------
# Base infrastructure
# ---------------------------------------------------------------------------
class BaseUnityTool:
"""Base class for every Unity code-generation tool.
Subclasses set the class attributes ``name``, ``description`` and
``input_schema`` and override :meth:`run` to return generated C# source
code. The ``run`` method receives validated keyword arguments matching
``input_schema`` and must return a string (typically the contents of one
or more ``.cs`` files joined by separators).
"""
name: str = "base_tool"
description: str = "Base Unity tool — override me."
input_schema: Dict[str, Any] = {}
def run(self, **kwargs: Any) -> str:
"""Generate and return C# source code for this tool."""
raise NotImplementedError
def get_schema(self) -> Dict[str, Any]:
"""Return the JSON-schema-style descriptor for this tool."""
return {
"name": self.name,
"description": self.description,
"input_schema": self.input_schema,
}
# -- helpers -----------------------------------------------------------
@staticmethod
def _wrap(code: str) -> str:
"""Dedent a triple-quoted code block for clean emission."""
return textwrap.dedent(code).strip() + "\n"
TOOL_REGISTRY: List[BaseUnityTool] = []
def register_tool(cls: type) -> type:
"""Class decorator that instantiates a tool and adds it to the registry."""
TOOL_REGISTRY.append(cls())
return cls
def get_tool(name: str) -> Optional[BaseUnityTool]:
"""Look up a registered tool by its ``name`` attribute."""
for tool in TOOL_REGISTRY:
if tool.name == name:
return tool
return None
def list_tools() -> List[Dict[str, Any]]:
"""Return schema descriptors for every registered tool."""
return [tool.get_schema() for tool in TOOL_REGISTRY]
# ===========================================================================
# 1. Player controller
# ===========================================================================
@register_tool
class CreatePlayerControllerTool(BaseUnityTool):
"""Generate a complete third-person player controller.
Produces a ``PlayerController.cs`` MonoBehaviour implementing WASD
movement relative to the camera, sprint, crouch, jump, melee attack,
and a camera-aware facing direction. Uses a ``CharacterController``
for collision, gravity integration in ``Update``, and root-motion-free
animation hooks (``IsWalking``, ``IsSprinting``, ``IsGrounded``,
``Speed``) for an Animator.
"""
name = "create_player_controller"
description = (
"Generate a full third-person player controller with WASD, sprint, "
"crouch, jump, melee and animation hooks."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "PlayerController"},
"move_speed": {"type": "number", "default": 5.0},
"sprint_multiplier": {"type": "number", "default": 2.0},
"crouch_multiplier": {"type": "number", "default": 0.5},
"jump_height": {"type": "number", "default": 2.0},
"gravity": {"type": "number", "default": -19.62},
"rotation_speed": {"type": "number", "default": 10.0},
},
}
def run(
self,
class_name: str = "PlayerController",
move_speed: float = 5.0,
sprint_multiplier: float = 2.0,
crouch_multiplier: float = 0.5,
jump_height: float = 2.0,
gravity: float = -19.62,
rotation_speed: float = 10.0,
**_: Any,
) -> str:
return self._wrap(f'''
using System;
using UnityEngine;
namespace OpenWorld.Player
{{
///
/// Third-person character controller. Movement is camera-relative and
/// supports walking, sprinting, crouching, jumping and a simple melee
/// attack. Animation parameters (IsWalking, IsSprinting, IsGrounded,
/// Speed, Attack) are pushed every frame for an Animator to consume.
///
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(Animator))]
public class {class_name} : MonoBehaviour
{{
[Header("Movement")]
[SerializeField] private float moveSpeed = {move_speed}f;
[SerializeField] private float sprintMultiplier = {sprint_multiplier}f;
[SerializeField] private float crouchMultiplier = {crouch_multiplier}f;
[SerializeField] private float rotationSpeed = {rotation_speed}f;
[SerializeField] private float jumpHeight = {jump_height}f;
[SerializeField] private float gravity = {gravity}f;
[Header("Ground Check")]
[SerializeField] private Transform groundCheck;
[SerializeField] private float groundDistance = 0.4f;
[SerializeField] private LayerMask groundMask;
[Header("Camera")]
[SerializeField] private Transform cameraTransform;
[Header("Combat")]
[SerializeField] private float meleeRange = 2.0f;
[SerializeField] private float meleeDamage = 25.0f;
[SerializeField] private float meleeCooldown = 0.8f;
private CharacterController _controller;
private Animator _animator;
private Vector3 _velocity;
private bool _isGrounded;
private bool _isCrouching;
private float _meleeTimer;
private float _turnSmoothVelocity;
private static readonly int IsWalkingHash = Animator.StringToHash("IsWalking");
private static readonly int IsSprintingHash = Animator.StringToHash("IsSprinting");
private static readonly int IsGroundedHash = Animator.StringToHash("IsGrounded");
private static readonly int SpeedHash = Animator.StringToHash("Speed");
private static readonly int AttackHash = Animator.StringToHash("Attack");
private void Awake()
{{
_controller = GetComponent();
_animator = GetComponent();
if (cameraTransform == null && Camera.main != null)
cameraTransform = Camera.main.transform;
}}
private void Update()
{{
GroundCheck();
HandleMovement();
HandleJump();
HandleCrouch();
HandleMelee();
ApplyGravity();
UpdateAnimator();
}}
private void GroundCheck()
{{
if (groundCheck != null)
_isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
else
_isGrounded = _controller.isGrounded;
}}
private void HandleMovement()
{{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 direction = new Vector3(horizontal, 0f, vertical).normalized;
float targetSpeed = moveSpeed;
if (_isCrouching) targetSpeed *= crouchMultiplier;
else if (Input.GetKey(KeyCode.LeftShift)) targetSpeed *= sprintMultiplier;
if (direction.magnitude >= 0.1f)
{{
float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
if (cameraTransform != null)
targetAngle += cameraTransform.eulerAngles.y;
float angle = Mathf.SmoothDampAngle(
transform.eulerAngles.y, targetAngle, ref _turnSmoothVelocity,
1f / rotationSpeed);
transform.rotation = Quaternion.Euler(0f, angle, 0f);
Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
_controller.Move(moveDir.normalized * targetSpeed * Time.deltaTime);
}}
}}
private void HandleJump()
{{
if (Input.GetButtonDown("Jump") && _isGrounded && !_isCrouching)
_velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}}
private void HandleCrouch()
{{
if (Input.GetKeyDown(KeyCode.C) && _isGrounded)
{{
_isCrouching = !_isCrouching;
_controller.height = _isCrouching ? 1.2f : 2.0f;
_controller.center = new Vector3(0f, _controller.height / 2f, 0f);
}}
}}
private void HandleMelee()
{{
_meleeTimer -= Time.deltaTime;
if (Input.GetMouseButtonDown(0) && _meleeTimer <= 0f)
{{
_meleeTimer = meleeCooldown;
_animator.SetTrigger(AttackHash);
if (Physics.Raycast(transform.position + Vector3.up, transform.forward,
out RaycastHit hit, meleeRange))
{{
var dmg = hit.collider.GetComponent();
dmg?.TakeDamage(meleeDamage, transform.position);
}}
}}
}}
private void ApplyGravity()
{{
if (_isGrounded && _velocity.y < 0f)
_velocity.y = -2f;
_velocity.y += gravity * Time.deltaTime;
_controller.Move(_velocity * Time.deltaTime);
}}
private void UpdateAnimator()
{{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
float speed = new Vector2(horizontal, vertical).sqrMagnitude;
_animator.SetBool(IsWalkingHash, speed > 0.01f);
_animator.SetBool(IsSprintingHash, Input.GetKey(KeyCode.LeftShift) && speed > 0.01f);
_animator.SetBool(IsGroundedHash, _isGrounded);
_animator.SetFloat(SpeedHash, speed);
}}
private void OnDrawGizmosSelected()
{{
if (groundCheck != null)
{{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(groundCheck.position, groundDistance);
}}
}}
}}
}}
''')
# ===========================================================================
# 2. Vehicle controller
# ===========================================================================
@register_tool
class CreateVehicleControllerTool(BaseUnityTool):
"""Generate a vehicle controller with arcade-realistic car physics."""
name = "create_vehicle_controller"
description = (
"Generate a full car controller with acceleration/steering curves, "
"brakes, handbrake, gears, engine sound, lights and damage."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "VehicleController"},
"max_torque": {"type": "number", "default": 1500.0},
"max_brake": {"type": "number", "default": 3000.0},
"max_steer": {"type": "number", "default": 30.0},
},
}
def run(
self,
class_name: str = "VehicleController",
max_torque: float = 1500.0,
max_brake: float = 3000.0,
max_steer: float = 30.0,
**_: Any,
) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Vehicles
{{
///
/// Arcade-realistic four-wheel car controller. WheelColliders provide
/// traction; a single Rigidbody carries mass and inertia. Includes gear
/// ratios, engine audio pitch, head/tail/brake/reverse lights and a
/// simple impact-damage model.
///
[RequireComponent(typeof(Rigidbody))]
public class {class_name} : MonoBehaviour
{{
[Header("Wheel Colliders")]
[SerializeField] private WheelCollider frontLeft;
[SerializeField] private WheelCollider frontRight;
[SerializeField] private WheelCollider rearLeft;
[SerializeField] private WheelCollider rearRight;
[Header("Wheel Meshes")]
[SerializeField] private Transform frontLeftMesh;
[SerializeField] private Transform frontRightMesh;
[SerializeField] private Transform rearLeftMesh;
[SerializeField] private Transform rearRightMesh;
[Header("Engine")]
[SerializeField] private float maxMotorTorque = {max_torque}f;
[SerializeField] private float maxBrakeTorque = {max_brake}f;
[SerializeField] private float maxSteerAngle = {max_steer}f;
[SerializeField] private float[] gearRatios = {{ 3.6f, 2.5f, 1.8f, 1.3f, 1.0f, 0.8f }};
[SerializeField] private float reverseRatio = -3.2f;
[SerializeField] private float downforce = 50f;
[Header("Audio")]
[SerializeField] private AudioSource engineAudio;
[SerializeField] private float minPitch = 0.4f;
[SerializeField] private float maxPitch = 2.2f;
[Header("Lights")]
[SerializeField] private Light[] headLights;
[SerializeField] private Light[] brakeLights;
[SerializeField] private Light[] reverseLights;
[SerializeField] private Material brakeMat;
[SerializeField] private Color brakeOn = Color.red;
[SerializeField] private Color brakeOff = new Color(0.3f, 0f, 0f);
[Header("Damage")]
[SerializeField] private float maxHealth = 100f;
[SerializeField] private ParticleSystem smokePrefab;
[SerializeField] private ParticleSystem firePrefab;
private Rigidbody _rb;
private int _currentGear;
private float _currentSpeed;
private float _health;
private bool _braking;
private bool _reversing;
public float SpeedKph => _currentSpeed * 3.6f;
public float HealthPercent => Mathf.Clamp01(_health / maxHealth);
public int CurrentGear => _reversing ? -1 : _currentGear + 1;
private void Awake()
{{
_rb = GetComponent();
_health = maxHealth;
if (engineAudio != null) engineAudio.loop = true;
}}
private void FixedUpdate()
{{
Steer();
Drive();
Handbrake();
UpdateWheels();
ApplyDownforce();
AutoShift();
}}
private void Update()
{{
UpdateLights();
UpdateAudio();
}}
private void Steer()
{{
float steerInput = Input.GetAxis("Horizontal");
float speedFactor = Mathf.Clamp01(_currentSpeed / 30f);
float steer = maxSteerAngle * steerInput * (1f - speedFactor * 0.4f);
frontLeft.steerAngle = steer;
frontRight.steerAngle = steer;
}}
private void Drive()
{{
float throttle = Input.GetAxis("Vertical");
_braking = Mathf.Abs(throttle) < 0.05f;
_reversing = throttle < -0.05f && _currentSpeed < 2f;
float ratio = _reversing ? reverseRatio : gearRatios[_currentGear];
float torque = throttle * maxMotorTorque * Mathf.Abs(ratio);
foreach (var wc in new[] {{ rearLeft, rearRight }})
{{
wc.motorTorque = _braking ? 0f : torque;
wc.brakeTorque = _braking ? maxBrakeTorque * 0.1f : 0f;
}}
foreach (var wc in new[] {{ frontLeft, frontRight }})
wc.brakeTorque = _braking ? maxBrakeTorque * 0.1f : 0f;
_currentSpeed = _rb.velocity.magnitude;
}}
private void Handbrake()
{{
if (Input.GetKey(KeyCode.Space))
{{
rearLeft.brakeTorque = maxBrakeTorque;
rearRight.brakeTorque = maxBrakeTorque;
rearLeft.motorTorque = 0f;
rearRight.motorTorque = 0f;
}}
}}
private void UpdateWheels()
{{
UpdateWheelPose(frontLeft, frontLeftMesh);
UpdateWheelPose(frontRight, frontRightMesh);
UpdateWheelPose(rearLeft, rearLeftMesh);
UpdateWheelPose(rearRight, rearRightMesh);
}}
private static void UpdateWheelPose(WheelCollider collider, Transform mesh)
{{
if (mesh == null) return;
collider.GetWorldPose(out Vector3 pos, out Quaternion rot);
mesh.SetPositionAndRotation(pos, rot);
}}
private void ApplyDownforce()
{{
_rb.AddForce(-transform.up * downforce * _rb.velocity.magnitude);
}}
private void AutoShift()
{{
float speed = SpeedKph;
int target = Mathf.Clamp(Mathf.FloorToInt(speed / 30f), 0, gearRatios.Length - 1);
if (target != _currentGear) _currentGear = target;
}}
private void UpdateLights()
{{
bool brakeOnNow = _braking || Input.GetKey(KeyCode.Space);
if (brakeMat != null)
brakeMat.SetColor("_EmissionColor", brakeOnNow ? brakeOn : brakeOff);
foreach (var l in brakeLights) if (l != null) l.enabled = brakeOnNow;
foreach (var l in reverseLights) if (l != null) l.enabled = _reversing;
}}
private void UpdateAudio()
{{
if (engineAudio == null) return;
float t = Mathf.Clamp01(_currentSpeed / 60f);
float gearFactor = (_currentGear + 1f) / gearRatios.Length;
engineAudio.pitch = Mathf.Lerp(minPitch, maxPitch, t * gearFactor + 0.2f);
}}
private void OnCollisionEnter(Collision collision)
{{
float impact = collision.relativeVelocity.magnitude;
if (impact > 8f)
{{
_health -= impact * 0.5f;
if (_health < maxHealth * 0.3f && smokePrefab != null && !smokePrefab.isPlaying)
smokePrefab.Play();
if (_health <= 0f && firePrefab != null) firePrefab.Play();
}}
}}
}}
}}
''')
# ===========================================================================
# 3. Procedural city
# ===========================================================================
@register_tool
class CreateProceduralCityTool(BaseUnityTool):
"""Generate a procedural grid-city builder with multiple districts."""
name = "create_procedural_city"
description = (
"Generate a procedural grid city with 200+ buildings, districts "
"(downtown, residential, industrial), variable heights and window "
"patterns per district."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "ProceduralCity"},
"grid_size": {"type": "integer", "default": 16},
"block_size": {"type": "number", "default": 40.0},
"road_width": {"type": "number", "default": 12.0},
},
}
def run(
self,
class_name: str = "ProceduralCity",
grid_size: int = 16,
block_size: float = 40.0,
road_width: float = 12.0,
**_: Any,
) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.World
{{
///
/// Procedurally generates a grid-based city. The grid is partitioned
/// into districts by region; each district defines building height
/// ranges, footprint sizes and window texture tints. Roads separate
/// every block.
///
public class {class_name} : MonoBehaviour
{{
[Header("Grid")]
[SerializeField] private int gridSize = {grid_size};
[SerializeField] private float blockSize = {block_size}f;
[SerializeField] private float roadWidth = {road_width}f;
[SerializeField] private int seed = 1337;
[Header("Prefabs")]
[SerializeField] private GameObject roadPrefab;
[SerializeField] private GameObject sidewalkPrefab;
[SerializeField] private Material[] windowMaterials;
[Header("Districts")]
[SerializeField] private District[] districts;
private System.Random _rng;
[Serializable]
public struct District
{{
public string name;
public Vector2 heightRange;
public Vector2 footprintRange;
public Color windowTint;
public float density;
}}
private void Awake()
{{
_rng = new System.Random(seed);
Generate();
}}
public void Generate()
{{
ClearChildren();
for (int x = 0; x < gridSize; x++)
for (int z = 0; z < gridSize; z++)
{{
Vector3 center = new Vector3(
x * (blockSize + roadWidth), 0f, z * (blockSize + roadWidth));
District d = PickDistrict(x, z);
BuildBlock(center, d);
BuildRoads(center);
}}
}}
private District PickDistrict(int x, int z)
{{
// Downtown near centre, residential mid, industrial edges.
float dx = (x - gridSize / 2f) / gridSize;
float dz = (z - gridSize / 2f) / gridSize;
float dist = Mathf.Sqrt(dx * dx + dz * dz);
int idx = dist < 0.25f ? 0 : dist < 0.45f ? 1 : 2;
return districts[Mathf.Clamp(idx, 0, districts.Length - 1)];
}}
private void BuildBlock(Vector3 center, District d)
{{
int count = Mathf.FloorToInt(blockSize / 12f * d.density);
for (int i = 0; i < count; i++)
{{
float fx = (float)_rng.NextDouble();
float fz = (float)_rng.NextDouble();
Vector3 pos = center + new Vector3(
(fx - 0.5f) * (blockSize - 6f), 0f,
(fz - 0.5f) * (blockSize - 6f));
float height = Mathf.Lerp(d.heightRange.x, d.heightRange.y,
(float)_rng.NextDouble());
float footprint = Mathf.Lerp(d.footprintRange.x, d.footprintRange.y,
(float)_rng.NextDouble());
GameObject b = GameObject.CreatePrimitive(PrimitiveType.Cube);
b.transform.SetParent(transform);
b.transform.position = pos + Vector3.up * (height * 0.5f);
b.transform.localScale = new Vector3(footprint, height, footprint);
ApplyWindowMaterial(b, d, height);
}}
}}
private void ApplyWindowMaterial(GameObject building, District d, float height)
{{
Renderer r = building.GetComponent();
if (r == null || windowMaterials == null || windowMaterials.Length == 0) return;
Material m = windowMaterials[_rng.Next(windowMaterials.Length)];
Material inst = new Material(m);
inst.SetColor("_EmissionColor", d.windowTint * 0.5f);
inst.mainTextureScale = new Vector2(
Mathf.Max(1, Mathf.FloorToInt(height / 3f)),
Mathf.Max(1, Mathf.FloorToInt(height / 3f)));
r.sharedMaterial = inst;
}}
private void BuildRoads(Vector3 center)
{{
if (roadPrefab == null) return;
Vector3 horiz = center + Vector3.left * (blockSize + roadWidth) * 0.5f;
Vector3 vert = center + Vector3.back * (blockSize + roadWidth) * 0.5f;
Instantiate(roadPrefab, horiz, Quaternion.Euler(0f, 90f, 0f), transform)
.transform.localScale = new Vector3(blockSize, 1f, roadWidth);
Instantiate(roadPrefab, vert, Quaternion.identity, transform)
.transform.localScale = new Vector3(roadWidth, 1f, blockSize);
}}
private void ClearChildren()
{{
for (int i = transform.childCount - 1; i >= 0; i--)
DestroyImmediate(transform.GetChild(i).gameObject);
}}
}}
}}
''')
# ===========================================================================
# 4. Third-person camera
# ===========================================================================
@register_tool
class CreateThirdPersonCameraTool(BaseUnityTool):
"""Generate a smooth follow third-person camera with collision & zoom."""
name = "create_third_person_camera"
description = (
"Generate a third-person camera with smooth follow, collision, "
"zoom and aim mode."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "ThirdPersonCamera"},
"distance": {"type": "number", "default": 6.0},
"height": {"type": "number", "default": 2.5},
"sensitivity": {"type": "number", "default": 3.0},
},
}
def run(
self,
class_name: str = "ThirdPersonCamera",
distance: float = 6.0,
height: float = 2.5,
sensitivity: float = 3.0,
**_: Any,
) -> str:
return self._wrap(f'''
using UnityEngine;
namespace OpenWorld.Camera
{{
///
/// Smooth third-person follow camera with mouse orbit, scroll-wheel
/// zoom, camera collision via SphereCast and an aim mode that pulls
/// the camera over the shoulder.
///
public class {class_name} : MonoBehaviour
{{
[Header("Target")]
[SerializeField] private Transform target;
[SerializeField] private Vector3 targetOffset = new Vector3(0f, {height}f, 0f);
[Header("Orbit")]
[SerializeField] private float distance = {distance}f;
[SerializeField] private float minDistance = 2f;
[SerializeField] private float maxDistance = 12f;
[SerializeField] private float sensitivity = {sensitivity}f;
[SerializeField] private float pitchMin = -40f;
[SerializeField] private float pitchMax = 80f;
[SerializeField] private float smoothTime = 0.12f;
[Header("Collision")]
[SerializeField] private float collisionRadius = 0.3f;
[SerializeField] private LayerMask collisionMask = ~0;
[Header("Aim Mode")]
[SerializeField] private Vector3 aimOffset = new Vector3(0.7f, 1.4f, 0f);
[SerializeField] private float aimDistance = 2.5f;
private float _yaw;
private float _pitch = 15f;
private float _currentDistance;
private Vector3 _smoothVel;
private void Start()
{{
_currentDistance = distance;
if (target != null) _yaw = target.eulerAngles.y;
Cursor.lockState = CursorLockMode.Locked;
}}
private void LateUpdate()
{{
if (target == null) return;
ReadInput();
UpdateOrbit();
HandleCollision();
transform.position = Vector3.SmoothDamp(
transform.position, DesiredPosition(), ref _smoothVel, smoothTime);
transform.LookAt(target.position + targetOffset);
}}
private void ReadInput()
{{
_yaw += Input.GetAxis("Mouse X") * sensitivity;
_pitch -= Input.GetAxis("Mouse Y") * sensitivity;
_pitch = Mathf.Clamp(_pitch, pitchMin, pitchMax);
float scroll = Input.GetAxis("Mouse ScrollWheel");
distance = Mathf.Clamp(distance - scroll * 4f, minDistance, maxDistance);
}}
private void UpdateOrbit()
{{
if (Input.GetMouseButton(1))
{{
target.rotation = Quaternion.Euler(0f, _yaw, 0f);
}}
}}
private Vector3 DesiredPosition()
{{
bool aim = Input.GetMouseButton(1);
float dist = aim ? aimDistance : _currentDistance;
Vector3 offset = aim ? aimOffset : targetOffset;
Quaternion rot = Quaternion.Euler(_pitch, _yaw, 0f);
return target.position + offset - (rot * Vector3.forward * dist);
}}
private void HandleCollision()
{{
Vector3 origin = target.position + targetOffset;
Vector3 dir = transform.position - origin;
float dist = dir.magnitude;
dir.Normalize();
if (Physics.SphereCast(origin, collisionRadius, dir,
out RaycastHit hit, dist, collisionMask))
_currentDistance = Mathf.Min(distance, hit.distance);
else
_currentDistance = Mathf.Lerp(_currentDistance, distance, 0.1f);
}}
}}
}}
''')
# ===========================================================================
# 5. FPS controller
# ===========================================================================
@register_tool
class CreateFpsControllerTool(BaseUnityTool):
"""Generate an FPS controller with mouse-look, lean and crouch."""
name = "create_fps_controller"
description = (
"Generate an FPS controller with mouse look, WASD, jump, crouch "
"and lean mechanics."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "FpsController"},
"move_speed": {"type": "number", "default": 5.0},
"look_sensitivity": {"type": "number", "default": 2.0},
},
}
def run(
self,
class_name: str = "FpsController",
move_speed: float = 5.0,
look_sensitivity: float = 2.0,
**_: Any,
) -> str:
return self._wrap(f'''
using UnityEngine;
namespace OpenWorld.Player
{{
///
/// First-person controller. Mouse look drives a yaw on the body and
/// pitch on the camera. WASD moves on the horizontal plane, jump uses
/// an impulse, crouch shrinks the capsule, and Q/E lean the camera.
///
[RequireComponent(typeof(CharacterController))]
public class {class_name} : MonoBehaviour
{{
[Header("Movement")]
[SerializeField] private float walkSpeed = {move_speed}f;
[SerializeField] private float sprintSpeed = 8f;
[SerializeField] private float crouchSpeed = 2.5f;
[SerializeField] private float jumpHeight = 1.4f;
[SerializeField] private float gravity = -19.62f;
[Header("Look")]
[SerializeField] private Transform cameraTransform;
[SerializeField] private float lookSensitivity = {look_sensitivity}f;
[SerializeField] private float pitchLimit = 85f;
[Header("Lean")]
[SerializeField] private float leanAngle = 25f;
[SerializeField] private float leanSpeed = 8f;
[SerializeField] private float leanOffset = 0.6f;
[Header("Crouch")]
[SerializeField] private float standHeight = 2f;
[SerializeField] private float crouchHeight = 1.2f;
private CharacterController _cc;
private float _pitch;
private float _targetLean;
private float _currentLean;
private Vector3 _velocity;
private bool _isCrouching;
private void Awake()
{{
_cc = GetComponent();
if (cameraTransform == null && Camera.main != null)
cameraTransform = Camera.main.transform;
Cursor.lockState = CursorLockMode.Locked;
}}
private void Update()
{{
Look();
Move();
Crouch();
Lean();
Jump();
ApplyGravity();
}}
private void Look()
{{
float mx = Input.GetAxis("Mouse X") * lookSensitivity;
float my = Input.GetAxis("Mouse Y") * lookSensitivity;
transform.Rotate(Vector3.up * mx);
_pitch = Mathf.Clamp(_pitch - my, -pitchLimit, pitchLimit);
cameraTransform.localEulerAngles = new Vector3(_pitch, 0f, _currentLean);
}}
private void Move()
{{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
Vector3 move = (transform.right * h + transform.forward * v);
float speed = _isCrouching ? crouchSpeed :
(Input.GetKey(KeyCode.LeftShift) ? sprintSpeed : walkSpeed);
_cc.Move(move.normalized * speed * Time.deltaTime);
}}
private void Crouch()
{{
if (Input.GetKeyDown(KeyCode.LeftControl))
{{
_isCrouching = !_isCrouching;
_cc.height = _isCrouching ? crouchHeight : standHeight;
_cc.center = new Vector3(0f, _cc.height * 0.5f, 0f);
}}
}}
private void Lean()
{{
if (Input.GetKey(KeyCode.Q)) _targetLean = leanAngle;
else if (Input.GetKey(KeyCode.E)) _targetLean = -leanAngle;
else _targetLean = 0f;
_currentLean = Mathf.Lerp(_currentLean, _targetLean, leanSpeed * Time.deltaTime);
Vector3 camPos = cameraTransform.localPosition;
camPos.x = Mathf.Lerp(camPos.x, (_targetLean / leanAngle) * leanOffset,
leanSpeed * Time.deltaTime);
cameraTransform.localPosition = camPos;
}}
private void Jump()
{{
if (Input.GetButtonDown("Jump") && _cc.isGrounded)
_velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}}
private void ApplyGravity()
{{
if (_cc.isGrounded && _velocity.y < 0f) _velocity.y = -2f;
_velocity.y += gravity * Time.deltaTime;
_cc.Move(_velocity * Time.deltaTime);
}}
}}
}}
''')
# ===========================================================================
# 6. Day/night cycle
# ===========================================================================
@register_tool
class CreateDayNightCycleTool(BaseUnityTool):
"""Generate a 24-hour day/night cycle with sky gradients and lighting."""
name = "create_day_night_cycle"
description = (
"Generate a 24-hour sun arc, sky-color gradient, automatic street "
"lights and crowd-density changes."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "DayNightCycle"},
"day_length_minutes": {"type": "number", "default": 24.0},
},
}
def run(
self,
class_name: str = "DayNightCycle",
day_length_minutes: float = 24.0,
**_: Any,
) -> str:
return self._wrap(f'''
using UnityEngine;
namespace OpenWorld.World
{{
///
/// Drives a continuous 24-hour cycle. The directional sun rotates around
/// the world; the ambient/sky color is sampled from a gradient; street
/// lights toggle automatically at dusk/dawn; a callback notifies other
/// systems (e.g. crowd density) of the current hour.
///
public class {class_name} : MonoBehaviour
{{
[Header("Time")]
[SerializeField] private float dayLengthMinutes = {day_length_minutes}f;
[SerializeField] [Range(0f, 24f)] private float startHour = 8f;
public float CurrentHour {{ get; private set; }}
[Header("Sun")]
[SerializeField] private Light sun;
[SerializeField] private float sunIntensityDay = 1.2f;
[SerializeField] private float sunIntensityNight = 0.05f;
[SerializeField] private Color sunColorDay = new Color(1f, 0.96f, 0.86f);
[SerializeField] private Color sunColorSunset = new Color(1f, 0.55f, 0.25f);
[Header("Sky")]
[SerializeField] private Gradient skyColor;
[SerializeField] private Gradient ambientColor;
[SerializeField] private Material skyboxMaterial;
[Header("Lights")]
[SerializeField] private Light[] streetLights;
[SerializeField] private float lightOnHour = 18.5f;
[SerializeField] private float lightOffHour = 6.5f;
public event System.Action OnHourChanged;
private float _hoursPerSecond;
private void Awake()
{{
_hoursPerSecond = 24f / (dayLengthMinutes * 60f);
CurrentHour = startHour;
if (skyColor == null) BuildDefaultGradient();
}}
private void Update()
{{
CurrentHour += _hoursPerSecond * Time.deltaTime;
if (CurrentHour >= 24f) CurrentHour -= 24f;
UpdateSun();
UpdateSky();
UpdateStreetLights();
OnHourChanged?.Invoke(CurrentHour);
}}
private void UpdateSun()
{{
float sunAngle = (CurrentHour / 24f) * 360f - 90f;
if (sun != null)
{{
sun.transform.rotation = Quaternion.Euler(sunAngle, 170f, 0f);
float dayFactor = Mathf.Clamp01(Mathf.Sin((CurrentHour / 24f) * Mathf.PI * 2f - Mathf.PI / 2f) * 0.5f + 0.5f);
sun.intensity = Mathf.Lerp(sunIntensityNight, sunIntensityDay, dayFactor);
sun.color = (CurrentHour < 7f || CurrentHour > 17f) ? sunColorSunset : sunColorDay;
}}
}}
private void UpdateSky()
{{
float t = CurrentHour / 24f;
Color sky = skyColor.Evaluate(t);
Color ambient = ambientColor.Evaluate(t);
RenderSettings.skybox?.SetColor("_Tint", sky);
RenderSettings.ambientLight = ambient;
if (skyboxMaterial != null) skyboxMaterial.SetColor("_Tint", sky);
}}
private void UpdateStreetLights()
{{
bool on = CurrentHour >= lightOnHour || CurrentHour <= lightOffHour;
if (streetLights == null) return;
foreach (var l in streetLights) if (l != null) l.enabled = on;
}}
private void BuildDefaultGradient()
{{
skyColor = new Gradient();
var keys = new GradientColorKey[]
{{
new GradientColorKey(new Color(0.02f, 0.03f, 0.08f), 0.00f), // midnight
new GradientColorKey(new Color(0.5f, 0.3f, 0.4f), 0.22f), // dawn
new GradientColorKey(new Color(0.53f, 0.81f, 0.98f), 0.35f), // morning
new GradientColorKey(new Color(0.53f, 0.81f, 0.98f), 0.65f), // afternoon
new GradientColorKey(new Color(1.0f, 0.45f, 0.20f), 0.78f), // sunset
new GradientColorKey(new Color(0.02f, 0.03f, 0.08f), 1.00f), // night
}};
var alpha = new GradientAlphaKey[] {{ new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) }};
skyColor.SetKeys(keys, alpha);
ambientColor = new Gradient();
var akeys = new GradientColorKey[]
{{
new GradientColorKey(new Color(0.05f, 0.05f, 0.1f), 0f),
new GradientColorKey(new Color(0.4f, 0.4f, 0.5f), 0.3f),
new GradientColorKey(new Color(0.6f, 0.6f, 0.6f), 0.5f),
new GradientColorKey(new Color(0.4f, 0.35f, 0.3f), 0.78f),
new GradientColorKey(new Color(0.05f, 0.05f, 0.1f), 1f),
}};
ambientColor.SetKeys(akeys, alpha);
}}
}}
}}
''')
# ===========================================================================
# 7. AI NPC
# ===========================================================================
@register_tool
class CreateAiNpcTool(BaseUnityTool):
"""Generate an AI NPC with NavMesh wandering, fleeing and chasing."""
name = "create_ai_npc"
description = (
"Generate an AI NPC with NavMesh wandering, fleeing, chasing, "
"ragdoll death and voice lines."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "AiNpc"},
"wander_radius": {"type": "number", "default": 30.0},
"detect_range": {"type": "number", "default": 15.0},
},
}
def run(
self,
class_name: str = "AiNpc",
wander_radius: float = 30.0,
detect_range: float = 15.0,
**_: Any,
) -> str:
return self._wrap(f'''
using System;
using UnityEngine;
using UnityEngine.AI;
namespace OpenWorld.AI
{{
///
/// Generic NPC driven by a NavMeshAgent. Behaviour switches between
/// Wander, Flee and Chase states based on a threat Transform. On death
/// the agent is disabled and a ragdoll is enabled. Periodic voice lines
/// are played from an AudioClip pool.
///
[RequireComponent(typeof(NavMeshAgent))]
public class {class_name} : MonoBehaviour
{{
public enum State {{ Wander, Flee, Chase, Dead }}
[Header("Movement")]
[SerializeField] private float wanderRadius = {wander_radius}f;
[SerializeField] private float detectRange = {detect_range}f;
[SerializeField] private float fleeRange = 25f;
[SerializeField] private float chaseSpeed = 5.5f;
[SerializeField] private float wanderSpeed = 1.6f;
[SerializeField] private float repathInterval = 3f;
[Header("Health")]
[SerializeField] private float maxHealth = 100f;
[Header("Audio")]
[SerializeField] private AudioClip[] voiceLines;
[SerializeField] private float voiceInterval = 12f;
[SerializeField] private AudioSource audioSource;
[Header("Ragdoll")]
[SerializeField] private Rigidbody[] ragdollBodies;
[SerializeField] private Collider[] ragdollColliders;
[SerializeField] private Animator animator;
[Header("Threat")]
[SerializeField] private Transform player;
private NavMeshAgent _agent;
private State _state = State.Wander;
private float _health;
private float _repathTimer;
private float _voiceTimer;
public float Health => _health;
public State CurrentState => _state;
private void Awake()
{{
_agent = GetComponent();
_health = maxHealth;
SetRagdoll(false);
}}
private void Update()
{{
switch (_state)
{{
case State.Wander: Wander(); break;
case State.Flee: Flee(); break;
case State.Chase: Chase(); break;
case State.Dead: return;
}}
UpdateVoice();
}}
private void Wander()
{{
_agent.speed = wanderSpeed;
_repathTimer -= Time.deltaTime;
if (_repathTimer <= 0f || _agent.remainingDistance < 0.5f)
{{
_repathTimer = repathInterval;
_agent.SetDestination(RandomNavPoint(transform.position, wanderRadius));
}}
if (animator != null) animator.SetFloat("Speed", _agent.velocity.magnitude);
if (ThreatDistance() < detectRange && IsThreatHostile())
_state = State.Chase;
}}
private void Flee()
{{
_agent.speed = chaseSpeed;
if (player == null) {{ _state = State.Wander; return; }}
Vector3 away = (transform.position - player.position).normalized * fleeRange;
_agent.SetDestination(transform.position + away);
if (ThreatDistance() > fleeRange) _state = State.Wander;
}}
private void Chase()
{{
if (player == null) {{ _state = State.Wander; return; }}
_agent.speed = chaseSpeed;
_agent.SetDestination(player.position);
if (ThreatDistance() > detectRange * 1.5f) _state = State.Wander;
}}
private float ThreatDistance()
=> player == null ? Mathf.Infinity : Vector3.Distance(transform.position, player.position);
protected virtual bool IsThreatHostile() => true;
private static Vector3 RandomNavPoint(Vector3 origin, float radius)
{{
for (int i = 0; i < 20; i++)
{{
Vector3 rnd = origin + UnityEngine.Random.insideUnitSphere * radius;
rnd.y = origin.y;
if (NavMesh.SamplePosition(rnd, out NavMeshHit hit, radius, NavMesh.AllAreas))
return hit.position;
}}
return origin;
}}
private void UpdateVoice()
{{
if (audioSource == null || voiceLines == null || voiceLines.Length == 0) return;
_voiceTimer -= Time.deltaTime;
if (_voiceTimer <= 0f)
{{
_voiceTimer = voiceInterval;
audioSource.PlayOneShot(voiceLines[UnityEngine.Random.Range(0, voiceLines.Length)]);
}}
}}
public void TakeDamage(float dmg, Vector3 from)
{{
if (_state == State.Dead) return;
_health -= dmg;
if (_health <= 0f) Die();
else _state = State.Flee;
}}
private void Die()
{{
_state = State.Dead;
_agent.isStopped = true;
_agent.enabled = false;
if (animator != null) animator.enabled = false;
SetRagdoll(true);
foreach (var rb in ragdollBodies)
if (rb != null) rb.AddForce((transform.forward + Vector3.up) * 3f, ForceMode.Impulse);
}}
private void SetRagdoll(bool enabled)
{{
foreach (var rb in ragdollBodies) if (rb != null) rb.isKinematic = !enabled;
foreach (var col in ragdollColliders) if (col != null) col.enabled = enabled;
}}
}}
}}
''')
# ===========================================================================
# 8. Pickup system
# ===========================================================================
@register_tool
class CreatePickupSystemTool(BaseUnityTool):
"""Generate a pickup system for weapons, health, money and ammo."""
name = "create_pickup_system"
description = (
"Generate pickups for weapons, health, money and ammo with glow "
"and rotation."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "Pickup"},
},
}
def run(self, class_name: str = "Pickup", **_: Any) -> str:
return self._wrap(f'''
using UnityEngine;
namespace OpenWorld.Items
{{
///
/// Generic pickup. Rotates and bobs in place, emits a glow, and on
/// trigger applies an effect (health, money, ammo or weapon) to the
/// player via the IPickupReceiver interface.
///
public class {class_name} : MonoBehaviour
{{
public enum PickupType {{ Health, Money, Ammo, Weapon, Armor }}
[Header("Config")]
[SerializeField] private PickupType type = PickupType.Health;
[SerializeField] private int amount = 25;
[SerializeField] private string weaponId = "pistol";
[SerializeField] private bool respawn = true;
[SerializeField] private float respawnDelay = 30f;
[Header("Visual")]
[SerializeField] private float rotateSpeed = 90f;
[SerializeField] private float bobHeight = 0.2f;
[SerializeField] private float bobSpeed = 2f;
[SerializeField] private Light glowLight;
[SerializeField] private ParticleSystem glowParticles;
[SerializeField] private AudioClip collectSound;
private Vector3 _startPos;
private AudioSource _audio;
private void Awake()
{{
_startPos = transform.position;
_audio = GetComponent();
if (glowLight != null)
{{
glowLight.color = type switch
{{
PickupType.Health => Color.green,
PickupType.Money => Color.yellow,
PickupType.Ammo => Color.cyan,
PickupType.Weapon => Color.red,
PickupType.Armor => Color.blue,
_ => Color.white,
}};
}}
}}
private void Update()
{{
transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime);
float y = Mathf.Sin(Time.time * bobSpeed) * bobHeight;
transform.position = _startPos + Vector3.up * y;
}}
private void OnTriggerEnter(Collider other)
{{
if (!other.CompareTag("Player")) return;
var receiver = other.GetComponent();
if (receiver == null || !receiver.CanReceive(type)) return;
Apply(receiver);
if (collectSound != null && _audio != null)
_audio.PlayOneShot(collectSound);
if (glowParticles != null) glowParticles.Play();
if (respawn) Invoke(nameof(Reactivate), respawnDelay);
gameObject.SetActive(false);
}}
private void Apply(IPickupReceiver r)
{{
switch (type)
{{
case PickupType.Health: r.AddHealth(amount); break;
case PickupType.Money: r.AddMoney(amount); break;
case PickupType.Ammo: r.AddAmmo(amount); break;
case PickupType.Armor: r.AddArmor(amount); break;
case PickupType.Weapon: r.AddWeapon(weaponId, amount); break;
}}
}}
private void Reactivate()
{{
gameObject.SetActive(true);
if (glowParticles != null) glowParticles.Stop();
}}
}}
public interface IPickupReceiver
{{
bool CanReceive(CreatePickupSystemTool.PickupType type);
void AddHealth(int amount);
void AddMoney(int amount);
void AddAmmo(int amount);
void AddArmor(int amount);
void AddWeapon(string weaponId, int ammo);
}}
}}
''').replace("CreatePickupSystemTool.PickupType", "Pickup.PickupType")
# ===========================================================================
# 9. Health system
# ===========================================================================
@register_tool
class CreateHealthSystemTool(BaseUnityTool):
"""Generate a health system with regen, armor, damage types and respawn."""
name = "create_health_system"
description = (
"Generate a health system with health regen, armor, damage types, "
"death screen and respawn."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "HealthSystem"},
"max_health": {"type": "number", "default": 100.0},
},
}
def run(self, class_name: str = "HealthSystem", max_health: float = 100.0, **_: Any) -> str:
return self._wrap(f'''
using System;
using UnityEngine;
namespace OpenWorld.Player
{{
///
/// Player health & armor manager. Tracks health, armor, damage type
/// resistances, regen-after-delay, death state and respawn flow.
/// Exposes the IDamageable interface used by weapons and NPCs.
///
[DisallowMultipleComponent]
public class {class_name} : MonoBehaviour, IDamageable
{{
public enum DamageType {{ Bullet, Fire, Explosion, Fall, Melee, Drown }}
[Header("Health")]
[SerializeField] private float maxHealth = {max_health}f;
[SerializeField] private float maxArmor = 100f;
[Header("Regen")]
[SerializeField] private float regenDelay = 5f;
[SerializeField] private float regenRate = 8f;
[SerializeField] private float regenThreshold = 30f;
[Header("Resistances")]
[SerializeField] private float armorAbsorb = 0.5f;
[SerializeField] private float fallThreshold = 8f;
[Header("Respawn")]
[SerializeField] private Transform respawnPoint;
[SerializeField] private float respawnDelay = 4f;
[SerializeField] private GameObject deathScreen;
public float Health {{ get; private set; }}
public float Armor {{ get; private set; }}
public bool IsDead {{ get; private set; }}
public event Action OnHealthChanged; // health, armor
public event Action OnDeath;
public event Action OnRespawn;
private float _regenTimer;
private float _respawnTimer;
private void Awake()
{{
Health = maxHealth;
Armor = 0f;
OnHealthChanged?.Invoke(Health, Armor);
}}
private void Update()
{{
if (IsDead)
{{
_respawnTimer -= Time.deltaTime;
if (_respawnTimer <= 0f) Respawn();
return;
}}
TryRegen();
}}
private void TryRegen()
{{
if (Health >= maxHealth) return;
_regenTimer -= Time.deltaTime;
if (_regenTimer > 0f) return;
Health = Mathf.Clamp(Health + regenRate * Time.deltaTime, 0f, maxHealth);
OnHealthChanged?.Invoke(Health, Armor);
}}
public void TakeDamage(float amount, Vector3 source)
{{
TakeDamage(amount, DamageType.Bullet, source);
}}
public void TakeDamage(float amount, DamageType type, Vector3 source)
{{
if (IsDead) return;
float dmg = ApplyResistances(amount, type);
if (Armor > 0f)
{{
float absorbed = Mathf.Min(Armor, dmg * armorAbsorb);
Armor -= absorbed;
dmg -= absorbed;
}}
Health = Mathf.Max(0f, Health - dmg);
_regenTimer = regenDelay;
OnHealthChanged?.Invoke(Health, Armor);
if (Health <= 0f) Die();
}}
protected virtual float ApplyResistances(float amount, DamageType type)
{{
return type switch
{{
DamageType.Fire => amount * 0.7f,
DamageType.Explosion => amount * 1.2f,
DamageType.Melee => amount * 0.8f,
_ => amount,
}};
}}
public void Heal(float amount)
{{
Health = Mathf.Clamp(Health + amount, 0f, maxHealth);
OnHealthChanged?.Invoke(Health, Armor);
}}
public void AddArmor(float amount)
{{
Armor = Mathf.Clamp(Armor + amount, 0f, maxArmor);
OnHealthChanged?.Invoke(Health, Armor);
}}
private void Die()
{{
IsDead = true;
_respawnTimer = respawnDelay;
if (deathScreen != null) deathScreen.SetActive(true);
OnDeath?.Invoke();
}}
private void Respawn()
{{
IsDead = false;
Health = maxHealth;
Armor = 0f;
if (respawnPoint != null) transform.position = respawnPoint.position;
if (deathScreen != null) deathScreen.SetActive(false);
OnHealthChanged?.Invoke(Health, Armor);
OnRespawn?.Invoke();
}}
private void OnCollisionEnter(Collision c)
{{
if (c.relativeVelocity.y < -fallThreshold)
TakeDamage(Mathf.Abs(c.relativeVelocity.y) * 4f, DamageType.Fall, transform.position);
}}
}}
public interface IDamageable
{{
void TakeDamage(float amount, Vector3 source);
}}
}}
''')
# ===========================================================================
# 10. Weapon system
# ===========================================================================
@register_tool
class CreateWeaponSystemTool(BaseUnityTool):
"""Generate a weapon system supporting multiple weapon archetypes."""
name = "create_weapon_system"
description = (
"Generate a weapon system supporting pistol, rifle, shotgun, SMG "
"and rocket launcher with muzzle flash, recoil, spread, reload "
"and ammo."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "WeaponSystem"},
},
}
def run(self, class_name: str = "WeaponSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Combat
{{
///
/// Weapon controller supporting multiple archetypes (Pistol, Rifle,
/// Shotgun, SMG, RocketLauncher). Each weapon defines fire rate,
/// damage, spread, mag size, reload time, recoil curve and audio.
/// Ammo is tracked per-weapon with a shared reserve.
///
public class {class_name} : MonoBehaviour
{{
[Serializable]
public enum WeaponType {{ Pistol, Rifle, Shotgun, SMG, RocketLauncher }}
[Serializable]
public class WeaponData
{{
public string id = "pistol";
public WeaponType type = WeaponType.Pistol;
public float damage = 25f;
public float fireRate = 6f; // shots/sec
public int magSize = 12;
public int reserveAmmo = 96;
public float reloadTime = 1.5f;
public float spread = 1.5f;
public float range = 100f;
public float recoil = 1.2f;
public int pellets = 1;
public AudioClip fireSound;
public AudioClip reloadSound;
public GameObject muzzleFlashPrefab;
public GameObject projectilePrefab; // for rockets
}}
[Header("Setup")]
[SerializeField] private List weapons = new List();
[SerializeField] private Camera cam;
[SerializeField] private Transform muzzlePoint;
[SerializeField] private LayerMask hitMask = ~0;
[SerializeField] private AudioSource audioSource;
private int _currentIndex;
private WeaponData _current;
private int _magAmmo;
private float _nextFireTime;
private float _reloadEndTime;
private bool _reloading;
public WeaponData CurrentWeapon => _current;
public int CurrentMagAmmo => _magAmmo;
public bool IsReloading => _reloading;
public event Action OnWeaponChanged; // mag, reserve, id
private void Awake()
{{
if (cam == null) cam = Camera.main;
if (weapons.Count > 0) SelectWeapon(0);
}}
private void Update()
{{
if (weapons.Count == 0) return;
HandleSwitch();
if (_reloading)
{{
if (Time.time >= _reloadEndTime) FinishReload();
return;
}}
if (Input.GetKeyDown(KeyCode.R)) StartReload();
if (Input.GetMouseButton(0) && Time.time >= _nextFireTime) Fire();
}}
private void HandleSwitch()
{{
if (Input.GetKeyDown(KeyCode.Alpha1)) SelectWeapon(0);
if (Input.GetKeyDown(KeyCode.Alpha2)) SelectWeapon(1);
if (Input.GetKeyDown(KeyCode.Alpha3)) SelectWeapon(2);
if (Input.GetKeyDown(KeyCode.Alpha4)) SelectWeapon(3);
if (Input.GetKeyDown(KeyCode.Alpha5)) SelectWeapon(4);
}}
public void SelectWeapon(int index)
{{
if (index < 0 || index >= weapons.Count) return;
_currentIndex = index;
_current = weapons[index];
_magAmmo = _current.magSize;
_reloading = false;
OnWeaponChanged?.Invoke(_magAmmo, _current.reserveAmmo, _current.id);
}}
private void Fire()
{{
if (_magAmmo <= 0) {{ StartReload(); return; }}
_magAmmo--;
_nextFireTime = Time.time + 1f / _current.fireRate;
PlayFireEffects();
ApplyRecoil();
for (int i = 0; i < _current.pellets; i++) FireRay();
if (_current.type == WeaponType.RocketLauncher) FireProjectile();
OnWeaponChanged?.Invoke(_magAmmo, _current.reserveAmmo, _current.id);
}}
private void FireRay()
{{
Vector3 dir = cam.transform.forward;
dir = Quaternion.Euler(
UnityEngine.Random.Range(-_current.spread, _current.spread),
UnityEngine.Random.Range(-_current.spread, _current.spread),
0f) * dir;
if (Physics.Raycast(cam.transform.position, dir, out RaycastHit hit,
_current.range, hitMask))
{{
var dmg = hit.collider.GetComponent();
dmg?.TakeDamage(_current.damage, hit.point);
SpawnImpact(hit);
}}
}}
private void FireProjectile()
{{
if (_current.projectilePrefab == null || muzzlePoint == null) return;
Instantiate(_current.projectilePrefab, muzzlePoint.position,
Quaternion.LookRotation(cam.transform.forward));
}}
private void PlayFireEffects()
{{
if (audioSource != null && _current.fireSound != null)
audioSource.PlayOneShot(_current.fireSound);
if (_current.muzzleFlashPrefab != null && muzzlePoint != null)
{{
GameObject flash = Instantiate(_current.muzzleFlashPrefab, muzzlePoint);
Destroy(flash, 0.08f);
}}
}}
private void ApplyRecoil()
{{
// Camera recoil handled by camera controller via event.
transform.localPosition -= Vector3.forward * _current.recoil * 0.05f;
}}
private void SpawnImpact(RaycastHit hit)
{{
// Subclass or pool impact effects.
}}
private void StartReload()
{{
if (_reloading || _magAmmo == _current.magSize || _current.reserveAmmo <= 0) return;
_reloading = true;
_reloadEndTime = Time.time + _current.reloadTime;
if (audioSource != null && _current.reloadSound != null)
audioSource.PlayOneShot(_current.reloadSound);
}}
private void FinishReload()
{{
_reloading = false;
int needed = _current.magSize - _magAmmo;
int taken = Mathf.Min(needed, _current.reserveAmmo);
_magAmmo += taken;
_current.reserveAmmo -= taken;
OnWeaponChanged?.Invoke(_magAmmo, _current.reserveAmmo, _current.id);
}}
}}
}}
''')
# ===========================================================================
# 11. Audio manager
# ===========================================================================
@register_tool
class CreateAudioManagerTool(BaseUnityTool):
"""Generate a 3D audio manager with music, SFX and radio stations."""
name = "create_audio_manager"
description = (
"Generate a 3D audio manager with spatial SFX, engine sounds, "
"gunshots, footsteps, radio stations and ambient beds."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "AudioManager"},
},
}
def run(self, class_name: str = "AudioManager", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Audio
{{
///
/// Central audio service. Maintains pools for SFX, a music bus, an
/// ambient bed and multiple radio stations. Spatial 3D sources are
/// created on demand for one-shot SFX; looping engine sounds are
/// tracked per-vehicle.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Header("Buses")]
[SerializeField] private AudioSource sfxPrefab;
[SerializeField] private AudioSource musicSource;
[SerializeField] private AudioSource ambientSource;
[SerializeField] private int sfxPoolSize = 32;
[Header("Radio Stations")]
[SerializeField] private RadioStation[] stations;
[SerializeField] private AudioSource radioSource;
[Serializable]
public class RadioStation
{{
public string name;
public AudioClip[] songs;
[NonSerialized] public int currentIndex;
}}
private readonly List _pool = new List();
private readonly Dictionary _looping = new Dictionary();
private int _currentStation = -1;
private void Awake()
{{
Instance = this;
for (int i = 0; i < sfxPoolSize; i++)
{{
AudioSource src = Instantiate(sfxPrefab, transform);
src.gameObject.SetActive(false);
_pool.Add(src);
}}
}}
public void PlaySfx(AudioClip clip, Vector3 position, float volume = 1f, float pitch = 1f)
{{
AudioSource src = GetFreeSource();
if (src == null) return;
src.transform.position = position;
src.clip = clip;
src.volume = volume;
src.pitch = pitch;
src.spatialBlend = 1f;
src.gameObject.SetActive(true);
src.Play();
}}
public int PlayLooping(AudioClip clip, Vector3 position, float volume = 1f)
{{
AudioSource src = GetFreeSource();
if (src == null) return -1;
src.transform.position = position;
src.clip = clip;
src.volume = volume;
src.loop = true;
src.spatialBlend = 1f;
src.gameObject.SetActive(true);
src.Play();
int id = src.GetInstanceID();
_looping[id] = src;
return id;
}}
public void StopLooping(int id)
{{
if (_looping.TryGetValue(id, out AudioSource src))
{{
src.Stop();
src.loop = false;
src.gameObject.SetActive(false);
_looping.Remove(id);
}}
}}
public void UpdateLoopingPosition(int id, Vector3 pos)
{{
if (_looping.TryGetValue(id, out AudioSource src))
src.transform.position = pos;
}}
public void PlayMusic(AudioClip clip)
{{
if (musicSource == null) return;
musicSource.clip = clip;
musicSource.Play();
}}
public void SetAmbient(AudioClip clip)
{{
if (ambientSource == null) return;
ambientSource.clip = clip;
ambientSource.loop = true;
ambientSource.Play();
}}
public void PlayRadio(int stationIndex)
{{
if (radioSource == null || stations == null || stationIndex < 0 || stationIndex >= stations.Length) return;
_currentStation = stationIndex;
PlayCurrentSong();
}}
public void NextSong()
{{
if (_currentStation < 0) return;
stations[_currentStation].currentIndex =
(stations[_currentStation].currentIndex + 1) % stations[_currentStation].songs.Length;
PlayCurrentSong();
}}
public void StopRadio()
{{
_currentStation = -1;
if (radioSource != null) radioSource.Stop();
}}
private void PlayCurrentSong()
{{
var s = stations[_currentStation];
radioSource.clip = s.songs[s.currentIndex];
radioSource.Play();
}}
private AudioSource GetFreeSource()
{{
foreach (var src in _pool) if (!src.isPlaying) return src;
return null;
}}
private void Update()
{{
if (_currentStation >= 0 && radioSource != null && !radioSource.isPlaying)
NextSong();
}}
}}
}}
''')
# ===========================================================================
# 12. UI manager
# ===========================================================================
@register_tool
class CreateUiManagerTool(BaseUnityTool):
"""Generate a HUD UI manager with health bar, minimap, money, wanted."""
name = "create_ui_manager"
description = (
"Generate a UI manager with health bar, minimap, money counter, "
"wanted level stars, weapon wheel and phone."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "UiManager"},
},
}
def run(self, class_name: str = "UiManager", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace OpenWorld.UI
{{
///
/// Central HUD manager. Subscribes to gameplay events and updates
/// health/armor bars, money counter, wanted-level stars, weapon label
/// and the in-game phone. Weapon wheel is shown on tab hold.
///
public class {class_name} : MonoBehaviour
{{
[Header("Health")]
[SerializeField] private Image healthBar;
[SerializeField] private Image armorBar;
[SerializeField] private Text healthText;
[Header("Money")]
[SerializeField] private Text moneyText;
[Header("Wanted")]
[SerializeField] private Image[] wantedStars;
[SerializeField] private Color starOff = new Color(0.2f, 0.2f, 0.2f, 0.4f);
[SerializeField] private Color starOn = Color.yellow;
[Header("Weapon")]
[SerializeField] private Text weaponNameText;
[SerializeField] private Text ammoText;
[SerializeField] private GameObject weaponWheel;
[SerializeField] private Transform wheelCenter;
[SerializeField] private float wheelRadius = 120f;
[Header("Phone")]
[SerializeField] private GameObject phoneRoot;
[SerializeField] private CanvasGroup phoneCanvas;
[Header("Minimap")]
[SerializeField] private RawImage minimapImage;
private int _money;
private int _wanted;
public int Money
{{
get => _money;
set {{ _money = Mathf.Max(0, value); moneyText.text = "$" + _money.ToString("N0"); }}
}}
public int Wanted
{{
get => _wanted;
set
{{
_wanted = Mathf.Clamp(value, 0, wantedStars.Length);
for (int i = 0; i < wantedStars.Length; i++)
wantedStars[i].color = i < _wanted ? starOn : starOff;
}}
}}
private void Awake()
{{
if (weaponWheel != null) weaponWheel.SetActive(false);
if (phoneRoot != null) phoneRoot.SetActive(false);
}}
private void Update()
{{
HandleWeaponWheel();
HandlePhone();
}}
public void SetHealth(float current, float max)
{{
if (healthBar != null) healthBar.fillAmount = current / max;
if (healthText != null) healthText.text = Mathf.CeilToInt(current) + " / " + Mathf.CeilToInt(max);
}}
public void SetArmor(float current, float max)
{{
if (armorBar != null) armorBar.fillAmount = max > 0 ? current / max : 0f;
}}
public void SetWeapon(string name, int mag, int reserve)
{{
if (weaponNameText != null) weaponNameText.text = name;
if (ammoText != null) ammoText.text = mag + " / " + reserve;
}}
private void HandleWeaponWheel()
{{
if (weaponWheel == null) return;
bool show = Input.GetKey(KeyCode.Tab);
weaponWheel.SetActive(show);
if (show && wheelCenter != null)
{{
Vector2 mouse = Input.mousePosition;
Vector2 center = RectTransformUtility.WorldToScreenPoint(null, wheelCenter.position);
Vector2 dir = (mouse - center).normalized;
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
int slot = Mathf.RoundToInt(((angle + 360f) % 360f) / (360f / 8f)) % 8;
if (Input.GetMouseButtonUp(0)) SelectWheelSlot(slot);
}}
}}
private void SelectWheelSlot(int slot)
{{
// Hook into WeaponSystem.SelectWeapon(slot).
Debug.Log("Selected weapon slot " + slot);
}}
private void HandlePhone()
{{
if (phoneRoot == null) return;
if (Input.GetKeyDown(KeyCode.P))
{{
bool open = !phoneRoot.activeSelf;
phoneRoot.SetActive(open);
if (open) Time.timeScale = 0.3f;
else Time.timeScale = 1f;
}}
}}
}}
}}
''')
# ===========================================================================
# 13. Terrain generator
# ===========================================================================
@register_tool
class CreateTerrainGeneratorTool(BaseUnityTool):
"""Generate a heightmap terrain with roads, rivers and elevation."""
name = "create_terrain_generator"
description = (
"Generate a heightmap terrain generator with roads, rivers and "
"elevation noise."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "TerrainGenerator"},
"size": {"type": "integer", "default": 1024},
"height": {"type": "number", "default": 600.0},
},
}
def run(self, class_name: str = "TerrainGenerator", size: int = 1024, height: float = 600.0, **_: Any) -> str:
return self._wrap(f'''
using UnityEngine;
namespace OpenWorld.World
{{
///
/// Procedural terrain generator. Builds a TerrainData from layered
/// Perlin noise (continental + hills + detail), carves a river spline
/// and stamps a road network onto the heightmap. Splat prototypes are
/// assigned by elevation/slope.
///
public class {class_name} : MonoBehaviour
{{
[Header("Dimensions")]
[SerializeField] private int size = {size};
[SerializeField] private float worldHeight = {height}f;
[SerializeField] private int resolution = 513;
[Header("Noise")]
[SerializeField] private float continentScale = 0.003f;
[SerializeField] private float hillsScale = 0.01f;
[SerializeField] private float detailScale = 0.05f;
[SerializeField] private float continentWeight = 0.7f;
[SerializeField] private float hillsWeight = 0.25f;
[SerializeField] private float detailWeight = 0.05f;
[SerializeField] private int seed = 1;
[Header("River")]
[SerializeField] private AnimationCurve riverFalloff;
[SerializeField] private float riverWidth = 30f;
[SerializeField] private float riverDepth = 12f;
[Header("Textures")]
[SerializeField] private Texture2D grassTexture;
[SerializeField] private Texture2D rockTexture;
[SerializeField] private Texture2D sandTexture;
public Terrain Generated {{ get; private set; }}
public void Generate()
{{
TerrainData data = new TerrainData();
data.heightmapResolution = resolution;
data.size = new Vector3(size, worldHeight, size);
data.baseMapResolution = 1024;
data.SetDetailResolution(1024, 8);
float[,] heights = new float[resolution, resolution];
BuildHeights(heights);
CarveRiver(heights);
data.SetHeights(0, 0, heights);
ApplyTextures(data, heights);
Terrain terrain = Terrain.CreateTerrainGameObject(data);
terrain.transform.SetParent(transform);
Generated = terrain;
}}
private void BuildHeights(float[,] heights)
{{
float offsetX = seed * 137.13f;
float offsetZ = seed * 71.7f;
for (int z = 0; z < resolution; z++)
for (int x = 0; x < resolution; x++)
{{
float c = Mathf.PerlinNoise(x * continentScale + offsetX, z * continentScale + offsetZ);
float h = Mathf.PerlinNoise(x * hillsScale + offsetX, z * hillsScale + offsetZ);
float d = Mathf.PerlinNoise(x * detailScale + offsetX, z * detailScale + offsetZ);
heights[z, x] = c * continentWeight + h * hillsWeight + d * detailWeight;
}}
}}
private void CarveRiver(float[,] heights)
{{
// River meanders diagonally across the map.
for (int z = 0; z < resolution; z++)
{{
float t = z / (float)resolution;
int xc = Mathf.RoundToInt((Mathf.Sin(t * Mathf.PI * 4f) * 0.15f + 0.5f) * resolution);
for (int x = xc - (int)riverWidth; x < xc + (int)riverWidth; x++)
{{
if (x < 0 || x >= resolution) continue;
float dist = Mathf.Abs(x - xc) / riverWidth;
float carve = riverFalloff != null ? riverFalloff.Evaluate(dist) : 1f - dist;
heights[z, x] = Mathf.Max(0f, heights[z, x] - carve * riverDepth / worldHeight);
}}
}}
}}
private void ApplyTextures(TerrainData data, float[,] heights)
{{
if (grassTexture == null) return;
var proto = new[]
{{
new TerrainLayer {{ diffuseTexture = grassTexture, tileSize = new Vector2(8, 8) }},
new TerrainLayer {{ diffuseTexture = rockTexture, tileSize = new Vector2(8, 8) }},
new TerrainLayer {{ diffuseTexture = sandTexture, tileSize = new Vector2(8, 8) }},
}};
data.terrainLayers = proto;
float[,,] map = new float[resolution, resolution, proto.Length];
for (int z = 0; z < resolution; z++)
for (int x = 0; x < resolution; x++)
{{
float h = heights[z, x];
if (h < 0.05f) {{ map[z, x, 2] = 1f; }} // sand near water
else if (h > 0.6f) {{ map[z, x, 1] = 1f; }} // rock up high
else {{ map[z, x, 0] = 1f; }} // grass
}}
data.SetAlphamaps(0, 0, map);
}}
}}
}}
''')
# ===========================================================================
# 14. Water shader
# ===========================================================================
@register_tool
class CreateWaterShaderTool(BaseUnityTool):
"""Generate an animated water surface shader and controller."""
name = "create_water_shader"
description = "Generate an animated water shader with reflections and waves."
input_schema = {
"type": "object",
"properties": {
"shader_name": {"type": "string", "default": "OpenWorld/Water"},
},
}
def run(self, shader_name: str = "OpenWorld/Water", **_: Any) -> str:
shader_code = self._wrap(f'''
Shader "{shader_name}"
{{
Properties
{{
_MainColor ("Main Color", Color) = (0.1, 0.4, 0.7, 0.8)
_DeepColor ("Deep Color", Color) = (0.02, 0.1, 0.25, 1.0)
_WaveSpeed ("Wave Speed", Float) = 1.0
_WaveHeight ("Wave Height", Float) = 0.2
_WaveTiling ("Wave Tiling", Float) = 4.0
_Glossiness ("Smoothness", Range(0,1)) = 0.9
_FresnelPower ("Fresnel Power", Float) = 3.0
}}
SubShader
{{
Tags {{ "RenderType"="Transparent" "Queue"="Transparent" }}
LOD 200
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma surface surf Standard alpha vertex:vert
#pragma target 3.0
struct Input
{{
float2 uv_MainTex;
float3 worldPos;
float3 viewDir;
}};
fixed4 _MainColor;
fixed4 _DeepColor;
float _WaveSpeed;
float _WaveHeight;
float _WaveTiling;
half _Glossiness;
float _FresnelPower;
float Wave(float2 p, float t)
{{
return sin(p.x * _WaveTiling + t * _WaveSpeed) * 0.5 +
sin(p.y * _WaveTiling * 1.3 + t * _WaveSpeed * 0.8) * 0.5;
}}
void vert(inout appdata_full v)
{{
float t = _Time.y;
float w = Wave(v.texcoord.xy * 10.0, t);
v.vertex.y += w * _WaveHeight;
v.normal = normalize(float3(-cos(v.texcoord.x * 10.0 * _WaveTiling + t * _WaveSpeed) * _WaveHeight * _WaveTiling, 1.0, 0.0));
}}
void surf(Input IN, inout SurfaceOutputStandard o)
{{
float t = _Time.y;
float w = Wave(IN.uv_MainTex * 10.0, t);
fixed4 c = lerp(_DeepColor, _MainColor, w * 0.5 + 0.5);
float fres = pow(1.0 - saturate(dot(normalize(IN.viewDir), float3(0,1,0))), _FresnelPower);
o.Albedo = c.rgb;
o.Alpha = c.a + fres * 0.3;
o.Metallic = 0.0;
o.Smoothness = _Glossiness;
o.Emission = fres * 0.2;
}}
ENDCG
}}
FallBack "Transparent/VertexLit"
}}
''')
controller = self._wrap('''
using UnityEngine;
namespace OpenWorld.World
{
///
/// Drives the animated water material — adjusts wave height by weather
/// and toggles reflection probe refresh on demand.
///
[ExecuteAlways]
public class WaterController : MonoBehaviour
{
[SerializeField] private Material waterMaterial;
[SerializeField] private float calmHeight = 0.15f;
[SerializeField] private float stormHeight = 0.6f;
[SerializeField] private float transitionSpeed = 0.5f;
private float _currentHeight = 0.15f;
private float _targetHeight = 0.15f;
public float StormFactor
{
set => _targetHeight = Mathf.Lerp(calmHeight, stormHeight, Mathf.Clamp01(value));
}
private void Update()
{
_currentHeight = Mathf.Lerp(_currentHeight, _targetHeight, transitionSpeed * Time.deltaTime);
if (waterMaterial != null)
waterMaterial.SetFloat("_WaveHeight", _currentHeight);
}
}
}
''')
return f"// === SHADER ===\n{shader_code}\n// === CONTROLLER ===\n{controller}"
# ===========================================================================
# 15. Particle effects
# ===========================================================================
@register_tool
class CreateParticleEffectsTool(BaseUnityTool):
"""Generate a particle effect factory for explosions, blood, smoke."""
name = "create_particle_effects"
description = (
"Generate a particle effect factory: explosions, blood, smoke, "
"sparks, muzzle flash, tire smoke."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "ParticleEffectFactory"},
},
}
def run(self, class_name: str = "ParticleEffectFactory", **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.FX
{{
///
/// Pool-backed factory for one-shot particle effects. Configure prefabs
/// per effect type and call at world position. The
/// factory pools instances to avoid runtime GC.
///
public class {class_name} : MonoBehaviour
{{
public enum EffectType {{ Explosion, Blood, Smoke, Sparks, MuzzleFlash, TireSmoke, Debris, Fire }}
[System.Serializable]
public class EffectEntry
{{
public EffectType type;
public ParticleSystem prefab;
public int prewarmCount = 4;
}}
[SerializeField] private EffectEntry[] entries;
private readonly Dictionary> _pools =
new Dictionary>();
private readonly Dictionary _prefabs =
new Dictionary();
public static {class_name} Instance {{ get; private set; }}
private void Awake()
{{
Instance = this;
foreach (var e in entries)
{{
_prefabs[e.type] = e.prefab;
_pools[e.type] = new Queue();
for (int i = 0; i < e.prewarmCount; i++)
_pools[e.type].Enqueue(CreateInstance(e.type));
}}
}}
public void Spawn(EffectType type, Vector3 pos, Quaternion rot = default)
{{
if (!_pools.TryGetValue(type, out var pool) || pool.Count == 0)
pool.Enqueue(CreateInstance(type));
ParticleSystem ps = pool.Dequeue();
ps.transform.SetPositionAndRotation(pos, rot == default ? Quaternion.identity : rot);
ps.gameObject.SetActive(true);
ps.Play();
StartCoroutine(ReturnAfter(ps, pool, ps.main.duration));
}}
private System.Collections.IEnumerator ReturnAfter(ParticleSystem ps, Queue pool, float delay)
{{
yield return new WaitForSeconds(delay + 0.5f);
ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
ps.gameObject.SetActive(false);
pool.Enqueue(ps);
}}
private ParticleSystem CreateInstance(EffectType type)
{{
if (!_prefabs.TryGetValue(type, out var prefab)) return null;
ParticleSystem ps = Instantiate(prefab, transform);
ps.gameObject.SetActive(false);
return ps;
}}
public void SpawnExplosion(Vector3 pos, float radius)
{{
Spawn(EffectType.Explosion, pos);
Spawn(EffectType.Fire, pos);
Spawn(EffectType.Debris, pos);
Collider[] hits = Physics.OverlapSphere(pos, radius);
foreach (var h in hits)
{{
var dmg = h.GetComponent();
dmg?.TakeDamage(80f * (1f - Vector3.Distance(pos, h.transform.position) / radius), pos);
if (h.TryGetComponent(out var rb))
rb.AddExplosionForce(800f, pos, radius);
}}
}}
}}
}}
''')
# ===========================================================================
# 16. Save system
# ===========================================================================
@register_tool
class CreateSaveSystemTool(BaseUnityTool):
"""Generate a JSON save/load system with player, vehicles and inventory."""
name = "create_save_system"
description = (
"Generate a JSON save/load system storing position, health, money, "
"weapons and vehicles."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "SaveSystem"},
},
}
def run(self, class_name: str = "SaveSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace OpenWorld.Persistence
{{
///
/// JSON-based save/load. Serializes player state, money, weapons,
/// inventory and a list of parked vehicles to Application.persistentDataPath.
/// Slot-based (0-9). Exposes events for UI hooks.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Serializable]
public class WeaponSave
{{
public string id;
public int magAmmo;
public int reserveAmmo;
}}
[Serializable]
public class VehicleSave
{{
public string model;
public float x, y, z, qx, qy, qz, qw;
public float health;
public string color;
}}
[Serializable]
public class GameSave
{{
public int slot;
public string scene;
public float px, py, pz;
public float health, armor;
public int money;
public List weapons = new List();
public List vehicles = new List();
public long timestampTicks;
}}
public event Action OnSaveComplete;
public event Action OnLoadComplete;
private void Awake() {{ Instance = this; }}
public void Save(int slot, GameSave data)
{{
data.slot = slot;
data.timestampTicks = DateTime.UtcNow.Ticks;
string json = JsonUtility.ToJson(data, true);
string path = PathFor(slot);
File.WriteAllText(path, json);
OnSaveComplete?.Invoke(slot);
}}
public GameSave Load(int slot)
{{
string path = PathFor(slot);
if (!File.Exists(path)) return null;
string json = File.ReadAllText(path);
GameSave data = JsonUtility.FromJson(json);
OnLoadComplete?.Invoke(slot);
return data;
}}
public bool HasSave(int slot) => File.Exists(PathFor(slot));
public void Delete(int slot)
{{
string path = PathFor(slot);
if (File.Exists(path)) File.Delete(path);
}}
private static string PathFor(int slot)
=> Path.Combine(Application.persistentDataPath, $"save_{{slot}}.json");
public static Vector3 Vec(float x, float y, float z) => new Vector3(x, y, z);
}}
}}
''')
# ===========================================================================
# 17. Inventory system
# ===========================================================================
@register_tool
class CreateInventorySystemTool(BaseUnityTool):
"""Generate a grid inventory with drag-drop, weight and quick slots."""
name = "create_inventory_system"
description = (
"Generate a grid inventory system with drag-drop, weight, and "
"quick slots."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "InventorySystem"},
"grid_width": {"type": "integer", "default": 8},
"grid_height": {"type": "integer", "default": 6},
},
}
def run(self, class_name: str = "InventorySystem", grid_width: int = 8, grid_height: int = 6, **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Inventory
{{
///
/// Grid-based inventory. Items occupy a rectangular footprint on the
/// grid; weight and stack counts are enforced. Up to 8 quick-slots
/// can be bound for hotkey use. The UI layer reads this data to render
/// drag/drop icons.
///
[Serializable]
public class {class_name} : MonoBehaviour
{{
[Serializable]
public class Item
{{
public string id;
public string displayName;
public int width = 1;
public int height = 1;
public int stack = 1;
public int maxStack = 99;
public float weight = 0.1f;
public Sprite icon;
public bool consumable;
}}
[Header("Grid")]
[SerializeField] private int gridWidth = {grid_width};
[SerializeField] private int gridHeight = {grid_height};
[SerializeField] private float maxWeight = 80f;
[Header("Quick Slots")]
[SerializeField] private int quickSlotCount = 8;
private readonly List- _items = new List
- ();
private readonly Item[] _quickSlots;
public float CurrentWeight {{ get; private set; }}
public IReadOnlyList
- Items => _items;
public event Action OnChanged;
public {class_name}()
{{
_quickSlots = new Item[quickSlotCount];
}}
public bool Add(Item item)
{{
if (CurrentWeight + item.weight > maxWeight) return false;
Item existing = _items.Find(i => i.id == item.id && i.stack < i.maxStack);
if (existing != null && existing.width == item.width && existing.height == item.height)
{{
int can = Mathf.Min(item.stack, existing.maxStack - existing.stack);
existing.stack += can;
item.stack -= can;
}}
if (item.stack > 0)
{{
if (_items.Count >= gridWidth * gridHeight) return false;
_items.Add(item);
}}
CurrentWeight += item.weight * item.stack;
OnChanged?.Invoke();
return true;
}}
public bool Remove(string id, int count = 1)
{{
Item item = _items.Find(i => i.id == id);
if (item == null || item.stack < count) return false;
item.stack -= count;
CurrentWeight -= item.weight * count;
if (item.stack <= 0) _items.Remove(item);
OnChanged?.Invoke();
return true;
}}
public bool AssignQuickSlot(int slot, Item item)
{{
if (slot < 0 || slot >= _quickSlots.Length) return false;
_quickSlots[slot] = item;
OnChanged?.Invoke();
return true;
}}
public Item GetQuickSlot(int slot)
=> (slot >= 0 && slot < _quickSlots.Length) ? _quickSlots[slot] : null;
public void UseQuickSlot(int slot)
{{
Item item = GetQuickSlot(slot);
if (item == null || !item.consumable) return;
Remove(item.id, 1);
}}
public int Count(string id)
{{
int total = 0;
foreach (var i in _items) if (i.id == id) total += i.stack;
return total;
}}
public void Sort()
{{
_items.Sort((a, b) => string.Compare(a.displayName, b.displayName, StringComparison.Ordinal));
OnChanged?.Invoke();
}}
}}
}}
''')
# ===========================================================================
# 18. Quest system
# ===========================================================================
@register_tool
class CreateQuestSystemTool(BaseUnityTool):
"""Generate a quest system with tracking, objectives and rewards."""
name = "create_quest_system"
description = (
"Generate a quest system with mission tracking, objectives, "
"rewards and mission markers."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "QuestSystem"},
},
}
def run(self, class_name: str = "QuestSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Quests
{{
///
/// Quest/mission tracker. Quests contain ordered objectives; each
/// objective has a type (Reach, Kill, Collect, Talk). Completion
/// awards money/XP and triggers the next objective. Mission markers
/// are exposed via events for the minimap.
///
public class {class_name} : MonoBehaviour
{{
public enum ObjectiveType {{ Reach, Kill, Collect, Talk, Escort, Survive }}
[Serializable]
public class Objective
{{
public string id;
public string description;
public ObjectiveType type;
public string targetId;
public int requiredCount = 1;
public int currentCount;
public Vector3 markerPosition;
public bool optional;
[NonSerialized] public bool complete;
}}
[Serializable]
public class Quest
{{
public string id;
public string title;
public string giverId;
public List objectives = new List();
public int moneyReward;
public int xpReward;
public bool accepted;
public bool completed;
public bool failed;
}}
[SerializeField] private List allQuests = new List();
private readonly List _active = new List();
public IReadOnlyList ActiveQuests => _active;
public event Action OnQuestAccepted;
public event Action OnObjectiveCompleted;
public event Action OnQuestCompleted;
public event Action OnMarkerUpdated;
public void Accept(string questId)
{{
Quest q = allQuests.Find(x => x.id == questId);
if (q == null || q.accepted) return;
q.accepted = true;
_active.Add(q);
OnQuestAccepted?.Invoke(q);
UpdateMarker(q);
}}
public void Progress(string objectiveId, int amount = 1, string targetId = null)
{{
foreach (var q in _active)
{{
if (q.completed) continue;
foreach (var o in q.objectives)
{{
if (o.complete || o.id != objectiveId) continue;
if (!string.IsNullOrEmpty(targetId) && o.targetId != targetId) continue;
o.currentCount = Mathf.Min(o.requiredCount, o.currentCount + amount);
if (o.currentCount >= o.requiredCount)
{{
o.complete = true;
OnObjectiveCompleted?.Invoke(q, o);
if (AllObjectivesComplete(q)) CompleteQuest(q);
else UpdateMarker(q);
}}
}}
}}
}}
private bool AllObjectivesComplete(Quest q)
{{
foreach (var o in q.objectives) if (!o.optional && !o.complete) return false;
return true;
}}
private void CompleteQuest(Quest q)
{{
q.completed = true;
_active.Remove(q);
OnQuestCompleted?.Invoke(q);
// Rewards dispatched by EconomySystem/SkillTree listeners.
}}
private void UpdateMarker(Quest q)
{{
foreach (var o in q.objectives)
if (!o.complete) {{ OnMarkerUpdated?.Invoke(o.markerPosition); return; }}
}}
public void FailQuest(string questId)
{{
Quest q = _active.Find(x => x.id == questId);
if (q == null) return;
q.failed = true;
_active.Remove(q);
}}
}}
}}
''')
# ===========================================================================
# 19. Building generator
# ===========================================================================
@register_tool
class CreateBuildingGeneratorTool(BaseUnityTool):
"""Generate a building generator with interiors, stairs and furniture."""
name = "create_building_generator"
description = (
"Generate a building generator with interiors, stairs, elevators, "
"rooms and furniture."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "BuildingGenerator"},
"max_floors": {"type": "integer", "default": 8},
},
}
def run(self, class_name: str = "BuildingGenerator", max_floors: int = 8, **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.World
{{
///
/// Generates an enterable building. Floors are stacked with a central
/// stairwell and an elevator shaft; rooms are subdivided per floor and
/// optionally furnished from a prefab set. Exteriors are windowed.
///
public class {class_name} : MonoBehaviour
{{
[Header("Footprint")]
[SerializeField] private Vector2Int footprint = new Vector2Int(20, 20);
[SerializeField] private float floorHeight = 3.5f;
[SerializeField] private int floors = {max_floors};
[Header("Prefabs")]
[SerializeField] private GameObject floorPrefab;
[SerializeField] private GameObject wallPrefab;
[SerializeField] private GameObject stairPrefab;
[SerializeField] private GameObject elevatorPrefab;
[SerializeField] private GameObject[] furniturePrefabs;
[SerializeField] private Material exteriorMaterial;
[SerializeField] private Material interiorMaterial;
private readonly List _parts = new List();
public void Generate()
{{
Clear();
for (int f = 0; f < floors; f++)
{{
Vector3 basePos = new Vector3(0, f * floorHeight, 0);
BuildFloor(basePos, f);
BuildStairwell(basePos, f);
BuildElevator(basePos, f);
SubdivideRooms(basePos, f);
}}
BuildExterior();
}}
private void BuildFloor(Vector3 basePos, int floor)
{{
GameObject slab = Instantiate(floorPrefab, basePos, Quaternion.identity, transform);
slab.transform.localScale = new Vector3(footprint.x, 0.2f, footprint.y);
_parts.Add(slab);
}}
private void BuildStairwell(Vector3 basePos, int floor)
{{
Vector3 stairPos = basePos + new Vector3(-footprint.x * 0.4f, 0, 0);
GameObject stair = Instantiate(stairPrefab, stairPos, Quaternion.identity, transform);
_parts.Add(stair);
}}
private void BuildElevator(Vector3 basePos, int floor)
{{
Vector3 elevPos = basePos + new Vector3(footprint.x * 0.4f, 0, 0);
GameObject elev = Instantiate(elevatorPrefab, elevPos, Quaternion.identity, transform);
_parts.Add(elev);
}}
private void SubdivideRooms(Vector3 basePos, int floor)
{{
int roomsX = 2, roomsZ = 2;
for (int rx = 0; rx < roomsX; rx++)
for (int rz = 0; rz < roomsZ; rz++)
{{
if (furniturePrefabs == null || furniturePrefabs.Length == 0) continue;
Vector3 roomCenter = basePos + new Vector3(
(rx - 0.5f) * footprint.x * 0.4f, 0, (rz - 0.5f) * footprint.y * 0.4f);
GameObject furn = furniturePrefabs[Random.Range(0, furniturePrefabs.Length)];
Instantiate(furn, roomCenter, Quaternion.Euler(0, Random.Range(0, 360f), 0), transform);
}}
}}
private void BuildExterior()
{{
for (int f = 0; f < floors; f++)
{{
Vector3 basePos = new Vector3(0, f * floorHeight + floorHeight * 0.5f, 0);
GameObject wall = Instantiate(wallPrefab, basePos, Quaternion.identity, transform);
wall.transform.localScale = new Vector3(footprint.x, floorHeight, 0.2f);
if (exteriorMaterial != null) wall.GetComponent().sharedMaterial = exteriorMaterial;
_parts.Add(wall);
}}
}}
private void Clear()
{{
foreach (var p in _parts) if (p != null) DestroyImmediate(p);
_parts.Clear();
}}
}}
}}
''')
# ===========================================================================
# 20. Road network
# ===========================================================================
@register_tool
class CreateRoadNetworkTool(BaseUnityTool):
"""Generate a road network with intersections, traffic lights and lanes."""
name = "create_road_network"
description = (
"Generate a road network with highways, streets, intersections, "
"traffic lights, lane markings and sidewalks."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "RoadNetwork"},
},
}
def run(self, class_name: str = "RoadNetwork", **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.World
{{
///
/// Builds a graph of road nodes and segments, instantiating meshes for
/// asphalt, lane markings, sidewalks and traffic lights at intersections.
/// Lane data is exposed for the traffic system to follow.
///
public class {class_name} : MonoBehaviour
{{
public enum RoadClass {{ Highway, Arterial, Street, Alley }}
[System.Serializable]
public class RoadNode
{{
public Vector3 position;
public bool isIntersection;
public TrafficLight light;
}}
[System.Serializable]
public class RoadSegment
{{
public RoadNode a, b;
public RoadClass roadClass = RoadClass.Street;
public int laneCount = 2;
public float width = 8f;
public LineRenderer markings;
}}
[System.Serializable]
public class TrafficLight
{{
public enum Phase {{ Red, Yellow, Green }}
public Phase phase = Phase.Red;
public float timer;
public float redDuration = 8f;
public float yellowDuration = 2f;
public float greenDuration = 8f;
public Light redLight, yellowLight, greenLight;
}}
[Header("Prefabs")]
[SerializeField] private GameObject asphaltPrefab;
[SerializeField] private GameObject sidewalkPrefab;
[SerializeField] private GameObject trafficLightPrefab;
[SerializeField] private Material markingMaterial;
[Header("Nodes")]
[SerializeField] private List nodes = new List();
[SerializeField] private List segments = new List();
public IReadOnlyList Segments => segments;
public void BuildNetwork()
{{
foreach (var seg in segments) BuildSegment(seg);
foreach (var node in nodes)
if (node.isIntersection) BuildIntersection(node);
}}
private void BuildSegment(RoadSegment seg)
{{
Vector3 dir = seg.b.position - seg.a.position;
float length = dir.magnitude;
GameObject road = Instantiate(asphaltPrefab, seg.a.position + dir * 0.5f,
Quaternion.LookRotation(dir), transform);
road.transform.localScale = new Vector3(seg.width, 1f, length);
BuildMarkings(seg);
BuildSidewalks(seg, dir, length);
}}
private void BuildMarkings(RoadSegment seg)
{{
GameObject marks = new GameObject("Markings");
marks.transform.SetParent(transform);
var lr = marks.AddComponent();
lr.positionCount = 2;
lr.SetPosition(0, seg.a.position + Vector3.up * 0.02f);
lr.SetPosition(1, seg.b.position + Vector3.up * 0.02f);
lr.startWidth = 0.2f;
lr.endWidth = 0.2f;
lr.material = markingMaterial;
lr.numCornerVertices = 4;
lr.numCapVertices = 4;
seg.markings = lr;
}}
private void BuildSidewalks(RoadSegment seg, Vector3 dir, float length)
{{
Vector3 normal = Vector3.Cross(Vector3.up, dir).normalized;
Vector3 offset = normal * (seg.width * 0.5f + 1.5f);
Vector3 center = seg.a.position + dir * 0.5f;
Instantiate(sidewalkPrefab, center + offset, Quaternion.LookRotation(dir), transform)
.transform.localScale = new Vector3(2f, 0.2f, length);
Instantiate(sidewalkPrefab, center - offset, Quaternion.LookRotation(dir), transform)
.transform.localScale = new Vector3(2f, 0.2f, length);
}}
private void BuildIntersection(RoadNode node)
{{
Instantiate(asphaltPrefab, node.position, Quaternion.identity, transform)
.transform.localScale = new Vector3(12f, 1f, 12f);
if (trafficLightPrefab != null)
{{
var tlObj = Instantiate(trafficLightPrefab, node.position + Vector3.up * 4f,
Quaternion.identity, transform);
node.light = tlObj.GetComponent();
}}
}}
private void Update()
{{
foreach (var node in nodes)
if (node.isIntersection && node.light != null)
UpdateTrafficLight(node.light);
}}
private void UpdateTrafficLight(TrafficLight tl)
{{
tl.timer -= Time.deltaTime;
if (tl.timer > 0f) return;
switch (tl.phase)
{{
case TrafficLight.Phase.Red: tl.phase = TrafficLight.Phase.Green; tl.timer = tl.greenDuration; break;
case TrafficLight.Phase.Green: tl.phase = TrafficLight.Phase.Yellow; tl.timer = tl.yellowDuration; break;
case TrafficLight.Phase.Yellow: tl.phase = TrafficLight.Phase.Red; tl.timer = tl.redDuration; break;
}}
if (tl.redLight != null) tl.redLight.enabled = tl.phase == TrafficLight.Phase.Red;
if (tl.yellowLight != null) tl.yellowLight.enabled = tl.phase == TrafficLight.Phase.Yellow;
if (tl.greenLight != null) tl.greenLight.enabled = tl.phase == TrafficLight.Phase.Green;
}}
}}
}}
''')
# ===========================================================================
# 21. Wanted system
# ===========================================================================
@register_tool
class CreateWantedSystemTool(BaseUnityTool):
"""Generate a police wanted system with escalation and roadblocks."""
name = "create_wanted_system"
description = (
"Generate a police wanted system with 1-5 stars, police cars, "
"helicopters, roadblocks and escalation."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "WantedSystem"},
},
}
def run(self, class_name: str = "WantedSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.AI
{{
///
/// Police wanted-level system (1-5 stars). Crimes add heat; heat decays
/// when the player is out of sight. Higher levels spawn additional
/// units: cars at 2+, helicopters at 3+, roadblocks at 4+, SWAT at 5.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Header("Heat")]
[SerializeField] private float maxHeat = 100f;
[SerializeField] private float heatDecayPerSecond = 4f;
[SerializeField] private float[] starThresholds = {{ 0f, 15f, 35f, 55f, 80f }};
[Header("Spawning")]
[SerializeField] private GameObject policeCarPrefab;
[SerializeField] private GameObject helicopterPrefab;
[SerializeField] private GameObject roadblockPrefab;
[SerializeField] private GameObject swatPrefab;
[SerializeField] private Transform player;
[SerializeField] private float spawnRadius = 80f;
[SerializeField] private float spawnInterval = 6f;
[SerializeField] private int maxUnits = 16;
public int Stars {{ get; private set; }}
public float Heat {{ get; private set; }}
public bool Hidden {{ get; set; }}
public event Action OnStarsChanged;
private readonly List _activeUnits = new List();
private float _spawnTimer;
private void Awake() {{ Instance = this; }}
private void Update()
{{
DecayHeat();
UpdateStars();
ManageSpawns();
}}
public void ReportCrime(float heat)
{{
Heat = Mathf.Min(maxHeat, Heat + heat);
Hidden = false;
}}
private void DecayHeat()
{{
if (Hidden && Stars > 0)
Heat = Mathf.Max(0f, Heat - heatDecayPerSecond * Time.deltaTime);
}}
private void UpdateStars()
{{
int newStars = 0;
for (int i = 0; i < starThresholds.Length; i++)
if (Heat >= starThresholds[i]) newStars = i + 1;
if (newStars == Stars) return;
Stars = newStars;
OnStarsChanged?.Invoke(Stars);
}}
private void ManageSpawns()
{{
if (Stars == 0) {{ DespawnAll(); return; }}
_spawnTimer -= Time.deltaTime;
if (_spawnTimer > 0f) return;
_spawnTimer = spawnInterval;
int desired = Mathf.Min(maxUnits, Stars * 2);
while (_activeUnits.Count < desired) SpawnUnit();
}}
private void SpawnUnit()
{{
Vector3 pos = player.position + UnityEngine.Random.insideUnitSphere * spawnRadius;
pos.y = player.position.y;
GameObject prefab = Stars switch
{{
5 => swatPrefab,
4 => roadblockPrefab,
3 => helicopterPrefab,
_ => policeCarPrefab,
}};
if (prefab == null) return;
_activeUnits.Add(Instantiate(prefab, pos, Quaternion.identity));
}}
private void DespawnAll()
{{
foreach (var u in _activeUnits) if (u != null) Destroy(u);
_activeUnits.Clear();
}}
public void UnitDestroyed(GameObject unit)
{{
if (_activeUnits.Remove(unit)) ReportCrime(5f);
}}
}}
}}
''')
# ===========================================================================
# 22. Traffic system
# ===========================================================================
@register_tool
class CreateTrafficSystemTool(BaseUnityTool):
"""Generate AI traffic cars that follow lanes and obey lights."""
name = "create_traffic_system"
description = (
"Generate an AI traffic system: cars on lanes, traffic light "
"obedience, lane following and accidents."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "TrafficSystem"},
},
}
def run(self, class_name: str = "TrafficSystem", **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
namespace OpenWorld.AI
{{
///
/// Spawns AI traffic vehicles that follow NavMesh paths along roads,
/// stop at red lights and react to collisions. Each vehicle is a
/// simplified kinematic car: it accelerates toward a target waypoint
/// and brakes for obstacles ahead via a sphere cast.
///
public class {class_name} : MonoBehaviour
{{
[Header("Spawn")]
[SerializeField] private GameObject[] vehiclePrefabs;
[SerializeField] private Transform[] spawnPoints;
[SerializeField] private int maxVehicles = 30;
[SerializeField] private float spawnInterval = 3f;
[SerializeField] private Transform player;
[Header("Behaviour")]
[SerializeField] private float cruiseSpeed = 12f;
[SerializeField] private float detectionRange = 12f;
[SerializeField] private LayerMask obstacleMask = ~0;
private readonly List _vehicles = new List();
private float _spawnTimer;
private void Update()
{{
_spawnTimer -= Time.deltaTime;
if (_spawnTimer <= 0f)
{{
_spawnTimer = spawnInterval;
if (_vehicles.Count < maxVehicles) SpawnVehicle();
}}
for (int i = _vehicles.Count - 1; i >= 0; i--)
{{
_vehicles[i].Tick();
if (_vehicles[i].Expired) {{ Destroy(_vehicles[i].gameObject); _vehicles.RemoveAt(i); }}
}}
}}
private void SpawnVehicle()
{{
if (vehiclePrefabs == null || vehiclePrefabs.Length == 0 || spawnPoints.Length == 0) return;
Transform sp = spawnPoints[Random.Range(0, spawnPoints.Length)];
GameObject go = Instantiate(vehiclePrefabs[Random.Range(0, vehiclePrefabs.Length)],
sp.position, sp.rotation);
var v = go.AddComponent();
v.Initialize(this, cruiseSpeed, detectionRange, obstacleMask, player);
_vehicles.Add(v);
}}
public bool IsRedLightAhead(Vector3 pos, Vector3 forward)
{{
if (Physics.SphereCast(pos, 1f, forward, out RaycastHit hit, 6f, obstacleMask))
return hit.collider.CompareTag("TrafficLight_Red");
return false;
}}
}}
public class TrafficVehicle : MonoBehaviour
{{
private TrafficSystem _system;
private float _speed;
private float _cruise;
private float _range;
private LayerMask _mask;
private Transform _player;
private NavMeshAgent _agent;
private Vector3 _target;
private float _life = 120f;
public bool Expired {{ get; private set; }}
public void Initialize(TrafficSystem sys, float cruise, float range, LayerMask mask, Transform player)
{{
_system = sys; _cruise = cruise; _range = range; _mask = mask; _player = player;
_agent = gameObject.AddComponent();
_agent.speed = cruise;
_agent.acceleration = 8f;
_agent.angularSpeed = 180f;
PickNewDestination();
}}
private void PickNewDestination()
{{
Vector3 rnd = transform.position + Random.insideUnitSphere * 60f;
rnd.y = transform.position.y;
_agent.SetDestination(rnd);
_target = rnd;
}}
public void Tick()
{{
_life -= Time.deltaTime;
if (_life <= 0f || (_player != null && Vector3.Distance(transform.position, _player.position) > 200f))
Expired = true;
if (_agent.remainingDistance < 4f) PickNewDestination();
if (ObstacleAhead() || _system.IsRedLightAhead(transform.position, transform.forward))
_speed = Mathf.Lerp(_speed, 0f, 4f * Time.deltaTime);
else
_speed = Mathf.Lerp(_speed, _cruise, 2f * Time.deltaTime);
_agent.speed = _speed;
}}
private bool ObstacleAhead()
{{
return Physics.SphereCast(transform.position + Vector3.up, 1.2f, transform.forward,
out _, _range, _mask);
}}
private void OnCollisionEnter(Collision c)
{{
if (c.relativeVelocity.magnitude > 8f) Expired = true;
}}
}}
}}
''')
# ===========================================================================
# 23. Pedestrian system
# ===========================================================================
@register_tool
class CreatePedestrianSystemTool(BaseUnityTool):
"""Generate walking pedestrians, crowds and panic behaviour."""
name = "create_pedestrian_system"
description = (
"Generate a pedestrian system: walking NPCs, crowds, panic, "
"conversations and reactions to the player."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "PedestrianSystem"},
},
}
def run(self, class_name: str = "PedestrianSystem", **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
namespace OpenWorld.AI
{{
///
/// Spawns and manages pedestrians in a radius around the player. Each
/// pedestrian wanders, occasionally forms a conversation cluster with
/// another, and flees on hearing gunshots/explosions. Crowd density is
/// driven by the day/night hour.
///
public class {class_name} : MonoBehaviour
{{
[Header("Spawn")]
[SerializeField] private GameObject[] pedestrianPrefabs;
[SerializeField] private Transform player;
[SerializeField] private float spawnRadius = 60f;
[SerializeField] private float despawnRadius = 100f;
[SerializeField] private int maxPedestrians = 80;
[SerializeField] private float spawnInterval = 0.5f;
[Header("Density")]
[SerializeField] private AnimationCurve densityByHour = AnimationCurve.EaseInOut(0, 0.2f, 24, 0.2f);
private readonly List _peds = new List();
private float _spawnTimer;
public void SetHour(float hour)
{{
float density = densityByHour.Evaluate(hour);
maxPedestrians = Mathf.RoundToInt(80 * density);
}}
public void TriggerPanic(Vector3 source, float radius)
{{
foreach (var p in _peds)
{{
if (p == null) continue;
if (Vector3.Distance(p.transform.position, source) < radius)
p.GetComponent()?.Panic(source);
}}
}}
private void Update()
{{
_spawnTimer -= Time.deltaTime;
if (_spawnTimer <= 0f)
{{
_spawnTimer = spawnInterval;
if (_peds.Count < maxPedestrians) SpawnPed();
}}
for (int i = _peds.Count - 1; i >= 0; i--)
{{
if (_peds[i] == null) {{ _peds.RemoveAt(i); continue; }}
float dist = Vector3.Distance(_peds[i].transform.position, player.position);
if (dist > despawnRadius) {{ Destroy(_peds[i]); _peds.RemoveAt(i); }}
}}
}}
private void SpawnPed()
{{
if (pedestrianPrefabs == null || pedestrianPrefabs.Length == 0) return;
Vector3 pos = player.position + Random.insideUnitSphere * spawnRadius;
pos.y = player.position.y;
if (!NavMesh.SamplePosition(pos, out NavMeshHit hit, spawnRadius, NavMesh.AllAreas)) return;
GameObject go = Instantiate(pedestrianPrefabs[Random.Range(0, pedestrianPrefabs.Length)],
hit.position, Quaternion.identity);
var ped = go.GetComponent();
if (ped == null) ped = go.AddComponent();
ped.Initialize(player);
_peds.Add(go);
}}
}}
public class Pedestrian : MonoBehaviour
{{
private NavMeshAgent _agent;
private Transform _player;
private float _repath;
private bool _panicking;
public void Initialize(Transform player) {{ _player = player; _agent = GetComponent(); }}
private void Update()
{{
if (_agent == null) return;
_repath -= Time.deltaTime;
if (_repath > 0f) return;
_repath = Random.Range(2f, 5f);
if (_panicking) Flee();
else Wander();
if (Vector3.Distance(transform.position, _player.position) < 4f && Random.value < 0.2f)
ReactToPlayer();
}}
private void Wander()
{{
_agent.speed = 1.4f;
Vector3 rnd = transform.position + Random.insideUnitSphere * 20f;
rnd.y = transform.position.y;
_agent.SetDestination(rnd);
}}
private void Flee()
{{
_agent.speed = 5f;
Vector3 away = (transform.position - _player.position).normalized * 30f;
_agent.SetDestination(transform.position + away);
}}
public void Panic(Vector3 source)
{{
_panicking = true;
Invoke(nameof(Calm), 8f);
}}
private void Calm() => _panicking = false;
private void ReactToPlayer()
{{
// Look at player, play idle anim — handled by Animator hooks.
transform.rotation = Quaternion.LookRotation((_player.position - transform.position).normalized);
}}
}}
}}
''')
# ===========================================================================
# 24. Economy system
# ===========================================================================
@register_tool
class CreateEconomySystemTool(BaseUnityTool):
"""Generate an economy system with jobs, stores, bank and laundering."""
name = "create_economy_system"
description = (
"Generate an economy system with jobs, stores, purchases, bank and "
"money laundering."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "EconomySystem"},
},
}
def run(self, class_name: str = "EconomySystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Economy
{{
///
/// Central money & market service. Tracks cash and bank balance,
/// stores with item prices, jobs that pay out on completion, and a
/// laundering flow that converts dirty money (from crimes) to clean
/// cash at a fee over time.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Serializable]
public class StoreItem
{{
public string id;
public string displayName;
public int price;
public bool legal = true;
}}
[Serializable]
public class Store
{{
public string id;
public string name;
public List items = new List();
}}
[Serializable]
public class Job
{{
public string id;
public string title;
public int basePay;
public float cooldown;
}}
[Header("Balances")]
[SerializeField] private int cash = 500;
[SerializeField] private int bank = 0;
[SerializeField] private int dirtyMoney = 0;
[SerializeField] private float launderFee = 0.3f;
[SerializeField] private float launderRatePerSecond = 50f;
[Header("Catalog")]
[SerializeField] private List stores = new List();
[SerializeField] private List jobs = new List();
public int Cash => cash;
public int Bank => bank;
public int Dirty => dirtyMoney;
public event Action OnBalanceChanged; // cash, bank, dirty
private void Awake() {{ Instance = this; }}
public void AddCash(int amount) {{ cash += amount; OnBalanceChanged?.Invoke(cash, bank, dirtyMoney); }}
public void AddDirty(int amount) {{ dirtyMoney += amount; OnBalanceChanged?.Invoke(cash, bank, dirtyMoney); }}
public bool Purchase(string storeId, string itemId)
{{
Store s = stores.Find(x => x.id == storeId);
if (s == null) return false;
StoreItem item = s.items.Find(i => i.id == itemId);
if (item == null || cash < item.price) return false;
cash -= item.price;
OnBalanceChanged?.Invoke(cash, bank, dirtyMoney);
return true;
}}
public void Deposit(int amount)
{{
amount = Mathf.Min(amount, cash);
cash -= amount;
bank += amount;
OnBalanceChanged?.Invoke(cash, bank, dirtyMoney);
}}
public void Withdraw(int amount)
{{
amount = Mathf.Min(amount, bank);
bank -= amount;
cash += amount;
OnBalanceChanged?.Invoke(cash, bank, dirtyMoney);
}}
public void Work(string jobId)
{{
Job j = jobs.Find(x => x.id == jobId);
if (j == null) return;
AddCash(j.basePay);
}}
public void StartLaundering() => InvokeRepeating(nameof(LaunderTick), 1f, 1f);
public void StopLaundering() => CancelInvoke(nameof(LaunderTick));
private void LaunderTick()
{{
if (dirtyMoney <= 0) return;
int amount = Mathf.Min(dirtyMoney, Mathf.RoundToInt(launderRatePerSecond));
dirtyMoney -= amount;
cash += Mathf.RoundToInt(amount * (1f - launderFee));
OnBalanceChanged?.Invoke(cash, bank, dirtyMoney);
}}
}}
}}
''')
# ===========================================================================
# 25. Phone system
# ===========================================================================
@register_tool
class CreatePhoneSystemTool(BaseUnityTool):
"""Generate an in-game phone with apps (map, contacts, messages, etc.)."""
name = "create_phone_system"
description = (
"Generate an in-game phone with apps: map, contacts, messages, "
"internet, camera."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "PhoneSystem"},
},
}
def run(self, class_name: str = "PhoneSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace OpenWorld.UI
{{
///
/// In-game smartphone. Toggle with P; navigate apps with on-screen
/// buttons. Apps: Home, Map, Contacts, Messages, Internet, Camera.
/// Each app is a panel toggled on/off. Phone open slows game time.
///
public class {class_name} : MonoBehaviour
{{
public enum App {{ Home, Map, Contacts, Messages, Internet, Camera }}
[Header("Root")]
[SerializeField] private GameObject phoneRoot;
[SerializeField] private CanvasGroup canvasGroup;
[SerializeField] private Button[] appButtons;
[SerializeField] private GameObject[] appPanels;
[SerializeField] private Button homeButton;
[SerializeField] private Button closeButton;
[Header("Camera App")]
[SerializeField] private Camera photoCamera;
[SerializeField] private RawImage photoPreview;
private bool _open;
private App _current = App.Home;
public bool IsOpen => _open;
public event Action OnAppOpened;
private void Awake()
{{
for (int i = 0; i < appButtons.Length; i++)
{{
int idx = i;
if (appButtons[i] != null)
appButtons[i].onClick.AddListener(() => OpenApp((App)idx));
}}
if (homeButton != null) homeButton.onClick.AddListener(() => OpenApp(App.Home));
if (closeButton != null) closeButton.onClick.AddListener(Close);
Close();
}}
private void Update()
{{
if (Input.GetKeyDown(KeyCode.P)) Toggle();
}}
public void Toggle() {{ if (_open) Close(); else Open(); }}
public void Open()
{{
_open = true;
if (phoneRoot != null) phoneRoot.SetActive(true);
Time.timeScale = 0.3f;
OpenApp(App.Home);
}}
public void Close()
{{
_open = false;
if (phoneRoot != null) phoneRoot.SetActive(false);
Time.timeScale = 1f;
}}
public void OpenApp(App app)
{{
_current = app;
for (int i = 0; i < appPanels.Length; i++)
if (appPanels[i] != null) appPanels[i].SetActive(i == (int)app);
if (app == App.Camera) ActivateCamera();
OnAppOpened?.Invoke(app);
}}
private void ActivateCamera()
{{
if (photoCamera == null) return;
photoCamera.gameObject.SetActive(true);
if (Input.GetKeyDown(KeyCode.F) && photoPreview != null)
{{
StartCoroutine(CapturePhoto());
}}
}}
private System.Collections.IEnumerator CapturePhoto()
{{
yield return new WaitForEndOfFrame();
RenderTexture rt = photoCamera.targetTexture;
if (rt == null) yield break;
Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
RenderTexture.active = rt;
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
tex.Apply();
photoPreview.texture = tex;
}}
}}
}}
''')
# ===========================================================================
# 26. Radio system
# ===========================================================================
@register_tool
class CreateRadioSystemTool(BaseUnityTool):
"""Generate a radio system with stations and switching."""
name = "create_radio_system"
description = (
"Generate a radio system with multiple stations, song lists and "
"station switching."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "RadioSystem"},
},
}
def run(self, class_name: str = "RadioSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Audio
{{
///
/// In-vehicle radio. Holds a list of stations each with a song queue.
/// Player can switch station (Q/E), skip song (N) and toggle power.
/// Audio is routed through a single AudioSource whose clip is swapped
/// on track change.
///
public class {class_name} : MonoBehaviour
{{
[Serializable]
public class Station
{{
public string name;
public string dj;
public AudioClip[] songs;
[NonSerialized] public int index;
}}
[Header("Source")]
[SerializeField] private AudioSource source;
[SerializeField] private List stations = new List();
[SerializeField] private int currentIndex;
[SerializeField] private bool powered = true;
[SerializeField] private float volume = 0.7f;
public Station Current => (currentIndex >= 0 && currentIndex < stations.Count) ? stations[currentIndex] : null;
public int StationCount => stations.Count;
public event Action OnStationChanged;
public event Action OnSongChanged;
private void Awake()
{{
if (source == null) source = gameObject.AddComponent();
source.loop = false;
source.volume = volume;
source.spatialBlend = 0f;
if (stations.Count > 0) PlayCurrent();
}}
private void Update()
{{
if (!powered || source == null) return;
if (!source.isPlaying) NextSong();
if (Input.GetKeyDown(KeyCode.Q)) PreviousStation();
if (Input.GetKeyDown(KeyCode.E)) NextStation();
if (Input.GetKeyDown(KeyCode.N)) NextSong();
if (Input.GetKeyDown(KeyCode.M)) TogglePower();
}}
public void NextStation()
{{
if (stations.Count == 0) return;
currentIndex = (currentIndex + 1) % stations.Count;
PlayCurrent();
}}
public void PreviousStation()
{{
if (stations.Count == 0) return;
currentIndex = (currentIndex - 1 + stations.Count) % stations.Count;
PlayCurrent();
}}
public void NextSong()
{{
var s = Current;
if (s == null || s.songs.Length == 0) return;
s.index = (s.index + 1) % s.songs.Length;
source.clip = s.songs[s.index];
source.Play();
OnSongChanged?.Invoke(s, s.songs[s.index]);
}}
private void PlayCurrent()
{{
var s = Current;
if (s == null) return;
source.clip = s.songs[s.index];
source.Play();
OnStationChanged?.Invoke(s);
}}
public void TogglePower()
{{
powered = !powered;
if (source != null) {{ if (powered) source.Play(); else source.Stop(); }}
}}
public void SetVolume(float v) {{ volume = Mathf.Clamp01(v); if (source != null) source.volume = volume; }}
}}
}}
''')
# ===========================================================================
# 27. Weapon wheel
# ===========================================================================
@register_tool
class CreateWeaponWheelTool(BaseUnityTool):
"""Generate a radial weapon wheel with slow-mo selection."""
name = "create_weapon_wheel"
description = (
"Generate a radial weapon wheel selector with slow-motion during "
"selection."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "WeaponWheel"},
},
}
def run(self, class_name: str = "WeaponWheel", **_: Any) -> str:
return self._wrap(f'''
using System;
using UnityEngine;
using UnityEngine.UI;
namespace OpenWorld.UI
{{
///
/// Radial weapon selector. While held (Tab/Q), slows time to 0.2x,
/// shows 8 weapon slots arranged in a circle, highlights the one
/// under the mouse, and on release equips it via a callback.
///
public class {class_name} : MonoBehaviour
{{
[Serializable]
public class WheelSlot
{{
public string id;
public Sprite icon;
public Color tint = Color.white;
}}
[Header("Visual")]
[SerializeField] private GameObject root;
[SerializeField] private Image[] slotIcons;
[SerializeField] private Image highlight;
[SerializeField] private Transform wheelCenter;
[SerializeField] private float radius = 120f;
[Header("Slow-Mo")]
[SerializeField] private float slowScale = 0.2f;
[SerializeField] private float transitionSpeed = 8f;
[SerializeField] private KeyCode openKey = KeyCode.Tab;
[Header("Data")]
[SerializeField] private WheelSlot[] slots = new WheelSlot[8];
private bool _open;
private int _hovered = -1;
private float _targetScale = 1f;
public event Action OnSlotSelected;
private void Awake()
{{
if (root != null) root.SetActive(false);
RenderSlots();
}}
private void Update()
{{
HandleOpen();
if (!_open) return;
HandleHover();
Time.timeScale = Mathf.Lerp(Time.timeScale, _targetScale, transitionSpeed * Time.unscaledDeltaTime);
if (Input.GetKeyUp(openKey)) Confirm();
}}
private void HandleOpen()
{{
bool held = Input.GetKey(openKey);
if (held && !_open) {{ _open = true; _targetScale = slowScale; if (root != null) root.SetActive(true); }}
if (!held && _open && Time.timeScale < 0.5f) {{ _targetScale = 1f; }}
}}
private void HandleHover()
{{
if (wheelCenter == null) return;
Vector2 mouse = Input.mousePosition;
Vector2 center = RectTransformUtility.WorldToScreenPoint(null, wheelCenter.position);
Vector2 dir = mouse - center;
if (dir.magnitude > radius * 1.5f) {{ _hovered = -1; return; }}
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
if (angle < 0f) angle += 360f;
_hovered = Mathf.RoundToInt(angle / (360f / slots.Length)) % slots.Length;
if (highlight != null && _hovered >= 0 && slotIcons[_hovered] != null)
highlight.transform.position = slotIcons[_hovered].transform.position;
}}
private void Confirm()
{{
_open = false;
_targetScale = 1f;
if (root != null) root.SetActive(false);
if (_hovered >= 0) OnSlotSelected?.Invoke(_hovered);
}}
private void RenderSlots()
{{
for (int i = 0; i < slotIcons.Length && i < slots.Length; i++)
{{
float angle = i * (360f / slots.Length) * Mathf.Deg2Rad;
Vector2 pos = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
if (slotIcons[i] != null)
{{
slotIcons[i].rectTransform.anchoredPosition = pos;
slotIcons[i].sprite = slots[i].icon;
slotIcons[i].color = slots[i].tint;
}}
}}
}}
public void SetSlot(int index, WheelSlot slot)
{{
if (index < 0 || index >= slots.Length) return;
slots[index] = slot;
RenderSlots();
}}
}}
}}
''')
# ===========================================================================
# 28. Minimap system
# ===========================================================================
@register_tool
class CreateMinimapSystemTool(BaseUnityTool):
"""Generate a rotating minimap with player, NPCs, missions and police."""
name = "create_minimap_system"
description = (
"Generate a rotating minimap with player, NPCs, missions and police "
"markers."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "MinimapSystem"},
},
}
def run(self, class_name: str = "MinimapSystem", **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace OpenWorld.UI
{{
///
/// Top-down minimap. A dedicated orthographic camera follows the
/// player and rotates with heading. Markers (NPC, police, mission,
/// vehicle) are world-space sprites projected into screen space and
/// clamped to the minimap's circular mask.
///
public class {class_name} : MonoBehaviour
{{
public enum MarkerType {{ Player, Npc, Police, Mission, Vehicle, Pickup }}
[System.Serializable]
public class Marker
{{
public Transform target;
public MarkerType type;
public Sprite icon;
}}
[Header("Camera")]
[SerializeField] private Camera minimapCamera;
[SerializeField] private Transform player;
[SerializeField] private float cameraHeight = 100f;
[SerializeField] private float zoom = 60f;
[Header("UI")]
[SerializeField] private RectTransform minimapRect;
[SerializeField] private RectTransform markerContainer;
[SerializeField] private GameObject markerPrefab;
[SerializeField] private float mapRadius = 90f;
[Header("Markers")]
[SerializeField] private List markers = new List();
[SerializeField] private Sprite policeIcon, missionIcon, npcIcon, vehicleIcon, pickupIcon;
private readonly Dictionary _instances = new Dictionary();
private void LateUpdate()
{{
if (player == null || minimapCamera == null) return;
FollowPlayer();
UpdateMarkers();
}}
private void FollowPlayer()
{{
Vector3 pos = player.position;
minimapCamera.transform.position = new Vector3(pos.x, cameraHeight, pos.z);
minimapCamera.transform.rotation = Quaternion.Euler(90f, -player.eulerAngles.y, 0f);
minimapCamera.orthographicSize = zoom;
}}
private void UpdateMarkers()
{{
foreach (var kvp in _instances)
if (kvp.Value != null) kvp.Value.SetActive(false);
foreach (var m in markers)
{{
if (m.target == null) continue;
GameObject go = GetOrCreate(m);
Vector3 local = minimapCamera.transform.InverseTransformPoint(m.target.position);
Vector2 uiPos = new Vector2(local.x, local.z) / (zoom * 2f) * mapRadius * 2f;
if (uiPos.magnitude > mapRadius) uiPos = uiPos.normalized * mapRadius;
go.GetComponent().anchoredPosition = uiPos;
go.SetActive(true);
}}
}}
private GameObject GetOrCreate(Marker m)
{{
if (_instances.TryGetValue(m.target, out GameObject go) && go != null) return go;
go = Instantiate(markerPrefab, markerContainer);
var img = go.GetComponent();
if (img != null)
img.sprite = m.icon ?? m.type switch
{{
MarkerType.Police => policeIcon,
MarkerType.Mission => missionIcon,
MarkerType.Npc => npcIcon,
MarkerType.Vehicle => vehicleIcon,
MarkerType.Pickup => pickupIcon,
_ => null,
}};
_instances[m.target] = go;
return go;
}}
public void RegisterMarker(Marker m) {{ if (!markers.Contains(m)) markers.Add(m); }}
public void UnregisterMarker(Transform t)
{{
markers.RemoveAll(m => m.target == t);
if (_instances.TryGetValue(t, out GameObject go) && go != null) Destroy(go);
_instances.Remove(t);
}}
}}
}}
''')
# ===========================================================================
# 29. Explosion system
# ===========================================================================
@register_tool
class CreateExplosionSystemTool(BaseUnityTool):
"""Generate an explosion system with chain reactions and debris."""
name = "create_explosion_system"
description = (
"Generate an explosion system with chain reactions, vehicle "
"explosions, building damage and debris."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "ExplosionSystem"},
},
}
def run(self, class_name: str = "ExplosionSystem", **_: Any) -> str:
return self._wrap(f'''
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.FX
{{
///
/// Manages explosion events. Applies radial damage & physics impulse,
/// spawns fire/smoke/debris, and propagates to nearby explosive
/// barrels/vehicles for chain reactions.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Header("Damage")]
[SerializeField] private float baseDamage = 100f;
[SerializeField] private float baseRadius = 10f;
[SerializeField] private float baseForce = 1200f;
[Header("Effects")]
[SerializeField] private GameObject explosionVfx;
[SerializeField] private GameObject fireVfx;
[SerializeField] private GameObject smokeVfx;
[SerializeField] private GameObject debrisPrefab;
[SerializeField] private int debrisCount = 12;
[SerializeField] private AudioClip explosionSound;
[SerializeField] private AudioSource audioSource;
[Header("Chain Reaction")]
[SerializeField] private LayerMask explosiveMask;
[SerializeField] private float chainDelay = 0.15f;
private void Awake() {{ Instance = this; }}
public void Explode(Vector3 center, float damageMult = 1f, float radiusMult = 1f)
{{
float dmg = baseDamage * damageMult;
float radius = baseRadius * radiusMult;
SpawnEffects(center, radius);
PlaySound(center);
ApplyDamageAndForce(center, dmg, radius);
SpawnDebris(center, radius);
PropagateChain(center, radius, damageMult, radiusMult);
}}
private void SpawnEffects(Vector3 center, float radius)
{{
if (explosionVfx != null) Instantiate(explosionVfx, center, Quaternion.identity);
if (fireVfx != null)
{{
GameObject fire = Instantiate(fireVfx, center, Quaternion.identity);
Destroy(fire, 6f);
}}
if (smokeVfx != null)
{{
GameObject smoke = Instantiate(smokeVfx, center, Quaternion.identity);
Destroy(smoke, 10f);
}}
}}
private void PlaySound(Vector3 center)
{{
if (audioSource != null && explosionSound != null)
audioSource.PlayOneShot(explosionSound);
}}
private void ApplyDamageAndForce(Vector3 center, float dmg, float radius)
{{
Collider[] hits = Physics.OverlapSphere(center, radius);
foreach (var h in hits)
{{
float falloff = 1f - Mathf.Clamp01(Vector3.Distance(center, h.transform.position) / radius);
var dmgable = h.GetComponent();
if (dmgable != null) dmgable.TakeDamage(dmg * falloff, center);
if (h.TryGetComponent(out var rb))
rb.AddExplosionForce(baseForce * falloff, center, radius, 0.5f, ForceMode.Impulse);
var vehicle = h.GetComponent();
if (vehicle != null) vehicle.Damage(dmg * falloff);
}}
}}
private void SpawnDebris(Vector3 center, float radius)
{{
if (debrisPrefab == null) return;
for (int i = 0; i < debrisCount; i++)
{{
Vector3 offset = Random.onUnitSphere * radius * 0.5f;
offset.y = Mathf.Abs(offset.y);
GameObject debris = Instantiate(debrisPrefab, center + offset, Random.rotation);
if (debris.TryGetComponent(out var rb))
rb.AddForce(offset.normalized * 8f, ForceMode.Impulse);
Destroy(debris, 8f);
}}
}}
private void PropagateChain(Vector3 center, float radius, float dmgMult, float radMult)
{{
Collider[] hits = Physics.OverlapSphere(center, radius, explosiveMask);
foreach (var h in hits)
{{
var explodable = h.GetComponent();
if (explodable == null) continue;
StartCoroutine(ChainExplode(explodable.transform.position, dmgMult * 0.8f, radMult * 0.8f));
}}
}}
private IEnumerator ChainExplode(Vector3 pos, float dmgMult, float radMult)
{{
yield return new WaitForSeconds(chainDelay + Random.value * 0.1f);
Explode(pos, dmgMult, radMult);
}}
}}
public class ExplodableVehicle : MonoBehaviour
{{
[SerializeField] private float health = 100f;
public void Damage(float amount)
{{
health -= amount;
if (health <= 0f)
ExplosionSystem.Instance.Explode(transform.position, 1.5f, 1.3f);
}}
}}
}}
''')
# ===========================================================================
# 30. Ragdoll system
# ===========================================================================
@register_tool
class CreateRagdollSystemTool(BaseUnityTool):
"""Generate a ragdoll system with death physics and impact reactions."""
name = "create_ragdoll_system"
description = (
"Generate a ragdoll system with death physics, body piles and "
"impact reactions."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "RagdollSystem"},
},
}
def run(self, class_name: str = "RagdollSystem", **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.AI
{{
///
/// Toggles a humanoid ragdoll. When active, Animator is disabled and
/// per-bone Rigidbodies/Colliders are enabled; forces can be applied
/// for impact reactions. Dead bodies persist so piles form naturally.
///
[RequireComponent(typeof(Animator))]
public class {class_name} : MonoBehaviour
{{
[Header("Bones")]
[SerializeField] private Rigidbody[] ragdollBodies;
[SerializeField] private Collider[] ragdollColliders;
[SerializeField] private Transform hipBone;
[Header("Reaction")]
[SerializeField] private float impactForce = 6f;
[SerializeField] private float settleThreshold = 0.3f;
[SerializeField] private float settleTime = 2f;
private Animator _animator;
private bool _active;
private float _settleTimer;
public bool IsActive => _active;
private void Awake()
{{
_animator = GetComponent();
SetRagdoll(false);
}}
public void Activate(Vector3 impulseSource, float force = 1f)
{{
if (_active) return;
_active = true;
if (_animator != null) _animator.enabled = false;
SetRagdoll(true);
ApplyImpulse(impulseSource, force);
}}
public void Deactivate()
{{
_active = false;
if (_animator != null) _animator.enabled = true;
SetRagdoll(false);
}}
private void SetRagdoll(bool enabled)
{{
foreach (var rb in ragdollBodies) if (rb != null) rb.isKinematic = !enabled;
foreach (var col in ragdollColliders) if (col != null) col.enabled = enabled;
}}
private void ApplyImpulse(Vector3 source, float mult)
{{
if (hipBone == null) return;
Vector3 dir = (hipBone.position - source).normalized + Vector3.up * 0.5f;
foreach (var rb in ragdollBodies)
if (rb != null) rb.AddForce(dir * impactForce * mult, ForceMode.Impulse);
}}
private void Update()
{{
if (!_active) return;
float maxVel = 0f;
foreach (var rb in ragdollBodies)
if (rb != null) maxVel = Mathf.Max(maxVel, rb.velocity.magnitude);
if (maxVel < settleThreshold)
{{
_settleTimer += Time.deltaTime;
if (_settleTimer >= settleTime) Settle();
}}
else _settleTimer = 0f;
}}
private void Settle()
{{
// Optionally convert ragdoll into a static corpse mesh for perf.
foreach (var rb in ragdollBodies) if (rb != null) rb.isKinematic = true;
}}
}}
}}
''')
# ===========================================================================
# 31. Cloth physics
# ===========================================================================
@register_tool
class CreateClothPhysicsTool(BaseUnityTool):
"""Generate a cloth physics system for flags, curtains and clothing."""
name = "create_cloth_physics"
description = (
"Generate a cloth physics system for flags, curtains and clothing "
"simulation."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "ClothPhysicsSystem"},
},
}
def run(self, class_name: str = "ClothPhysicsSystem", **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.FX
{{
///
/// Wraps Unity's Cloth component for interactive cloth (flags, curtains,
/// character capes). Provides wind control, preset attachment weights
/// and runtime toggling. Multiple cloth objects can be registered.
///
public class {class_name} : MonoBehaviour
{{
[System.Serializable]
public class ClothInstance
{{
public string id;
public Cloth cloth;
public float windFactor = 1f;
}}
[Header("Global Wind")]
[SerializeField] private Vector3 windDirection = new Vector3(1f, 0f, 0.2f);
[SerializeField] private float windStrength = 2f;
[SerializeField] private float gustFrequency = 0.3f;
[SerializeField] private float gustAmplitude = 1.5f;
[Header("Instances")]
[SerializeField] private List cloths = new List();
public void Register(ClothInstance ci)
{{
if (!cloths.Contains(ci)) cloths.Add(ci);
}}
public void Unregister(string id) => cloths.RemoveAll(c => c.id == id);
private void Update()
{{
float gust = 1f + Mathf.Sin(Time.time * gustFrequency * Mathf.PI * 2f) * gustAmplitude;
Vector3 wind = windDirection.normalized * windStrength * gust;
foreach (var ci in cloths)
{{
if (ci.cloth == null) continue;
ci.cloth.externalAcceleration = wind * ci.windFactor;
ci.cloth.randomAcceleration = wind * 0.2f * ci.windFactor;
}}
}}
public void AttachTopRow(string id)
{{
var ci = cloths.Find(c => c.id == id);
if (ci?.cloth == null) return;
var coefficients = ci.cloth.coefficients;
int vertsPerRow = Mathf.CeilToInt(Mathf.Sqrt(coefficients.Length));
for (int i = 0; i < coefficients.Length; i++)
{{
int row = i / vertsPerRow;
coefficients[i].maxDistance = row == 0 ? 0f : float.PositiveInfinity;
}}
ci.cloth.coefficients = coefficients;
}}
}}
}}
''')
# ===========================================================================
# 32. Weather system
# ===========================================================================
@register_tool
class CreateWeatherSystemTool(BaseUnityTool):
"""Generate a weather system with rain, fog, clear and storm states."""
name = "create_weather_system"
description = (
"Generate a weather system: rain, fog, clear, storm with particle "
"effects and lighting changes."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "WeatherSystem"},
},
}
def run(self, class_name: str = "WeatherSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using UnityEngine;
namespace OpenWorld.World
{{
///
/// State-machine weather controller. Cycles between Clear, Cloudy,
/// Rain, Fog and Storm. Each state adjusts ambient light, fog density,
/// particle systems (rain/snow), audio bed and wind. Transitions are
/// smoothed over a few seconds.
///
public class {class_name} : MonoBehaviour
{{
public enum Weather {{ Clear, Cloudy, Rain, Fog, Storm }}
[Serializable]
public struct WeatherPreset
{{
public Weather type;
public Color ambientColor;
public float fogDensity;
public Color fogColor;
public float lightIntensity;
public float windStrength;
public ParticleSystem particles;
public AudioClip ambientSound;
}}
[Header("State")]
[SerializeField] private Weather current = Weather.Clear;
[SerializeField] private WeatherPreset[] presets;
[SerializeField] private float transitionTime = 8f;
[SerializeField] private float changeInterval = 120f;
[Header("Refs")]
[SerializeField] private Light sun;
[SerializeField] private AudioSource ambientSource;
[SerializeField] private WindZone windZone;
private WeatherPreset _target;
private WeatherPreset _current;
private float _transitionProgress = 1f;
private float _changeTimer;
public event Action OnWeatherChanged;
private void Awake()
{{
_current = GetPreset(current);
_target = _current;
ApplyImmediate(_current);
}}
private void Update()
{{
HandleAutoChange();
HandleTransition();
}}
private void HandleAutoChange()
{{
_changeTimer += Time.deltaTime;
if (_changeTimer < changeInterval) return;
_changeTimer = 0f;
Weather[] values = (Weather[])Enum.GetValues(typeof(Weather));
Weather next;
do {{ next = values[UnityEngine.Random.Range(0, values.Length)]; }}
while (next == current);
SetWeather(next);
}}
public void SetWeather(Weather w)
{{
current = w;
_target = GetPreset(w);
_transitionProgress = 0f;
OnWeatherChanged?.Invoke(w);
if (ambientSource != null && _target.ambientSound != null)
{{
ambientSource.clip = _target.ambientSound;
ambientSource.Play();
}}
if (_target.particles != null) _target.particles.Play();
}}
private void HandleTransition()
{{
if (_transitionProgress >= 1f) return;
_transitionProgress = Mathf.Min(1f, _transitionProgress + Time.deltaTime / transitionTime);
float t = _transitionProgress;
RenderSettings.ambientLight = Color.Lerp(_current.ambientColor, _target.ambientColor, t);
RenderSettings.fogColor = Color.Lerp(_current.fogColor, _target.fogColor, t);
RenderSettings.fogDensity = Mathf.Lerp(_current.fogDensity, _target.fogDensity, t);
if (sun != null) sun.intensity = Mathf.Lerp(_current.lightIntensity, _target.lightIntensity, t);
if (windZone != null) windZone.windMain = Mathf.Lerp(_current.windStrength, _target.windStrength, t);
if (_transitionProgress >= 1f)
{{
_current = _target;
if (_current.particles != null) _current.particles.Stop();
}}
}}
private void ApplyImmediate(WeatherPreset p)
{{
RenderSettings.ambientLight = p.ambientColor;
RenderSettings.fogColor = p.fogColor;
RenderSettings.fogDensity = p.fogDensity;
RenderSettings.fog = p.fogDensity > 0.001f;
if (sun != null) sun.intensity = p.lightIntensity;
if (windZone != null) windZone.windMain = p.windStrength;
}}
private WeatherPreset GetPreset(Weather w)
{{
foreach (var p in presets) if (p.type == w) return p;
return default;
}}
}}
}}
''')
# ===========================================================================
# 33. CCTV system
# ===========================================================================
@register_tool
class CreateCctvSystemTool(BaseUnityTool):
"""Generate a CCTV security camera system with detection and alarms."""
name = "create_cctv_system"
description = (
"Generate a CCTV security camera system with detection, alarm "
"triggering and recording hooks."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "CctvSystem"},
},
}
def run(self, class_name: str = "CctvSystem", **_: Any) -> str:
return self._wrap(f'''
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Security
{{
///
/// Security camera. Sweeps a cone via FieldOfView raycasts; on
/// detecting the player (or a tagged target) it raises an alarm event
/// and starts recording (rendered to a RenderTexture). Guards can be
/// alerted via the OnDetected event.
///
public class {class_name} : MonoBehaviour
{{
[Header("Detection")]
[SerializeField] private Transform pivot;
[SerializeField] private float viewDistance = 18f;
[SerializeField] private float viewAngle = 45f;
[SerializeField] private float sweepSpeed = 30f;
[SerializeField] private float sweepRange = 90f;
[SerializeField] private LayerMask targetMask;
[SerializeField] private LayerMask obstructionMask;
[SerializeField] private Transform alarmTarget;
[Header("Visual")]
[SerializeField] private Light indicator;
[SerializeField] private Color idleColor = Color.green;
[SerializeField] private Color alertColor = Color.red;
[SerializeField] private Camera renderCamera;
[SerializeField] private RenderTexture outputTexture;
public bool Alerted {{ get; private set; }}
public static System.Action OnGlobalDetection;
private float _baseYaw;
private float _sweepT;
private void Awake()
{{
if (pivot != null) _baseYaw = pivot.eulerAngles.y;
if (renderCamera != null) renderCamera.targetTexture = outputTexture;
}}
private void Update()
{{
Sweep();
Detect();
}}
private void Sweep()
{{
if (Alerted || pivot == null) return;
_sweepT += Time.deltaTime * sweepSpeed;
float yaw = _baseYaw + Mathf.Sin(_sweepT * Mathf.Deg2Rad) * sweepRange * 0.5f;
pivot.rotation = Quaternion.Euler(0f, yaw, 0f);
}}
private void Detect()
{{
if (pivot == null) return;
Collider[] candidates = Physics.OverlapSphere(pivot.position, viewDistance, targetMask);
foreach (var c in candidates)
{{
Vector3 dir = (c.transform.position - pivot.position).normalized;
if (Vector3.Angle(pivot.forward, dir) > viewAngle * 0.5f) continue;
if (Physics.Raycast(pivot.position, dir, out RaycastHit hit, viewDistance, obstructionMask))
if (hit.transform != c.transform) continue;
RaiseAlarm(c.transform);
return;
}}
}}
private void RaiseAlarm(Transform target)
{{
Alerted = true;
if (indicator != null) indicator.color = alertColor;
OnGlobalDetection?.Invoke(target);
WantedSystem.Instance?.ReportCrime(20f);
if (renderCamera != null) renderCamera.gameObject.SetActive(true);
}}
public void Reset()
{{
Alerted = false;
if (indicator != null) indicator.color = idleColor;
if (renderCamera != null) renderCamera.gameObject.SetActive(false);
}}
private void OnDrawGizmosSelected()
{{
if (pivot == null) return;
Gizmos.color = Alerted ? Color.red : Color.yellow;
Gizmos.DrawWireSphere(pivot.position, viewDistance);
Vector3 left = Quaternion.Euler(0, -viewAngle * 0.5f, 0) * pivot.forward * viewDistance;
Vector3 right = Quaternion.Euler(0, viewAngle * 0.5f, 0) * pivot.forward * viewDistance;
Gizmos.DrawLine(pivot.position, pivot.position + left);
Gizmos.DrawLine(pivot.position, pivot.position + right);
}}
}}
}}
''')
# ===========================================================================
# 34. Dialogue system
# ===========================================================================
@register_tool
class CreateDialogueSystemTool(BaseUnityTool):
"""Generate a branching dialogue system with subtitles."""
name = "create_dialogue_system"
description = (
"Generate a dialogue system with NPC conversations, branching "
"dialogue and subtitles."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "DialogueSystem"},
},
}
def run(self, class_name: str = "DialogueSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace OpenWorld.Dialogue
{{
///
/// Branching dialogue engine. Conversations are directed graphs of
/// nodes; each node has NPC lines and a list of player responses that
/// link to other nodes. Subtitles render via a Text panel and a typewriter
/// coroutine. Choices are shown as buttons.
///
public class {class_name} : MonoBehaviour
{{
[Serializable]
public class DialogueNode
{{
public string id;
[TextArea] public string npcLine;
public AudioClip voiceClip;
public List choices = new List();
public string onEnterEvent;
}}
[Serializable]
public class DialogueChoice
{{
[TextArea] public string text;
public string targetNodeId;
public string requiredFlag;
public string setFlag;
}}
[Serializable]
public class Conversation
{{
public string id;
public List nodes = new List();
}}
[Header("UI")]
[SerializeField] private GameObject panel;
[SerializeField] private Text npcNameText;
[SerializeField] private Text subtitleText;
[SerializeField] private Transform choiceContainer;
[SerializeField] private Button choicePrefab;
[SerializeField] private float typeSpeed = 40f;
[Header("Audio")]
[SerializeField] private AudioSource voiceSource;
[Header("Data")]
[SerializeField] private List conversations = new List();
private readonly HashSet _flags = new HashSet();
private Conversation _current;
private DialogueNode _node;
private bool _typing;
public event Action OnEventTriggered;
public bool IsActive => _current != null;
public void StartConversation(string id, string npcName)
{{
_current = conversations.Find(c => c.id == id);
if (_current == null || _current.nodes.Count == 0) return;
if (npcNameText != null) npcNameText.text = npcName;
if (panel != null) panel.SetActive(true);
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
ShowNode(_current.nodes[0]);
}}
public void EndConversation()
{{
_current = null;
_node = null;
if (panel != null) panel.SetActive(false);
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}}
private void ShowNode(DialogueNode node)
{{
_node = node;
if (!string.IsNullOrEmpty(node.onEnterEvent)) OnEventTriggered?.Invoke(node.onEnterEvent);
if (node.voiceClip != null && voiceSource != null) voiceSource.PlayOneShot(node.voiceClip);
StartCoroutine(TypeLine(node.npcLine));
RenderChoices(node);
}}
private System.Collections.IEnumerator TypeLine(string line)
{{
_typing = true;
if (subtitleText != null) subtitleText.text = "";
foreach (char c in line)
{{
if (subtitleText != null) subtitleText.text += c;
yield return new WaitForSeconds(1f / typeSpeed);
}}
_typing = false;
}}
private void RenderChoices(DialogueNode node)
{{
if (choiceContainer == null || choicePrefab == null) return;
foreach (Transform child in choiceContainer) Destroy(child.gameObject);
foreach (var ch in node.choices)
{{
if (!string.IsNullOrEmpty(ch.requiredFlag) && !_flags.Contains(ch.requiredFlag)) continue;
Button btn = Instantiate(choicePrefab, choiceContainer);
btn.GetComponentInChildren().text = ch.text;
btn.onClick.AddListener(() => Choose(ch));
}}
}}
private void Choose(DialogueChoice choice)
{{
if (!string.IsNullOrEmpty(choice.setFlag)) _flags.Add(choice.setFlag);
if (string.IsNullOrEmpty(choice.targetNodeId)) {{ EndConversation(); return; }}
var next = _current.nodes.Find(n => n.id == choice.targetNodeId);
if (next != null) ShowNode(next); else EndConversation();
}}
private void Update()
{{
if (!IsActive) return;
if (Input.GetKeyDown(KeyCode.Escape)) EndConversation();
if (_typing && Input.GetMouseButtonDown(0)) _typing = false;
}}
}}
}}
''')
# ===========================================================================
# 35. Mission system
# ===========================================================================
@register_tool
class CreateMissionSystemTool(BaseUnityTool):
"""Generate a mission system with story, side and random events."""
name = "create_mission_system"
description = (
"Generate a mission system with story missions, side missions, "
"random events and mission givers."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "MissionSystem"},
},
}
def run(self, class_name: str = "MissionSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Missions
{{
///
/// High-level mission director. Story missions unlock linearly; side
/// missions are available from givers; random events spawn opportunistically.
/// Each mission has a starting trigger and a completion check.
///
public class {class_name} : MonoBehaviour
{{
public enum MissionKind {{ Story, Side, RandomEvent }}
[Serializable]
public class Mission
{{
public string id;
public string title;
public MissionKind kind;
public string giverId;
public Vector3 startMarker;
public string questId; // links to QuestSystem
public int moneyReward;
public int xpReward;
public string unlocksMissionId;
public bool available;
public bool started;
public bool completed;
}}
[Header("Missions")]
[SerializeField] private List missions = new List();
[SerializeField] private Mission[] randomEventPool;
[Header("Random Events")]
[SerializeField] private float randomEventInterval = 180f;
[SerializeField] private float randomEventChance = 0.5f;
private float _randomTimer;
private Mission _activeStory;
public IReadOnlyList All => missions;
public event Action OnMissionStarted;
public event Action OnMissionCompleted;
private void Start()
{{
// Unlock the first story mission.
foreach (var m in missions)
if (m.kind == MissionKind.Story && !m.completed) {{ m.available = true; _activeStory = m; break; }}
_randomTimer = randomEventInterval;
}}
private void Update()
{{
HandleRandomEvents();
CheckCompletion();
}}
public void StartMission(string id)
{{
Mission m = missions.Find(x => x.id == id);
if (m == null || !m.available || m.started) return;
m.started = true;
QuestSystem.Instance?.Accept(m.questId);
OnMissionStarted?.Invoke(m);
}}
private void CheckCompletion()
{{
foreach (var m in missions)
{{
if (!m.started || m.completed) continue;
if (QuestSystem.Instance == null) continue;
var q = QuestSystem.Instance.ActiveQuests;
bool done = false;
foreach (var qq in q) if (qq.id == m.questId && qq.completed) done = true;
if (done) CompleteMission(m);
}}
}}
private void CompleteMission(Mission m)
{{
m.completed = true;
EconomySystem.Instance?.AddCash(m.moneyReward);
OnMissionCompleted?.Invoke(m);
if (!string.IsNullOrEmpty(m.unlocksMissionId))
{{
Mission next = missions.Find(x => x.id == m.unlocksMissionId);
if (next != null) next.available = true;
}}
}}
private void HandleRandomEvents()
{{
_randomTimer -= Time.deltaTime;
if (_randomTimer > 0f) return;
_randomTimer = randomEventInterval;
if (randomEventPool == null || randomEventPool.Length == 0) return;
if (UnityEngine.Random.value > randomEventChance) return;
Mission re = randomEventPool[UnityEngine.Random.Range(0, randomEventPool.Length)];
Mission copy = new Mission
{{
id = re.id + "_" + Time.frameCount,
title = re.title,
kind = MissionKind.RandomEvent,
giverId = re.giverId,
startMarker = re.startMarker,
questId = re.questId,
moneyReward = re.moneyReward,
xpReward = re.xpReward,
available = true
}};
missions.Add(copy);
StartMission(copy.id);
}}
}}
}}
''')
# ===========================================================================
# 36. Garage system
# ===========================================================================
@register_tool
class CreateGarageSystemTool(BaseUnityTool):
"""Generate a garage system for vehicle storage and customization."""
name = "create_garage_system"
description = (
"Generate a garage system with vehicle storage, customization, "
"painting and repairs."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "GarageSystem"},
},
}
def run(self, class_name: str = "GarageSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Vehicles
{{
///
/// Garage service. Stores up to N vehicles per garage, restores health
/// on storage, and exposes customization actions: repaint, repair,
/// upgrade engine/armor.
///
public class {class_name} : MonoBehaviour
{{
[Serializable]
public class StoredVehicle
{{
public string modelId;
public float health;
public Color paint = Color.white;
public int engineLevel = 1;
public int armorLevel = 1;
public int tireLevel = 1;
}}
[Header("Capacity")]
[SerializeField] private int capacity = 10;
[SerializeField] private Transform spawnPoint;
[SerializeField] private List stored = new List();
[SerializeField] private GameObject[] vehiclePrefabs;
[Header("Costs")]
[SerializeField] private int repairCost = 500;
[SerializeField] private int repaintCost = 200;
[SerializeField] private int upgradeCost = 2000;
public IReadOnlyList Stored => stored;
public bool Store(GameObject vehicle, string modelId)
{{
if (stored.Count >= capacity) return false;
var sv = new StoredVehicle
{{
modelId = modelId,
health = 100f,
paint = vehicle.GetComponent()?.material.color ?? Color.white
}};
stored.Add(sv);
Destroy(vehicle);
return true;
}}
public GameObject Retrieve(int index)
{{
if (index < 0 || index >= stored.Count || spawnPoint == null) return null;
StoredVehicle sv = stored[index];
GameObject prefab = System.Array.Find(vehiclePrefabs, p => p.name == sv.modelId);
if (prefab == null) return null;
GameObject go = Instantiate(prefab, spawnPoint.position, spawnPoint.rotation);
if (go.TryGetComponent(out var r)) r.material.color = sv.paint;
stored.RemoveAt(index);
return go;
}}
public bool Repair(int index)
{{
if (!Charge(repairCost)) return false;
if (index < 0 || index >= stored.Count) return false;
stored[index].health = 100f;
return true;
}}
public bool Repaint(int index, Color color)
{{
if (!Charge(repaintCost)) return false;
if (index < 0 || index >= stored.Count) return false;
stored[index].paint = color;
return true;
}}
public bool UpgradeEngine(int index)
{{
if (!Charge(upgradeCost)) return false;
stored[index].engineLevel = Mathf.Min(5, stored[index].engineLevel + 1);
return true;
}}
public bool UpgradeArmor(int index)
{{
if (!Charge(upgradeCost)) return false;
stored[index].armorLevel = Mathf.Min(5, stored[index].armorLevel + 1);
return true;
}}
private bool Charge(int amount)
{{
var econ = EconomySystem.Instance;
if (econ == null || econ.Cash < amount) return false;
econ.AddCash(-amount);
return true;
}}
}}
}}
''')
# ===========================================================================
# 37. Property system
# ===========================================================================
@register_tool
class CreatePropertySystemTool(BaseUnityTool):
"""Generate a property system for buying, selling and income."""
name = "create_property_system"
description = (
"Generate a property system: buy/sell properties, income "
"generation, safe houses."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "PropertySystem"},
},
}
def run(self, class_name: str = "PropertySystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Economy
{{
///
/// Real-estate manager. Each property has a price, weekly income, and
/// may serve as a safe house (heal/save point). Income is paid out on
/// a configurable interval.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Serializable]
public class Property
{{
public string id;
public string displayName;
public int price;
public int weeklyIncome;
public bool safeHouse;
public Vector3 location;
[NonSerialized] public bool owned;
}}
[Header("Catalog")]
[SerializeField] private List properties = new List();
[Header("Income")]
[SerializeField] private float payoutIntervalMinutes = 5f;
private float _payoutTimer;
public IReadOnlyList All => properties;
public event Action OnPurchased;
public event Action OnSold;
public event Action OnIncomePaid;
private void Awake() {{ Instance = this; }}
public bool Buy(string id)
{{
Property p = properties.Find(x => x.id == id);
if (p == null || p.owned) return false;
var econ = EconomySystem.Instance;
if (econ == null || econ.Cash < p.price) return false;
econ.AddCash(-p.price);
p.owned = true;
OnPurchased?.Invoke(p);
return true;
}}
public bool Sell(string id)
{{
Property p = properties.Find(x => x.id == id);
if (p == null || !p.owned) return false;
EconomySystem.Instance?.AddCash(p.price / 2);
p.owned = false;
OnSold?.Invoke(p);
return true;
}}
public bool UseSafeHouse(string id)
{{
Property p = properties.Find(x => x.id == id);
if (p == null || !p.owned || !p.safeHouse) return false;
// Player health/save handled by listeners.
return true;
}}
private void Update()
{{
_payoutTimer += Time.deltaTime;
float interval = payoutIntervalMinutes * 60f;
if (_payoutTimer < interval) return;
_payoutTimer = 0f;
int total = 0;
foreach (var p in properties) if (p.owned) total += p.weeklyIncome;
if (total > 0)
{{
EconomySystem.Instance?.AddCash(total);
OnIncomePaid?.Invoke(total);
}}
}}
}}
}}
''')
# ===========================================================================
# 38. Character customization
# ===========================================================================
@register_tool
class CreateCharacterCustomizationTool(BaseUnityTool):
"""Generate a character customization system (skin, hair, clothing)."""
name = "create_character_customization"
description = (
"Generate a character customization system: skin, hair, clothing, "
"accessories."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "CharacterCustomization"},
},
}
def run(self, class_name: str = "CharacterCustomization", **_: Any) -> str:
return self._wrap(f'''
using System;
using UnityEngine;
namespace OpenWorld.Player
{{
///
/// Character appearance manager. Blends skin tones via material color,
/// swaps hair meshes from a list, and toggles clothing/attachment
/// GameObjects. Saves an appearance string for persistence.
///
public class {class_name} : MonoBehaviour
{{
[Serializable]
public struct HairOption {{ public string id; public GameObject mesh; }}
[Serializable]
public struct ClothingOption {{ public string id; public GameObject mesh; }}
[Serializable]
public struct AccessoryOption {{ public string id; public GameObject mesh; }}
[Header("Body")]
[SerializeField] private SkinnedMeshRenderer bodyMesh;
[SerializeField] private Color[] skinTones;
[SerializeField] private int skinIndex;
[Header("Hair")]
[SerializeField] private HairOption[] hairOptions;
[SerializeField] private int hairIndex;
[Header("Clothing")]
[SerializeField] private ClothingOption[] tops;
[SerializeField] private int topIndex;
[SerializeField] private ClothingOption[] bottoms;
[SerializeField] private int bottomIndex;
[Header("Accessories")]
[SerializeField] private AccessoryOption[] accessories;
[SerializeField] private bool[] accessoryToggles;
public int SkinIndex => skinIndex;
public int HairIndex => hairIndex;
public int TopIndex => topIndex;
public int BottomIndex => bottomIndex;
public void SetSkin(int index)
{{
skinIndex = Mathf.Clamp(index, 0, skinTones.Length - 1);
if (bodyMesh != null) bodyMesh.material.color = skinTones[skinIndex];
}}
public void SetHair(int index)
{{
hairIndex = Mathf.Clamp(index, 0, hairOptions.Length - 1);
for (int i = 0; i < hairOptions.Length; i++)
if (hairOptions[i].mesh != null) hairOptions[i].mesh.SetActive(i == hairIndex);
}}
public void SetTop(int index)
{{
topIndex = Mathf.Clamp(index, 0, tops.Length - 1);
for (int i = 0; i < tops.Length; i++)
if (tops[i].mesh != null) tops[i].mesh.SetActive(i == topIndex);
}}
public void SetBottom(int index)
{{
bottomIndex = Mathf.Clamp(index, 0, bottoms.Length - 1);
for (int i = 0; i < bottoms.Length; i++)
if (bottoms[i].mesh != null) bottoms[i].mesh.SetActive(i == bottomIndex);
}}
public void ToggleAccessory(int index)
{{
if (index < 0 || index >= accessories.Length) return;
accessoryToggles[index] = !accessoryToggles[index];
if (accessories[index].mesh != null) accessories[index].mesh.SetActive(accessoryToggles[index]);
}}
public string Serialize()
{{
return $"{{skinIndex}}|{{hairIndex}}|{{topIndex}}|{{bottomIndex}}|{{string.Join(",", accessoryToggles)}}";
}}
public void Deserialize(string data)
{{
if (string.IsNullOrEmpty(data)) return;
string[] parts = data.Split('|');
if (parts.Length < 4) return;
if (int.TryParse(parts[0], out int s)) SetSkin(s);
if (int.TryParse(parts[1], out int h)) SetHair(h);
if (int.TryParse(parts[2], out int t)) SetTop(t);
if (int.TryParse(parts[3], out int b)) SetBottom(b);
}}
}}
}}
''')
# ===========================================================================
# 39. Cutscene system
# ===========================================================================
@register_tool
class CreateCutsceneSystemTool(BaseUnityTool):
"""Generate a cutscene system with camera sequences and letterbox."""
name = "create_cutscene_system"
description = (
"Generate a cutscene system with camera sequences, character "
"animation and letterbox."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "CutsceneSystem"},
},
}
def run(self, class_name: str = "CutsceneSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace OpenWorld.Cinematics
{{
///
/// Sequencer-based cutscene player. Each shot has a camera, duration,
/// look-at target and dialogue line. A black letterbox bar animates in
/// at the start. Player input is disabled during playback.
///
public class {class_name} : MonoBehaviour
{{
[Serializable]
public class Shot
{{
public Camera camera;
public Transform lookAt;
public float duration = 4f;
[TextArea] public string subtitle;
public AnimationClip actorAnimation;
public Animator actorAnimator;
}}
[Serializable]
public class Cutscene
{{
public string id;
public List shots = new List();
}}
[Header("Letterbox")]
[SerializeField] private RectTransform topBar;
[SerializeField] private RectTransform bottomBar;
[SerializeField] private float barHeight = 100f;
[SerializeField] private float barAnimDuration = 0.6f;
[Header("Subtitles")]
[SerializeField] private Text subtitleText;
[Header("Input")]
[SerializeField] private MonoBehaviour playerController;
[Header("Data")]
[SerializeField] private List cutscenes = new List();
private bool _playing;
public bool IsPlaying => _playing;
public event Action OnCutsceneStart;
public event Action OnCutsceneEnd;
public void Play(string id)
{{
Cutscene cs = cutscenes.Find(c => c.id == id);
if (cs == null) return;
StartCoroutine(PlayRoutine(cs));
}}
private IEnumerator PlayRoutine(Cutscene cs)
{{
_playing = true;
OnCutsceneStart?.Invoke(cs.id);
if (playerController != null) playerController.enabled = false;
Cursor.visible = false;
yield return AnimateBars(true);
foreach (var shot in cs.shots)
{{
if (shot.camera != null) shot.camera.gameObject.SetActive(true);
if (shot.actorAnimator != null && shot.actorAnimation != null)
shot.actorAnimator.Play(shot.actorAnimation.name);
if (subtitleText != null) subtitleText.text = shot.subtitle;
yield return new WaitForSeconds(shot.duration);
if (shot.camera != null) shot.camera.gameObject.SetActive(false);
}}
yield return AnimateBars(false);
if (playerController != null) playerController.enabled = true;
_playing = false;
OnCutsceneEnd?.Invoke(cs.id);
}}
private IEnumerator AnimateBars(bool show)
{{
float from = show ? 0f : barHeight;
float to = show ? barHeight : 0f;
float t = 0f;
while (t < barAnimDuration)
{{
t += Time.deltaTime;
float h = Mathf.Lerp(from, to, t / barAnimDuration);
if (topBar != null) topBar.sizeDelta = new Vector2(0, h);
if (bottomBar != null) bottomBar.sizeDelta = new Vector2(0, h);
yield return null;
}}
}}
}}
}}
''')
# ===========================================================================
# 40. Multiplayer netcode
# ===========================================================================
@register_tool
class CreateMultiplayerNetcodeTool(BaseUnityTool):
"""Generate multiplayer netcode stubs with sync and voice hooks."""
name = "create_multiplayer_netcode"
description = (
"Generate multiplayer netcode: network sync, player spawning, "
"voice chat hooks."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "NetworkManager"},
},
}
def run(self, class_name: str = "NetworkManager", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Net
{{
///
/// Lightweight host/client networking stub. Provides RPC-style
/// send/receive, authoritative object spawning, interpolation for
/// remote player transforms and voice-chat hook events. Designed as a
/// drop-in surface that a real transport (Netcode for GameObjects /
/// Mirror) can replace.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Header("Config")]
[SerializeField] private int port = 7777;
[SerializeField] private int maxPlayers = 32;
[SerializeField] private GameObject playerPrefab;
[SerializeField] private Transform[] spawnPoints;
[Header("State")]
public bool isHost;
public bool isClient;
public int localPlayerId = -1;
private readonly Dictionary _players = new Dictionary();
private float _sendInterval = 0.05f;
private float _sendTimer;
public event Action OnPlayerJoined;
public event Action OnPlayerLeft;
public event Action OnVoiceData;
private void Awake() {{ Instance = this; }}
public void StartHost()
{{
isHost = true; isClient = true;
localPlayerId = 0;
SpawnPlayer(localPlayerId, true);
}}
public void StartClient(string address)
{{
isHost = false; isClient = true;
// Transport connect here.
}}
public void Stop()
{{
foreach (var p in _players.Values) if (p != null) Destroy(p.gameObject);
_players.Clear();
isHost = isClient = false;
}}
private void SpawnPlayer(int id, bool isLocal)
{{
if (playerPrefab == null) return;
Transform sp = spawnPoints.Length > 0 ? spawnPoints[id % spawnPoints.Length] : transform;
GameObject go = Instantiate(playerPrefab, sp.position, sp.rotation);
var np = go.AddComponent();
np.Initialize(id, isLocal);
_players[id] = np;
OnPlayerJoined?.Invoke(id);
}}
public void SendVoice(int fromId, byte[] data)
{{
OnVoiceData?.Invoke(fromId, data);
// Broadcast to other players via transport.
}}
private void Update()
{{
if (!isClient) return;
_sendTimer -= Time.deltaTime;
if (_sendTimer > 0f) return;
_sendTimer = _sendInterval;
foreach (var p in _players.Values)
if (p != null) p.NetUpdate();
}}
}}
public class NetworkPlayer : MonoBehaviour
{{
public int Id {{ get; private set; }}
public bool IsLocal {{ get; private set; }}
private Vector3 _netPos;
private Quaternion _netRot;
private float _interpSpeed = 12f;
public void Initialize(int id, bool isLocal) {{ Id = id; IsLocal = isLocal; }}
public void NetUpdate()
{{
if (IsLocal)
{{
// Send transform to server (stub).
_netPos = transform.position;
_netRot = transform.rotation;
}}
else
{{
transform.position = Vector3.Lerp(transform.position, _netPos, _interpSpeed * Time.deltaTime);
transform.rotation = Quaternion.Slerp(transform.rotation, _netRot, _interpSpeed * Time.deltaTime);
}}
}}
public void ReceiveSnapshot(Vector3 pos, Quaternion rot)
{{
if (IsLocal) return;
_netPos = pos;
_netRot = rot;
}}
}}
}}
''')
# ===========================================================================
# 41. Setup Unity project
# ===========================================================================
@register_tool
class SetupUnityProjectTool(BaseUnityTool):
"""Generate a complete Unity project structure scaffold."""
name = "setup_unity_project"
description = (
"Generate a complete Unity project structure: folders, assembly "
"definitions, manifest.json and ProjectSettings entries."
)
input_schema = {
"type": "object",
"properties": {
"project_name": {"type": "string", "default": "OpenWorldGame"},
"unity_version": {"type": "string", "default": "2022.3.20f1"},
},
}
def run(self, project_name: str = "OpenWorldGame", unity_version: str = "2022.3.20f1", **_: Any) -> str:
manifest = self._wrap(f'''
{{
"dependencies": {{
"com.unity.ai.navigation": "1.1.5",
"com.unity.cinemachine": "2.9.7",
"com.unity.inputsystem": "1.7.0",
"com.unity.postprocessing": "3.2.2",
"com.unity.textmeshpro": "3.0.6",
"com.unity.ugui": "1.0.0",
"com.unity.modules.audio": "1.0.0",
"com.unity.modules.imageconversion": "1.0.0",
"com.unity.modules.particlesystem": "1.0.0",
"com.unity.modules.physics": "1.0.0",
"com.unity.modules.terrain": "1.0.0",
"com.unity.modules.ui": "1.0.0",
"com.unity.ugui": "1.0.0"
}}
}}
''')
asmdef = self._wrap(f'''
{{
"name": "OpenWorld",
"rootNamespace": "OpenWorld",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}}
''')
structure = self._wrap(f'''
# {project_name}
# Unity {unity_version}
Directory layout:
Assets/
OpenWorld/
Scripts/
Player/
Vehicles/
World/
AI/
Combat/
FX/
UI/
Audio/
Persistence/
Economy/
Missions/
Net/
Prefabs/
Materials/
Models/
Textures/
Audio/
Scenes/
ScriptableObjects/
Settings/
URP/ (or HDRP)
ProjectSettings/
Packages/
manifest.json
Runtime requirements:
- Unity {unity_version}
- URP or HDRP
- TextMeshPro imported
- Input System (both old and new enabled)
'''
)
return f"// === manifest.json ===\n{manifest}\n// === OpenWorld.asmdef ===\n{asmdef}\n{structure}"
# ===========================================================================
# 42. Write C# script
# ===========================================================================
@register_tool
class WriteCsharpScriptTool(BaseUnityTool):
"""Generate a custom C# script from a template and parameters."""
name = "write_csharp_script"
description = (
"Generate a custom C# MonoBehaviour/ScriptableObject from a "
"template, namespace and field list."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string"},
"namespace": {"type": "string", "default": "OpenWorld.Custom"},
"base_class": {"type": "string", "default": "MonoBehaviour"},
"fields": {"type": "array", "items": {"type": "string"}},
},
}
def run(
self,
class_name: str = "CustomScript",
namespace: str = "OpenWorld.Custom",
base_class: str = "MonoBehaviour",
fields: Optional[List[str]] = None,
**_: Any,
) -> str:
fields = fields or []
field_lines = []
for f in fields:
parts = f.split()
if len(parts) >= 2:
ftype, fname = parts[0], parts[1]
field_lines.append(f" [SerializeField] private {ftype} {fname};")
field_block = "\n".join(field_lines) if field_lines else " // (no fields)"
return self._wrap(f'''
using UnityEngine;
namespace {namespace}
{{
///
/// Auto-generated {class_name}. Extend the lifecycle methods as needed.
///
public class {class_name} : {base_class}
{{
{field_block}
private void Awake()
{{
// Cache components here.
}}
private void Start()
{{
// Initialization after Awake.
}}
private void Update()
{{
// Per-frame logic.
}}
private void FixedUpdate()
{{
// Physics-step logic.
}}
private void LateUpdate()
{{
// Post-update (camera, follow).
}}
private void OnEnable()
{{
// Subscribe to events.
}}
private void OnDisable()
{{
// Unsubscribe from events.
}}
private void OnDestroy()
{{
// Cleanup resources.
}}
}}
}}
''')
# ===========================================================================
# 43. Write scene file
# ===========================================================================
@register_tool
class WriteSceneFileTool(BaseUnityTool):
"""Generate a minimal Unity scene (.unity) YAML file."""
name = "write_scene_file"
description = "Generate a minimal Unity scene YAML file with a Camera and directional light."
input_schema = {
"type": "object",
"properties": {
"scene_name": {"type": "string", "default": "MainScene"},
},
}
def run(self, scene_name: str = "MainScene", **_: Any) -> str:
return self._wrap(f'''%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {{fileID: 0}}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
m_Fog: 0
m_FogColor: {{r: 0.5, g: 0.5, b: 0.5, a: 1}}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {{r: 0.2, g: 0.2, b: 0.2, a: 1}}
m_AmbientGroundColor: {{r: 0.2, g: 0.2, b: 0.2, a: 1}}
m_SkyboxMaterial: {{fileID: 0}}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {{fileID: 0}}
m_SpotCookie: {{fileID: 0}}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {{fileID: 0}}
m_Sun: {{fileID: 0}}
m_IndirectSpecularColor: {{r: 0, g: 0, b: 0, a: 1}}
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 11
m_GIWorkflowMode: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_TemporalCoherenceThreshold: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 1
m_LightmapEditorSettings:
serializedVersion: 9
m_Resolution: 2
m_BakeResolution: 40
m_TextureWidth: 1024
m_TextureHeight: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_Padding: 2
m_LightmapParameters: {{fileID: 0}}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_LightingDataAsset: {{fileID: 0}}
m_UseShadowmask: 1
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 2
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
debug:
m_Flags: 0
m_NavMeshData: {{fileID: 0}}
--- !u!1 &100000
GameObject:
m_ObjectHideFlags: 0
m_Name: MainCamera
m_IsActive: 1
--- !u!20 &100001
Camera:
m_ObjectHideFlags: 0
m_ClearFlags: 1
m_BackGroundColor: {{r: 0.192, g: 0.302, b: 0.474, a: 0.019}}
m_FieldOfView: 60
m_NearClipPlane: 0.3
m_FarClipPlane: 1000
--- !u!1 &200000
GameObject:
m_ObjectHideFlags: 0
m_Name: DirectionalLight
m_IsActive: 1
--- !u!108 &200001
Light:
m_ObjectHideFlags: 0
m_Type: 1
m_Color: {{r: 1, g: 0.956, b: 0.839, a: 1}}
m_Intensity: 1.2
m_Shadows:
m_Type: 2
''') + f"\n// Scene: {scene_name}\n"
# ===========================================================================
# 44. Generate complete game
# ===========================================================================
@register_tool
class GenerateCompleteGameTool(BaseUnityTool):
"""Generate a complete game by orchestrating every tool in the registry."""
name = "generate_complete_game"
description = (
"Run every registered tool and assemble a complete Unity project "
"in the given output directory."
)
input_schema = {
"type": "object",
"properties": {
"output_dir": {"type": "string", "default": "./OpenWorldGame"},
"project_name": {"type": "string", "default": "OpenWorldGame"},
"include_multiplayer": {"type": "boolean", "default": False},
},
}
def run(
self,
output_dir: str = "./OpenWorldGame",
project_name: str = "OpenWorldGame",
include_multiplayer: bool = False,
**_: Any,
) -> str:
# Build a manifest of generated files by invoking each tool.
manifest = [f"# Generated game: {project_name}", f"# Output: {output_dir}", ""]
skip = set()
if not include_multiplayer:
skip.add("create_multiplayer_netcode")
for tool in TOOL_REGISTRY:
if tool.name in skip or tool.name == self.name:
continue
try:
code = tool.run()
rel = f"Assets/OpenWorld/Scripts/{tool.name}.cs"
manifest.append(f"- {rel} ({len(code.splitlines())} lines)")
except Exception as exc: # pragma: no cover
manifest.append(f"- ERROR in {tool.name}: {exc}")
manifest.append("")
manifest.append(f"Total tools invoked: {sum(1 for t in TOOL_REGISTRY if t.name not in skip and t.name != self.name)}")
return "\n".join(manifest) + "\n"
# ===========================================================================
# 45. Loot system
# ===========================================================================
@register_tool
class CreateLootSystemTool(BaseUnityTool):
"""Generate a loot system with rarity tiers and containers."""
name = "create_loot_system"
description = (
"Generate a loot system with random loot drops, rarity tiers and "
"containers."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "LootSystem"},
},
}
def run(self, class_name: str = "LootSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Items
{{
///
/// Loot table & container system. Each table maps item IDs to weighted
/// drop chances per rarity tier. Containers (chests, corpses) roll the
/// table to populate contents on first open.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
public enum Rarity {{ Common, Uncommon, Rare, Epic, Legendary }}
[Serializable]
public class LootEntry
{{
public string itemId;
public Rarity rarity;
public int minStack = 1;
public int maxStack = 1;
public float weight = 1f;
}}
[Serializable]
public class LootTable
{{
public string id;
public List entries = new List();
public int minDrops = 1;
public int maxDrops = 3;
[NonSerialized] public float totalWeight;
}}
[Header("Color By Rarity")]
[SerializeField] private Color commonColor = Color.gray;
[SerializeField] private Color uncommonColor = Color.green;
[SerializeField] private Color rareColor = Color.cyan;
[SerializeField] private Color epicColor = new Color(0.6f, 0.2f, 0.9f);
[SerializeField] private Color legendaryColor = new Color(1f, 0.6f, 0.1f);
[Header("Tables")]
[SerializeField] private List tables = new List();
private readonly Dictionary> _generated = new Dictionary>();
private void Awake() {{ Instance = this; }}
public List Roll(string tableId)
{{
LootTable table = tables.Find(t => t.id == tableId);
if (table == null) return new List();
RecomputeWeights(table);
int count = UnityEngine.Random.Range(table.minDrops, table.maxDrops + 1);
List result = new List();
for (int i = 0; i < count; i++)
{{
float r = UnityEngine.Random.value * table.totalWeight;
float acc = 0f;
foreach (var e in table.entries)
{{
acc += e.weight;
if (r <= acc)
{{
result.Add(new LootEntry
{{
itemId = e.itemId,
rarity = e.rarity,
minStack = UnityEngine.Random.Range(e.minStack, e.maxStack + 1),
maxStack = e.maxStack,
weight = e.weight
}});
break;
}}
}}
}}
return result;
}}
public Color RarityColor(Rarity r)
{{
return r switch
{{
Rarity.Common => commonColor,
Rarity.Uncommon => uncommonColor,
Rarity.Rare => rareColor,
Rarity.Epic => epicColor,
Rarity.Legendary => legendaryColor,
_ => Color.white,
}};
}}
private void RecomputeWeights(LootTable table)
{{
table.totalWeight = 0f;
foreach (var e in table.entries) table.totalWeight += e.weight;
}}
}}
public class LootContainer : MonoBehaviour
{{
[SerializeField] private string tableId;
[SerializeField] private bool opened;
[SerializeField] private GameObject lid; // animates open
public event Action
> OnOpened;
public void Interact()
{{
if (opened) return;
opened = true;
if (lid != null) lid.transform.localRotation = Quaternion.Euler(-90f, 0f, 0f);
var loot = LootSystem.Instance?.Roll(tableId) ?? new List();
OnOpened?.Invoke(loot);
}}
}}
}}''').replace("CreateLootSystemTool.LootEntry", "LootSystem.LootEntry")
# ===========================================================================
# 46. Crafting system
# ===========================================================================
@register_tool
class CreateCraftingSystemTool(BaseUnityTool):
"""Generate a crafting system that combines items into new ones."""
name = "create_crafting_system"
description = "Generate a crafting system that combines items into new ones."
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "CraftingSystem"},
},
}
def run(self, class_name: str = "CraftingSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Items
{{
///
/// Recipe-driven crafting. Each recipe lists ingredient items/counts
/// and a resulting item. The Craft method consumes ingredients from an
/// IInventoryProvider and produces the output.
///
public class {class_name} : MonoBehaviour
{{
[Serializable]
public class Ingredient
{{
public string itemId;
public int count = 1;
}}
[Serializable]
public class Recipe
{{
public string id;
public string resultItemId;
public int resultCount = 1;
public List ingredients = new List();
public float craftTime = 2f;
public bool requiresWorkbench;
}}
public interface IInventoryProvider
{{
int Count(string itemId);
void Remove(string itemId, int count);
void Add(string itemId, int count);
}}
[Header("Recipes")]
[SerializeField] private List recipes = new List();
public IReadOnlyList Recipes => recipes;
public bool CanCraft(Recipe recipe, IInventoryProvider inv)
{{
if (recipe == null || inv == null) return false;
foreach (var ing in recipe.ingredients)
if (inv.Count(ing.itemId) < ing.count) return false;
return true;
}}
public bool Craft(Recipe recipe, IInventoryProvider inv)
{{
if (!CanCraft(recipe, inv)) return false;
foreach (var ing in recipe.ingredients) inv.Remove(ing.itemId, ing.count);
inv.Add(recipe.resultItemId, recipe.resultCount);
return true;
}}
public Recipe FindByResult(string itemId)
=> recipes.Find(r => r.resultItemId == itemId);
}}
}}
''')
# ===========================================================================
# 47. Skill tree
# ===========================================================================
@register_tool
class CreateSkillTreeTool(BaseUnityTool):
"""Generate a skill tree with XP, levels and unlockable abilities."""
name = "create_skill_tree"
description = (
"Generate a skill tree with XP, levels and unlockable abilities."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "SkillTree"},
},
}
def run(self, class_name: str = "SkillTree", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Skills
{{
///
/// Player progression. Awards XP from gameplay; levels up at thresholds
/// granting skill points. Skill points unlock nodes in a tree; each
/// node may require prerequisites and apply stat modifiers (events).
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Serializable]
public class SkillNode
{{
public string id;
public string displayName;
[TextArea] public string description;
public int cost = 1;
public string[] prerequisites;
public float statModifier = 0.1f;
public string statKey; // e.g. "max_health", "move_speed"
[NonSerialized] public bool unlocked;
}}
[Serializable]
public class SkillBranch
{{
public string id;
public string name;
public List nodes = new List();
}}
[Header("Progression")]
[SerializeField] private int level = 1;
[SerializeField] private int xp;
[SerializeField] private int xpToNext = 100;
[SerializeField] private float xpGrowth = 1.5f;
[SerializeField] private int skillPoints;
[Header("Tree")]
[SerializeField] private List branches = new List();
[SerializeField] private Dictionary stats = new Dictionary();
public int Level => level;
public int Xp => xp;
public int SkillPoints => skillPoints;
public IReadOnlyList Branches => branches;
public event Action OnLevelUp;
public event Action OnNodeUnlocked;
private void Awake()
{{
Instance = this;
stats["max_health"] = 100f;
stats["move_speed"] = 5f;
stats["sprint_speed"] = 8f;
}}
public void AddXp(int amount)
{{
xp += amount;
while (xp >= xpToNext)
{{
xp -= xpToNext;
level++;
skillPoints++;
xpToNext = Mathf.RoundToInt(xpToNext * xpGrowth);
OnLevelUp?.Invoke(level);
}}
}}
public bool UnlockNode(string branchId, string nodeId)
{{
SkillBranch branch = branches.Find(b => b.id == branchId);
if (branch == null) return false;
SkillNode node = branch.nodes.Find(n => n.id == nodeId);
if (node == null || node.unlocked || skillPoints < node.cost) return false;
if (!PrerequisitesMet(node)) return false;
node.unlocked = true;
skillPoints -= node.cost;
if (!string.IsNullOrEmpty(node.statKey))
stats[node.statKey] += stats.GetValueOrDefault(node.statKey) * node.statModifier;
OnNodeUnlocked?.Invoke(node);
return true;
}}
private bool PrerequisitesMet(SkillNode node)
{{
if (node.prerequisites == null) return true;
foreach (var prereq in node.prerequisites)
{{
bool found = false;
foreach (var branch in branches)
foreach (var n in branch.nodes)
if (n.id == prereq && n.unlocked) {{ found = true; break; }}
if (!found) return false;
}}
return true;
}}
public float GetStat(string key) => stats.GetValueOrDefault(key, 0f);
}}
}}
''')
# ===========================================================================
# 48. Faction system
# ===========================================================================
@register_tool
class CreateFactionSystemTool(BaseUnityTool):
"""Generate a faction system with territories and turf wars."""
name = "create_faction_system"
description = (
"Generate a faction system with gang territories, reputation and "
"turf wars."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "FactionSystem"},
},
}
def run(self, class_name: str = "FactionSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OpenWorld.Factions
{{
///
/// Faction & territory manager. Each faction has a reputation with the
/// player (-100 hostile..+100 allied) and owns territories (zones on
/// the map). Turf wars trigger when an attacking faction's power
/// exceeds the defender's within a zone.
///
public class {class_name} : MonoBehaviour
{{
public static {class_name} Instance {{ get; private set; }}
[Serializable]
public class Faction
{{
public string id;
public string displayName;
public Color color = Color.red;
public int power = 100;
public int reputation; // -100..100 with player
public bool hostileToPlayer;
}}
[Serializable]
public class Territory
{{
public string id;
public string ownerFactionId;
public Bounds bounds;
public int defenseValue = 50;
}}
[Header("Factions")]
[SerializeField] private List factions = new List();
[SerializeField] private List territories = new List();
[Header("Turf War")]
[SerializeField] private float warCheckInterval = 60f;
[SerializeField] private int attackThreshold = 80;
private float _warTimer;
public IReadOnlyList Factions => factions;
public IReadOnlyList Territories => territories;
public event Action OnReputationChanged;
public event Action OnTurfWarStarted;
private void Awake() {{ Instance = this; }}
public Faction GetFaction(string id) => factions.Find(f => f.id == id);
public void AdjustReputation(string factionId, int delta)
{{
Faction f = GetFaction(factionId);
if (f == null) return;
f.reputation = Mathf.Clamp(f.reputation + delta, -100, 100);
f.hostileToPlayer = f.reputation < -25;
OnReputationChanged?.Invoke(f, f.reputation);
}}
public Territory GetTerritoryAt(Vector3 pos)
{{
foreach (var t in territories) if (t.bounds.Contains(pos)) return t;
return null;
}}
private void Update()
{{
_warTimer -= Time.deltaTime;
if (_warTimer > 0f) return;
_warTimer = warCheckInterval;
EvaluateTurfWars();
}}
private void EvaluateTurfWars()
{{
foreach (var territory in territories)
{{
Faction owner = GetFaction(territory.ownerFactionId);
if (owner == null) continue;
foreach (var attacker in factions)
{{
if (attacker.id == owner.id) continue;
if (attacker.power - territory.defenseValue > attackThreshold)
{{
OnTurfWarStarted?.Invoke(attacker, owner, territory);
ResolveWar(attacker, owner, territory);
break;
}}
}}
}}
}}
private void ResolveWar(Faction attacker, Faction defender, Territory territory)
{{
if (attacker.power > defender.power + territory.defenseValue)
{{
territory.ownerFactionId = attacker.id;
attacker.power -= 30;
defender.power -= 20;
}}
else
{{
attacker.power -= 20;
defender.power -= 10;
}}
}}
}}
}}
''')
# ===========================================================================
# 49. Stunt system
# ===========================================================================
@register_tool
class CreateStuntSystemTool(BaseUnityTool):
"""Generate a stunt system with jumps, flips, points and slow-mo."""
name = "create_stunt_system"
description = (
"Generate a stunt system with jumps, flips, points and slow-mo "
"replays."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "StuntSystem"},
},
}
def run(self, class_name: str = "StuntSystem", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.Collections;
using UnityEngine;
namespace OpenWorld.Gameplay
{{
///
/// Detects vehicle stunts (jumps, flips, air-time, near-miss) and
/// awards points. On a big stunt triggers a slow-mo window and an
/// event for the replay system to capture.
///
[RequireComponent(typeof(Rigidbody))]
public class {class_name} : MonoBehaviour
{{
[Header("Detection")]
[SerializeField] private float airTimeThreshold = 0.5f;
[SerializeField] private float flipThreshold = 360f;
[SerializeField] private float nearMissDistance = 2.5f;
[SerializeField] private float nearMissSpeed = 15f;
[SerializeField] private LayerMask groundMask;
[Header("Scoring")]
[SerializeField] private int airTimePoints = 50;
[SerializeField] private int flipPoints = 200;
[SerializeField] private int nearMissPoints = 100;
[SerializeField] private int bigStuntThreshold = 500;
[Header("Slow-Mo")]
[SerializeField] private float slowMoScale = 0.3f;
[SerializeField] private float slowMoDuration = 1.5f;
[SerializeField] private float transitionSpeed = 4f;
public int TotalPoints {{ get; private set; }}
public int CurrentCombo {{ get; private set; }}
private Rigidbody _rb;
private bool _airborne;
private float _airTime;
private float _yawAccumulator;
private float _lastYaw;
private float _comboTimer;
public event Action OnStuntScored; // points, label
public event Action OnBigStunt;
private void Awake()
{{
_rb = GetComponent();
_lastYaw = transform.eulerAngles.y;
}}
private void Update()
{{
CheckAirTime();
CheckFlips();
CheckNearMiss();
UpdateCombo();
}}
private void CheckAirTime()
{{
bool grounded = Physics.Raycast(transform.position, -transform.up, 1.2f, groundMask);
if (!grounded)
{{
_airTime += Time.deltaTime;
if (!_airborne && _airTime >= airTimeThreshold)
{{
_airborne = true;
}}
}}
else if (_airborne)
{{
ScoreStunt(airTimePoints, "Air Time x" + Mathf.RoundToInt(_airTime));
_airTime = 0f;
_airborne = false;
}}
}}
private void CheckFlips()
{{
float yaw = transform.eulerAngles.y;
float delta = Mathf.DeltaAngle(_lastYaw, yaw);
_yawAccumulator += Mathf.Abs(delta);
_lastYaw = yaw;
if (_yawAccumulator >= flipThreshold)
{{
ScoreStunt(flipPoints, "Barrel Roll!");
_yawAccumulator = 0f;
}}
}}
private void CheckNearMiss()
{{
if (_rb.velocity.magnitude < nearMissSpeed) return;
Collider[] hits = Physics.OverlapSphere(transform.position, nearMissDistance);
foreach (var h in hits)
{{
if (h.transform == transform) continue;
if (h.CompareTag("Vehicle"))
{{
ScoreStunt(nearMissPoints, "Near Miss");
return;
}}
}}
}}
private void ScoreStunt(int points, string label)
{{
CurrentCombo++;
int total = points * CurrentCombo;
TotalPoints += total;
_comboTimer = 3f;
OnStuntScored?.Invoke(total, label);
if (total >= bigStuntThreshold) StartCoroutine(BigStuntSlowMo());
}}
private void UpdateCombo()
{{
if (_comboTimer <= 0f) {{ CurrentCombo = 0; return; }}
_comboTimer -= Time.deltaTime;
}}
private IEnumerator BigStuntSlowMo()
{{
OnBigStunt?.Invoke(TotalPoints);
float original = Time.timeScale;
Time.timeScale = slowMoScale;
yield return new WaitForSecondsRealtime(slowMoDuration);
float t = 0f;
while (t < transitionSpeed)
{{
t += Time.unscaledDeltaTime;
Time.timeScale = Mathf.Lerp(slowMoScale, original, t / transitionSpeed);
yield return null;
}}
Time.timeScale = original;
}}
}}
}}
''')
# ===========================================================================
# 50. Photo mode
# ===========================================================================
@register_tool
class CreatePhotoModeTool(BaseUnityTool):
"""Generate a photo mode with free camera, filters and screenshots."""
name = "create_photo_mode"
description = (
"Generate a photo mode with free camera, filters and screenshot "
"export."
)
input_schema = {
"type": "object",
"properties": {
"class_name": {"type": "string", "default": "PhotoMode"},
},
}
def run(self, class_name: str = "PhotoMode", **_: Any) -> str:
return self._wrap(f'''
using System;
using System.IO;
using UnityEngine;
using UnityEngine.PostProcessing;
namespace OpenWorld.UI
{{
///
/// Photo mode. On activation, detaches the camera into a free-fly
/// state, exposes filter presets and captures screenshots to disk via
/// ReadPixels. Press F to capture, Esc to exit.
///
public class {class_name} : MonoBehaviour
{{
public enum Filter {{ None, Vintage, Noir, Vibrant, Cool, Warm }}
[Header("Camera")]
[SerializeField] private Camera photoCamera;
[SerializeField] private float moveSpeed = 12f;
[SerializeField] private float lookSpeed = 2f;
[SerializeField] private float rollSpeed = 30f;
[SerializeField] private float fovMin = 20f;
[SerializeField] private float fovMax = 90f;
[Header("Filters")]
[SerializeField] private Material[] filterMaterials;
[Header("Capture")]
[SerializeField] private int captureWidth = 1920;
[SerializeField] private int captureHeight = 1080;
[SerializeField] private string outputFolder = "Photos";
[Header("UI")]
[SerializeField] private GameObject uiRoot;
[SerializeField] private MonoBehaviour gameplayController;
public bool Active {{ get; private set; }}
public Filter CurrentFilter {{ get; private set; }}
public event Action OnPhotoSaved;
private float _yaw, _pitch, _roll;
private void Awake()
{{
if (photoCamera == null) photoCamera = Camera.main;
if (uiRoot != null) uiRoot.SetActive(false);
}}
private void Update()
{{
if (Input.GetKeyDown(KeyCode.F3)) Toggle();
if (!Active) return;
HandleMovement();
HandleLook();
if (Input.GetKeyDown(KeyCode.F)) Capture();
if (Input.GetKeyDown(KeyCode.LeftBracket)) CycleFilter(-1);
if (Input.GetKeyDown(KeyCode.RightBracket)) CycleFilter(1);
}}
public void Toggle()
{{
Active = !Active;
if (uiRoot != null) uiRoot.SetActive(Active);
if (gameplayController != null) gameplayController.enabled = !Active;
Cursor.lockState = Active ? CursorLockMode.Locked : CursorLockMode.None;
Cursor.visible = !Active;
if (Active)
{{
_yaw = photoCamera.transform.eulerAngles.y;
_pitch = photoCamera.transform.eulerAngles.x;
}}
}}
private void HandleMovement()
{{
Vector3 move = new Vector3(
Input.GetAxis("Horizontal"),
Input.GetKey(KeyCode.Space) ? 1f : Input.GetKey(KeyCode.LeftControl) ? -1f : 0f,
Input.GetAxis("Vertical"));
float speed = Input.GetKey(KeyCode.LeftShift) ? moveSpeed * 2f : moveSpeed;
photoCamera.transform.Translate(move * speed * Time.unscaledDeltaTime, Space.Self);
float fov = photoCamera.fieldOfView - Input.GetAxis("Mouse ScrollWheel") * 20f;
photoCamera.fieldOfView = Mathf.Clamp(fov, fovMin, fovMax);
}}
private void HandleLook()
{{
_yaw += Input.GetAxis("Mouse X") * lookSpeed;
_pitch -= Input.GetAxis("Mouse Y") * lookSpeed;
if (Input.GetKey(KeyCode.Q)) _roll += rollSpeed * Time.unscaledDeltaTime;
if (Input.GetKey(KeyCode.E)) _roll -= rollSpeed * Time.unscaledDeltaTime;
photoCamera.transform.rotation = Quaternion.Euler(_pitch, _yaw, _roll);
}}
private void CycleFilter(int dir)
{{
int count = Enum.GetValues(typeof(Filter)).Length;
CurrentFilter = (Filter)(((int)CurrentFilter + dir + count) % count);
ApplyFilter();
}}
private void ApplyFilter()
{{
if (filterMaterials == null || filterMaterials.Length == 0) return;
int idx = (int)CurrentFilter;
// Apply via a full-screen blit — hook into OnRenderImage.
}}
private void OnRenderImage(RenderTexture src, RenderTexture dst)
{{
if (filterMaterials != null && (int)CurrentFilter < filterMaterials.Length && filterMaterials[(int)CurrentFilter] != null)
Graphics.Blit(src, dst, filterMaterials[(int)CurrentFilter]);
else
Graphics.Blit(src, dst);
}}
public void Capture()
{{
StartCoroutine(CaptureRoutine());
}}
private System.Collections.IEnumerator CaptureRoutine()
{{
yield return new WaitForEndOfFrame();
RenderTexture rt = new RenderTexture(captureWidth, captureHeight, 24);
photoCamera.targetTexture = rt;
photoCamera.Render();
RenderTexture.active = rt;
Texture2D tex = new Texture2D(captureWidth, captureHeight, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(0, 0, captureWidth, captureHeight), 0, 0);
tex.Apply();
photoCamera.targetTexture = null;
RenderTexture.active = null;
Destroy(rt);
string dir = Path.Combine(Application.persistentDataPath, outputFolder);
Directory.CreateDirectory(dir);
string path = Path.Combine(dir, "photo_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".png");
File.WriteAllBytes(path, tex.EncodeToPNG());
Destroy(tex);
OnPhotoSaved?.Invoke(path);
}}
}}
}}
''')
# ===========================================================================
# Module exports
# ===========================================================================
__all__ = [
"BaseUnityTool",
"TOOL_REGISTRY",
"register_tool",
"get_tool",
"list_tools",
# Tool classes (useful for direct instantiation / testing)
"CreatePlayerControllerTool",
"CreateVehicleControllerTool",
"CreateProceduralCityTool",
"CreateThirdPersonCameraTool",
"CreateFpsControllerTool",
"CreateDayNightCycleTool",
"CreateAiNpcTool",
"CreatePickupSystemTool",
"CreateHealthSystemTool",
"CreateWeaponSystemTool",
"CreateAudioManagerTool",
"CreateUiManagerTool",
"CreateTerrainGeneratorTool",
"CreateWaterShaderTool",
"CreateParticleEffectsTool",
"CreateSaveSystemTool",
"CreateInventorySystemTool",
"CreateQuestSystemTool",
"CreateBuildingGeneratorTool",
"CreateRoadNetworkTool",
"CreateWantedSystemTool",
"CreateTrafficSystemTool",
"CreatePedestrianSystemTool",
"CreateEconomySystemTool",
"CreatePhoneSystemTool",
"CreateRadioSystemTool",
"CreateWeaponWheelTool",
"CreateMinimapSystemTool",
"CreateExplosionSystemTool",
"CreateRagdollSystemTool",
"CreateClothPhysicsTool",
"CreateWeatherSystemTool",
"CreateCctvSystemTool",
"CreateDialogueSystemTool",
"CreateMissionSystemTool",
"CreateGarageSystemTool",
"CreatePropertySystemTool",
"CreateCharacterCustomizationTool",
"CreateCutsceneSystemTool",
"CreateMultiplayerNetcodeTool",
"SetupUnityProjectTool",
"WriteCsharpScriptTool",
"WriteSceneFileTool",
"GenerateCompleteGameTool",
"CreateLootSystemTool",
"CreateCraftingSystemTool",
"CreateSkillTreeTool",
"CreateFactionSystemTool",
"CreateStuntSystemTool",
"CreatePhotoModeTool",
]
if __name__ == "__main__":
# Quick self-check: print tool count and line counts for each generated file.
print(f"Registered tools: {len(TOOL_REGISTRY)}")
total = 0
for t in TOOL_REGISTRY:
try:
code = t.run()
lines = code.count("\n") + 1
total += lines
print(f" {t.name:32s} -> {lines:5d} lines")
except Exception as exc: # pragma: no cover
print(f" {t.name:32s} -> ERROR: {exc}")
print(f"Total generated C# lines: {total}")