using System; using System.Collections; using UnityEngine; using TMPro; public class GPSTaskManager : MonoBehaviour, ITask { [Header("Target Location")] [SerializeField] private double targetLatitude = 48.8584; [SerializeField] private double targetLongitude = 2.2945; [Header("Task Settings")] [SerializeField] private float completionRadius = 15f; [SerializeField] private float updateInterval = 1f; [Header("GPS Initialization")] [SerializeField] private float gpsInitTimeout = 20f; [SerializeField] private float desiredAccuracyMeters = 5f; [SerializeField] private float updateDistanceMeters = 1f; [Header("UI References")] [SerializeField] private TMP_Text distanceText; [SerializeField] private TMP_Text bearingText; [SerializeField] private TMP_Text statusText; public event Action OnTaskCompleted; public event Action OnLocationUpdated; private Action _onCompleted; private Action _onExit; public string TaskID { get; set; } public TaskType TaskType { get; set; } public string TaskName { get; set; } public (double, double) TaskLocation { get; set; } public bool IsCompleted { get; private set; } private bool _gpsInitialized = false; private bool _taskCompleted = false; private bool _isRunning = false; private Coroutine _trackingCoroutine; private const double EarthRadiusMeters = 6_371_000.0; private void Start() { SetStatus("Initializing GPS…"); _trackingCoroutine = StartCoroutine(InitializeAndTrack()); } private void OnDisable() { StopTracking(); } public void SetTarget(double latitude, double longitude) { targetLatitude = latitude; targetLongitude = longitude; _taskCompleted = false; IsCompleted = false; Debug.Log($"[GPS] New target → ({latitude:F6}, {longitude:F6})"); } public void StopTracking() { _isRunning = false; if (_trackingCoroutine != null) { StopCoroutine(_trackingCoroutine); _trackingCoroutine = null; } Input.location.Stop(); Debug.Log("[GPS] Tracking stopped."); } public void Initialize(Action onCompleted) { _onCompleted = onCompleted; IsCompleted = false; } public void Complete() { if (IsCompleted) return; IsCompleted = true; _onCompleted?.Invoke(this); } public void ExitTask(Action onExit) { _onExit = onExit; _onExit?.Invoke(this); StopTracking(); } private IEnumerator InitializeAndTrack() { if (!Input.location.isEnabledByUser) { SetStatus("ERROR: GPS disabled."); yield break; } Input.location.Start(desiredAccuracyMeters, updateDistanceMeters); SetStatus("Starting GPS…"); float elapsed = 0f; while (Input.location.status == LocationServiceStatus.Initializing && elapsed < gpsInitTimeout) { elapsed += 0.5f; yield return new WaitForSeconds(0.5f); } if (Input.location.status != LocationServiceStatus.Running) { SetStatus("ERROR: GPS failed."); yield break; } _gpsInitialized = true; _isRunning = true; SetStatus("GPS Active ✓"); while (_isRunning) { LocationInfo loc = Input.location.lastData; float distance = CalculateDistance(loc.latitude, loc.longitude, targetLatitude, targetLongitude); float bearing = CalculateBearing(loc.latitude, loc.longitude, targetLatitude, targetLongitude); UpdateUI(distance, bearing); OnLocationUpdated?.Invoke(distance, bearing); if (!_taskCompleted && distance <= completionRadius) { _taskCompleted = true; HandleTaskCompleted(distance); } yield return new WaitForSeconds(updateInterval); } } private void HandleTaskCompleted(float finalDistance) { Debug.Log($"[GPS] TASK COMPLETED ({finalDistance:F1}m)"); SetStatus($" Done ({finalDistance:F1}m)"); OnTaskCompleted?.Invoke(); Complete(); } private float CalculateDistance(double lat1, double lon1, double lat2, double lon2) { double dLat = ToRadians(lat2 - lat1); double dLon = ToRadians(lon2 - lon1); double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) * Math.Sin(dLon / 2) * Math.Sin(dLon / 2); double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); return (float)(EarthRadiusMeters * c); } private float CalculateBearing(double lat1, double lon1, double lat2, double lon2) { double dLon = ToRadians(lon2 - lon1); double y = Math.Sin(dLon) * Math.Cos(ToRadians(lat2)); double x = Math.Cos(ToRadians(lat1)) * Math.Sin(ToRadians(lat2)) - Math.Sin(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) * Math.Cos(dLon); double brng = ToDegrees(Math.Atan2(y, x)); return (float)((brng + 360) % 360); } private void UpdateUI(float distance, float bearing) { if (distanceText != null) distanceText.text = $"Distance: {distance:F1} m"; if (bearingText != null) bearingText.text = $"Bearing: {bearing:F1}°"; } private void SetStatus(string message) { if (statusText != null) statusText.text = message; } private static double ToRadians(double deg) => deg * Math.PI / 180.0; private static double ToDegrees(double rad) => rad * 180.0 / Math.PI; }