// ============================================== // AchievementManager.cs - Achievement System // ============================================== // Handles achievement display and notifications. using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace BreakpilotDrive { [Serializable] public class Achievement { public string id; public string name; public string description; public string icon; public string category; public int threshold; public int progress; public bool unlocked; } [Serializable] public class AchievementResponse { public string user_id; public int total; public int unlocked_count; public Achievement[] achievements; } [Serializable] private class AchievementArrayWrapper { public Achievement[] achievements; } public class AchievementManager : MonoBehaviour { public static AchievementManager Instance { get; private set; } [Header("UI References")] [SerializeField] private GameObject achievementPopupPrefab; [SerializeField] private Transform popupContainer; [SerializeField] private float popupDuration = 3f; [Header("Audio")] [SerializeField] private AudioClip unlockSound; // Events public UnityEvent OnAchievementUnlocked; // Cached data private List achievements = new List(); private HashSet previouslyUnlocked = new HashSet(); private Queue popupQueue = new Queue(); private bool isShowingPopup = false; void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } // ============================================== // Load Achievements from API // ============================================== public IEnumerator LoadAchievements(string userId, Action onComplete = null) { string url = $"{GetBaseUrl()}/achievements/{userId}"; using (var request = UnityEngine.Networking.UnityWebRequest.Get(url)) { // Add auth header if available string authHeader = AuthManager.Instance?.GetAuthHeader(); if (!string.IsNullOrEmpty(authHeader)) { request.SetRequestHeader("Authorization", authHeader); } yield return request.SendWebRequest(); if (request.result == UnityEngine.Networking.UnityWebRequest.Result.Success) { var response = JsonUtility.FromJson(request.downloadHandler.text); // Store previously unlocked for comparison previouslyUnlocked.Clear(); foreach (var a in achievements) { if (a.unlocked) previouslyUnlocked.Add(a.id); } // Update achievements achievements.Clear(); if (response.achievements != null) { achievements.AddRange(response.achievements); } onComplete?.Invoke(response); } else { Debug.LogWarning($"Failed to load achievements: {request.error}"); onComplete?.Invoke(null); } } } // ============================================== // Check for New Achievements // ============================================== public void CheckForNewUnlocks() { foreach (var achievement in achievements) { if (achievement.unlocked && !previouslyUnlocked.Contains(achievement.id)) { // New unlock! ShowAchievementPopup(achievement); OnAchievementUnlocked?.Invoke(achievement); } } } // Call after each game session to check for new achievements public IEnumerator RefreshAndCheckAchievements(string userId) { yield return LoadAchievements(userId, (response) => { if (response != null) { CheckForNewUnlocks(); } }); } // ============================================== // Achievement Popup // ============================================== public void ShowAchievementPopup(Achievement achievement) { popupQueue.Enqueue(achievement); if (!isShowingPopup) { StartCoroutine(ProcessPopupQueue()); } } private IEnumerator ProcessPopupQueue() { isShowingPopup = true; while (popupQueue.Count > 0) { var achievement = popupQueue.Dequeue(); yield return ShowSinglePopup(achievement); yield return new WaitForSeconds(0.3f); // Gap between popups } isShowingPopup = false; } private IEnumerator ShowSinglePopup(Achievement achievement) { Debug.Log($"[Achievement] Unlocked: {achievement.name}"); // Play sound if (unlockSound != null && AudioManager.Instance != null) { AudioManager.Instance.PlaySFX(unlockSound); } // Create popup UI if (achievementPopupPrefab != null && popupContainer != null) { var popup = Instantiate(achievementPopupPrefab, popupContainer); var popupScript = popup.GetComponent(); if (popupScript != null) { popupScript.Setup(achievement); } yield return new WaitForSeconds(popupDuration); if (popup != null) { Destroy(popup); } } else { // Fallback: just wait yield return new WaitForSeconds(popupDuration); } } // ============================================== // Public API // ============================================== public List GetAllAchievements() { return new List(achievements); } public List GetUnlockedAchievements() { return achievements.FindAll(a => a.unlocked); } public List GetLockedAchievements() { return achievements.FindAll(a => !a.unlocked); } public List GetAchievementsByCategory(string category) { return achievements.FindAll(a => a.category == category); } public int GetUnlockedCount() { int count = 0; foreach (var a in achievements) { if (a.unlocked) count++; } return count; } public int GetTotalCount() { return achievements.Count; } public float GetCompletionPercentage() { if (achievements.Count == 0) return 0f; return (float)GetUnlockedCount() / achievements.Count * 100f; } public Achievement GetAchievement(string id) { return achievements.Find(a => a.id == id); } // ============================================== // Icon Mapping // ============================================== public Sprite GetIconSprite(string iconName) { // Load from Resources/Icons/Achievements/ string path = $"Icons/Achievements/{iconName}"; var sprite = Resources.Load(path); if (sprite == null) { // Fallback icon sprite = Resources.Load("Icons/Achievements/default"); } return sprite; } // ============================================== // Helpers // ============================================== private string GetBaseUrl() { if (BreakpilotAPI.Instance != null) { return "http://localhost:8000/api/game"; // TODO: Get from BreakpilotAPI } return "http://localhost:8000/api/game"; } } // ============================================== // Achievement Popup Component // ============================================== public class AchievementPopup : MonoBehaviour { [SerializeField] private UnityEngine.UI.Text titleText; [SerializeField] private UnityEngine.UI.Text descriptionText; [SerializeField] private UnityEngine.UI.Image iconImage; public void Setup(Achievement achievement) { if (titleText != null) titleText.text = achievement.name; if (descriptionText != null) descriptionText.text = achievement.description; if (iconImage != null) { var sprite = AchievementManager.Instance?.GetIconSprite(achievement.icon); if (sprite != null) iconImage.sprite = sprite; } // Animate in StartCoroutine(AnimateIn()); } private IEnumerator AnimateIn() { var canvasGroup = GetComponent(); if (canvasGroup != null) { canvasGroup.alpha = 0f; float duration = 0.3f; float elapsed = 0f; while (elapsed < duration) { elapsed += Time.deltaTime; canvasGroup.alpha = Mathf.Lerp(0f, 1f, elapsed / duration); yield return null; } canvasGroup.alpha = 1f; } } } }