유니티를 조금씩 공부를 하고는 있는데 궁금해서 한 번 질문을 해봤다.
결국엔 약간 바보같은? 질문을 해버린듯? 그래도 궁금증 해소를 위해서...
원래의 질문 - 씬이 많아지면 성능이 떨어지는지?
유니티에서 씬(Scene)을 많이 구성하는 것이 성능에 영향을 미칠 수 있지만, 이는 주로 어떻게 구성하고 관리하느냐에 따라 다릅니다.
특히, 모바일 앱 개발에서는 메모리와 성능 최적화가 중요한 요소이므로 다음 사항들을 고려해야 합니다.
1. 씬 수가 많아도 성능에 직접적인 영향은 적음
유니티는 한 번에 하나의 씬만 로드되므로, 로드되지 않은 씬은 메모리나 CPU 자원을 소모하지 않습니다.
따라서 씬의 개수 자체는 성능 저하의 주요 원인이 아닙니다.
2. 씬 로딩 시간
씬 전환 시 리소스를 로드하는 과정에서 로딩 시간이 발생합니다.
씬에 포함된 에셋의 크기와 개수가 많거나, 동적 객체 생성이 많다면 로딩 시간 증가 및 성능 저하가 생길 수 있습니다.
- Addressables 또는 Asset Bundle을 활용하여 필요한 리소스만 동적으로 로드.
- 씬 분할(Chunking): 큰 씬을 여러 개의 작은 씬으로 나누어 점진적으로 로드.
* 현재 문제점
- 큰 씬을 로드할 때 UI가 멈추는 현상 발생.
- 로딩 과정에서 플레이어 경험 저하.
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneLoader : MonoBehaviour
{
public void LoadSceneSynchronously(string sceneName)
{
// 동기적으로 씬 로드
SceneManager.LoadScene(sceneName);
Debug.Log("Scene loaded synchronously: " + sceneName);
}
}
* 개선 후 : 최적화된 비동기 로딩 방식
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class AsyncSceneLoader : MonoBehaviour
{
public GameObject loadingUI; // 로딩 화면 UI
public UnityEngine.UI.Slider progressBar; // 로딩 진행 표시용 슬라이더
public void LoadSceneAsynchronously(string sceneName)
{
// 로딩 UI 활성화
if (loadingUI != null)
loadingUI.SetActive(true);
// 비동기 로드 시작
StartCoroutine(LoadSceneCoroutine(sceneName));
}
private IEnumerator LoadSceneCoroutine(string sceneName)
{
// 씬 비동기 로드 시작
AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName);
// 로드가 완료될 때까지 대기
while (!asyncOperation.isDone)
{
float progress = Mathf.Clamp01(asyncOperation.progress / 0.9f); // 0.9f가 max 값
if (progressBar != null)
progressBar.value = progress; // 진행률 업데이트
Debug.Log("Loading progress: " + progress * 100 + "%");
yield return null; // 한 프레임 대기
}
// 로드 완료 후 로딩 UI 비활성화
if (loadingUI != null)
loadingUI.SetActive(false);
Debug.Log("Scene loaded asynchronously: " + sceneName);
}
}
public class ExampleUsage : MonoBehaviour
{
public AsyncSceneLoader asyncLoader;
public void LoadGameScene()
{
// 기존 방식
//asyncLoader.LoadSceneSynchronously("GameScene");
// 최적화된 방식
asyncLoader.LoadSceneAsynchronously("GameScene");
}
}
3. 씬 관리
씬이 많아질수록 프로젝트 관리가 복잡해질 수 있으며, 로직 간의 연결(Dependencies)이 늘어날 수 있습니다.
적절한 씬 분할 전략이 필요합니다:
- 공유 리소스 씬: UI, 공통 오브젝트 등을 별도 씬으로 분리하고, Additive Load를 활용.
- 구역 씬: 게임 구역을 씬 단위로 나누어 필요한 구역만 로드.
* 현재의 문제점
- 씬이 크면 메모리 사용량과 로딩 시간이 크게 증가.
- 불필요한 오브젝트까지 로드되어 성능 저하.
- 로딩 중 앱이 멈춘 것처럼 보일 수 있음
using UnityEngine;
using UnityEngine.SceneManagement;
public class LargeSceneLoader : MonoBehaviour
{
public void LoadLargeScene()
{
// 큰 단일 씬을 로드
SceneManager.LoadScene("LargeScene");
Debug.Log("Large scene loaded");
}
}
* 씬 분할 및 Additive (첨가제? 첨가물?) 방식
- BaseScene: 공통 UI, 카메라, 기본 환경 포함.
- EnvironmentScene: 배경, 지형 등 환경 오브젝트 포함.
- EnemyScene: 적 캐릭터 포함.
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class SplitSceneLoader : MonoBehaviour
{
public void LoadBaseScene()
{
// 기본 베이스 씬 로드
SceneManager.LoadScene("BaseScene", LoadSceneMode.Single);
Debug.Log("Base scene loaded");
}
public void LoadAdditionalScene(string sceneName)
{
StartCoroutine(LoadSceneAdditively(sceneName));
}
public void UnloadAdditionalScene(string sceneName)
{
StartCoroutine(UnloadScene(sceneName));
}
private IEnumerator LoadSceneAdditively(string sceneName)
{
// 추가 씬을 비동기로 로드
AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
while (!asyncOperation.isDone)
{
Debug.Log($"Loading {sceneName}: {asyncOperation.progress * 100}%");
yield return null;
}
Debug.Log($"Additional scene {sceneName} loaded");
}
private IEnumerator UnloadScene(string sceneName)
{
// 추가 씬 언로드
AsyncOperation asyncOperation = SceneManager.UnloadSceneAsync(sceneName);
while (!asyncOperation.isDone)
{
Debug.Log($"Unloading {sceneName}: {asyncOperation.progress * 100}%");
yield return null;
}
Debug.Log($"Additional scene {sceneName} unloaded");
}
}
public class GameManager : MonoBehaviour
{
public SplitSceneLoader sceneLoader;
void Start()
{
// 베이스 씬 로드
sceneLoader.LoadBaseScene();
// 필요한 씬 추가 로드
sceneLoader.LoadAdditionalScene("EnvironmentScene");
sceneLoader.LoadAdditionalScene("EnemyScene");
}
public void OnPlayerExitZone()
{
// 필요 없어진 씬 언로드
sceneLoader.UnloadAdditionalScene("EnemyScene");
}
}
* 추가적인 최적화 팁
- 씬별 리소스 관리:
- 큰 에셋은 Addressables로 관리하여 필요할 때만 로드.
- 씬 간 데이터 전달:
- PlayerPrefs, DontDestroyOnLoad, 또는 ScriptableObject를 활용하여 데이터를 유지.
- 로딩 중 UX 개선:
- 씬 로드 중 로딩 화면 표시 및 진행 상태 보여주기.
4.모바일 앱 성능 최적화 고려사항
- 메모리 사용량: 모바일은 메모리 용량이 제한적이므로, 씬 전환 시 불필요한 리소스 정리가 필요합니다. Resources.UnloadUnusedAssets()를 호출하여 사용하지 않는 에셋 정리.
- 씬 전환 효과: 부드러운 전환을 위해 비동기 로드(SceneManager.LoadSceneAsync)를 사용.
- 최소화된 폴리곤과 텍스처: 씬 내부의 오브젝트가 많으면 GPU 부담이 커질 수 있으므로 경량화 필요.
* 현재 문제점
- 씬 전환 후 이전 씬의 리소스가 메모리에 남아 있을 수 있음.
- 불필요한 오브젝트의 참조가 남아 메모리 누수 발생.
public class PoorMemoryManagement : MonoBehaviour
{
public void LoadNewScene(string sceneName)
{
// 씬 전환 (리소스 정리 없이 실행)
SceneManager.LoadScene(sceneName);
Debug.Log("Scene loaded without memory cleanup");
}
public void DestroyObject(GameObject obj)
{
// 게임 오브젝트 파괴
Destroy(obj);
Debug.Log("Object destroyed without clearing references");
}
}
* 개선 사항 : 리소스를 명시적으로 해제하고 메모리 정리를 수행
using UnityEngine;
using UnityEngine.SceneManagement;
public class EffectiveMemoryManagement : MonoBehaviour
{
public void LoadNewScene(string sceneName)
{
// 사용하지 않는 리소스를 먼저 정리하고 새로운 씬 로드
StartCoroutine(LoadSceneWithMemoryCleanup(sceneName));
}
public void DestroyObject(GameObject obj)
{
// 게임 오브젝트 파괴 후 메모리 정리
if (obj != null)
{
Destroy(obj);
Resources.UnloadUnusedAssets();
Debug.Log("Object destroyed and unused assets unloaded");
}
}
private IEnumerator LoadSceneWithMemoryCleanup(string sceneName)
{
// 메모리 정리
Resources.UnloadUnusedAssets();
System.GC.Collect(); // 강제 Garbage Collection 호출
Debug.Log("Unused assets and garbage collected");
// 새 씬 로드
AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName);
while (!asyncOperation.isDone)
{
Debug.Log($"Loading {sceneName}: {asyncOperation.progress * 100}%");
yield return null;
}
Debug.Log($"Scene {sceneName} loaded with memory cleanup");
}
}
5. 씬 대신 데이터 기반 로드 사용
많은 씬이 필요한 경우라도, 씬을 나누지 않고 데이터 기반 콘텐츠 로드 방식을 활용할 수 있습니다.
- 예: JSON, ScriptableObject, XML 등으로 씬 데이터를 관리하고, 하나의 씬에서 동적으로 생성 및 배치.
* 수정 전
- 씬이 커지고 복잡해질수록 메모리 사용량 증가.
- 유연성 부족: 오브젝트를 조건에 따라 생성/삭제하기 어려움.
using UnityEngine;
public class HardCodedObjectLoader : MonoBehaviour
{
public GameObject enemyPrefab;
void Start()
{
// 모든 오브젝트가 씬에 미리 배치되어 있음
Instantiate(enemyPrefab, new Vector3(0, 0, 0), Quaternion.identity);
Instantiate(enemyPrefab, new Vector3(2, 0, 0), Quaternion.identity);
Instantiate(enemyPrefab, new Vector3(-2, 0, 0), Quaternion.identity);
Debug.Log("Enemies loaded in the scene");
}
}
* 데이터 기반 수정
- 유연성: JSON 데이터를 수정하는 것만으로도 오브젝트 배치를 변경 가능.
- 메모리 최적화: 필요한 데이터만 로드하고 생성.
- 확장성: 새로운 오브젝트나 속성을 쉽게 추가 가능.
[
{ "name": "Enemy1", "x": 0, "y": 0, "z": 0 },
{ "name": "Enemy2", "x": 2, "y": 0, "z": 0 },
{ "name": "Enemy3", "x": -2, "y": 0, "z": 0 }
]
using UnityEngine;
using System.Collections.Generic;
using System.IO;
[System.Serializable]
public class EnemyData
{
public string name;
public float x;
public float y;
public float z;
}
public class DataDrivenObjectLoader : MonoBehaviour
{
public GameObject enemyPrefab;
void Start()
{
// JSON 데이터를 읽어와 적 생성
string jsonFilePath = Path.Combine(Application.dataPath, "Resources/EnemyData.json");
string jsonData = File.ReadAllText(jsonFilePath);
List<EnemyData> enemyList = JsonUtility.FromJson<Wrapper<EnemyData>>(jsonData).items;
foreach (var enemy in enemyList)
{
Vector3 position = new Vector3(enemy.x, enemy.y, enemy.z);
GameObject enemyObj = Instantiate(enemyPrefab, position, Quaternion.identity);
enemyObj.name = enemy.name;
Debug.Log($"Enemy created: {enemy.name} at position {position}");
}
}
[System.Serializable]
private class Wrapper<T>
{
public List<T> items;
}
}
추가 최적화 팁
- ScriptableObject 활용: JSON 대신 ScriptableObject를 사용하면 유니티 에디터에서 쉽게 데이터를 관리 가능.
- Addressables 사용: 큰 에셋은 Addressables로 로드하여 메모리 사용량 감소.
- 오브젝트 풀링: 자주 생성/삭제되는 오브젝트는 풀링 기법으로 성능 최적화.
6. 프로파일링으로 확인
유니티의 Profiler를 사용해 다음 항목들을 모니터링하세요:
- 씬 전환 시의 메모리 사용량, CPU 부하, GPU 부하.
- Garbage Collection(GC) 호출 빈도.
- 오브젝트 생성 및 소멸 시 성능 변화.
요약
씬의 개수 자체는 큰 문제가 되지 않지만, 씬의 로딩 방식과 리소스 관리가 모바일 성능에 큰 영향을 미칩니다. 효율적인 씬 관리와 로딩 최적화를 통해 성능 문제를 줄일 수 있습니다. 씬을 많이 나누되 필요한 순간에만 로드하고, 공통 리소스는 Additive 방식으로 관리하면 더 나은 성능을 얻을 수 있습니다.
'게임개발 관련 > Tool, 게임엔진' 카테고리의 다른 글
[게임개발] 스프라이트 (Sprites)에 대해서 그리고 장단점 (4) | 2024.10.05 |
---|---|
[간단한 사운드 편집 프로그램이 필요할때] GoldWave / 골드웨이브 (0) | 2015.12.25 |
[유니티 Unity 3D] 기본에셋으로 기본맵을 만들어보기 (0) | 2015.12.20 |
cocos2d-x 학습 진행 중... (0) | 2015.08.16 |
Unity 게임엔진 사용 후기... 사용법은 계속 익히는 중... (0) | 2014.05.20 |