using UnityEngine; using System.Collections.Generic; namespace GameSystems.Optimization { [DisallowMultipleComponent] [AddComponentMenu("Game Systems/Optimization/Player Range Manager")] public sealed class PlayerRangeManager : MonoBehaviour { #region Serialized Fields [Header("=== 필수 설정 ===")] [SerializeField, Tooltip("최적화 설정 에셋")] private RenderOptimizationConfig _config; [SerializeField, Tooltip("환경 오브젝트들이 들어있는 부모 폴더 리스트")] private List _environmentParents = new List(); // ⭐ 리스트 방식으로 변경 #endregion #region Private Fields private Transform _playerTransform; private readonly List _renderGroups = new List(512); private bool _isInitialized; private bool _isPaused; private int _totalObjectCount; private int _visibleObjectCount; private int _lastFrameChangedCount; private float _nextCheckTime; #endregion #region Public Properties public bool IsInitialized => _isInitialized; public bool IsPaused => _isPaused; public int TotalObjectCount => _totalObjectCount; public int VisibleObjectCount => _visibleObjectCount; public float CullingEfficiency => _totalObjectCount > 0 ? 1f - ((float)_visibleObjectCount / _totalObjectCount) : 0f; #endregion private void Awake() { ValidateConfiguration(); } private void Start() { InitializeSystem(); } private void Update() { if (!_isInitialized || _isPaused) return; if (Time.time >= _nextCheckTime) { UpdateCulling(); _nextCheckTime = Time.time + GetCheckInterval(); } } private void ValidateConfiguration() { if (_config == null) _config = RenderOptimizationConfig.CreateDefault(); } public void InitializeSystem() { if (!FindPlayer()) return; if (!ScanEnvironmentObjects()) return; _isInitialized = true; _nextCheckTime = Time.time; } private bool FindPlayer() { GameObject playerObj = GameObject.FindGameObjectWithTag(GetPlayerTag()); _playerTransform = (playerObj != null) ? playerObj.transform : transform; return true; } // ⭐ [핵심 수정] 리스트 내의 모든 폴더를 뒤져서 '낱개' 단위로 등록합니다. private bool ScanEnvironmentObjects() { if (_environmentParents == null || _environmentParents.Count == 0) return false; _renderGroups.Clear(); foreach (Transform parent in _environmentParents) { if (parent == null) continue; // ⭐ 폴더 구조가 몇 층이든 상관없이 모든 Renderer를 낱낱이 찾아냅니다. Renderer[] allRenderers = parent.GetComponentsInChildren(_config.IncludeInactiveObjects); foreach (Renderer r in allRenderers) { if (r == null) continue; // 각 렌더러를 독립적인 그룹으로 생성 (개별 최적화의 핵심) _renderGroups.Add(new RenderGroup(new Renderer[] { r })); } } _totalObjectCount = _renderGroups.Count; return _totalObjectCount > 0; } private void UpdateCulling() { if (_playerTransform == null) return; Vector3 playerPosition = _playerTransform.position; float maxDistance = GetRenderRange(); int changedCount = 0; int visibleCount = 0; for (int i = 0; i < _renderGroups.Count; i++) { if (_renderGroups[i].UpdateVisibility(playerPosition, maxDistance)) changedCount++; if (_renderGroups[i].IsVisible) visibleCount++; } _visibleObjectCount = visibleCount; _lastFrameChangedCount = changedCount; } public void RefreshObjectList() { ScanEnvironmentObjects(); } public void ShowAllObjects() { foreach (var g in _renderGroups) g.ForceShow(); _visibleObjectCount = _totalObjectCount; } public void HideAllObjects() { foreach (var g in _renderGroups) g.ForceHide(); _visibleObjectCount = 0; } public void SetPaused(bool paused) => _isPaused = paused; private float GetRenderRange() => _config.RenderRange; private float GetCheckInterval() => _config.CheckInterval; private string GetPlayerTag() => _config.PlayerTag; private bool ShouldShowGizmos() => _config.ShowGizmos; private void OnDrawGizmos() { if (!ShouldShowGizmos() || _playerTransform == null) return; Gizmos.color = new Color(1f, 0f, 0f, 0.3f); Gizmos.DrawWireSphere(_playerTransform.position, GetRenderRange()); } private void OnDrawGizmosSelected() { if (!ShouldShowGizmos() || !_isInitialized) return; foreach (var group in _renderGroups) { Gizmos.color = group.IsVisible ? new Color(0f, 1f, 0f, 0.5f) : new Color(0.5f, 0.5f, 0.5f, 0.2f); Gizmos.DrawSphere(group.ActualCenter, 0.3f); } } } }