Unity WebGL 런타임 성능 최적화 가이드
Unity WebGL 환경에서 런타임 성능을 최적화하는 건, 앱인토스 미니앱에서 부드러운 게임 경험을 만드는 핵심이에요.
이 가이드에서는 프레임률 향상, CPU 사용량 감소, GPU 최적화 방법을 함께 다뤄요.
CPU 최적화
1. 프레임률 관리 시스템
c#
using UnityEngine;
using System.Collections;
public class PerformanceManager : MonoBehaviour
{
[Header("성능 설정")]
public int targetFrameRate = 60;
public bool adaptiveFrameRate = true;
public float performanceCheckInterval = 2f;
[Header("성능 임계값")]
public float lowPerformanceThreshold = 40f;
public float highPerformanceThreshold = 55f;
private float averageFrameRate;
private int qualityLevel;
private bool isLowPerformanceMode = false;
private void Start()
{
qualityLevel = QualitySettings.GetQualityLevel();
Application.targetFrameRate = targetFrameRate;
if (adaptiveFrameRate)
{
StartCoroutine(AdaptivePerformanceRoutine());
}
}
private IEnumerator AdaptivePerformanceRoutine()
{
while (true)
{
yield return new WaitForSeconds(performanceCheckInterval);
averageFrameRate = 1.0f / Time.smoothDeltaTime;
AdjustPerformanceSettings();
// AppsInToss 플랫폼에 성능 정보 전송
SendPerformanceDataToAppsInToss();
}
}
private void AdjustPerformanceSettings()
{
if (averageFrameRate < lowPerformanceThreshold && !isLowPerformanceMode)
{
EnableLowPerformanceMode();
}
else if (averageFrameRate > highPerformanceThreshold && isLowPerformanceMode)
{
DisableLowPerformanceMode();
}
}
private void EnableLowPerformanceMode()
{
isLowPerformanceMode = true;
// 품질 설정 낮추기
QualitySettings.SetQualityLevel(Mathf.Max(0, qualityLevel - 1));
// 그림자 비활성화
QualitySettings.shadows = ShadowQuality.Disable;
// 파티클 수 감소
QualitySettings.particleRaycastBudget = 16;
// 물리 업데이트 빈도 감소
Time.fixedDeltaTime = 1f / 30f;
Debug.Log("저성능 모드 활성화됨");
}
private void DisableLowPerformanceMode()
{
isLowPerformanceMode = false;
// 원래 품질 설정 복원
QualitySettings.SetQualityLevel(qualityLevel);
QualitySettings.shadows = ShadowQuality.All;
QualitySettings.particleRaycastBudget = 256;
Time.fixedDeltaTime = 1f / 50f;
Debug.Log("일반 성능 모드 복원됨");
}
private void SendPerformanceDataToAppsInToss()
{
string performanceData = JsonUtility.ToJson(new {
frameRate = averageFrameRate,
isLowPerformanceMode = isLowPerformanceMode,
qualityLevel = QualitySettings.GetQualityLevel()
});
Application.ExternalCall("SendPerformanceDataToAppsInToss", performanceData);
}
}2. 효율적인 업데이트 관리
c#
using UnityEngine;
using System.Collections.Generic;
public class UpdateManager : MonoBehaviour
{
private static UpdateManager instance;
public static UpdateManager Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<UpdateManager>();
if (instance == null)
{
GameObject go = new GameObject("UpdateManager");
instance = go.AddComponent<UpdateManager>();
DontDestroyOnLoad(go);
}
}
return instance;
}
}
private List<IUpdatable> updatables = new List<IUpdatable>();
private List<IFixedUpdatable> fixedUpdatables = new List<IFixedUpdatable>();
private List<ILateUpdatable> lateUpdatables = new List<ILateUpdatable>();
// 성능 최적화를 위한 업데이트 빈도 제어
private int frameCount = 0;
private void Update()
{
frameCount++;
// 매 프레임 업데이트
for (int i = updatables.Count - 1; i >= 0; i--)
{
if (updatables[i] != null)
updatables[i].OnUpdate();
}
// 2프레임마다 업데이트 (30Hz)
if (frameCount % 2 == 0)
{
for (int i = updatables.Count - 1; i >= 0; i--)
{
if (updatables[i] is ISlowUpdatable slowUpdatable)
slowUpdatable.OnSlowUpdate();
}
}
}
private void FixedUpdate()
{
for (int i = fixedUpdatables.Count - 1; i >= 0; i--)
{
if (fixedUpdatables[i] != null)
fixedUpdatables[i].OnFixedUpdate();
}
}
private void LateUpdate()
{
for (int i = lateUpdatables.Count - 1; i >= 0; i--)
{
if (lateUpdatables[i] != null)
lateUpdatables[i].OnLateUpdate();
}
}
public void RegisterUpdatable(IUpdatable updatable)
{
if (!updatables.Contains(updatable))
updatables.Add(updatable);
}
public void UnregisterUpdatable(IUpdatable updatable)
{
updatables.Remove(updatable);
}
}
// 인터페이스들
public interface IUpdatable
{
void OnUpdate();
}
public interface ISlowUpdatable
{
void OnSlowUpdate();
}
public interface IFixedUpdatable
{
void OnFixedUpdate();
}
public interface ILateUpdatable
{
void OnLateUpdate();
}
// 사용 예제
public class OptimizedBehavior : MonoBehaviour, IUpdatable, ISlowUpdatable
{
private Vector3 targetPosition;
private bool needsPositionUpdate = true;
private void OnEnable()
{
UpdateManager.Instance.RegisterUpdatable(this);
}
private void OnDisable()
{
UpdateManager.Instance.UnregisterUpdatable(this);
}
public void OnUpdate()
{
// 매 프레임 필요한 업데이트
if (needsPositionUpdate)
{
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * 5f);
if (Vector3.Distance(transform.position, targetPosition) < 0.01f)
{
needsPositionUpdate = false;
}
}
}
public void OnSlowUpdate()
{
// 2프레임마다 수행되는 업데이트 (AI, 경로 찾기 등)
UpdateAI();
}
private void UpdateAI()
{
// 비용이 큰 AI 로직
}
}GPU 최적화
1. 배칭 최적화
c#
using UnityEngine;
using System.Collections.Generic;
public class BatchingOptimizer : MonoBehaviour
{
[Header("배칭 설정")]
public bool enableStaticBatching = true;
public bool enableDynamicBatching = true;
public bool enableInstancing = true;
[Header("인스턴싱")]
public GameObject instancedPrefab;
public Material instancedMaterial;
public int maxInstances = 1000;
private Matrix4x4[] instanceMatrices;
private MaterialPropertyBlock propertyBlock;
private Mesh instanceMesh;
private List<Vector3> instancePositions = new List<Vector3>();
private void Start()
{
SetupBatching();
SetupInstancing();
}
private void SetupBatching()
{
// 정적 배칭 활성화
if (enableStaticBatching)
{
GameObject[] staticObjects = GameObject.FindGameObjectsWithTag("StaticBatchable");
StaticBatchingUtility.Combine(staticObjects, this.gameObject);
}
}
private void SetupInstancing()
{
if (!enableInstancing || instancedPrefab == null) return;
instanceMatrices = new Matrix4x4[maxInstances];
propertyBlock = new MaterialPropertyBlock();
MeshRenderer renderer = instancedPrefab.GetComponent<MeshRenderer>();
if (renderer != null)
{
instanceMesh = instancedPrefab.GetComponent<MeshFilter>().sharedMesh;
}
}
private void Update()
{
if (enableInstancing && instanceMesh != null)
{
RenderInstances();
}
}
private void RenderInstances()
{
int instanceCount = Mathf.Min(instancePositions.Count, maxInstances);
for (int i = 0; i < instanceCount; i++)
{
instanceMatrices[i] = Matrix4x4.TRS(instancePositions[i], Quaternion.identity, Vector3.one);
}
Graphics.DrawMeshInstanced(
instanceMesh,
0,
instancedMaterial,
instanceMatrices,
instanceCount,
propertyBlock
);
}
public void AddInstance(Vector3 position)
{
if (instancePositions.Count < maxInstances)
{
instancePositions.Add(position);
}
}
public void RemoveInstance(Vector3 position)
{
instancePositions.Remove(position);
}
public void ClearInstances()
{
instancePositions.Clear();
}
}2. 텍스처 아틀라스 관리
c#
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "TextureAtlasConfig", menuName = "Performance/Texture Atlas Config")]
public class TextureAtlasConfig : ScriptableObject
{
[System.Serializable]
public class AtlasEntry
{
public string name;
public Texture2D texture;
public Rect uvRect;
}
[Header("아틀라스 설정")]
public Texture2D atlasTexture;
public List<AtlasEntry> entries = new List<AtlasEntry>();
public int atlasSize = 2048;
private Dictionary<string, Rect> uvLookup;
public void Initialize()
{
uvLookup = new Dictionary<string, Rect>();
foreach (var entry in entries)
{
uvLookup[entry.name] = entry.uvRect;
}
}
public Rect GetUVRect(string textureName)
{
if (uvLookup == null) Initialize();
return uvLookup.TryGetValue(textureName, out Rect rect) ? rect : new Rect(0, 0, 1, 1);
}
}
public class TextureAtlasManager : MonoBehaviour
{
[Header("아틀라스 설정")]
public TextureAtlasConfig atlasConfig;
public Material atlasMaterial;
private void Start()
{
if (atlasConfig != null)
{
atlasConfig.Initialize();
ApplyAtlasToMaterials();
}
}
private void ApplyAtlasToMaterials()
{
// 씬의 모든 렌더러에 아틀라스 적용
MeshRenderer[] renderers = FindObjectsOfType<MeshRenderer>();
foreach (var renderer in renderers)
{
if (renderer.material.name.Contains("Atlasable"))
{
renderer.material = atlasMaterial;
// UV 좌표 조정
string textureName = renderer.name; // 또는 다른 식별 방법
Rect uvRect = atlasConfig.GetUVRect(textureName);
renderer.material.SetVector("_MainTex_ST", new Vector4(uvRect.width, uvRect.height, uvRect.x, uvRect.y));
}
}
}
}AppsInToss 플랫폼 연동
1. 성능 모니터링 시스템
tsx
interface PerformanceMetrics {
frameRate: number;
drawCalls: number;
triangles: number;
vertices: number;
memoryUsage: number;
loadTime: number;
}
class PerformanceMonitor {
private static instance: PerformanceMonitor;
private metrics: PerformanceMetrics = {
frameRate: 0,
drawCalls: 0,
triangles: 0,
vertices: 0,
memoryUsage: 0,
loadTime: 0
};
private frameCount = 0;
private lastTime = 0;
public static getInstance(): PerformanceMonitor {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor();
}
return PerformanceMonitor.instance;
}
public startMonitoring(): void {
this.lastTime = performance.now();
this.monitoringLoop();
}
private monitoringLoop(): void {
const currentTime = performance.now();
this.frameCount++;
// FPS 계산 (1초마다)
if (currentTime - this.lastTime >= 1000) {
this.metrics.frameRate = this.frameCount;
this.frameCount = 0;
this.lastTime = currentTime;
this.collectMetrics();
}
requestAnimationFrame(() => this.monitoringLoop());
}
private collectMetrics(): void {
// Unity에서 성능 메트릭 수집
const unityInstance = (window as any).unityInstance;
if (unityInstance) {
// Unity에서 렌더링 통계 요청
unityInstance.SendMessage('PerformanceManager', 'CollectRenderingStats', '');
}
// 메모리 사용량 수집
const memInfo = (performance as any).memory;
if (memInfo) {
this.metrics.memoryUsage = memInfo.usedJSHeapSize;
}
}
public updateMetrics(data: Partial<PerformanceMetrics>): void {
Object.assign(this.metrics, data);
}
public getMetrics(): PerformanceMetrics {
return { ...this.metrics };
}
// 성능 경고 시스템
public checkPerformanceWarnings(): void {
const warnings = [];
if (this.metrics.frameRate < 30) {
warnings.push('낮은 프레임률 감지');
}
if (this.metrics.drawCalls > 200) {
warnings.push('과도한 드로우 콜');
}
if (this.metrics.memoryUsage > 100 * 1024 * 1024) { // 100MB
warnings.push('높은 메모리 사용량');
}
}
}
// Unity에서 호출할 함수들
(window as any).UpdatePerformanceMetrics = (data: string) => {
const metrics = JSON.parse(data);
PerformanceMonitor.getInstance().updateMetrics(metrics);
};
(window as any).OnUnityLoaded = () => {
PerformanceMonitor.getInstance().startMonitoring();
};2. 동적 품질 조정
c#
using UnityEngine;
using System.Runtime.InteropServices;
public class DynamicQualityManager : MonoBehaviour
{
[Header("품질 설정")]
public QualityProfile[] qualityProfiles;
[System.Serializable]
public class QualityProfile
{
public string name;
public int textureQuality;
public ShadowQuality shadowQuality;
public int particleBudget;
public float renderScale;
public bool enablePostProcessing;
}
[DllImport("__Internal")]
private static extern void SendQualityChangeToAppsInToss(string qualityData);
private int currentQualityIndex = 1; // 보통 품질로 시작
private float lastFrameTime;
private int lowFrameCount = 0;
private void Start()
{
if (qualityProfiles.Length > 0)
{
ApplyQualityProfile(currentQualityIndex);
}
}
private void Update()
{
MonitorPerformance();
}
private void MonitorPerformance()
{
float currentFrameTime = Time.unscaledDeltaTime;
// 프레임 시간이 33ms(30fps) 이상인 경우
if (currentFrameTime > 1f / 30f)
{
lowFrameCount++;
}
else
{
lowFrameCount = Mathf.Max(0, lowFrameCount - 1);
}
// 연속으로 저성능이 감지되면 품질 낮추기
if (lowFrameCount > 10 && currentQualityIndex > 0)
{
ChangeQuality(currentQualityIndex - 1);
lowFrameCount = 0;
}
// 성능이 개선되면 품질 올리기
else if (lowFrameCount == 0 && currentFrameTime < 1f / 50f && currentQualityIndex < qualityProfiles.Length - 1)
{
ChangeQuality(currentQualityIndex + 1);
}
lastFrameTime = currentFrameTime;
}
public void ChangeQuality(int qualityIndex)
{
if (qualityIndex < 0 || qualityIndex >= qualityProfiles.Length) return;
currentQualityIndex = qualityIndex;
ApplyQualityProfile(qualityIndex);
// AppsInToss 플랫폼에 품질 변경 알림
NotifyQualityChange();
}
private void ApplyQualityProfile(int index)
{
QualityProfile profile = qualityProfiles[index];
// 텍스처 품질
QualitySettings.masterTextureLimit = profile.textureQuality;
// 그림자 품질
QualitySettings.shadows = profile.shadowQuality;
// 파티클 예산
QualitySettings.particleRaycastBudget = profile.particleBudget;
// 렌더 스케일 (가능한 경우)
if (profile.renderScale != 1f)
{
Camera.main.pixelRect = new Rect(0, 0,
Screen.width * profile.renderScale,
Screen.height * profile.renderScale);
}
// 포스트 프로세싱
var postProcessVolume = FindObjectOfType<UnityEngine.Rendering.PostProcessing.PostProcessVolume>();
if (postProcessVolume != null)
{
postProcessVolume.enabled = profile.enablePostProcessing;
}
Debug.Log($"품질 프로필 적용됨: {profile.name}");
}
private void NotifyQualityChange()
{
QualityProfile currentProfile = qualityProfiles[currentQualityIndex];
string qualityData = JsonUtility.ToJson(new {
qualityLevel = currentQualityIndex,
qualityName = currentProfile.name,
timestamp = Time.time
});
SendQualityChangeToAppsInToss(qualityData);
}
// 외부에서 품질 강제 변경 (AppsInToss에서 호출)
public void ForceQualityChange(string qualityData)
{
var data = JsonUtility.FromJson<QualityChangeData>(qualityData);
ChangeQuality(data.qualityLevel);
}
[System.Serializable]
private class QualityChangeData
{
public int qualityLevel;
}
}렌더링 최적화
1. LOD (Level of Detail) 시스템
c#
using UnityEngine;
public class DynamicLODManager : MonoBehaviour
{
[Header("LOD 설정")]
public float[] lodDistances = { 50f, 100f, 200f };
public float cullDistance = 300f;
public Transform playerTransform;
[Header("성능 기반 LOD")]
public bool enablePerformanceLOD = true;
public float performanceThreshold = 45f; // FPS
private LODGroup[] allLODGroups;
private Camera mainCamera;
private float performanceLODBias = 1f;
private void Start()
{
mainCamera = Camera.main;
allLODGroups = FindObjectsOfType<LODGroup>();
if (enablePerformanceLOD)
{
InvokeRepeating(nameof(UpdatePerformanceLOD), 1f, 2f);
}
}
private void Update()
{
UpdateLODs();
}
private void UpdateLODs()
{
if (playerTransform == null) return;
foreach (LODGroup lodGroup in allLODGroups)
{
if (lodGroup == null) continue;
float distance = Vector3.Distance(playerTransform.position, lodGroup.transform.position);
// 컬링 거리 확인
if (distance > cullDistance)
{
lodGroup.gameObject.SetActive(false);
continue;
}
else if (!lodGroup.gameObject.activeInHierarchy)
{
lodGroup.gameObject.SetActive(true);
}
// 성능 기반 LOD 바이어스 적용
float adjustedDistance = distance * performanceLODBias;
// LOD 레벨 결정
int lodLevel = GetLODLevel(adjustedDistance);
lodGroup.ForceLOD(lodLevel);
}
}
private int GetLODLevel(float distance)
{
for (int i = 0; i < lodDistances.Length; i++)
{
if (distance < lodDistances[i])
return i;
}
return lodDistances.Length; // 가장 낮은 품질 또는 컬링
}
private void UpdatePerformanceLOD()
{
float currentFPS = 1.0f / Time.smoothDeltaTime;
if (currentFPS < performanceThreshold)
{
// 성능이 낮으면 LOD 바이어스를 줄여서 더 낮은 품질 사용
performanceLODBias = Mathf.Max(0.5f, performanceLODBias - 0.1f);
}
else if (currentFPS > performanceThreshold + 10f)
{
// 성능이 좋으면 LOD 바이어스를 높여서 더 높은 품질 사용
performanceLODBias = Mathf.Min(1f, performanceLODBias + 0.1f);
}
QualitySettings.lodBias = performanceLODBias;
}
}2. 오클루전 컬링
c#
using UnityEngine;
using System.Collections.Generic;
public class CustomOcclusionCulling : MonoBehaviour
{
[Header("오클루전 설정")]
public LayerMask occluderLayers = -1;
public float raycastDistance = 100f;
public int raysPerObject = 9; // 3x3 그리드
[Header("성능 설정")]
public int objectsPerFrame = 10;
public float updateInterval = 0.1f;
private List<Renderer> allRenderers = new List<Renderer>();
private Queue<Renderer> renderersToCheck = new Queue<Renderer>();
private Camera mainCamera;
private void Start()
{
mainCamera = Camera.main;
CollectRenderers();
InvokeRepeating(nameof(ProcessOcclusion), 0f, updateInterval);
}
private void CollectRenderers()
{
Renderer[] renderers = FindObjectsOfType<Renderer>();
foreach (Renderer renderer in renderers)
{
// 스킨드 메시나 파티클은 제외
if (renderer is MeshRenderer || renderer is SpriteRenderer)
{
allRenderers.Add(renderer);
renderersToCheck.Enqueue(renderer);
}
}
}
private void ProcessOcclusion()
{
int processedCount = 0;
while (renderersToCheck.Count > 0 && processedCount < objectsPerFrame)
{
Renderer renderer = renderersToCheck.Dequeue();
if (renderer != null && renderer.gameObject.activeInHierarchy)
{
bool isVisible = CheckVisibility(renderer);
renderer.enabled = isVisible;
// 다음 프레임에 다시 검사하도록 큐에 추가
renderersToCheck.Enqueue(renderer);
}
processedCount++;
}
}
private bool CheckVisibility(Renderer renderer)
{
Bounds bounds = renderer.bounds;
Vector3 cameraPosition = mainCamera.transform.position;
// 카메라 프러스텀 내부에 있는지 확인
if (!GeometryUtility.TestPlanesAABB(GeometryUtility.CalculateFrustumPlanes(mainCamera), bounds))
{
return false;
}
// 레이캐스트를 통한 오클루전 테스트
Vector3 boundsCenter = bounds.center;
Vector3 boundsSize = bounds.size;
int visibleRays = 0;
// 3x3 그리드로 레이 발사
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
Vector3 testPoint = boundsCenter + new Vector3(
x * boundsSize.x * 0.25f,
y * boundsSize.y * 0.25f,
0
);
Vector3 direction = (testPoint - cameraPosition).normalized;
float distance = Vector3.Distance(cameraPosition, testPoint);
if (!Physics.Raycast(cameraPosition, direction, distance, occluderLayers))
{
visibleRays++;
if (visibleRays > raysPerObject / 3) // 1/3 이상 보이면 렌더링
{
return true;
}
}
}
}
return visibleRays > 0;
}
}물리 최적화
1. 물리 업데이트 관리
c#
using UnityEngine;
using System.Collections.Generic;
public class PhysicsOptimizer : MonoBehaviour
{
[Header("물리 설정")]
public float physicsTimeStep = 0.02f; // 50Hz
public int maxSubSteps = 8;
public float sleepThreshold = 0.005f;
[Header("성능 기반 조정")]
public bool enableAdaptivePhysics = true;
public float targetFrameRate = 60f;
private List<Rigidbody> managedRigidbodies = new List<Rigidbody>();
private float originalFixedDeltaTime;
private void Start()
{
originalFixedDeltaTime = Time.fixedDeltaTime;
Time.fixedDeltaTime = physicsTimeStep;
Physics.sleepThreshold = sleepThreshold;
CollectRigidbodies();
if (enableAdaptivePhysics)
{
InvokeRepeating(nameof(AdaptPhysicsSettings), 1f, 2f);
}
}
private void CollectRigidbodies()
{
Rigidbody[] rigidbodies = FindObjectsOfType<Rigidbody>();
foreach (Rigidbody rb in rigidbodies)
{
managedRigidbodies.Add(rb);
OptimizeRigidbody(rb);
}
}
private void OptimizeRigidbody(Rigidbody rb)
{
// 잠자기 설정 최적화
rb.sleepThreshold = sleepThreshold;
// 불필요한 물리 연산 비활성화
if (rb.GetComponent<Collider>() == null)
{
rb.detectCollisions = false;
}
// 정적 오브젝트는 키네마틱으로 설정
if (rb.GetComponent<Collider>() != null && rb.GetComponent<Collider>().isTrigger)
{
rb.isKinematic = true;
}
}
private void AdaptPhysicsSettings()
{
float currentFrameRate = 1.0f / Time.smoothDeltaTime;
if (currentFrameRate < targetFrameRate - 10f)
{
// 성능이 낮으면 물리 업데이트 빈도 감소
Time.fixedDeltaTime = Mathf.Min(0.033f, Time.fixedDeltaTime + 0.002f);
// 일부 리지드바디를 잠자기 상태로 전환
SleepDistantRigidbodies();
}
else if (currentFrameRate > targetFrameRate + 5f)
{
// 성능이 좋으면 물리 업데이트 빈도 증가
Time.fixedDeltaTime = Mathf.Max(physicsTimeStep, Time.fixedDeltaTime - 0.002f);
}
}
private void SleepDistantRigidbodies()
{
Vector3 playerPosition = Camera.main.transform.position;
float maxActiveDistance = 50f;
foreach (Rigidbody rb in managedRigidbodies)
{
if (rb == null) continue;
float distance = Vector3.Distance(rb.transform.position, playerPosition);
if (distance > maxActiveDistance && !rb.IsSleeping())
{
rb.Sleep();
}
else if (distance <= maxActiveDistance && rb.IsSleeping())
{
rb.WakeUp();
}
}
}
}프로파일링 및 디버깅
1. 성능 분석 도구
c#
using UnityEngine;
using System.Text;
using System.Collections.Generic;
public class PerformanceProfiler : MonoBehaviour
{
[Header("프로파일링 설정")]
public bool enableProfiling = true;
public KeyCode toggleKey = KeyCode.F1;
public float updateInterval = 1f;
private Dictionary<string, float> performanceData = new Dictionary<string, float>();
private StringBuilder displayText = new StringBuilder();
private bool showGUI = false;
private void Update()
{
if (Input.GetKeyDown(toggleKey))
{
showGUI = !showGUI;
}
if (enableProfiling)
{
CollectPerformanceData();
}
}
private void CollectPerformanceData()
{
// FPS
performanceData["FPS"] = 1.0f / Time.smoothDeltaTime;
// 메모리 사용량
performanceData["Memory (MB)"] = System.GC.GetTotalMemory(false) / 1024f / 1024f;
// 렌더링 통계
performanceData["SetPass Calls"] = UnityEngine.Rendering.FrameDebugger.enabled ?
UnityEngine.Rendering.FrameDebugger.GetFrameEventCount() : 0;
// 활성 오브젝트 수
performanceData["Active GameObjects"] = FindObjectsOfType<GameObject>().Length;
// 활성 렌더러 수
performanceData["Active Renderers"] = FindObjectsOfType<Renderer>().Length;
}
private void OnGUI()
{
if (!showGUI) return;
displayText.Clear();
displayText.AppendLine("=== 성능 분석 ===");
foreach (var kvp in performanceData)
{
displayText.AppendFormat("{0}: {1:F2}\n", kvp.Key, kvp.Value);
}
GUI.Box(new Rect(10, 10, 300, 200), displayText.ToString());
}
public void LogPerformanceData()
{
StringBuilder log = new StringBuilder();
log.AppendLine("Performance Report:");
foreach (var kvp in performanceData)
{
log.AppendFormat("{0}: {1:F2}\n", kvp.Key, kvp.Value);
}
Debug.Log(log.ToString());
// AppsInToss 플랫폼에 전송
Application.ExternalCall("SendPerformanceReport", log.ToString());
}
}베스트 프랙티스
- 적응형 성능 관리 - 실시간으로 성능을 모니터링하고 품질을 조정
- 효율적인 업데이트 - Update 함수 대신 이벤트 기반 시스템 사용
- 배칭 최적화 - 드로우 콜 수 최소화
- 메모리 관리 - 불필요한 할당과 가비지 생성 방지
- AppsInToss 플랫폼 활용 - 네이티브 성능 모니터링 기능 활용
