// Unity Version : 2021.3.5f1 using System.Collections; using UnityEngine; public class ShowFPSMobile : MonoBehaviour { // provided by Niter88 private string fps = ""; private WaitForSecondsRealtime waitForFrequency; GUIStyle style = new GUIStyle(); Rect rect; bool isInicialized = false; private void Awake() { //float fraction = 0.5f; // Render at half the resolution of current screen //float fraction = 0.8f; //float fraction = 1f; //Screen.SetResolution((int)(Screen.currentResolution.width * fraction), (int)(Screen.currentResolution.height * fraction), true); //don't use vsync on mobile, limit fps instead // Sync framerate to monitors refresh rate //Use 'Don't Sync' (0) to not wait for VSync. Value must be 0, 1, 2, 3, or 4 QualitySettings.vSyncCount = 0; // Disable screen dimming Screen.sleepTimeout = SleepTimeout.NeverSleep; Inicialize(true); //use for testing on editor } private IEnumerator FPS() { int lastFrameCount; float lastTime; float timeSpan; int frameCount; for (; ; ) { // Capture frame-per-second lastFrameCount = Time.frameCount; lastTime = Time.realtimeSinceStartup; yield return waitForFrequency; timeSpan = Time.realtimeSinceStartup - lastTime; frameCount = Time.frameCount - lastFrameCount; fps = string.Format("FPS: {0}", Mathf.RoundToInt(frameCount / timeSpan)); } } void OnGUI() { // Display // GUI.Label(rect, fps, style); // GUI.Label(new Rect(Screen.width - 110, 5, 0, Screen.height * 2 / 100), fps, style); GUI.Label(new Rect(10, 10, Screen.width, Screen.height * 2 / 100), fps, style); //GUI.Label(new Rect(Screen.width - 100, 10, 150, 20), fps, style); } private void Inicialize(bool showFps) { isInicialized = true; style.alignment = TextAnchor.UpperLeft; style.fontSize = Screen.height * 3 / 100; style.normal.textColor = new Color32(0, 200, 0, 255); rect = new Rect(Screen.width * 90 / 100, 5, 0, Screen.height * 2 / 100); if (showFps) StartCoroutine(FPS()); } public void SetNewConfig(GraphicSettingsMB gSettings) { Application.targetFrameRate = gSettings.targetFrameRate; waitForFrequency = new WaitForSecondsRealtime(gSettings.testFpsFrequency); if (!isInicialized) Inicialize(gSettings.showFps); if (!gSettings.showFps) Destroy(this.gameObject); } } [SerializeField] public class GraphicSettingsMB { public byte targetFrameRate = 90; public byte testFpsFrequency = 1; public bool showFps = false; }
https://forum.unity.com/threads/fps-counter.505495/
보통 게임을 만들때, 특히 출시까지 고려하게 되면 최적화는 피할 수 없는 문제가 된다.
그런데 내가 처음 게임을 만들때는 이런 기준이 전혀 잡히지 않아서 난감하고 막막했었다.
하지만 물론 게임마다 다르겠지만, 대략적으로 기준을 잡는 방법이 있다
아마 듣고 나면 당연해서 왜 생각 못했지 싶을 것 같다.
아무튼 상당히 명료한 기준이다.
바로 fps를 기준으로 잡으면 된다.
뭔소리고 하니, 일반적인 게임은 60fps 혹은 120fps정도로 맞춘다.
물론 그 이상의 프레임을 원하는 유저들도 있지만, 보통은 저정도 프레임률이 나오면 보통은 만족한다.
안드로이드는 60fps, pc는 120fps를 기준으로 잡으면 된다.
fps는 초당 프레임률이기 때문에, fps를 역수 취해주면 프레임당 초가 나온다.
1/120= 0.00833…, 따라서 8ms 이내로 하나의 프레임을 처리 할 수 있으면 된다.
60fps로 잡으면 그 두배인 16ms면 이내로 처리하면 될 것이다.
여기부터는 유니티 기준 얘기다.
프레임단위 처리에 소요시간을 기준으로 판단해야한다는 것을 알았다.
이를 확인하는 방법은 여러가지가 있겠으나,
가장 편리한 방법은 유니티 프로파일러를 이용하는 것이다.
나머지는 해당 글에서 확인
UniTask
https://github.com/Cysharp/UniTask
SingletonMonobehaviour.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SingletonMonobehaviour<T> : MonoBehaviour where T : MonoBehaviour { static T _Instance = null; public static T Instance { get { if (_Instance == null) { _Instance = (T)FindObjectOfType(typeof(T)); if (_Instance == null) { var _NewGameObject = new GameObject(typeof(T).ToString()); _Instance = _NewGameObject.AddComponent<T>(); } } return _Instance; } } protected virtual void Awake() { if (_Instance == null) _Instance = this as T; DontDestroyOnLoad(gameObject); } }
ApplicationManager.cs
using UnityEngine; using Cysharp.Threading.Tasks; using System.Threading; public partial class ApplicationManager : SingletonMonobehaviour<ApplicationManager> { [Header("== :: Initial Settings :: ==")] [SerializeField] byte TargetFPS; protected override void Awake() { base.Awake(); ActiveFPSDebugger(); ChangeTargetFrameRate(TargetFPS); } } #region == :: FPS Debuger Region :: == public partial class ApplicationManager { public float CurFPS_f { get; private set; } public string CurFPS_str => string.Format($"FPS : {CurFPS_f:0.0}"); public bool IsActiveFPSDebuger => FPSTask.Status == UniTaskStatus.Pending; byte _FpsFrequency = 1; GUIStyle _GUIStyle = new(); Rect _Rect; UniTask FPSTask { get; set; } CancellationTokenSource FPSTaskCancelTokenSource { get; set; } void OnGUI() { if (!IsActiveFPSDebuger) return; GUI.Label(_Rect, CurFPS_str, _GUIStyle); } public void ChangeTargetFrameRate(byte _targetFrame) { QualitySettings.vSyncCount = 0; Application.targetFrameRate = _targetFrame; #if (UNITY_EDITOR) Debug.LogFormat($"<Color=red>Change the TargetFrame.{_targetFrame}</color>"); #endif } public void ActiveFPSDebugger() { if (IsActiveFPSDebuger) { #if (UNITY_EDITOR) Debug.LogFormat($"<Color=red>FPS Debugger is already running.</color>"); #endif return; } FPSTaskCancelTokenSource = new CancellationTokenSource(); FPSTask = FPSDebuggerTask(); _GUIStyle.alignment = TextAnchor.UpperLeft; _GUIStyle.fontSize = Screen.height / 25; _GUIStyle.normal.textColor = new(0, 200, 0, 255); _GUIStyle.fontStyle = FontStyle.Bold; _Rect = new(Screen.width / 20, Screen.height / 20, 100, 100); } public void DeActiveFPSDebugger() { if (!IsActiveFPSDebuger) { #if (UNITY_EDITOR) Debug.LogFormat($"<Color=red>FPS Debugger is not running.</color>"); #endif return; } FPSTaskCancelTokenSource.Cancel(); } private async UniTask FPSDebuggerTask() { float _elapsedTime = 0f; int _frameCount = 0; var _Token = FPSTaskCancelTokenSource.Token; while (!_Token.IsCancellationRequested) { await UniTask.DelayFrame(_FpsFrequency); _frameCount++; _elapsedTime += Time.deltaTime; if (_elapsedTime >= _FpsFrequency) { CurFPS_f = _frameCount / _elapsedTime; _frameCount = 0; _elapsedTime = 0f; } } } } #endregion