| | | 1 | | using UnityEngine; |
| | | 2 | | using System.Collections.Generic; |
| | | 3 | | using System.Linq; |
| | | 4 | | |
| | | 5 | | public class TacticalPanelController : MonoBehaviour { |
| | 0 | 6 | | public static TacticalPanelController Instance { get; private set; } = null!; |
| | | 7 | | |
| | 0 | 8 | | private void Awake() { |
| | 0 | 9 | | if (Instance == null) { |
| | 0 | 10 | | Instance = this; |
| | 0 | 11 | | InitializeController(); |
| | 0 | 12 | | } else { |
| | 0 | 13 | | Destroy(gameObject); |
| | 0 | 14 | | } |
| | 0 | 15 | | } |
| | | 16 | | |
| | | 17 | | [Tooltip("The UI group that contains the radar symbology elements")] |
| | | 18 | | [SerializeField] |
| | 0 | 19 | | private GameObject _radarUIGroup = null!; |
| | | 20 | | |
| | | 21 | | [SerializeField] |
| | | 22 | | [Tooltip("How often to refresh symbol positions (in seconds)")] |
| | 0 | 23 | | private float _refreshRate = 0.1f; |
| | | 24 | | |
| | | 25 | | [SerializeField] |
| | 0 | 26 | | private float _keyboardPanSpeed = 500f; // Adjust this value in the inspector |
| | | 27 | | |
| | | 28 | | [SerializeField] |
| | 0 | 29 | | private float _panelZoomSpeed = 10.0f; |
| | | 30 | | |
| | 0 | 31 | | private RectTransform _radarUIGroupRectTransform = null!; |
| | 0 | 32 | | private IADS _iads = null!; |
| | | 33 | | private float _timeSinceLastRefresh; |
| | 0 | 34 | | private readonly Dictionary<TrackFileData, GameObject> _trackSymbols = |
| | | 35 | | new Dictionary<TrackFileData, GameObject>(); |
| | | 36 | | |
| | 0 | 37 | | private List<GameObject> _originSymbols = new List<GameObject>(); |
| | | 38 | | |
| | 0 | 39 | | private TacticalPolarGridGraphic _polarGridGraphic = null!; |
| | | 40 | | |
| | 0 | 41 | | private void Start() { |
| | 0 | 42 | | _iads = IADS.Instance; |
| | 0 | 43 | | SetupRadarUIGroup(); |
| | 0 | 44 | | _timeSinceLastRefresh = 0f; |
| | 0 | 45 | | CreateOriginSymbol(); |
| | 0 | 46 | | } |
| | | 47 | | |
| | 0 | 48 | | private void Update() { |
| | 0 | 49 | | HandleRefreshTimer(); |
| | 0 | 50 | | } |
| | | 51 | | |
| | 0 | 52 | | private void OnDisable() { |
| | 0 | 53 | | ClearAllSymbols(); |
| | 0 | 54 | | } |
| | | 55 | | |
| | 0 | 56 | | private void InitializeController() { |
| | | 57 | | // Initialize any controller-specific settings here if needed |
| | | 58 | | // Create polar grid |
| | 0 | 59 | | _polarGridGraphic = _radarUIGroup.GetComponent<TacticalPolarGridGraphic>(); |
| | 0 | 60 | | if (_polarGridGraphic == null) { |
| | 0 | 61 | | Debug.LogError("TacticalPolarGridGraphic not found on radar UI group"); |
| | 0 | 62 | | } |
| | 0 | 63 | | } |
| | | 64 | | |
| | 0 | 65 | | private void SetupRadarUIGroup() { |
| | 0 | 66 | | _radarUIGroupRectTransform = _radarUIGroup.GetComponent<RectTransform>(); |
| | 0 | 67 | | ResetRadarUITransform(); |
| | 0 | 68 | | } |
| | | 69 | | |
| | 0 | 70 | | private void ResetRadarUITransform() { |
| | 0 | 71 | | _radarUIGroupRectTransform.localScale = Vector3.one; |
| | 0 | 72 | | _radarUIGroupRectTransform.localPosition = Vector3.zero; |
| | 0 | 73 | | } |
| | | 74 | | |
| | 0 | 75 | | private void HandleRefreshTimer() { |
| | 0 | 76 | | _timeSinceLastRefresh += Time.unscaledDeltaTime; |
| | | 77 | | |
| | 0 | 78 | | if (_timeSinceLastRefresh >= _refreshRate) { |
| | 0 | 79 | | UpdateSymbols(); |
| | 0 | 80 | | _timeSinceLastRefresh = 0f; |
| | 0 | 81 | | } |
| | 0 | 82 | | } |
| | | 83 | | |
| | 0 | 84 | | private void UpdateSymbols() { |
| | 0 | 85 | | UpdateTrackSymbols(_iads.GetThreatTracks()); |
| | 0 | 86 | | UpdateTrackSymbols(_iads.GetInterceptorTracks()); |
| | 0 | 87 | | } |
| | | 88 | | |
| | | 89 | | private void UpdateTrackSymbols<T>(List<T> currentTracks) |
| | 0 | 90 | | where T : TrackFileData { |
| | | 91 | | // Remove inactive symbols |
| | 0 | 92 | | var tracksToRemove = |
| | | 93 | | _trackSymbols.Keys.OfType<T>().Where(track => !currentTracks.Contains(track)).ToList(); |
| | | 94 | | |
| | 0 | 95 | | foreach (var track in tracksToRemove) { |
| | 0 | 96 | | RemoveTrackSymbol(track); |
| | 0 | 97 | | } |
| | | 98 | | |
| | | 99 | | // Update or create active symbols |
| | 0 | 100 | | foreach (var track in currentTracks) { |
| | 0 | 101 | | if (track.Status == TrackStatus.DESTROYED) { |
| | 0 | 102 | | RemoveTrackSymbol(track); |
| | 0 | 103 | | continue; |
| | | 104 | | } |
| | | 105 | | |
| | 0 | 106 | | if (_trackSymbols.TryGetValue(track, out GameObject symbolObj)) { |
| | 0 | 107 | | UpdateSymbolPosition(symbolObj, track.Agent.transform.position); |
| | 0 | 108 | | UpdateSymbolScale(symbolObj); |
| | 0 | 109 | | UpdateSymbolRotation(symbolObj, track.Agent.transform.forward); |
| | 0 | 110 | | UpdateSymbolSpeedAlt(symbolObj.GetComponent<TacticalSymbol>(), track); |
| | 0 | 111 | | } else { |
| | 0 | 112 | | CreateTrackSymbol(track); |
| | 0 | 113 | | } |
| | 0 | 114 | | } |
| | 0 | 115 | | } |
| | | 116 | | |
| | 0 | 117 | | private GameObject CreateSymbolPrefab() { |
| | 0 | 118 | | return Instantiate(Resources.Load<GameObject>("Prefabs/Symbols/SymbolPrefab"), |
| | | 119 | | _radarUIGroupRectTransform); |
| | 0 | 120 | | } |
| | | 121 | | |
| | 0 | 122 | | private void CreateOriginSymbol() { |
| | 0 | 123 | | GameObject symbolPrefab = CreateSymbolPrefab(); |
| | 0 | 124 | | symbolPrefab.GetComponent<TacticalSymbol>().SetSprite("friendly_destroyer_present"); |
| | 0 | 125 | | symbolPrefab.GetComponent<TacticalSymbol>().DisableDirectionArrow(); |
| | 0 | 126 | | symbolPrefab.GetComponent<TacticalSymbol>().SetSpeedAlt(""); |
| | 0 | 127 | | symbolPrefab.GetComponent<TacticalSymbol>().SetType(""); |
| | 0 | 128 | | symbolPrefab.GetComponent<TacticalSymbol>().SetUniqueDesignator(""); |
| | 0 | 129 | | _originSymbols.Add(symbolPrefab); |
| | 0 | 130 | | } |
| | | 131 | | |
| | 0 | 132 | | private void CreateTrackSymbol(TrackFileData trackFile) { |
| | 0 | 133 | | GameObject symbolObj = CreateSymbolPrefab(); |
| | 0 | 134 | | TacticalSymbol tacticalSymbol = symbolObj.GetComponent<TacticalSymbol>(); |
| | | 135 | | |
| | | 136 | | // Set common properties |
| | 0 | 137 | | tacticalSymbol.SetSprite(trackFile.Agent.staticConfig.VisualizationConfig?.SymbolPresent); |
| | 0 | 138 | | tacticalSymbol.SetDirectionArrowRotation( |
| | | 139 | | Mathf.Atan2(trackFile.Agent.GetVelocity().z, trackFile.Agent.GetVelocity().x) * |
| | | 140 | | Mathf.Rad2Deg); |
| | | 141 | | |
| | 0 | 142 | | UpdateSymbolSpeedAlt(tacticalSymbol, trackFile); |
| | | 143 | | |
| | | 144 | | // Set type-specific properties |
| | 0 | 145 | | if (trackFile is ThreatData) { |
| | 0 | 146 | | tacticalSymbol.SetType(trackFile.Agent.staticConfig.AgentType.ToString()); |
| | 0 | 147 | | } else if (trackFile is InterceptorData) { |
| | 0 | 148 | | tacticalSymbol.SetType("Interceptor"); |
| | 0 | 149 | | } |
| | | 150 | | |
| | 0 | 151 | | tacticalSymbol.SetUniqueDesignator(trackFile.TrackID); |
| | | 152 | | |
| | 0 | 153 | | UpdateSymbolPosition(symbolObj, trackFile.Agent.transform.position); |
| | 0 | 154 | | UpdateSymbolRotation(symbolObj, trackFile.Agent.transform.forward); |
| | 0 | 155 | | UpdateSymbolScale(symbolObj); |
| | | 156 | | |
| | 0 | 157 | | _trackSymbols.Add(trackFile, symbolObj); |
| | 0 | 158 | | } |
| | | 159 | | |
| | 0 | 160 | | private void UpdateSymbolSpeedAlt(TacticalSymbol tacticalSymbol, TrackFileData trackFile) { |
| | 0 | 161 | | tacticalSymbol.SetSpeedAlt( |
| | | 162 | | $"{Utilities.ConvertMpsToKnots(trackFile.Agent.GetVelocity().magnitude):F0}kts/" + |
| | | 163 | | $"{Utilities.ConvertMetersToFeet(trackFile.Agent.transform.position.y):F0}ft"); |
| | 0 | 164 | | } |
| | | 165 | | |
| | | 166 | | /// Updates the position of a symbol based on the threat's real-world position. |
| | 0 | 167 | | private void UpdateSymbolPosition(GameObject symbolObj, Vector3 threatPosition) { |
| | 0 | 168 | | if (_polarGridGraphic == null) { |
| | 0 | 169 | | Debug.LogError("TacticalPolarGridGraphic reference is missing."); |
| | 0 | 170 | | return; |
| | | 171 | | } |
| | | 172 | | |
| | | 173 | | // Get the current scale factor from the grid |
| | 0 | 174 | | float scaleFactor = _polarGridGraphic.CurrentScaleFactor; |
| | | 175 | | |
| | | 176 | | // Assuming threatPosition is in meters, convert to the grid's scale |
| | | 177 | | // Adjust the division factor as per your real-world scaling |
| | 0 | 178 | | float scaleDivisionFactor = 1000f; // Example: 1000 meters = 1 unit on grid |
| | | 179 | | |
| | 0 | 180 | | Vector3 scaledPosition = new Vector3(threatPosition.z / scaleDivisionFactor, |
| | | 181 | | threatPosition.x / scaleDivisionFactor, 0f); |
| | | 182 | | |
| | | 183 | | // Apply the scaleFactor to ensure positioning aligns with grid scaling |
| | 0 | 184 | | symbolObj.transform.localPosition = scaledPosition * scaleFactor; |
| | 0 | 185 | | } |
| | | 186 | | |
| | 0 | 187 | | private void UpdateSymbolRotation(GameObject symbolObj, Vector3 forward) { |
| | 0 | 188 | | symbolObj.GetComponent<TacticalSymbol>().SetDirectionArrowRotation( |
| | | 189 | | -1 * Mathf.Atan2(forward.z, forward.x) * Mathf.Rad2Deg); |
| | 0 | 190 | | } |
| | | 191 | | |
| | 0 | 192 | | private void RemoveTrackSymbol(TrackFileData trackFile) { |
| | 0 | 193 | | if (_trackSymbols.TryGetValue(trackFile, out GameObject symbolObj)) { |
| | 0 | 194 | | Destroy(symbolObj); |
| | 0 | 195 | | _trackSymbols.Remove(trackFile); |
| | 0 | 196 | | } |
| | 0 | 197 | | } |
| | | 198 | | |
| | 0 | 199 | | private void ClearAllSymbols() { |
| | 0 | 200 | | foreach (var symbol in _trackSymbols.Values) { |
| | 0 | 201 | | Destroy(symbol); |
| | 0 | 202 | | } |
| | 0 | 203 | | _trackSymbols.Clear(); |
| | 0 | 204 | | } |
| | | 205 | | |
| | 0 | 206 | | public void CycleRangeUp() { |
| | 0 | 207 | | _polarGridGraphic.CycleRangeUp(); |
| | 0 | 208 | | } |
| | | 209 | | |
| | 0 | 210 | | public void CycleRangeDown() { |
| | 0 | 211 | | _polarGridGraphic.CycleRangeDown(); |
| | 0 | 212 | | } |
| | | 213 | | |
| | 0 | 214 | | public void ZoomIn(float amount) { |
| | 0 | 215 | | AdjustRadarScale(amount * _panelZoomSpeed); |
| | 0 | 216 | | } |
| | | 217 | | |
| | 0 | 218 | | public void ZoomOut(float amount) { |
| | 0 | 219 | | AdjustRadarScale(-amount * _panelZoomSpeed); |
| | 0 | 220 | | } |
| | | 221 | | |
| | 0 | 222 | | public void Pan(Vector2 delta) { |
| | 0 | 223 | | Vector3 currentPos = _radarUIGroupRectTransform.localPosition; |
| | 0 | 224 | | _radarUIGroupRectTransform.localPosition = |
| | | 225 | | new Vector3(currentPos.x + delta.x, currentPos.y + delta.y, currentPos.z); |
| | 0 | 226 | | } |
| | | 227 | | |
| | 0 | 228 | | public void PanWithKeyboard(Vector2 direction) { |
| | 0 | 229 | | Vector2 delta = direction * _keyboardPanSpeed * Time.unscaledDeltaTime; |
| | 0 | 230 | | Pan(delta); |
| | 0 | 231 | | } |
| | | 232 | | |
| | | 233 | | /// Adjusts the radar scale by the specified amount. |
| | 0 | 234 | | private void AdjustRadarScale(float amount) { |
| | 0 | 235 | | Vector3 newScale = _radarUIGroupRectTransform.localScale + new Vector3(amount, amount, 0f); |
| | | 236 | | |
| | | 237 | | // Prevent negative or too small scaling |
| | 0 | 238 | | if (newScale.x < 0.01f || newScale.y < 0.01f) { |
| | 0 | 239 | | return; |
| | | 240 | | } |
| | | 241 | | |
| | 0 | 242 | | _radarUIGroupRectTransform.localScale = newScale; |
| | | 243 | | |
| | | 244 | | // Update all existing symbols' scales |
| | 0 | 245 | | foreach (var symbol in _trackSymbols.Values) { |
| | 0 | 246 | | UpdateSymbolScale(symbol); |
| | 0 | 247 | | } |
| | | 248 | | // Temporarily necessary until we implement IADS vessel system |
| | 0 | 249 | | UpdateSymbolScale(_originSymbols[0]); |
| | 0 | 250 | | } |
| | | 251 | | |
| | 0 | 252 | | private void UpdateSymbolScale(GameObject symbolObj) { |
| | | 253 | | // Calculate inverse scale to maintain constant visual size |
| | 0 | 254 | | float inverseScale = 2f / _radarUIGroupRectTransform.localScale.x; |
| | 0 | 255 | | symbolObj.transform.localScale = new Vector3(inverseScale, inverseScale, 1f); |
| | 0 | 256 | | } |
| | | 257 | | } |