A polygon-based zone system for defining map regions with encounter difficulty, modifiers, and safe areas.
The Zone System lets you paint polygon regions on your map and assign encounter rules to each region. At runtime, you query a world position and get back a fully resolved ZoneContext with the encounter table, difficulty tier, and chance.
ZonesObjectHolder component.| Map Plane | Use Case | Axes | Ignored |
|---|---|---|---|
XY | Flat sprite map, UI map | X, Y | Z |
XZ | 3D world map (standard Unity 3D) | X, Z | Y |
YZ | Side-on map | Y, Z | X |
The Zone Editor window (Window → Zone System → Zone Editor) is the primary tool for managing zones.
Shows all ZoneInstance objects in the current scene. Each row displays:
📋): Creates an independent copy with a new asset✕): Removes the zone and its assetZones with missing ZoneData show a warning icon with options to add data or delete the zone.
Click Create New Zone to open the creation dropdown:
The zone is created in-memory and enters edit mode immediately. You must click Save to persist the asset.
When editing a zone, all ZoneData fields are available inline:
On save, the editor checks for:
zoneIdIf a conflict is found, an error is displayed and saving is blocked.
At the bottom of the editor window, expand Export Zones to JSON to export all scene zones to a JSON file for runtime loading.
When a zone is in edit mode, yellow handles appear in the Scene view for each polygon vertex.
| Action | Input | Description |
|---|---|---|
| Move vertex | Drag handle | Drag any yellow handle to reposition a vertex |
| Insert vertex | Ctrl + Click edge | Adds a new vertex on the closest edge (cyan highlight shows target) |
| Delete vertex | Shift + Click vertex | Removes the vertex (minimum 3 vertices). Handles turn red while Shift is held. |
| Stop editing | Esc | Exits shape edit mode |
| Shape | Default | Notes |
|---|---|---|
Square | 4 vertices, 2-unit half-size | Can be reshaped into any quad |
Circle | 24-segment approximation, radius 2 | Drag the radius handle to resize. Regenerates vertices on radius change. |
Polygon | 12 vertices, radius 3 | Fully freeform — add, remove, and drag vertices freely |
ZoneData asset| Role | Purpose | Fields |
|---|---|---|
| Base | Defines the encounter table and baseline difficulty | Encounter Table ID, Difficulty Tier, Encounter Chance |
| Modifier | Stacks multiplicatively on top of a Base zone | Chance Multiplier, Difficulty Tier Bonus |
| Override | Replaces everything — towns, story events, safe areas | Is Safe Zone, Encounter Table ID, Encounter Chance, Difficulty Tier |
When querying a world position, the ZoneResolver follows this order:
ZoneContextModifiers are multiplicative, so each one is independent:
Base chance 0.30 x Cursed Road 1.8 x Night Modifier 1.2 = 0.648
Higher priority values take precedence. When multiple zones of the same role overlap, the highest-priority one wins (for Base and Override) or all are stacked (for Modifier).
Access via Window → Zone System → Settings. If no settings asset exists, one is created automatically.
| Field | Description | Default |
|---|---|---|
mapPlane | Which two world axes your map lies on | XZ |
zoneDataFolder | Folder path where new ZoneData assets are saved | Assets/ZoneData |
roleColors | Debug color for each zone role (used in scene rendering) | Blue (Base), Yellow (Modifier), Green (Override) |
Each ZoneRole has a configurable color in the settings. When you change a zone’s role in the editor, its debug color is automatically updated to match. Colors are used for:
ZoneRole enum, call SyncRoleEntries() on the settings asset or click the settings menu item — missing roles will be added with a default gray color.
The main entry point for runtime zone queries.
// Create the API with a reference to the ZonesObjectHolder
ZoneSystemApi api = new ZoneSystemApi(zonesObjectHolder);
// Full zone resolution at a world position
ZoneContext ctx = api.QueryZone(partyWorldPosition);
if(!ctx.isSafe && Random.value < ctx.finalEncounterChance)
TriggerEncounter(ctx.encounterTableId, ctx.finalDifficultyTier);
| Method | Returns | Description |
|---|---|---|
QueryZone(Vector3) | ZoneContext | Full resolution: finds overlapping zones, applies modifiers, returns final context |
GetOverlappingZones(Vector3) | List<ZoneData> | Raw list of all zones containing the position, sorted by descending priority |
IsInSafeZone(Vector3) | bool | Quick check — true if any Override zone with isSafeZone contains the position |
Register(ZoneInstance) | void | Register a dynamically spawned zone |
Unregister(ZoneInstance) | void | Unregister a zone before destroying it |
| Field | Type | Description |
|---|---|---|
encounterTableId | string | ID of the encounter table to use |
finalEncounterChance | float | Final encounter probability (0–1), after modifier stacking |
finalDifficultyTier | DifficultyTier | Final difficulty tier, after modifier bonuses |
isSafe | bool | True if in a safe zone (no encounters) |
resolvedZoneName | string | Name of the zone that “won” resolution (for debug/UI) |
// After instantiating a zone at runtime:
api.Register(zoneInstance);
// Before destroying:
api.Unregister(zoneInstance);
| Value | Description |
|---|---|
Base | Provides the encounter table and baseline difficulty |
Modifier | Mutates difficulty/chance on top of a Base zone |
Override | Completely replaces everything (safe towns, story events) |
| Value | Description |
|---|---|
Square | 4-vertex quadrilateral |
Circle | 24-segment circular approximation with adjustable radius |
Polygon | Freeform polygon with 12 default vertices |
| Value | Int |
|---|---|
Safe | 0 |
Mild | 1 |
Moderate | 2 |
Dangerous | 3 |
Deadly | 4 |
| Value | Axes | Depth |
|---|---|---|
XY | X, Y | Z |
XZ | X, Z | Y |
YZ | Y, Z | X |
Per-zone configuration asset. Created via the Zone Editor or Create → ZoneSystem → Zone Data.
| Field | Type | Description |
|---|---|---|
zoneId | string | Unique identifier |
zoneName | string | Display name |
role | ZoneRole | Base, Modifier, or Override |
priority | int | Higher wins in same-role conflicts |
debugColor | Color | Scene visualization color (auto-set from role) |
shape | ZoneShape | Shape type |
circleRadius | float | Radius (Circle shape only) |
polygon | List<Vector2> | Vertex positions (local to transform) |
Placed on a scene GameObject. References a ZoneData asset and provides spatial queries.
| Field / Method | Description |
|---|---|
data | Reference to the ZoneData asset |
Contains(Vector3, MapPlane) | Returns true if the world position is inside this zone |
RebuildBoundsCache() | Recalculates the AABB cache (call after modifying polygon) |
Scene manager that holds the map plane setting and provides access to all zones.
| Field / Property | Description |
|---|---|
mapPlane | Which plane the map lies on (XY, XZ, or YZ) |
AllZones | Read-only list of all ZoneInstance objects in the scene |
Static math utilities for polygon operations.
| Method | Description |
|---|---|
PointInPolygon(Vector2, List<Vector2>) | Ray-casting point-in-polygon test (Jordan curve theorem) |
PointInPolygon(Vector3, List<Vector2>, MapPlane) | Projects world position to plane, then tests |
Centroid(List<Vector2>) | Average center of polygon vertices |
Bounds(List<Vector2>) | Axis-aligned bounding box (min, max) |
PointInBounds(Vector2, Vector2, Vector2) | Fast AABB pre-check |
Triangulate(List<Vector2>) | Ear-clipping triangulation for concave polygon rendering |
Converts between 3D world positions and 2D plane coordinates.
| Method | Description |
|---|---|
ProjectToPlane(Vector3, MapPlane) | 3D world → 2D plane coordinates |
UnprojectFromPlane(Vector2, MapPlane, float) | 2D plane coordinates → 3D world |
Generates default polygon vertices for each shape type.
| Method | Description |
|---|---|
CreateDefault(ZoneShape) | Returns default vertices for the given shape |
CreateSquare(float) | 4-vertex square with given half-size |
CreateCircle(float, int) | N-segment circle approximation |
CreatePolygon(float, int) | Regular polygon with N vertices |
RegenerateCircle(ZoneData) | Rebuilds circle vertices from current radius |
Pure logic for resolving overlapping zones into a single ZoneContext.
| Method | Description |
|---|---|
Resolve(List<ZoneData>) | Takes overlapping zone data, applies role priority and modifier stacking, returns ZoneContext |
Serializes scene zones to a JSON structure for runtime loading.
| Method | Description |
|---|---|
BuildExport(ZoneInstance[], MapPlane) | Builds the export data structure from scene instances |
ToJson(ZoneExportRoot, bool) | Converts to JSON string (optionally pretty-printed) |
In the Zone Editor window, expand Export Zones to JSON, set the output path, and click Export Now.
string json = File.ReadAllText(Application.streamingAssetsPath + "/zones.json");
ZoneExportRoot root = JsonUtility.FromJson<ZoneExportRoot>(json);
Each zone is exported as a ZoneExportEntry containing all zone data fields plus the world-space polygon coordinates and transform position.
| Key | Context | Action |
|---|---|---|
| Esc | Scene view, shape editing active | Stop editing the zone shape |
| Ctrl + Click | Scene view, shape editing active | Insert a vertex on the nearest edge |
| Shift + Click | Scene view, shape editing active | Delete the clicked vertex (min 3) |
| Menu Path | Description |
|---|---|
| Window → Zone System → Zone Editor | Opens the main Zone Editor window |
| Window → Zone System → Settings | Selects (or creates) the ZoneEditorSettings asset |
| Window → Zone System → Documentation | Opens this documentation in your default browser |
| Jovian → ZoneSystem → Zone Editor Settings | Create menu for new ZoneEditorSettings asset |
| ZoneSystem → Zone Data | Create menu for new ZoneData asset |
Jovian Zone System v0.1.0 — com.jovian.zonesystem