-
유니티 쉽게 배우는 디자인 패턴 - 경량 패턴 / Unity Design Pattern - Flyweight Pattern유니티 2024. 8. 27. 19:06
경량 패턴이란?
대량의 객체의 메모리를 공유하여 최적화하는 패턴입니다.
유니티는 모바일 타겟으로 만드는 게임이 많기에 항상 메모리 최적화에 관심이 많습니다.
코루틴에서 WaitForSeconds가 new를 할 때마다 메모리를 할당하기에 캐싱하거나
딕셔너리의 키와 값으로 같은 키라면 값이 공유되도록 캐싱하거나
스크립터블 오브젝트의 공통된 자원은 공유되도록 캐싱하는 등
이미 여러곳에서 최적화를 위해 쓰이고 있습니다.
- 캐싱이란?
데이터의 접근 성능을 향상시키기 위해 자주 사용되는 데이터를 임시로 저장하는 기술입니다.딕셔너리로 캐싱하는 방법
Cache.cs
using System.Collections.Generic; using UnityEngine; using System; public class Cache { public class WaitForSeconds { static Dictionary<float, UnityEngine.WaitForSeconds> dic = new(); public static UnityEngine.WaitForSeconds Get(float sec) { if (!dic.ContainsKey(sec)) { UnityEngine.WaitForSeconds waitForSeconds = new(sec); dic[sec] = waitForSeconds; } return dic[sec]; } } public class Material { [Serializable] public class Item { public int code; public Texture2D texture; public Color color; } static Dictionary<int, UnityEngine.Material> dic = new(); static Item[] items; static UnityEngine.Material refMaterial; public static void Init(Item[] items, UnityEngine.Material refMaterial) { Material.items = items; Material.refMaterial = refMaterial; } public static UnityEngine.Material Get(int code) { if (items == null) { Debug.LogError("Init first."); return null; } if (!dic.ContainsKey(code)) { UnityEngine.Material material = new(refMaterial); var item = Array.Find(items, x => x.code == code); if (item == null) { Debug.LogError($"Item with code {code} not found."); return null; } material.mainTexture = item.texture; material.color = item.color; dic[code] = material; } return dic[code]; } } public class Enum { static Dictionary<System.Enum, string> dic = new(); public static string GetString(System.Enum value) { if (!dic.ContainsKey(value)) { dic[value] = value.ToString(); } return dic[value]; } } }
Test.cs
using System.Collections; using UnityEngine; public class Test : MonoBehaviour { public enum EState { Idle, Walk } [SerializeField] Cache.Material.Item[] materialItems; [SerializeField] Material refMaterial; void Start() { // WaitForSecond caching StartCoroutine(nameof(Co)); // Material caching Cache.Material.Init(materialItems, refMaterial); Debug.Log(Cache.Material.Get(1)); Debug.Log(Cache.Material.Get(2)); // Enum caching Debug.Log(Cache.Enum.GetString(EState.Idle)); Debug.Log(Cache.Enum.GetString(EState.Walk)); } IEnumerator Co() { yield return Cache.WaitForSeconds.Get(1); Debug.Log("Coroutine 1"); yield return Cache.WaitForSeconds.Get(1); Debug.Log("Coroutine 2"); } }
Test의 인스펙터에는 위와같이 대충 흉내만 내줍니다.
Cache.WaitForSeconds는 딕셔너리 키에 sec가 존재하지 않으면 WaitForSeconds를 생성해 넣고
키가 존재하면 값을 반환해줍니다.
('텍스쳐 캐싱기법' 영상이 그땐 몰랐는데 경량 패턴이었습니다.)
Cache.Material은 딕셔너리 키에 code가 존재하지 않으면 RefMaterial을 복제해
Cache.Material.Item의 데이터를 넣으면서 생성합니다.
키가 존재하면 값을 반환해줍니다.
Cache.Enum은 딕셔너리 키에 Enum이 존재하지 않으면 Enum을 ToString()하여 생성합니다.
키가 존재하면 값을 반환해줍니다.
Enum을 ToString()할 때는 리플렉션을 사용하여 성능을 조금 먹기에 캐싱을 추천합니다.
재생해보니 원하는 대로 로그가 나왔습니다.
그 외로도 딕셔너리로 캐싱하는 방법은 많은 곳에서 사용됩니다.
스크립터블 오브젝트로 캐싱하는 방법
DataSO.cs
using UnityEngine; [CreateAssetMenu(fileName = "DataSO", menuName = "ScriptableObject/DataSO")] public class DataSO : ScriptableObject { public string id; public Texture2D texture; public Color color; }
Test.cs
using UnityEngine; public class Test : MonoBehaviour { [SerializeField] GameObject squarePrefab; [SerializeField] DataSO dataSO; void Start() { for (int i = 0; i < 10; i++) { var render = Instantiate(squarePrefab, new Vector3(i, 0, 0), Quaternion.identity) .GetComponent<SpriteRenderer>(); render.name = dataSO.id; render.sprite = dataSO.sprite; render.color = dataSO.color; } } }
Project 우클릭 - Create - ScriptableObject - DataSO로 스크립터블 오브젝트를 생성합니다.
원하는 값을 넣어줍니다.
Square라는 Sprite Renderer가 달린 게임오브젝트를 프리팹으로 만듭니다.
Test에는 Square 프리팹과 DataSO 스크립터블 오브젝트를 참조합니다.
10개가 나란히 같은 스프라이트, 같은 이름, 같은 색깔로 생성된 것을 볼 수 있습니다.
이렇게 스크립터블 오브젝트를 활용하여 값이 바뀌지 않는 값을 공유할 때 메모리에 한 번 할당하므로 가볍습니다.
프리팹이 저마다의 변수로 string, Sprite, Color를 가지고 있다면 많이 있을 때 많이 할당되므로
메모리 최적화에서 스크립터블 오브젝트가 괜찮은 조건으로도 보입니다.
마무리
경량 패턴은 메모리 최적화를 위해 사용되는 패턴으로
딕셔너리로 캐싱하거나 스크립터블 오브젝트로 캐싱하는 법을 살펴보았습니다.
'유니티' 카테고리의 다른 글