diff --git a/.claude/worktrees/dazzling-hoover-3d45a2/.claude/settings.local.json b/.claude/worktrees/dazzling-hoover-3d45a2/.claude/settings.local.json
index b5a1a86..9a16201 100644
--- a/.claude/worktrees/dazzling-hoover-3d45a2/.claude/settings.local.json
+++ b/.claude/worktrees/dazzling-hoover-3d45a2/.claude/settings.local.json
@@ -6,7 +6,8 @@
"Bash(findstr /v \"Library\\\\PackageCache\")",
"Bash(dir \"C:\\\\Users\\\\catos\\\\UnityTowerDefense\" /a-d)",
"Bash(mkdir -p \"C:\\\\Users\\\\catos\\\\UnityTowerDefense\\\\Assets\\\\_Project\\\\UI\")",
- "Bash(mkdir -p \"C:\\\\Users\\\\catos\\\\UnityTowerDefense\\\\Assets\\\\_Project\\\\Scripts\\\\UI\")"
+ "Bash(mkdir -p \"C:\\\\Users\\\\catos\\\\UnityTowerDefense\\\\Assets\\\\_Project\\\\Scripts\\\\UI\")",
+ "Bash(mkdir -p \"C:/Users/catos/UnityTowerDefense/Assets/_Project/Scripts/UI/Minimap\")"
]
}
}
diff --git a/Assets/_Project/Art/Textures/MinimapRT.renderTexture b/Assets/_Project/Art/Textures/MinimapRT.renderTexture
deleted file mode 100644
index 5f7d9fb..0000000
--- a/Assets/_Project/Art/Textures/MinimapRT.renderTexture
+++ /dev/null
@@ -1,39 +0,0 @@
-%YAML 1.1
-%TAG !u! tag:unity3d.com,2011:
---- !u!84 &8400000
-RenderTexture:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_Name: MinimapRT
- m_ImageContentsHash:
- serializedVersion: 2
- Hash: 00000000000000000000000000000000
- m_IsAlphaChannelOptional: 0
- serializedVersion: 6
- m_Width: 256
- m_Height: 256
- m_AntiAliasing: 1
- m_MipCount: -1
- m_DepthStencilFormat: 94
- m_ColorFormat: 8
- m_MipMap: 0
- m_GenerateMips: 1
- m_SRGB: 0
- m_UseDynamicScale: 0
- m_UseDynamicScaleExplicit: 0
- m_BindMS: 0
- m_EnableCompatibleFormat: 1
- m_EnableRandomWrite: 0
- m_TextureSettings:
- serializedVersion: 2
- m_FilterMode: 1
- m_Aniso: 0
- m_MipBias: 0
- m_WrapU: 1
- m_WrapV: 1
- m_WrapW: 1
- m_Dimension: 2
- m_VolumeDepth: 1
- m_ShadowSamplingMode: 2
diff --git a/Assets/_Project/Scenes/Levels/Main.unity b/Assets/_Project/Scenes/Levels/Main.unity
index 69b7952..d8a2e6d 100644
--- a/Assets/_Project/Scenes/Levels/Main.unity
+++ b/Assets/_Project/Scenes/Levels/Main.unity
@@ -237,134 +237,6 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!1 &260379225
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 260379229}
- - component: {fileID: 260379228}
- - component: {fileID: 260379226}
- m_Layer: 0
- m_Name: MinimapCamera
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &260379226
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 260379225}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
- m_Name:
- m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
- m_RenderShadows: 1
- m_RequiresDepthTextureOption: 2
- m_RequiresOpaqueTextureOption: 2
- m_CameraType: 0
- m_Cameras: []
- m_RendererIndex: -1
- m_VolumeLayerMask:
- serializedVersion: 2
- m_Bits: 1
- m_VolumeTrigger: {fileID: 0}
- m_VolumeFrameworkUpdateModeOption: 2
- m_RenderPostProcessing: 0
- m_Antialiasing: 0
- m_AntialiasingQuality: 2
- m_StopNaN: 0
- m_Dithering: 0
- m_ClearDepth: 1
- m_AllowXRRendering: 1
- m_AllowHDROutput: 1
- m_UseScreenCoordOverride: 0
- m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
- m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
- m_RequiresDepthTexture: 0
- m_RequiresColorTexture: 0
- m_TaaSettings:
- m_Quality: 3
- m_FrameInfluence: 0.1
- m_JitterScale: 1
- m_MipBias: 0
- m_VarianceClampScale: 0.9
- m_ContrastAdaptiveSharpening: 0
- m_Version: 2
---- !u!20 &260379228
-Camera:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 260379225}
- m_Enabled: 1
- serializedVersion: 2
- m_ClearFlags: 1
- m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
- m_projectionMatrixMode: 1
- m_GateFitMode: 2
- m_FOVAxisMode: 0
- m_Iso: 200
- m_ShutterSpeed: 0.005
- m_Aperture: 16
- m_FocusDistance: 10
- m_FocalLength: 50
- m_BladeCount: 5
- m_Curvature: {x: 2, y: 11}
- m_BarrelClipping: 0.25
- m_Anamorphism: 0
- m_SensorSize: {x: 36, y: 24}
- m_LensShift: {x: 0, y: 0}
- m_NormalizedViewPortRect:
- serializedVersion: 2
- x: 0
- y: 0
- width: 1
- height: 1
- near clip plane: 0.3
- far clip plane: 1000
- field of view: 60
- orthographic: 1
- orthographic size: 80
- m_Depth: 0
- m_CullingMask:
- serializedVersion: 2
- m_Bits: 983
- m_RenderingPath: -1
- m_TargetTexture: {fileID: 8400000, guid: e640b4de7a1dee049b38712e7eff82ea, type: 2}
- m_TargetDisplay: 0
- m_TargetEye: 3
- m_HDR: 1
- m_AllowMSAA: 1
- m_AllowDynamicResolution: 0
- m_ForceIntoRT: 0
- m_OcclusionCulling: 1
- m_StereoConvergence: 10
- m_StereoSeparation: 0.022
---- !u!4 &260379229
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 260379225}
- serializedVersion: 2
- m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
- m_LocalPosition: {x: 14, y: 100, z: 39}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
- m_Children: []
- m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!1 &304575571
GameObject:
m_ObjectHideFlags: 0
@@ -748,7 +620,7 @@ Transform:
m_GameObject: {fileID: 441239879}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 1, y: 0, z: 2}
+ m_LocalPosition: {x: 5, y: 0, z: 3}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -867,7 +739,7 @@ Transform:
m_GameObject: {fileID: 611926972}
serializedVersion: 2
m_LocalRotation: {x: -0, y: 0.70710576, z: -0, w: 0.70710784}
- m_LocalPosition: {x: 39, y: 2, z: 40}
+ m_LocalPosition: {x: 43, y: 2, z: 41}
m_LocalScale: {x: 100, y: 5, z: 20}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -979,7 +851,7 @@ Transform:
m_GameObject: {fileID: 643505902}
serializedVersion: 2
m_LocalRotation: {x: -0, y: 0.70710576, z: -0, w: 0.70710784}
- m_LocalPosition: {x: 4, y: 2, z: 41}
+ m_LocalPosition: {x: 8, y: 2, z: 42}
m_LocalScale: {x: 8, y: 5, z: 12.6126}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -1098,8 +970,8 @@ BoxCollider:
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
- m_Size: {x: 32, y: 0.5, z: 87}
- m_Center: {x: 14.5, y: 0, z: 41}
+ m_Size: {x: 36, y: 0.5, z: 88}
+ m_Center: {x: 12.5, y: 0, z: 40.5}
--- !u!1 &1058315973
GameObject:
m_ObjectHideFlags: 0
@@ -1132,7 +1004,7 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::TD.UI.HUDController
placementController: {fileID: 1597884411}
placementManager: {fileID: 1507514108}
- minimapRenderTexture: {fileID: 8400000, guid: e640b4de7a1dee049b38712e7eff82ea, type: 2}
+ cameraController: {fileID: 1239994223}
rejectionMessageDuration: 2.5
--- !u!114 &1058315975
MonoBehaviour:
@@ -1479,8 +1351,8 @@ BoxCollider:
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
- m_Size: {x: 7, y: 1, z: 2}
- m_Center: {x: 0, y: 0, z: 2.5}
+ m_Size: {x: 7, y: 1, z: 4}
+ m_Center: {x: 0, y: 0, z: 1.5}
--- !u!1 &1464027360
GameObject:
m_ObjectHideFlags: 0
@@ -1588,7 +1460,7 @@ Transform:
m_GameObject: {fileID: 1464027360}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068}
- m_LocalPosition: {x: 14, y: 0, z: 39}
+ m_LocalPosition: {x: 14, y: 0, z: 41}
m_LocalScale: {x: 10, y: 1, z: 5}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -1990,7 +1862,7 @@ Transform:
m_GameObject: {fileID: 1949204941}
serializedVersion: 2
m_LocalRotation: {x: -0, y: 0.70710576, z: -0, w: 0.70710784}
- m_LocalPosition: {x: -11, y: 2, z: 40}
+ m_LocalPosition: {x: -7, y: 2, z: 41}
m_LocalScale: {x: 100, y: 5, z: 20}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -2170,7 +2042,7 @@ Transform:
m_GameObject: {fileID: 2024858685}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 14, y: 2, z: 89}
+ m_LocalPosition: {x: 18, y: 2, z: 90}
m_LocalScale: {x: 50, y: 5, z: 5}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -2282,7 +2154,7 @@ Transform:
m_GameObject: {fileID: 2105067734}
serializedVersion: 2
m_LocalRotation: {x: -0, y: 0.70710576, z: -0, w: 0.70710784}
- m_LocalPosition: {x: 24, y: 2, z: 41}
+ m_LocalPosition: {x: 28, y: 2, z: 42}
m_LocalScale: {x: 8, y: 5, z: 12.6126}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -2309,4 +2181,3 @@ SceneRoots:
- {fileID: 611926976}
- {fileID: 1222526238}
- {fileID: 1058315976}
- - {fileID: 260379229}
diff --git a/Assets/_Project/Scenes/Levels/TestLevel.asset b/Assets/_Project/Scenes/Levels/TestLevel.asset
index 5ab171e..169ec4f 100644
--- a/Assets/_Project/Scenes/Levels/TestLevel.asset
+++ b/Assets/_Project/Scenes/Levels/TestLevel.asset
@@ -18,208 +18,222 @@ MonoBehaviour:
Author: Matt
MapThumbnail: {fileID: 21300000, guid: d2e652d3e1c53454d80d3c1ec7888998, type: 3}
ScenePath: Assets/_Project/Scenes/Levels/Main.unity
- AuthoringHash: 613b9977ac3706b8fe14b6bc0e37256512c8c8d890d3051a3969d210e802649c
- LastBakeTimestamp: 2026-05-09T00:16:03.7794768Z
+ AuthoringHash: 23fbb3b3dc3b0e03aa6f52cd2607c08275b33919120b2d96ea68b69ade0656f9
+ LastBakeTimestamp: 2026-05-11T05:20:39.4889478Z
LastBakeOutcome: 1
LastBakeWarningCount: 2
GridOriginTile: {x: 0, y: 0}
- GridSize: {x: 32, y: 87}
- PlacementGrid: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202020202020200000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000000000000000000000000000202020202020200000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000002020202020202000000000000000000000000000000000000000000000000000202020202020200000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000002020202020202000000000000000000000000000000000000000000000000000202020202020200000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000002020202020202000000000000000000000000000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000002020202020202000000000000000000000000000000000000000000000000000202020202020200000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000002020202020202000000000000000000000000000000000000000000000000000202020202020200000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000
- WalkabilityGrid: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101010101010100000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000001010101010101010101010101010101010101010101010101010101010100000101010101010101010101010101010101010101010101010101010101010000010101010101010101010101010101010101010101010101010101010101000000000000000000000000000101010101010100000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000001010101010101000000000000000000000000000000000000000000000000000101010101010100000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000001010101010101000000000000000000000000000000000000000000000000000101010101010100000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000001010101010101000000000000000000000000000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000001010101010101000000000000000000000000000000000000000000000000000101010101010100000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000001010101010101000000000000000000000000000000000000000000000000000101010101010100000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000
- OwnerGrid: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000002020202020202020202020202020202020202020202020202020202020200000202020202020202020202020202020202020202020202020202020202020000020202020202020202020202020202020202020202020202020202020202000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000101010101010101010101010101010101010101010101010101010101000000010101010101010101010101010101010101010101010101010101010100000001010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
- MapAreaGrid: 010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101
+ GridSize: {x: 36, y: 88}
+ PlacementGrid: 000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000000000000000000000000000000000020202020202020000000000000000000000000000
+ WalkabilityGrid: 000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000000000000000000000000000000000010101010101010000000000000000000000000000
+ OwnerGrid: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000202020202020202020202020202020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ MapAreaGrid: 010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101
PlayerZones:
- Owner: 1
Spawners:
- SpawnerIdInZone: 0
- TilePosition: {x: 14, y: 83}
+ TilePosition: {x: 18, y: 84}
TileArea:
- - {x: 11, y: 80}
- - {x: 12, y: 80}
- - {x: 13, y: 80}
- - {x: 14, y: 80}
- - {x: 15, y: 80}
- - {x: 16, y: 80}
- - {x: 17, y: 80}
- - {x: 11, y: 81}
- - {x: 12, y: 81}
- - {x: 13, y: 81}
- - {x: 14, y: 81}
- {x: 15, y: 81}
- {x: 16, y: 81}
- {x: 17, y: 81}
- - {x: 11, y: 82}
- - {x: 12, y: 82}
- - {x: 13, y: 82}
- - {x: 14, y: 82}
+ - {x: 18, y: 81}
+ - {x: 19, y: 81}
+ - {x: 20, y: 81}
+ - {x: 21, y: 81}
- {x: 15, y: 82}
- {x: 16, y: 82}
- {x: 17, y: 82}
- - {x: 11, y: 83}
- - {x: 12, y: 83}
- - {x: 13, y: 83}
- - {x: 14, y: 83}
+ - {x: 18, y: 82}
+ - {x: 19, y: 82}
+ - {x: 20, y: 82}
+ - {x: 21, y: 82}
- {x: 15, y: 83}
- {x: 16, y: 83}
- {x: 17, y: 83}
- - {x: 11, y: 84}
- - {x: 12, y: 84}
- - {x: 13, y: 84}
- - {x: 14, y: 84}
+ - {x: 18, y: 83}
+ - {x: 19, y: 83}
+ - {x: 20, y: 83}
+ - {x: 21, y: 83}
- {x: 15, y: 84}
- {x: 16, y: 84}
- {x: 17, y: 84}
- - {x: 11, y: 85}
- - {x: 12, y: 85}
- - {x: 13, y: 85}
- - {x: 14, y: 85}
+ - {x: 18, y: 84}
+ - {x: 19, y: 84}
+ - {x: 20, y: 84}
+ - {x: 21, y: 84}
- {x: 15, y: 85}
- {x: 16, y: 85}
- {x: 17, y: 85}
- - {x: 11, y: 86}
- - {x: 12, y: 86}
- - {x: 13, y: 86}
- - {x: 14, y: 86}
+ - {x: 18, y: 85}
+ - {x: 19, y: 85}
+ - {x: 20, y: 85}
+ - {x: 21, y: 85}
- {x: 15, y: 86}
- {x: 16, y: 86}
- {x: 17, y: 86}
+ - {x: 18, y: 86}
+ - {x: 19, y: 86}
+ - {x: 20, y: 86}
+ - {x: 21, y: 86}
+ - {x: 15, y: 87}
+ - {x: 16, y: 87}
+ - {x: 17, y: 87}
+ - {x: 18, y: 87}
+ - {x: 19, y: 87}
+ - {x: 20, y: 87}
+ - {x: 21, y: 87}
Facing: 1
LeakExits:
- Target: 2
TileArea:
- - {x: 11, y: 37}
- - {x: 12, y: 37}
- - {x: 13, y: 37}
- - {x: 14, y: 37}
- - {x: 15, y: 37}
- - {x: 16, y: 37}
- - {x: 17, y: 37}
- - {x: 11, y: 38}
- - {x: 12, y: 38}
- - {x: 13, y: 38}
- - {x: 14, y: 38}
- {x: 15, y: 38}
- {x: 16, y: 38}
- {x: 17, y: 38}
- - {x: 11, y: 39}
- - {x: 12, y: 39}
- - {x: 13, y: 39}
- - {x: 14, y: 39}
+ - {x: 18, y: 38}
+ - {x: 19, y: 38}
+ - {x: 20, y: 38}
+ - {x: 21, y: 38}
- {x: 15, y: 39}
- {x: 16, y: 39}
- {x: 17, y: 39}
- - {x: 11, y: 40}
- - {x: 12, y: 40}
- - {x: 13, y: 40}
- - {x: 14, y: 40}
+ - {x: 18, y: 39}
+ - {x: 19, y: 39}
+ - {x: 20, y: 39}
+ - {x: 21, y: 39}
- {x: 15, y: 40}
- {x: 16, y: 40}
- {x: 17, y: 40}
- - {x: 11, y: 41}
- - {x: 12, y: 41}
- - {x: 13, y: 41}
- - {x: 14, y: 41}
+ - {x: 18, y: 40}
+ - {x: 19, y: 40}
+ - {x: 20, y: 40}
+ - {x: 21, y: 40}
- {x: 15, y: 41}
- {x: 16, y: 41}
- {x: 17, y: 41}
- - {x: 11, y: 42}
- - {x: 12, y: 42}
- - {x: 13, y: 42}
- - {x: 14, y: 42}
+ - {x: 18, y: 41}
+ - {x: 19, y: 41}
+ - {x: 20, y: 41}
+ - {x: 21, y: 41}
- {x: 15, y: 42}
- {x: 16, y: 42}
- {x: 17, y: 42}
- - {x: 11, y: 43}
- - {x: 12, y: 43}
- - {x: 13, y: 43}
- - {x: 14, y: 43}
+ - {x: 18, y: 42}
+ - {x: 19, y: 42}
+ - {x: 20, y: 42}
+ - {x: 21, y: 42}
- {x: 15, y: 43}
- {x: 16, y: 43}
- {x: 17, y: 43}
- - {x: 11, y: 44}
- - {x: 12, y: 44}
- - {x: 13, y: 44}
- - {x: 14, y: 44}
+ - {x: 18, y: 43}
+ - {x: 19, y: 43}
+ - {x: 20, y: 43}
+ - {x: 21, y: 43}
- {x: 15, y: 44}
- {x: 16, y: 44}
- {x: 17, y: 44}
- - {x: 11, y: 45}
- - {x: 12, y: 45}
- - {x: 13, y: 45}
- - {x: 14, y: 45}
+ - {x: 18, y: 44}
+ - {x: 19, y: 44}
+ - {x: 20, y: 44}
+ - {x: 21, y: 44}
- {x: 15, y: 45}
- {x: 16, y: 45}
- {x: 17, y: 45}
+ - {x: 18, y: 45}
+ - {x: 19, y: 45}
+ - {x: 20, y: 45}
+ - {x: 21, y: 45}
+ - {x: 15, y: 46}
+ - {x: 16, y: 46}
+ - {x: 17, y: 46}
+ - {x: 18, y: 46}
+ - {x: 19, y: 46}
+ - {x: 20, y: 46}
+ - {x: 21, y: 46}
NormalizedWeight: 1
- Owner: 2
Spawners:
- SpawnerIdInZone: 0
- TilePosition: {x: 14, y: 40}
+ TilePosition: {x: 18, y: 41}
TileArea:
- - {x: 11, y: 37}
- - {x: 12, y: 37}
- - {x: 13, y: 37}
- - {x: 14, y: 37}
- - {x: 15, y: 37}
- - {x: 16, y: 37}
- - {x: 17, y: 37}
- - {x: 11, y: 38}
- - {x: 12, y: 38}
- - {x: 13, y: 38}
- - {x: 14, y: 38}
- {x: 15, y: 38}
- {x: 16, y: 38}
- {x: 17, y: 38}
- - {x: 11, y: 39}
- - {x: 12, y: 39}
- - {x: 13, y: 39}
- - {x: 14, y: 39}
+ - {x: 18, y: 38}
+ - {x: 19, y: 38}
+ - {x: 20, y: 38}
+ - {x: 21, y: 38}
- {x: 15, y: 39}
- {x: 16, y: 39}
- {x: 17, y: 39}
- - {x: 11, y: 40}
- - {x: 12, y: 40}
- - {x: 13, y: 40}
- - {x: 14, y: 40}
+ - {x: 18, y: 39}
+ - {x: 19, y: 39}
+ - {x: 20, y: 39}
+ - {x: 21, y: 39}
- {x: 15, y: 40}
- {x: 16, y: 40}
- {x: 17, y: 40}
- - {x: 11, y: 41}
- - {x: 12, y: 41}
- - {x: 13, y: 41}
- - {x: 14, y: 41}
+ - {x: 18, y: 40}
+ - {x: 19, y: 40}
+ - {x: 20, y: 40}
+ - {x: 21, y: 40}
- {x: 15, y: 41}
- {x: 16, y: 41}
- {x: 17, y: 41}
- - {x: 11, y: 42}
- - {x: 12, y: 42}
- - {x: 13, y: 42}
- - {x: 14, y: 42}
+ - {x: 18, y: 41}
+ - {x: 19, y: 41}
+ - {x: 20, y: 41}
+ - {x: 21, y: 41}
- {x: 15, y: 42}
- {x: 16, y: 42}
- {x: 17, y: 42}
- - {x: 11, y: 43}
- - {x: 12, y: 43}
- - {x: 13, y: 43}
- - {x: 14, y: 43}
+ - {x: 18, y: 42}
+ - {x: 19, y: 42}
+ - {x: 20, y: 42}
+ - {x: 21, y: 42}
- {x: 15, y: 43}
- {x: 16, y: 43}
- {x: 17, y: 43}
+ - {x: 18, y: 43}
+ - {x: 19, y: 43}
+ - {x: 20, y: 43}
+ - {x: 21, y: 43}
+ - {x: 15, y: 44}
+ - {x: 16, y: 44}
+ - {x: 17, y: 44}
+ - {x: 18, y: 44}
+ - {x: 19, y: 44}
+ - {x: 20, y: 44}
+ - {x: 21, y: 44}
Facing: 1
LeakExits: []
Goals:
- TileArea:
- - {x: 11, y: 1}
- - {x: 12, y: 1}
- - {x: 13, y: 1}
- - {x: 14, y: 1}
+ - {x: 15, y: 0}
+ - {x: 16, y: 0}
+ - {x: 17, y: 0}
+ - {x: 18, y: 0}
+ - {x: 19, y: 0}
+ - {x: 20, y: 0}
+ - {x: 21, y: 0}
- {x: 15, y: 1}
- {x: 16, y: 1}
- {x: 17, y: 1}
- - {x: 11, y: 2}
- - {x: 12, y: 2}
- - {x: 13, y: 2}
- - {x: 14, y: 2}
+ - {x: 18, y: 1}
+ - {x: 19, y: 1}
+ - {x: 20, y: 1}
+ - {x: 21, y: 1}
- {x: 15, y: 2}
- {x: 16, y: 2}
- {x: 17, y: 2}
+ - {x: 18, y: 2}
+ - {x: 19, y: 2}
+ - {x: 20, y: 2}
+ - {x: 21, y: 2}
+ - {x: 15, y: 3}
+ - {x: 16, y: 3}
+ - {x: 17, y: 3}
+ - {x: 18, y: 3}
+ - {x: 19, y: 3}
+ - {x: 20, y: 3}
+ - {x: 21, y: 3}
diff --git a/Assets/_Project/Scenes/Levels/TestLevel_Thumbnail.png b/Assets/_Project/Scenes/Levels/TestLevel_Thumbnail.png
index 69e9589..5677440 100644
--- a/Assets/_Project/Scenes/Levels/TestLevel_Thumbnail.png
+++ b/Assets/_Project/Scenes/Levels/TestLevel_Thumbnail.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:57a63c71a6e777bc1d8852d8ba23b96474946cb7aa9b424f35a64cf7fd3149b4
-size 8744
+oid sha256:af5c029677d941da474a8bf47c7bafbf704308b7bde5335f4f311d0da621316c
+size 9770
diff --git a/Assets/_Project/Scripts/Core/PlayerColors.cs b/Assets/_Project/Scripts/Core/PlayerColors.cs
index 3d74d31..3fe5280 100644
--- a/Assets/_Project/Scripts/Core/PlayerColors.cs
+++ b/Assets/_Project/Scripts/Core/PlayerColors.cs
@@ -6,10 +6,11 @@ namespace TD.Core
/// Canonical color palette for player slots, used by editor gizmos to color volumes by owner.
///
///
- /// Color names follow the 9-player map design doc (red, green, blue, purple, yellow, gray,
- /// teal, olive, dark gray). The exact RGB values have been tuned for readability when drawn
- /// as translucent gizmos against Unity's default scene-view background. Tweak the constants
- /// below if any color reads poorly in practice.
+ /// Color names follow the 9-player map design doc (orange, green, blue, purple, yellow, gray,
+ /// teal, olive, dark gray). Red is reserved for enemy units on the minimap, so Player 1 uses
+ /// orange instead. The exact RGB values have been tuned for readability when drawn as
+ /// translucent gizmos against Unity's default scene-view background. Tweak the constants below
+ /// if any color reads poorly in practice.
///
/// The "ErrorPink" color is reserved for diagnostic use: if a volume's owner is
/// (which should never happen on a valid volume), the gizmo
@@ -19,7 +20,7 @@ namespace TD.Core
{
// Player colors. Hex values are RGB; alpha is set per-gizmo at draw time.
// Values are tuned to be saturated enough to read against Unity's default scene background.
- private static readonly Color Player1Red = HexRGB(0xE0, 0x3A, 0x3A); // red
+ private static readonly Color Player1Orange = HexRGB(0xE0, 0x7A, 0x2E); // orange (red reserved for enemies)
private static readonly Color Player2Green = HexRGB(0x3A, 0xC0, 0x4A); // green
private static readonly Color Player3Blue = HexRGB(0x3A, 0x7A, 0xE0); // blue
private static readonly Color Player4Purple = HexRGB(0xA0, 0x4A, 0xC0); // purple
@@ -43,7 +44,7 @@ namespace TD.Core
{
switch (slot)
{
- case PlayerSlot.Player1: return Player1Red;
+ case PlayerSlot.Player1: return Player1Orange;
case PlayerSlot.Player2: return Player2Green;
case PlayerSlot.Player3: return Player3Blue;
case PlayerSlot.Player4: return Player4Purple;
diff --git a/Assets/_Project/Scripts/Gameplay/Builder.cs b/Assets/_Project/Scripts/Gameplay/Builder.cs
index 6c6346f..18ca781 100644
--- a/Assets/_Project/Scripts/Gameplay/Builder.cs
+++ b/Assets/_Project/Scripts/Gameplay/Builder.cs
@@ -4,6 +4,7 @@ using Unity.Netcode;
using UnityEngine;
using TD.Core;
using TD.Towers;
+using TD.UI.Minimap;
namespace TD.Gameplay
{
@@ -45,7 +46,7 @@ namespace TD.Gameplay
/// traversal.
///
[RequireComponent(typeof(NetworkObject))]
- public class Builder : NetworkBehaviour
+ public class Builder : NetworkBehaviour, IMinimapEntity
{
// ----- Static registry --------------------------------------------
@@ -188,6 +189,7 @@ namespace TD.Gameplay
public override void OnNetworkSpawn()
{
s_byClientId[OwnerClientId] = this;
+ MinimapEntityRegistry.Register(this);
if (IsServer)
{
@@ -217,6 +219,7 @@ namespace TD.Gameplay
{
if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
s_byClientId.Remove(OwnerClientId);
+ MinimapEntityRegistry.Deregister(this);
// Server-only cleanup: despawn any remaining build-site visuals so they
// don't leak when a player disconnects mid-construction.
@@ -233,6 +236,32 @@ namespace TD.Gameplay
// (NetworkList is owned by NGO; no manual Dispose needed in NGO 2.x.)
+ // ----- IMinimapEntity ---------------------------------------------
+ //
+ // Read every minimap refresh tick. Position is read live (NetworkTransform
+ // interpolation handles remote-client smoothing). Color comes from the same
+ // OwnerClientId → PlayerSlot stub mapping used by ApplyOwnerColor; both will pick
+ // up the real mapping when MatchState lands.
+
+ Vector3 IMinimapEntity.WorldPosition => transform.position;
+
+ Color IMinimapEntity.MinimapColor
+ {
+ get
+ {
+ byte slotByte = (byte)(OwnerClientId + 1);
+ PlayerSlot slot = (slotByte >= 1 && slotByte <= 9)
+ ? (PlayerSlot)slotByte : PlayerSlot.None;
+ return PlayerColors.Get(slot);
+ }
+ }
+
+ MinimapIconKind IMinimapEntity.IconKind => MinimapIconKind.Builder;
+
+ // Diameter of the builder dot in world units. The view enforces a pixel-size floor so
+ // builders remain visible at full zoom-out regardless of this value.
+ float IMinimapEntity.MinimapWorldSize => 0.6f;
+
// ----- Owner color tinting ----------------------------------------
// Lazily allocated; reused across renderers. Construction in a field initializer
diff --git a/Assets/_Project/Scripts/Gameplay/CameraController.cs b/Assets/_Project/Scripts/Gameplay/CameraController.cs
index 9b56e65..8504ec0 100644
--- a/Assets/_Project/Scripts/Gameplay/CameraController.cs
+++ b/Assets/_Project/Scripts/Gameplay/CameraController.cs
@@ -3,6 +3,7 @@ using UnityEngine;
using UnityEngine.InputSystem;
using TD.Core;
using TD.Levels;
+using TD.UI;
namespace TD.Gameplay
{
@@ -218,6 +219,12 @@ namespace TD.Gameplay
var kb = Keyboard.current;
if (mouse == null) return;
+ // If the cursor is over an interactive HUD element (e.g., the minimap, which has
+ // its own scroll-wheel zoom), don't also drive the world camera. UI Toolkit's
+ // WheelEvent.StopPropagation only blocks UI-side bubbling — it has no effect on
+ // raw Input System polling, so we have to gate here explicitly.
+ if (HUDController.IsPointerOverInteractiveHud(mouse.position.ReadValue())) return;
+
// Scroll wheel values are inconsistent across platforms and devices:
// - Windows click-wheel mice: ±120 per click (legacy Win32 convention)
// - Free-spin wheels and trackpads: small fractional values per tick
diff --git a/Assets/_Project/Scripts/Gameplay/TowerInstance.cs b/Assets/_Project/Scripts/Gameplay/TowerInstance.cs
index ba5a535..9a47002 100644
--- a/Assets/_Project/Scripts/Gameplay/TowerInstance.cs
+++ b/Assets/_Project/Scripts/Gameplay/TowerInstance.cs
@@ -4,6 +4,7 @@ using Unity.Netcode;
using UnityEngine;
using TD.Core;
using TD.Towers;
+using TD.UI.Minimap;
namespace TD.Gameplay
{
@@ -42,7 +43,7 @@ namespace TD.Gameplay
/// TowerCombat component added to the same prefab.
///
[RequireComponent(typeof(NetworkObject))]
- public class TowerInstance : NetworkBehaviour
+ public class TowerInstance : NetworkBehaviour, IMinimapEntity
{
// ----- Networked state ------------------------------------------------
@@ -167,6 +168,9 @@ namespace TD.Gameplay
// Apply owner color to the mesh renderer.
ApplyOwnerColor();
+ // Register for minimap rendering.
+ MinimapEntityRegistry.Register(this);
+
if (resolvedDefinition != null)
{
Debug.Log($"[TowerInstance] Spawned '{resolvedDefinition.DisplayName}' " +
@@ -180,6 +184,33 @@ namespace TD.Gameplay
// Un-stamp the footprint when the tower is destroyed (sold, wave end, etc.)
// so the tiles become walkable and buildable again.
StampFootprint(walkable: true, occupied: false);
+
+ MinimapEntityRegistry.Deregister(this);
+ }
+
+ // ----- IMinimapEntity -------------------------------------------------
+ //
+ // Towers are static, so WorldPosition is cheap (no movement to track). Color reflects
+ // the replicated ownerSlot; reads safely on every client because ownerSlot is set in
+ // OnNetworkSpawn before this entity is added to the registry.
+
+ Vector3 IMinimapEntity.WorldPosition => transform.position;
+ Color IMinimapEntity.MinimapColor => PlayerColors.Get(ownerSlot.Value);
+ MinimapIconKind IMinimapEntity.IconKind => MinimapIconKind.Tower;
+
+ // Tower footprint in world units. Uses the larger axis if the footprint isn't square,
+ // so an Nx1 tower still occupies its full long-side on the minimap.
+ // Falls back to one tile if the definition hasn't resolved yet (transient, harmless).
+ float IMinimapEntity.MinimapWorldSize
+ {
+ get
+ {
+ if (resolvedDefinition == null) return GridCoordinates.TILE_SIZE;
+ int extent = Mathf.Max(
+ resolvedDefinition.FootprintSize.x,
+ resolvedDefinition.FootprintSize.y);
+ return extent * GridCoordinates.TILE_SIZE;
+ }
}
// ----- Private helpers ------------------------------------------------
diff --git a/Assets/_Project/Scripts/UI/HUDController.cs b/Assets/_Project/Scripts/UI/HUDController.cs
index 179aafd..616917c 100644
--- a/Assets/_Project/Scripts/UI/HUDController.cs
+++ b/Assets/_Project/Scripts/UI/HUDController.cs
@@ -4,6 +4,7 @@ using UnityEngine;
using UnityEngine.UIElements;
using TD.Gameplay;
using TD.Towers;
+using TD.UI.Minimap;
namespace TD.UI
{
@@ -24,9 +25,9 @@ namespace TD.UI
[Tooltip("The TowerPlacementManager NetworkObject in the scene.")]
[SerializeField] private TowerPlacementManager placementManager;
- [Header("Minimap")]
- [Tooltip("RenderTexture that the minimap camera renders into.")]
- [SerializeField] private RenderTexture minimapRenderTexture;
+ [Tooltip("The local client's CameraController. Used by the minimap for click-to-jump " +
+ "and drag-to-pan.")]
+ [SerializeField] private CameraController cameraController;
[Header("Settings")]
[SerializeField] private float rejectionMessageDuration = 2.5f;
@@ -48,6 +49,52 @@ namespace TD.UI
private Coroutine rejectionFadeCoroutine;
private bool gridPopulated;
private bool uiInitialized;
+ private MinimapView minimapView;
+ private IPanel myPanel; // tracked separately so OnDestroy only clears the static if it still points at us
+
+ // ----- Static hit-test probe --------------------------------------
+
+ // Set when InitializeUI succeeds; cleared on OnDestroy. Non-UI systems (camera,
+ // input handlers) can query IsPointerOverInteractiveHud without taking a direct
+ // reference to HUDController.
+ private static IPanel s_hudPanel;
+
+ ///
+ /// True if falls over an interactive (non-ignore)
+ /// HUD element. Non-UI systems that consume mouse input (camera scroll-zoom, edge-pan)
+ /// should gate their handling on this so a cursor over the minimap, command grid, or
+ /// any other interactive HUD region doesn't drive both the HUD and the world at once.
+ ///
+ ///
+ /// Convention: uses Unity Input System screen coords
+ /// (origin bottom-left, y up). Returns false before the HUD has initialized; safe to
+ /// call from any system at any time.
+ ///
+ public static bool IsPointerOverInteractiveHud(Vector2 screenMousePos)
+ {
+ if (s_hudPanel == null) return false;
+
+ // Coord convention rabbit hole:
+ // - Screen mouse position: origin bottom-left, y up (Unity Input System).
+ // - UI Toolkit panel coords: origin top-left, y down.
+ //
+ // RuntimePanelUtils.ScreenToPanel converts the SCALE (e.g., reference resolution
+ // vs. actual resolution) but does NOT flip Y. We flip manually using the visual
+ // tree's height so the result works regardless of PanelSettings scale mode.
+ //
+ // Subtle: visualTree.worldBound height may be 0 for one frame on the very first
+ // layout pass. The caller (CameraController) checks the result against "is over
+ // interactive HUD"; a one-frame false positive (camera zooms when it shouldn't)
+ // is harmless and self-corrects the next frame.
+ Vector2 scaled = RuntimePanelUtils.ScreenToPanel(s_hudPanel, screenMousePos);
+ float panelHeight = s_hudPanel.visualTree.worldBound.height;
+ Vector2 panelPos = new Vector2(scaled.x, panelHeight - scaled.y);
+
+ // panel.Pick returns null when the topmost element under the point has
+ // PickingMode.Ignore (or there's no element there at all). Non-null means an
+ // interactive HUD element is under the cursor.
+ return s_hudPanel.Pick(panelPos) != null;
+ }
// ----- Lifecycle --------------------------------------------------
@@ -101,15 +148,24 @@ namespace TD.UI
SetEnabled(root, "upgrade-btn", false);
SetEnabled(root, "sell-btn", false);
- // Minimap RenderTexture.
- if (minimapRenderTexture != null)
+ // Minimap. The MinimapView owns the two sub-elements (terrain + entity overlay)
+ // and drives them; we just hand it the host container and the camera controller.
+ // Bake is deferred until LevelLoader is ready — view tries each frame in Tick().
+ var minimapContainer = root.Q("minimap");
+ if (minimapContainer != null)
{
- var minimap = root.Q("minimap");
- if (minimap != null)
- minimap.style.backgroundImage =
- Background.FromRenderTexture(minimapRenderTexture);
+ if (cameraController == null)
+ Debug.LogWarning("[HUDController] cameraController not assigned. " +
+ "Click-to-jump and drag-to-pan on the minimap will be disabled.");
+ minimapView = new MinimapView(minimapContainer, cameraController);
}
+ // Publish the panel so non-UI systems can query "is pointer over the HUD".
+ // Stored on `myPanel` too so OnDestroy only clears the static if it still
+ // points at this instance (defensive against re-creation overlap).
+ myPanel = root.panel;
+ s_hudPanel = myPanel;
+
uiInitialized = true;
}
@@ -131,6 +187,17 @@ namespace TD.UI
TryPopulateCommandGrid();
RefreshGoldDisplay();
+ minimapView?.Tick();
+ }
+
+ private void OnDestroy()
+ {
+ minimapView?.Dispose();
+ minimapView = null;
+
+ if (s_hudPanel != null && s_hudPanel == myPanel)
+ s_hudPanel = null;
+ myPanel = null;
}
// ----- Gold display -----------------------------------------------
diff --git a/Assets/_Project/Art/Textures/MinimapRT.renderTexture.meta b/Assets/_Project/Scripts/UI/Minimap.meta
similarity index 52%
rename from Assets/_Project/Art/Textures/MinimapRT.renderTexture.meta
rename to Assets/_Project/Scripts/UI/Minimap.meta
index 0d58917..64f6505 100644
--- a/Assets/_Project/Art/Textures/MinimapRT.renderTexture.meta
+++ b/Assets/_Project/Scripts/UI/Minimap.meta
@@ -1,8 +1,8 @@
fileFormatVersion: 2
-guid: e640b4de7a1dee049b38712e7eff82ea
-NativeFormatImporter:
+guid: dd886c53adc748f4391bc440c289d0ea
+folderAsset: yes
+DefaultImporter:
externalObjects: {}
- mainObjectFileID: 8400000
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Assets/_Project/Scripts/UI/Minimap/MinimapEntityRegistry.cs b/Assets/_Project/Scripts/UI/Minimap/MinimapEntityRegistry.cs
new file mode 100644
index 0000000..9c14508
--- /dev/null
+++ b/Assets/_Project/Scripts/UI/Minimap/MinimapEntityRegistry.cs
@@ -0,0 +1,94 @@
+// Assets/_Project/Scripts/UI/Minimap/MinimapEntityRegistry.cs
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace TD.UI.Minimap
+{
+ ///
+ /// Visual category for a minimap entity. Determines the shape and size of the icon drawn
+ /// by . Color is supplied separately by the entity itself so the
+ /// view does not need to know about ownership / faction mapping.
+ ///
+ public enum MinimapIconKind : byte
+ {
+ Enemy,
+ Tower,
+ Builder,
+ }
+
+ ///
+ /// Anything that wants to appear on the minimap as a dynamic icon implements this. The view
+ /// reads every getter on each refresh tick, so values may change between reads (movement is
+ /// fine — no caching needed).
+ ///
+ public interface IMinimapEntity
+ {
+ Vector3 WorldPosition { get; }
+ Color MinimapColor { get; }
+ MinimapIconKind IconKind { get; }
+
+ ///
+ /// The entity's diameter (or footprint extent) in world units. The view converts this
+ /// to pixels using the current minimap zoom, so a 2×2 tower will appear as a 2-tile
+ /// square on the minimap and adjacent towers will visually touch — matching their
+ /// world-space relationship. For point-like entities (builders, enemies) the view
+ /// also enforces a minimum pixel size so they stay visible when fully zoomed out.
+ ///
+ float MinimapWorldSize { get; }
+ }
+
+ ///
+ /// Static registry of every entity currently visible on the minimap. Entities register
+ /// themselves on spawn and deregister on despawn; the iterates the
+ /// set on each refresh.
+ ///
+ ///
+ /// Backing store is a so duplicate Register calls are no-ops and
+ /// Deregister is O(1). Iteration uses the struct enumerator via to
+ /// avoid the boxing that would happen if we exposed an .
+ ///
+ /// Static lifetime is intentional: the registry survives scene transitions only if entities
+ /// remember to deregister. NGO's OnNetworkDespawn covers that for towers / builders /
+ /// enemies. Manual is provided for tests and for explicit reset on match
+ /// teardown if needed.
+ ///
+ public static class MinimapEntityRegistry
+ {
+ private static readonly HashSet s_entities = new HashSet();
+
+ /// Number of currently-registered entities. Cheap, no allocation.
+ public static int Count => s_entities.Count;
+
+ ///
+ /// Registers for minimap rendering. No-op if already registered.
+ /// Safe to call from any thread that Unity allows (i.e., the main thread).
+ ///
+ public static void Register(IMinimapEntity entity)
+ {
+ if (entity == null) return;
+ s_entities.Add(entity);
+ }
+
+ /// Removes from the registry. No-op if not present.
+ public static void Deregister(IMinimapEntity entity)
+ {
+ if (entity == null) return;
+ s_entities.Remove(entity);
+ }
+
+ ///
+ /// Invokes once per registered entity. Uses HashSet's struct
+ /// enumerator so iteration is allocation-free (the only allocation is the closure that
+ /// may carry, which is the caller's choice).
+ ///
+ public static void ForEach(Action action)
+ {
+ if (action == null) return;
+ foreach (var e in s_entities) action(e);
+ }
+
+ /// Drops every registered entity. Tests and explicit match-teardown use only.
+ public static void Clear() => s_entities.Clear();
+ }
+}
diff --git a/Assets/_Project/Scripts/UI/Minimap/MinimapEntityRegistry.cs.meta b/Assets/_Project/Scripts/UI/Minimap/MinimapEntityRegistry.cs.meta
new file mode 100644
index 0000000..11c129d
--- /dev/null
+++ b/Assets/_Project/Scripts/UI/Minimap/MinimapEntityRegistry.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 683a64c92b1c7a144b7f9717b854ef38
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/UI/Minimap/MinimapTerrainBaker.cs b/Assets/_Project/Scripts/UI/Minimap/MinimapTerrainBaker.cs
new file mode 100644
index 0000000..61864b7
--- /dev/null
+++ b/Assets/_Project/Scripts/UI/Minimap/MinimapTerrainBaker.cs
@@ -0,0 +1,247 @@
+// Assets/_Project/Scripts/UI/Minimap/MinimapTerrainBaker.cs
+using UnityEngine;
+using TD.Core;
+using TD.Gameplay;
+using TD.Levels;
+
+namespace TD.UI.Minimap
+{
+ ///
+ /// Builds the static terrain layer of the WC3-style minimap as a
+ /// from the baked . One pixel per tile; the UI element scales the
+ /// texture up with Point filtering so the result reads as crisp, chunky map abstraction
+ /// rather than a tiny blurred photograph.
+ ///
+ ///
+ /// The terrain layer represents what cannot change during a match: zone ownership, spawner
+ /// locations, goal locations, and the map shape itself. Towers and enemies are drawn on a
+ /// separate dynamic overlay by , so this texture is baked once at
+ /// level load and never re-baked.
+ ///
+ public static class MinimapTerrainBaker
+ {
+ // ----- Palette ----------------------------------------------------
+ // Picked for legibility at low resolution. Aim: each tile category reads as a distinct
+ // color block when scaled up 4–8×, even on a Steam Deck screen at running brightness.
+
+ /// Tiles outside the playable map area. Fully transparent so the panel
+ /// background shows through.
+ private static readonly Color32 OutOfMap = new Color32(0, 0, 0, 0);
+
+ /// In-map tiles that are not owned by any player and not part of a goal /
+ /// spawner. Dark neutral grey — reads as "playable but unallocated".
+ private static readonly Color32 NeutralInMap = new Color32(46, 46, 51, 255);
+
+ /// Spawner tiles. Bright cyan — pops against every player zone color.
+ private static readonly Color32 SpawnerTile = new Color32(102, 217, 255, 255);
+
+ /// Goal tiles. Matches (gold).
+ private static readonly Color32 GoalTile = new Color32(224, 176, 32, 255);
+
+ // ----- Public API -------------------------------------------------
+
+ ///
+ /// Bakes the current level into a Texture2D and returns it. Returns null if the loader
+ /// is not ready. The returned texture has FilterMode.Point and is non-readable after
+ /// bake (CPU-side pixel data is released).
+ ///
+ ///
+ /// Caller is responsible for the texture lifetime: assign it to the minimap element and
+ /// destroy it when no longer needed. Currently holds the only
+ /// reference and lets it live for the duration of the match.
+ ///
+ public static Texture2D Bake(LevelLoader loader)
+ {
+ if (loader == null || !loader.IsLoaded)
+ {
+ Debug.LogWarning("[MinimapTerrainBaker] LevelLoader not ready; cannot bake terrain.");
+ return null;
+ }
+
+ var data = loader.LevelData;
+ int w = data.GridSize.x;
+ int h = data.GridSize.y;
+ if (w <= 0 || h <= 0)
+ {
+ Debug.LogWarning($"[MinimapTerrainBaker] Invalid grid size {data.GridSize}; cannot bake.");
+ return null;
+ }
+
+ // RGBA32 supports alpha for out-of-map transparency. mipChain off — the texture
+ // is rendered at near-1:1 in the UI and mipmaps would only blur the abstract look.
+ var tex = new Texture2D(w, h, TextureFormat.RGBA32, mipChain: false, linear: false)
+ {
+ filterMode = FilterMode.Point,
+ wrapMode = TextureWrapMode.Clamp,
+ name = "MinimapTerrain"
+ };
+
+ var pixels = new Color32[w * h];
+
+ // Pass 1: base tile color from MapArea + Owner.
+ // Texture y=0 is the BOTTOM row in Unity convention. World z and grid y both
+ // increase northward, so a tile at grid-y=0 should land at the bottom of the
+ // texture. That's exactly what `pixels[y * w + x] = ...` gives us, since SetPixels32
+ // treats index 0 as bottom-left. No flip needed during bake — the flip happens at
+ // display time (UI y-axis points down, world z points up; MinimapView handles it).
+ for (int y = 0; y < h; y++)
+ {
+ for (int x = 0; x < w; x++)
+ {
+ int idx = y * w + x;
+ pixels[idx] = ResolveBaseColor(data, idx);
+ }
+ }
+
+ // Pass 2: stamp special tile categories on top — spawners and goals override base.
+ // PlayerZones[].LeakExits intentionally NOT painted: leak exits are conceptually
+ // part of the source player's zone (an enemy boundary, not a player-visible
+ // landmark), and visually highlighting them would clutter the minimap.
+ StampZoneSpawners(pixels, data);
+ StampGoals(pixels, data);
+
+ tex.SetPixels32(pixels);
+ tex.Apply(updateMipmaps: false, makeNoLongerReadable: true);
+
+ LogBakeSummary(data, w, h);
+ return tex;
+ }
+
+ // ----- Diagnostics ------------------------------------------------
+
+ ///
+ /// One-shot log of the bake breakdown. Useful for diagnosing apparent asymmetries in
+ /// the rendered minimap — a symmetric authoring should produce mirrored counts on
+ /// opposite edges. Per-player min/max columns reveal exactly where each zone starts
+ /// and ends across the grid.
+ ///
+ private static void LogBakeSummary(LevelData data, int w, int h)
+ {
+ int outOfMap = 0, neutralInMap = 0, spawnerTiles = 0, goalTiles = 0;
+ // Per-slot owned-tile bounds (1..9).
+ int[] ownedCount = new int[10];
+ int[] minX = new int[10]; int[] maxX = new int[10];
+ int[] minY = new int[10]; int[] maxY = new int[10];
+ for (int i = 0; i < 10; i++)
+ {
+ minX[i] = int.MaxValue; minY[i] = int.MaxValue;
+ maxX[i] = int.MinValue; maxY[i] = int.MinValue;
+ }
+
+ for (int y = 0; y < h; y++)
+ for (int x = 0; x < w; x++)
+ {
+ int idx = y * w + x;
+ bool inMap = data.MapAreaGrid != null && data.MapAreaGrid[idx];
+ if (!inMap) { outOfMap++; continue; }
+
+ PlayerSlot s = data.OwnerGrid != null ? data.OwnerGrid[idx] : PlayerSlot.None;
+ if (s == PlayerSlot.None) { neutralInMap++; continue; }
+
+ int si = (int)s;
+ if (si < 0 || si > 9) continue;
+ ownedCount[si]++;
+ if (x < minX[si]) minX[si] = x;
+ if (x > maxX[si]) maxX[si] = x;
+ if (y < minY[si]) minY[si] = y;
+ if (y > maxY[si]) maxY[si] = y;
+ }
+
+ if (data.PlayerZones != null)
+ foreach (var z in data.PlayerZones)
+ if (z?.Spawners != null)
+ foreach (var sp in z.Spawners)
+ if (sp?.TileArea != null) spawnerTiles += sp.TileArea.Length;
+ if (data.Goals != null)
+ foreach (var g in data.Goals)
+ if (g?.TileArea != null) goalTiles += g.TileArea.Length;
+
+ var sb = new System.Text.StringBuilder(256);
+ sb.AppendFormat("[MinimapTerrainBaker] '{0}' baked. Grid {1}×{2} origin {3}. ",
+ data.MapName, w, h, data.GridOriginTile);
+ sb.AppendFormat("Tiles: outOfMap={0}, neutralInMap={1}, spawner={2}, goal={3}.",
+ outOfMap, neutralInMap, spawnerTiles, goalTiles);
+ for (int i = 1; i <= 9; i++)
+ {
+ if (ownedCount[i] == 0) continue;
+ sb.AppendFormat(" P{0}={1} (x:{2}–{3}, y:{4}–{5})",
+ i, ownedCount[i], minX[i], maxX[i], minY[i], maxY[i]);
+ }
+ Debug.Log(sb.ToString());
+ }
+
+ // ----- Pass implementations ---------------------------------------
+
+ private static Color32 ResolveBaseColor(LevelData data, int idx)
+ {
+ // MapAreaGrid may be null on levels baked before that field existed; treat as
+ // "not in map" so we render transparent rather than a misleading neutral block.
+ bool inMap = data.MapAreaGrid != null
+ && idx < data.MapAreaGrid.Length
+ && data.MapAreaGrid[idx];
+ if (!inMap) return OutOfMap;
+
+ PlayerSlot owner = (data.OwnerGrid != null && idx < data.OwnerGrid.Length)
+ ? data.OwnerGrid[idx]
+ : PlayerSlot.None;
+
+ if (owner == PlayerSlot.None) return NeutralInMap;
+ return ToZoneColor(PlayerColors.Get(owner));
+ }
+
+ private static void StampZoneSpawners(Color32[] pixels, LevelData data)
+ {
+ if (data.PlayerZones == null) return;
+ foreach (var zone in data.PlayerZones)
+ {
+ if (zone == null || zone.Spawners == null) continue;
+ foreach (var spawner in zone.Spawners)
+ {
+ if (spawner == null || spawner.TileArea == null) continue;
+ foreach (var tile in spawner.TileArea)
+ PaintTile(pixels, data, tile, SpawnerTile);
+ }
+ }
+ }
+
+ private static void StampGoals(Color32[] pixels, LevelData data)
+ {
+ if (data.Goals == null) return;
+ foreach (var goal in data.Goals)
+ {
+ if (goal == null || goal.TileArea == null) continue;
+ foreach (var tile in goal.TileArea)
+ PaintTile(pixels, data, tile, GoalTile);
+ }
+ }
+
+ private static void PaintTile(Color32[] pixels, LevelData data, Vector2Int worldTile, Color32 color)
+ {
+ int x = worldTile.x - data.GridOriginTile.x;
+ int y = worldTile.y - data.GridOriginTile.y;
+ if (x < 0 || x >= data.GridSize.x || y < 0 || y >= data.GridSize.y) return;
+ pixels[y * data.GridSize.x + x] = color;
+ }
+
+ // ----- Color helpers ----------------------------------------------
+
+ ///
+ /// Translates a saturated player gizmo color into the dimmer, slightly desaturated tone
+ /// used for zone fill on the minimap. The full-saturation original is reserved for the
+ /// player's icons (towers, builders) so units pop against their own zone tint.
+ ///
+ private static Color32 ToZoneColor(Color full)
+ {
+ // Desaturate ~45% toward equal-luminance grey, then darken to ~70% brightness.
+ float lum = full.r * 0.299f + full.g * 0.587f + full.b * 0.114f;
+ float r = Mathf.Lerp(full.r, lum, 0.45f) * 0.70f;
+ float g = Mathf.Lerp(full.g, lum, 0.45f) * 0.70f;
+ float b = Mathf.Lerp(full.b, lum, 0.45f) * 0.70f;
+ return new Color32(
+ (byte)Mathf.RoundToInt(r * 255f),
+ (byte)Mathf.RoundToInt(g * 255f),
+ (byte)Mathf.RoundToInt(b * 255f),
+ 255);
+ }
+ }
+}
diff --git a/Assets/_Project/Scripts/UI/Minimap/MinimapTerrainBaker.cs.meta b/Assets/_Project/Scripts/UI/Minimap/MinimapTerrainBaker.cs.meta
new file mode 100644
index 0000000..7cbb17e
--- /dev/null
+++ b/Assets/_Project/Scripts/UI/Minimap/MinimapTerrainBaker.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 3a3122ca07726044f8b63bbfb8f0230c
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/UI/Minimap/MinimapView.cs b/Assets/_Project/Scripts/UI/Minimap/MinimapView.cs
new file mode 100644
index 0000000..cd5ba5a
--- /dev/null
+++ b/Assets/_Project/Scripts/UI/Minimap/MinimapView.cs
@@ -0,0 +1,502 @@
+// Assets/_Project/Scripts/UI/Minimap/MinimapView.cs
+using UnityEngine;
+using UnityEngine.UIElements;
+using TD.Core;
+using TD.Gameplay;
+using TD.Levels;
+
+namespace TD.UI.Minimap
+{
+ ///
+ /// UI Toolkit minimap controller. Manages two stacked sub-elements (terrain + entities)
+ /// inside a host , handles click-to-jump, drag-to-pan,
+ /// right-click-to-move-builder, and scroll-wheel zoom against a
+ /// and the local . Refreshes the entity overlay at a throttled
+ /// rate so 500+ enemies don't dominate frame time.
+ ///
+ ///
+ /// Coordinate spaces. Three are in play:
+ ///
+ /// - World — XZ plane positions of game-side entities. Z+ is north.
+ /// - UI local — pixel coordinates within the minimap container. Y+ is DOWN.
+ /// - Texture — handled entirely by the baker; not exposed here.
+ ///
+ /// World↔UI conversion uses the currently-visible world rectangle, which is the full map
+ /// at zoom 1 and a smaller rect centered on at zoom > 1.
+ ///
+ /// Zoom model. Zoom is anchored to the cursor position: the world point under
+ /// the cursor before the scroll stays under the cursor after, so the user can dial in on a
+ /// region by hovering over it and scrolling. Zoom defaults to 1 (fully zoomed out) and is
+ /// clamped to . When zoom returns to 1 the visible rect snaps back to
+ /// the full map.
+ ///
+ /// Terrain rendering. The terrain sub-element is sized and positioned so the
+ /// currently-visible world rect maps to the container's contentRect. At zoom 1 the element
+ /// fills the container exactly; at higher zoom it grows (in pixels) past the container and
+ /// the container's overflow: hidden clips the off-view portion. The texture itself
+ /// is baked once and never re-baked.
+ ///
+ public class MinimapView
+ {
+ // ----- Tuning -----------------------------------------------------
+
+ /// Entity overlay refresh rate. 20Hz handles 500+ enemies comfortably without
+ /// burning the frame budget on Painter2D. Revisit if testing shows it's not enough.
+ private const float EntityRepaintHz = 20f;
+
+ private const float MinZoom = 1f;
+ private const float MaxZoom = 4f;
+ /// Multiplicative factor applied per scroll-wheel tick. 1.2 ≈ 6 ticks to span
+ /// 1x → 4x, which feels neither sluggish nor twitchy in playtest.
+ private const float ZoomStepFactor = 1.2f;
+
+ // Pixel-size floor for point-like icons (builders, enemies). Ensures they stay visible
+ // even at full zoom-out on a large map. Towers use their actual footprint instead and
+ // do not need a floor — they're always at least a couple of pixels by virtue of being
+ // 2×2 or larger.
+ private const float BuilderMinRadiusPx = 2.4f;
+ private const float EnemyMinRadiusPx = 1.5f;
+
+ // Outline added to builder icons so they read against same-color zone fill.
+ private static readonly Color BuilderOutline = new Color(1f, 1f, 1f, 0.85f);
+
+ // ----- Refs -------------------------------------------------------
+
+ private readonly VisualElement container;
+ private readonly VisualElement terrainLayer;
+ private readonly VisualElement entityLayer;
+ private readonly CameraController cameraController;
+
+ // ----- Bake state -------------------------------------------------
+
+ // World-space rectangle the texture covers (the full map). Cached once at bake.
+ private Vector2 worldMin;
+ private Vector2 worldMax;
+
+ // Owned texture — kept so we can destroy it cleanly. Null until first successful bake.
+ private Texture2D bakedTerrain;
+
+ // True once terrain has been baked successfully.
+ private bool ready;
+
+ // ----- View state -------------------------------------------------
+
+ // Current zoom in [MinZoom, MaxZoom]. 1 = full map visible.
+ private float zoom = 1f;
+
+ // World-XZ point the visible rect is centered on. At zoom 1 this is forced to the
+ // world center; at higher zoom the user's scrolling determines it (clamped so the
+ // visible rect never leaves the map).
+ private Vector2 viewCenter;
+
+ // Visible world rect, derived from zoom + viewCenter in ApplyView().
+ private Vector2 visibleWorldMin;
+ private Vector2 visibleWorldMax;
+
+ // ----- Input state ------------------------------------------------
+
+ private bool isDragging;
+ private int dragPointerId;
+
+ // ----- Repaint throttle -------------------------------------------
+
+ private float lastEntityRepaintTime;
+
+ // ----- Construction -----------------------------------------------
+
+ public MinimapView(VisualElement container, CameraController cameraController)
+ {
+ this.container = container;
+ this.cameraController = cameraController;
+
+ // Two stacked sub-elements. Terrain is painted via background-image; the entity
+ // overlay uses generateVisualContent + Painter2D. Both are PickingMode.Ignore so
+ // pointer events bubble back to the container, where we capture them.
+ terrainLayer = new VisualElement { name = "minimap-terrain" };
+ terrainLayer.AddToClassList("minimap-terrain");
+ terrainLayer.pickingMode = PickingMode.Ignore;
+ container.Add(terrainLayer);
+
+ entityLayer = new VisualElement { name = "minimap-entities" };
+ entityLayer.AddToClassList("minimap-entities");
+ entityLayer.pickingMode = PickingMode.Ignore;
+ entityLayer.generateVisualContent += DrawEntities;
+ container.Add(entityLayer);
+
+ // Pointer events on the container drive click-to-jump, drag-to-pan, right-click-move.
+ container.RegisterCallback(OnPointerDown);
+ container.RegisterCallback(OnPointerMove);
+ container.RegisterCallback(OnPointerUp);
+ container.RegisterCallback(OnPointerCaptureOut);
+ container.RegisterCallback(OnWheel);
+
+ // Recompute terrain layout when the container resizes — the absolute pixel
+ // positions we set on terrainLayer are container-relative.
+ container.RegisterCallback(_ => { if (ready) ApplyView(); });
+ }
+
+ // ----- Lifecycle --------------------------------------------------
+
+ ///
+ /// Called from HUDController.Update. Performs lazy bake on the first frame
+ /// after finishes loading, then triggers throttled entity
+ /// overlay repaints.
+ ///
+ public void Tick()
+ {
+ if (!ready)
+ {
+ TryBake();
+ if (!ready) return;
+ }
+
+ float now = Time.unscaledTime;
+ if (now - lastEntityRepaintTime >= 1f / EntityRepaintHz)
+ {
+ lastEntityRepaintTime = now;
+ entityLayer.MarkDirtyRepaint();
+ }
+ }
+
+ /// Releases the baked texture. Call from HUDController.OnDestroy.
+ public void Dispose()
+ {
+ if (bakedTerrain != null)
+ {
+ Object.Destroy(bakedTerrain);
+ bakedTerrain = null;
+ }
+ ready = false;
+ }
+
+ // ----- Bake -------------------------------------------------------
+
+ private void TryBake()
+ {
+ var loader = LevelLoader.Instance;
+ if (loader == null || !loader.IsLoaded) return;
+
+ bakedTerrain = MinimapTerrainBaker.Bake(loader);
+ if (bakedTerrain == null) return;
+
+ terrainLayer.style.backgroundImage =
+ new StyleBackground(Background.FromTexture2D(bakedTerrain));
+
+ // World extents of the baked rectangle. Tile (n) covers world n - 0.5 to n + 0.5.
+ var data = loader.LevelData;
+ float halfTile = GridCoordinates.TILE_SIZE * 0.5f;
+ float minX = data.GridOriginTile.x * GridCoordinates.TILE_SIZE - halfTile;
+ float maxX = (data.GridOriginTile.x + data.GridSize.x) * GridCoordinates.TILE_SIZE - halfTile;
+ float minZ = data.GridOriginTile.y * GridCoordinates.TILE_SIZE - halfTile;
+ float maxZ = (data.GridOriginTile.y + data.GridSize.y) * GridCoordinates.TILE_SIZE - halfTile;
+ worldMin = new Vector2(minX, minZ);
+ worldMax = new Vector2(maxX, maxZ);
+
+ // Default view: centered, fully zoomed out.
+ zoom = MinZoom;
+ viewCenter = (worldMin + worldMax) * 0.5f;
+ ApplyView();
+
+ ready = true;
+ }
+
+ // ----- View math --------------------------------------------------
+
+ ///
+ /// Recomputes / from the
+ /// current and , clamps the view to stay
+ /// within the map, and resizes/positions the terrain sub-element so its rendered
+ /// region matches the visible world rect.
+ ///
+ private void ApplyView()
+ {
+ var rect = container.contentRect;
+ if (rect.width <= 0f || rect.height <= 0f) return;
+
+ // Half-extents of the visible world rect.
+ float halfWorldX = (worldMax.x - worldMin.x) * 0.5f / zoom;
+ float halfWorldZ = (worldMax.y - worldMin.y) * 0.5f / zoom;
+
+ // Clamp viewCenter so the visible rect doesn't leave the map. At zoom 1 the
+ // clamp range collapses to a single point (the world center), forcing the view.
+ viewCenter.x = Mathf.Clamp(viewCenter.x,
+ worldMin.x + halfWorldX, worldMax.x - halfWorldX);
+ viewCenter.y = Mathf.Clamp(viewCenter.y,
+ worldMin.y + halfWorldZ, worldMax.y - halfWorldZ);
+
+ visibleWorldMin = new Vector2(viewCenter.x - halfWorldX, viewCenter.y - halfWorldZ);
+ visibleWorldMax = new Vector2(viewCenter.x + halfWorldX, viewCenter.y + halfWorldZ);
+
+ // Position the terrain element so visibleWorldMin maps to container (0, 0) — in
+ // UI coords where y is top-down and the texture is flipped to put north at top.
+ // Terrain at zoom z is rect.size * z big, and we offset it so the visible window
+ // lands inside the container.
+ float worldRangeX = worldMax.x - worldMin.x;
+ float worldRangeZ = worldMax.y - worldMin.y;
+
+ float terrainW = rect.width * zoom;
+ float terrainH = rect.height * zoom;
+ float left = -(visibleWorldMin.x - worldMin.x) / worldRangeX * terrainW;
+ // Top: container's top corresponds to visibleWorldMax.z. Distance from texture's
+ // top (worldMax.z) to visibleWorldMax.z, as a fraction of world range, times height.
+ float top = -(worldMax.y - visibleWorldMax.y) / worldRangeZ * terrainH;
+
+ terrainLayer.style.width = terrainW;
+ terrainLayer.style.height = terrainH;
+ terrainLayer.style.left = left;
+ terrainLayer.style.top = top;
+ terrainLayer.style.right = StyleKeyword.Auto;
+ terrainLayer.style.bottom = StyleKeyword.Auto;
+
+ // Entity overlay needs a redraw to reflect the new visible rect.
+ entityLayer.MarkDirtyRepaint();
+ }
+
+ // ----- Pointer handling -------------------------------------------
+
+ private void OnPointerDown(PointerDownEvent evt)
+ {
+ if (!ready) return;
+
+ if (evt.button == 0)
+ {
+ // Left button: jump camera + start drag-to-pan.
+ if (cameraController == null) return;
+ isDragging = true;
+ dragPointerId = evt.pointerId;
+ container.CapturePointer(evt.pointerId);
+ cameraController.BeginDrag();
+ cameraController.JumpTo(UIToWorld(evt.localPosition));
+ evt.StopPropagation();
+ }
+ else if (evt.button == 1)
+ {
+ // Right button: move-and-pause command on the selected builder. Fires
+ // immediately; no drag/capture semantics (single-shot like an RTS).
+ HandleRightClickMove(evt.localPosition);
+ evt.StopPropagation();
+ }
+ }
+
+ private void OnPointerMove(PointerMoveEvent evt)
+ {
+ if (!isDragging || evt.pointerId != dragPointerId) return;
+ cameraController.JumpTo(UIToWorld(evt.localPosition));
+ evt.StopPropagation();
+ }
+
+ private void OnPointerUp(PointerUpEvent evt)
+ {
+ if (!isDragging || evt.pointerId != dragPointerId) return;
+ EndDragging(evt.pointerId);
+ evt.StopPropagation();
+ }
+
+ // Lost capture — end drag cleanly so we don't leave CameraController stuck.
+ private void OnPointerCaptureOut(PointerCaptureOutEvent evt)
+ {
+ if (!isDragging || evt.pointerId != dragPointerId) return;
+ EndDragging(evt.pointerId);
+ }
+
+ private void EndDragging(int pointerId)
+ {
+ isDragging = false;
+ cameraController?.EndDrag();
+ if (container.HasPointerCapture(pointerId))
+ container.ReleasePointer(pointerId);
+ }
+
+ private void HandleRightClickMove(Vector2 uiLocal)
+ {
+ var selection = SelectionState.Instance;
+ if (selection == null || !selection.HasSelection) return;
+
+ Vector3 worldTarget = UIToWorld(uiLocal);
+ // Same RPC the world right-click uses; server validates and side-effects the queue.
+ selection.SelectedBuilder.RequestMoveAndPauseRpc(worldTarget);
+ }
+
+ // ----- Zoom -------------------------------------------------------
+
+ private void OnWheel(WheelEvent evt)
+ {
+ if (!ready) return;
+
+ // Cursor-anchored zoom: capture the world point under the cursor before the
+ // zoom change, then move viewCenter so that same world point lands at the same
+ // UI position afterward. Result: the cursor "drills in" on whatever it's over.
+ Vector3 cursorWorldBefore = UIToWorld(evt.localMousePosition);
+
+ // WheelEvent.delta.y: positive = scroll down (typically zoom out), negative = up.
+ // Multiplicative steps feel natural and keep the rate consistent across zoom levels.
+ float steps = -evt.delta.y;
+ float newZoom = Mathf.Clamp(zoom * Mathf.Pow(ZoomStepFactor, steps), MinZoom, MaxZoom);
+ if (Mathf.Approximately(newZoom, zoom))
+ {
+ evt.StopPropagation();
+ return;
+ }
+
+ zoom = newZoom;
+
+ // Compute viewCenter that keeps cursorWorldBefore under the cursor at the new zoom.
+ // visibleWorldMin = viewCenter - halfWorld; visibleWorldMax = viewCenter + halfWorld
+ // UIToWorld(uiLocal) = visibleWorldMin + frac * (visibleWorldMax - visibleWorldMin)
+ // = viewCenter - halfWorld + frac * 2 * halfWorld
+ // = viewCenter + (2*frac - 1) * halfWorld
+ // Solve viewCenter from desired cursorWorldBefore at the cursor's frac.
+ var rect = container.contentRect;
+ float fx = Mathf.Clamp01(evt.localMousePosition.x / rect.width);
+ float fyTopDown = Mathf.Clamp01(evt.localMousePosition.y / rect.height);
+ float fzBottomUp = 1f - fyTopDown;
+
+ float halfWorldX = (worldMax.x - worldMin.x) * 0.5f / zoom;
+ float halfWorldZ = (worldMax.y - worldMin.y) * 0.5f / zoom;
+ viewCenter.x = cursorWorldBefore.x - (2f * fx - 1f) * halfWorldX;
+ viewCenter.y = cursorWorldBefore.z - (2f * fzBottomUp - 1f) * halfWorldZ;
+
+ ApplyView(); // clamps viewCenter and updates terrain element
+ evt.StopPropagation();
+ }
+
+ // ----- Coordinate transforms --------------------------------------
+
+ // UI local (y down) → world (z up). Clamps to visible map rect so dragging past the
+ // edge doesn't fly the camera off the map.
+ private Vector3 UIToWorld(Vector2 uiLocal)
+ {
+ var rect = container.contentRect;
+ if (rect.width <= 0f || rect.height <= 0f) return Vector3.zero;
+
+ float fx = Mathf.Clamp01(uiLocal.x / rect.width);
+ float fyTopDown = Mathf.Clamp01(uiLocal.y / rect.height);
+ float fzBottomUp = 1f - fyTopDown;
+
+ float worldX = Mathf.Lerp(visibleWorldMin.x, visibleWorldMax.x, fx);
+ float worldZ = Mathf.Lerp(visibleWorldMin.y, visibleWorldMax.y, fzBottomUp);
+ return new Vector3(worldX, GridCoordinates.BUILDABLE_PLANE_Y, worldZ);
+ }
+
+ // World → UI local. Used for placing entity icons.
+ //
+ // IMPORTANT: this must NOT clamp. Mathf.InverseLerp clamps to [0,1], which would
+ // pin off-screen entities to the minimap edge — making zoomed-in views show
+ // ghost icons stuck against every border. We compute the fraction manually so
+ // out-of-view entities get UI coords outside the container's rect, where the
+ // bounds check in DrawOneEntity culls them.
+ private Vector2 WorldToUI(Vector3 world)
+ {
+ var rect = container.contentRect;
+ float rangeX = visibleWorldMax.x - visibleWorldMin.x;
+ float rangeZ = visibleWorldMax.y - visibleWorldMin.y;
+ if (rangeX <= 0.0001f || rangeZ <= 0.0001f) return Vector2.zero;
+
+ float fx = (world.x - visibleWorldMin.x) / rangeX;
+ float fzBottomUp = (world.z - visibleWorldMin.y) / rangeZ;
+ float fyTopDown = 1f - fzBottomUp;
+ return new Vector2(fx * rect.width, fyTopDown * rect.height);
+ }
+
+ /// Pixels per world unit at the current zoom. Used to scale entity icons so
+ /// e.g. a 2×2 tower footprint reads as a 2-tile square on the minimap.
+ private float PixelsPerWorldUnit
+ {
+ get
+ {
+ var rect = container.contentRect;
+ float visibleWorldWidth = visibleWorldMax.x - visibleWorldMin.x;
+ return visibleWorldWidth > 0.0001f ? rect.width / visibleWorldWidth : 1f;
+ }
+ }
+
+ // ----- Entity overlay drawing -------------------------------------
+
+ private void DrawEntities(MeshGenerationContext mgc)
+ {
+ if (!ready) return;
+ var painter = mgc.painter2D;
+ float pxPerWorld = PixelsPerWorldUnit;
+
+ // The local player's builder always draws on top. Cache the reference once
+ // (Builder.Local does a dictionary lookup) and cast to the interface so the
+ // ReferenceEquals compare against registry entries works without boxing.
+ IMinimapEntity localBuilder = Builder.Local;
+
+ // Pass 1: every entity except the local builder. Order within this pass is
+ // arbitrary (registry is a HashSet); revisit if cross-entity layering between
+ // non-local entities ever matters.
+ MinimapEntityRegistry.ForEach(e =>
+ {
+ if (ReferenceEquals(e, localBuilder)) return;
+ DrawOneEntity(painter, e, pxPerWorld);
+ });
+
+ // Pass 2: local builder on top. Skipped if the local builder isn't spawned
+ // (e.g., on a dedicated server or before the local client's builder arrives).
+ if (localBuilder != null)
+ DrawOneEntity(painter, localBuilder, pxPerWorld);
+ }
+
+ private void DrawOneEntity(Painter2D p, IMinimapEntity entity, float pxPerWorld)
+ {
+ if (entity == null) return;
+
+ Vector2 ui = WorldToUI(entity.WorldPosition);
+
+ // Skip if obviously off-screen — overflow:hidden clips it anyway, but skipping
+ // saves Painter2D work for entities far outside the zoomed-in window.
+ var rect = container.contentRect;
+ if (ui.x < -8f || ui.y < -8f || ui.x > rect.width + 8f || ui.y > rect.height + 8f) return;
+
+ Color col = entity.MinimapColor; col.a = 1f;
+ p.fillColor = col;
+
+ switch (entity.IconKind)
+ {
+ case MinimapIconKind.Enemy:
+ {
+ float r = Mathf.Max(entity.MinimapWorldSize * 0.5f * pxPerWorld, EnemyMinRadiusPx);
+ DrawCircle(p, ui, r);
+ break;
+ }
+ case MinimapIconKind.Tower:
+ {
+ // Tower size follows footprint exactly — no pixel floor. Adjacent towers
+ // visually touch on the minimap because their drawn squares span their
+ // full footprint extent in world units.
+ float halfPx = entity.MinimapWorldSize * 0.5f * pxPerWorld;
+ DrawSquare(p, ui, halfPx);
+ break;
+ }
+ case MinimapIconKind.Builder:
+ {
+ float r = Mathf.Max(entity.MinimapWorldSize * 0.5f * pxPerWorld, BuilderMinRadiusPx);
+ DrawCircle(p, ui, r);
+ p.strokeColor = BuilderOutline;
+ p.lineWidth = 1f;
+ p.Stroke();
+ break;
+ }
+ }
+ }
+
+ private static void DrawCircle(Painter2D p, Vector2 center, float radius)
+ {
+ p.BeginPath();
+ p.Arc(center, radius, new Angle(0f, AngleUnit.Degree), new Angle(360f, AngleUnit.Degree));
+ p.Fill();
+ }
+
+ private static void DrawSquare(Painter2D p, Vector2 center, float halfSize)
+ {
+ p.BeginPath();
+ p.MoveTo(new Vector2(center.x - halfSize, center.y - halfSize));
+ p.LineTo(new Vector2(center.x + halfSize, center.y - halfSize));
+ p.LineTo(new Vector2(center.x + halfSize, center.y + halfSize));
+ p.LineTo(new Vector2(center.x - halfSize, center.y + halfSize));
+ p.ClosePath();
+ p.Fill();
+ }
+ }
+}
diff --git a/Assets/_Project/Scripts/UI/Minimap/MinimapView.cs.meta b/Assets/_Project/Scripts/UI/Minimap/MinimapView.cs.meta
new file mode 100644
index 0000000..5c0228d
--- /dev/null
+++ b/Assets/_Project/Scripts/UI/Minimap/MinimapView.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 0462e237a5a906242a62de1740fc4365
\ No newline at end of file
diff --git a/Assets/_Project/UI/HUD.uss b/Assets/_Project/UI/HUD.uss
index 52d31c8..e9520f8 100644
--- a/Assets/_Project/UI/HUD.uss
+++ b/Assets/_Project/UI/HUD.uss
@@ -165,13 +165,28 @@
border-top-color: rgba(90, 74, 16, 1);
}
-/* Minimap — RenderTexture applied as background-image at runtime */
+/* Minimap — host container. MinimapView injects two stacked sub-elements
+ (.minimap-terrain and .minimap-entities) at runtime. */
.minimap {
width: 110px;
flex-shrink: 0;
- background-color: rgb(10, 26, 10);
+ background-color: rgb(8, 10, 12);
border-right-width: 1px;
border-right-color: rgba(42, 58, 26, 1);
+ overflow: hidden;
+}
+
+/* Static baked terrain. Background-image assigned by MinimapView. */
+.minimap-terrain {
+ position: absolute;
+ top: 0; left: 0; right: 0; bottom: 0;
+ -unity-background-scale-mode: stretch-to-fill;
+}
+
+/* Dynamic entity overlay. Drawn via Painter2D in generateVisualContent. */
+.minimap-entities {
+ position: absolute;
+ top: 0; left: 0; right: 0; bottom: 0;
}
/* Portrait */