using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을 using System.Collections.Generic; // 리스트 기능을 사용할거에요 -> System.Collections.Generic을 namespace GameSystems.Optimization // 최적화 네임스페이스를 정의할거에요 -> GameSystems.Optimization으로 { [DisallowMultipleComponent] // 기능을 제한할거에요 -> 중복 부착을 방지하도록 [AddComponentMenu("Game Systems/Optimization/Player Range Manager")] // 메뉴를 추가할거에요 -> 컴포넌트 추가 경로에 public sealed class PlayerRangeManager : MonoBehaviour // 클래스를 선언할거에요 -> 최적화 관리자인 PlayerRangeManager를 { #region Serialized Fields [Header("=== 필수 설정 ===")] // 제목을 표시할거에요 -> === 필수 설정 === 을 [SerializeField, Tooltip("최적화 설정 에셋")] // 필드를 직렬화할거에요 -> 인스펙터 노출을 위해 private RenderOptimizationConfig _config; // 변수를 선언할거에요 -> 설정 에셋을 담을 _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(); } // 함수를 실행할거에요 -> 시작 시 설정을 검증하는 Awake를 private void Start() { InitializeSystem(); } // 함수를 실행할거에요 -> 시스템을 초기화하는 Start를 private void Update() // 함수를 실행할거에요 -> 매 프레임 업데이트 로직인 Update를 { if (!_isInitialized || _isPaused || _playerTransform == null) return; // 조건이 맞으면 중단할거에요 -> 동작 불가능한 상태라면 if (Time.time >= _nextCheckTime) // 조건이 맞으면 실행할거에요 -> 다음 검사 시간이 되었다면 { UpdateCulling(); // 함수를 실행할거에요 -> 컬링 업데이트 기능을 _nextCheckTime = Time.time + GetCheckInterval(); // 값을 갱신할거에요 -> 다음 검사 예정 시간을 } } private void ValidateConfiguration() // 함수를 선언할거에요 -> 설정 누락 여부를 확인하는 ValidateConfiguration을 { if (_config == null) // 조건이 맞으면 실행할거에요 -> 설정 에셋이 비어있다면 { Debug.LogWarning("[PlayerRangeManager] Config가 없습니다! 기본값을 생성합니다."); // 경고를 출력할거에요 -> 자동 생성 메시지를 _config = RenderOptimizationConfig.CreateDefault(); // 값을 설정할거에요 -> 기본값으로 생성된 에셋을 } } private void InitializeSystem() // 함수를 선언할거에요 -> 전체 시스템을 초기화하는 InitializeSystem을 { FindPlayer(); // 함수를 실행할거에요 -> 플레이어를 찾는 기능을 ScanEnvironmentObjects(); // 함수를 실행할거에요 -> 주변 오브젝트를 스캔하는 기능을 _isInitialized = true; // 상태를 바꿀거에요 -> 초기화 완료인 참(true)으로 if (ShouldLog()) Debug.Log($"[PlayerRangeManager] 초기화 완료. 대상 그룹: {_totalObjectCount}"); // 조건이 맞으면 로그를 출력할거에요 -> 결과 보고 메시지를 } private void FindPlayer() // 함수를 선언할거에요 -> 플레이어를 탐색하는 FindPlayer를 { GameObject player = GameObject.FindGameObjectWithTag(GetPlayerTag()); // 오브젝트를 찾을거에요 -> 지정된 태그를 가진 플레이어를 if (player != null) // 조건이 맞으면 실행할거에요 -> 플레이어를 찾았다면 { _playerTransform = player.transform; // 값을 저장할거에요 -> 플레이어의 Transform 정보를 } else // 조건이 틀리면 실행할거에요 -> 플레이어를 못 찾았다면 { Debug.LogError($"[PlayerRangeManager] '{GetPlayerTag()}' 태그의 플레이어를 찾을 수 없습니다!"); // 에러를 출력할거에요 -> 태그 불일치 메시지를 } } private void ScanEnvironmentObjects() // 함수를 선언할거에요 -> 환경 오브젝트들을 스캔하는 ScanEnvironmentObjects를 { _renderGroups.Clear(); // 리스트를 비울거에요 -> 기존에 있던 렌더 그룹들을 foreach (var parent in _environmentParents) // 반복할거에요 -> 등록된 모든 부모 폴더에 대해 { if (parent == null) continue; // 조건이 맞으면 건너뛸거에요 -> 부모가 비어있다면 foreach (Transform child in parent) // 반복할거에요 -> 부모 폴더의 모든 자식 오브젝트에 대해 { Renderer[] renderers = child.GetComponentsInChildren(true); // 배열을 가져올거에요 -> 자식 내부의 모든 렌더러들을 if (renderers.Length > 0) // 조건이 맞으면 실행할거에요 -> 렌더러가 존재한다면 { _renderGroups.Add(new RenderGroup(renderers)); // 리스트에 추가할거에요 -> 새로운 렌더 그룹을 생성해서 } } } _totalObjectCount = _renderGroups.Count; // 값을 저장할거에요 -> 총 관리 대상 개수를 } private void UpdateCulling() // 함수를 선언할거에요 -> 컬링을 업데이트하는 UpdateCulling을 { Vector3 playerPos = _playerTransform.position; // 값을 가져올거에요 -> 현재 플레이어의 위치를 float range = GetRenderRange(); // 값을 가져올거에요 -> 설정된 렌더링 거리 수치를 int changedCount = 0; // 변수를 초기화할거에요 -> 변경 개수를 0으로 int visibleCount = 0; // 변수를 초기화할거에요 -> 표시 중인 개수를 0으로 for (int i = 0; i < _renderGroups.Count; i++) // 반복할거에요 -> 모든 렌더 그룹에 대해 { if (_renderGroups[i].UpdateVisibility(playerPos, range)) 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 bool ShouldLog() => _config.EnableVerboseLogging; // 값을 가져올거에요 -> 설정 파일의 로그 활성화 여부를 private void OnDrawGizmos() // 함수를 실행할거에요 -> 기즈모를 그리는 OnDrawGizmos를 { if (!ShouldShowGizmos() || _playerTransform == null) return; // 조건이 맞으면 중단할거에요 -> 표시 설정이 꺼졌거나 플레이어가 없다면 Gizmos.color = new Color(1f, 0f, 0f, 0.3f); // 색상을 설정할거에요 -> 빨간색 반투명으로 Gizmos.DrawWireSphere(_playerTransform.position, GetRenderRange()); // 그림을 그릴거에요 -> 플레이어 중심의 사거리 구체를 } private void OnDrawGizmosSelected() // 함수를 실행할거에요 -> 선택 시 기즈모를 그리는 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.DrawWireSphere(group.ActualCenter, group.BoundingRadius); // 그림을 그릴거에요 -> 각 그룹의 경계면 구체를 } } } }