using UnityEngine; /// /// 프레임레이트를 제한한다. 상시 구동 앱에서는 이게 없으면 안 된다. /// /// 기본 상태의 Unity 는 vSync 가 꺼져 있고 targetFrameRate 가 -1 이라 /// "낼 수 있는 만큼" 렌더링한다. 거의 빈 씬이면 수백~수천 FPS 가 나오고, /// 그동안 CPU 코어 하나와 GPU 를 계속 물고 있는다. 게임이라면 상관없지만 /// 이 앱은 사용자가 다른 일을 하는 내내 떠 있으므로 그대로 두면 안 된다. /// runInBackground = true 라 창이 뒤로 가도 멈추지 않는다는 점이 더 나쁘다. /// /// 한 걸음 더: 아무 일도 없을 때는 더 낮춘다. 캐릭터가 발판에 가만히 서 있고 /// 커서도 근처에 없다면 초당 몇 장만 그려도 눈에 띄지 않는다. 사용자가 손을 /// 가져가거나 캐릭터가 움직이기 시작하면 즉시 올린다. /// public class FrameRateLimiter : MonoBehaviour { [Header("프레임 제한")] [Tooltip("사용자와 상호작용 중이거나 캐릭터가 움직일 때의 상한")] [Range(15, 144)] [SerializeField] int activeFrameRate = 60; [Tooltip("아무 일도 없을 때의 상한. 낮출수록 배터리와 발열에 유리하다")] [Range(5, 60)] [SerializeField] int idleFrameRate = 15; [Tooltip("상호작용이 끝난 뒤 이만큼은 높은 프레임을 유지한다(초). " + "값이 0 이면 커서를 뗄 때마다 뚝뚝 끊겨 보인다")] [SerializeField] float activeLinger = 1.5f; [Tooltip("끄면 항상 activeFrameRate 로 고정한다")] [SerializeField] bool dropWhenIdle = true; [Header("참조 (비우면 씬에서 탐색)")] [SerializeField] ClickThroughHitTest hitTest; [SerializeField] CharacterDragger dragger; [SerializeField] WindowClimber climber; [SerializeField] ChatController chat; float activeUntil; int applied = -1; void Awake() { if (hitTest == null) hitTest = FindFirstObjectByType(); if (dragger == null) dragger = FindFirstObjectByType(); if (climber == null) climber = FindFirstObjectByType(); if (chat == null) chat = FindFirstObjectByType(); // targetFrameRate 는 vSync 가 꺼져 있어야 듣는다. 품질 레벨이 바뀌면 // vSync 가 되살아날 수 있으므로 여기서 못박는다. QualitySettings.vSyncCount = 0; } void Update() { if (IsBusy()) activeUntil = Time.unscaledTime + Mathf.Max(0f, activeLinger); bool active = !dropWhenIdle || Time.unscaledTime < activeUntil; Apply(active ? activeFrameRate : idleFrameRate); } /// 지금 부드러워야 하는 상황인지. bool IsBusy() { // 커서가 캐릭터나 채팅창 위에 있다 — 곧 뭔가 할 참이다. if (hitTest != null && (hitTest.IsOverCharacter || hitTest.IsOverUi)) return true; if (dragger != null && dragger.IsDragging) return true; // 채팅창이 열려 있으면 글자가 흘러나오는 중일 수 있다. if (chat != null && chat.IsOpen) return true; // 캐릭터가 제자리에 서 있지 않다면 움직이는 중이다. if (climber != null && climber.State != ClimbState.Standing) return true; return false; } void Apply(int frameRate) { if (applied == frameRate) return; applied = frameRate; Application.targetFrameRate = frameRate; } }