using System; using System.Collections.Generic; using System.Linq; using Core; using Core.Lifecycle; using Unity.Cinemachine; using UnityEngine; namespace Common.Camera { /// /// Serializable mapping between a camera state and its Cinemachine camera. /// Used to assign cameras in the Inspector for each enum state. /// [Serializable] public class CameraStateMapping where TState : Enum { [Tooltip("The state this camera represents")] public TState state; [Tooltip("The Cinemachine camera for this state")] public CinemachineCamera camera; public CameraStateMapping(TState state) { this.state = state; this.camera = null; } } /// /// Generic state-based camera controller using Cinemachine. /// Manages camera transitions by setting priorities on virtual cameras. /// Type parameter TState must be an enum representing camera states. /// /// public abstract class CameraStateManager : ManagedBehaviour where TState : Enum { #region Configuration [Header("Camera Mappings")] [Tooltip("Assign cameras for each state - list auto-populates from enum")] [SerializeField] protected List> cameraMappings = new List>(); [Header("Camera Priority Settings")] [Tooltip("Priority for inactive cameras")] [SerializeField] protected int inactivePriority = 10; [Tooltip("Priority for the active camera")] [SerializeField] protected int activePriority = 20; [Header("Debug")] [SerializeField] protected bool showDebugLogs = false; #endregion #region State private Dictionary _cameraMap = new Dictionary(); private TState _currentState; private bool _isInitialized = false; public TState CurrentState => _currentState; #endregion #region Events /// /// Fired when camera state changes. Parameters: (TState oldState, TState newState) /// public event Action OnStateChanged; #endregion #region Lifecycle /// /// Initialize camera mappings and validate them. /// Subclasses should call base.OnManagedAwake() to get automatic initialization. /// If custom initialization is needed, override without calling base. /// internal override void OnManagedAwake() { base.OnManagedAwake(); // Initialize cameras from Inspector mappings InitializeCameraMap(); // Validate all cameras are assigned ValidateCameras(); } #endregion #region Initialization /// /// Initialize camera mappings from Inspector-assigned list. /// Call this in OnManagedAwake - no need to manually register cameras! /// This is the preferred method for new implementations. /// protected void InitializeCameraMap() { _cameraMap.Clear(); // Build dictionary from serialized mappings foreach (var mapping in cameraMappings) { if (mapping.camera == null) { Logging.Warning($"[{GetType().Name}] No camera assigned for state {mapping.state}"); continue; } _cameraMap[mapping.state] = mapping.camera; mapping.camera.Priority.Value = inactivePriority; if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Registered camera '{mapping.camera.gameObject.name}' for state {mapping.state}"); } _isInitialized = true; if (_cameraMap.Count == 0) { Logging.Warning($"[{GetType().Name}] No cameras registered!"); } if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Initialized with {_cameraMap.Count} cameras"); } /// /// DEPRECATED: Use InitializeCameraMap() instead for cleaner code. /// Kept for backward compatibility with existing implementations. /// protected void RegisterCamera(TState state, CinemachineCamera pCamera) { if (pCamera == null) { Logging.Warning($"[{GetType().Name}] Attempted to register null camera for state {state}"); return; } if (_cameraMap.ContainsKey(state)) { Logging.Warning($"[{GetType().Name}] Camera for state {state} already registered, overwriting"); } _cameraMap[state] = pCamera; pCamera.Priority.Value = inactivePriority; if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Registered camera '{pCamera.gameObject.name}' for state {state}"); } /// /// DEPRECATED: Use InitializeCameraMap() instead. /// Kept for backward compatibility with existing implementations. /// protected void FinalizeInitialization() { _isInitialized = true; if (_cameraMap.Count == 0) { Logging.Warning($"[{GetType().Name}] No cameras registered!"); } if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Initialized with {_cameraMap.Count} cameras"); } #endregion #region State Management /// /// Switch to a specific camera state /// public virtual void SwitchToState(TState newState) { if (!_isInitialized) { Logging.Error($"[{GetType().Name}] Cannot switch state - not initialized!"); return; } if (!_cameraMap.ContainsKey(newState)) { Logging.Error($"[{GetType().Name}] No camera registered for state {newState}!"); return; } TState oldState = _currentState; _currentState = newState; // Set all cameras to inactive priority foreach (var kvp in _cameraMap) { kvp.Value.Priority.Value = inactivePriority; } // Set target camera to active priority _cameraMap[newState].Priority.Value = activePriority; if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Switched from {oldState} to {newState} (camera: {_cameraMap[newState].gameObject.name})"); OnStateChanged?.Invoke(oldState, newState); } /// /// Get the camera for a specific state /// public CinemachineCamera GetCamera(TState state) { if (_cameraMap.TryGetValue(state, out CinemachineCamera pCamera)) { return pCamera; } Logging.Warning($"[{GetType().Name}] No camera found for state {state}"); return null; } /// /// Check if a camera is registered for a state /// public bool HasCamera(TState state) { return _cameraMap.ContainsKey(state); } #endregion #region Validation /// /// Validate that all enum states have cameras registered. /// Override to add custom validation (e.g., check for specific components). /// protected virtual void ValidateCameras() { // Check that all enum values have cameras assigned foreach (TState state in Enum.GetValues(typeof(TState))) { if (!_cameraMap.ContainsKey(state)) { Logging.Warning($"[{GetType().Name}] No camera assigned for state {state}"); } else if (_cameraMap[state] == null) { Logging.Error($"[{GetType().Name}] Camera for state {state} is null!"); } } } #endregion #region Editor Support #if UNITY_EDITOR /// /// Auto-populate camera mappings list with all enum values. /// Called automatically in the Editor when the component is added or values change. /// protected virtual void OnValidate() { // Get all enum values TState[] allStates = (TState[])Enum.GetValues(typeof(TState)); // Add missing states to the list foreach (TState state in allStates) { bool exists = cameraMappings.Any(m => EqualityComparer.Default.Equals(m.state, state)); if (!exists) { cameraMappings.Add(new CameraStateMapping(state)); } } // Remove mappings for states that no longer exist in the enum cameraMappings.RemoveAll(m => !System.Array.Exists(allStates, s => EqualityComparer.Default.Equals(s, m.state))); // Sort by enum order for cleaner Inspector display cameraMappings = cameraMappings.OrderBy(m => (int)(object)m.state).ToList(); } /// /// Initialize list when component is first added /// protected virtual void Reset() { OnValidate(); } #endif #endregion } }