오늘 학습 키워드

유니티 심화, 스탠다드 반 강의

오늘 학습 한 내용을 나만의 언어로 정리하기

유니티 심화 (2D)

오브젝트 풀링

  • 오브젝트를 재사용
public interface IPoolable  
{  
    void Initialize(Action<GameObject> returnAction);  
  
    void OnSpawn();  
    void OnDespawn();  
}
public class ObjectPoolManager : MonoBehaviour  
{  
    public GameObject[] prefabs;  
    private Dictionary<int, Queue<GameObject>> pools = new Dictionary<int, Queue<GameObject>>();  
      
    public static ObjectPoolManager Instance { get; private set; }  
  
    private void Awake()  
    {  
        Instance = this;  
  
        for (int i = 0; i < prefabs.Length; i++)  
        {  
            pools[i] = new Queue<GameObject>();  
        }  
          
    }  
  
    public GameObject GetObject(int prefabIndex, Vector3 position, Quaternion rotation)  
    {  
        if (!pools.ContainsKey(prefabIndex))  
        {  
            Debug.Log($"프리팹 인덱스 {prefabIndex} 에 대한 풀이 존재하지 않습니다.");  
            return null;  
        }  
          
        GameObject obj;  
        if (pools[prefabIndex].Count > 0)  
        {  
        // 풀에서 꺼내기
            obj = pools[prefabIndex].Dequeue();  
        }  
        else  
        {  
        // 새로 만들어서 풀에 집어넣기
            obj = Instantiate(prefabs[prefabIndex]);  
            // 풀에 돌아갈 때 작동할 함수 넣어주기
            obj.GetComponent<IPoolable>()?.Initialize(o => ReturnObject(prefabIndex, o));  
        }  
          
        obj.transform.SetPositionAndRotation(position, rotation);  
        obj.SetActive(true);  
        obj.GetComponent<IPoolable>().OnSpawn();  
        return obj;  
    }  
  
    public void ReturnObject(int prefabIndex, GameObject obj)  
    {  
        if (!pools.ContainsKey(prefabIndex))  
        {  
            Destroy(obj);  
            return;  
        }  
          
        obj.SetActive(false);  
        pools[prefabIndex].Enqueue(obj);  
    }  
}

확장 가능한 스탯 시스템

// 스탯 생성
public enum StatType  
{  
    Health,  
    Speed,  
    ProjectileCount  
}  
  
[CreateAssetMenu(fileName = "New StatData", menuName = "Stats/Character Stats")]  
public class StatData : ScriptableObject  
{  
    public string CharacterName;  
    public List<StatEntry> stats;  
}  
  
[System.Serializable]  
public class StatEntry  
{  
    public StatType statType;  
    public float baseValue;  
}
// StatHandler 수정
public class StatHandler : MonoBehaviour  
{  
    public StatData statData;  
    private Dictionary<StatType, float> currentStats = new Dictionary<StatType, float>();  
  
    private void Awake()  
    {  
        InitializeStats();  
    }  
  
    private void InitializeStats()  
    {  
        foreach (StatEntry entry in statData.stats)  
        {  
            currentStats[entry.statType] = entry.baseValue;  
        }  
    }  
  
    public float GetStat(StatType statType)  
    {  
        return currentStats.ContainsKey(statType) ? currentStats[statType] : 0;  
    }  
  
    public void ModifyStat(StatType statType, float amount, bool isPermanent = true, float duration = 0)  
    {  
        if (!currentStats.ContainsKey(statType)) return;  
          
        currentStats[statType] += amount;  
  
        if (!isPermanent)  
        {  
            StartCoroutine(RemoveStatAfterDuration(statType, amount, duration));  
        }  
    }  
  
    private IEnumerator RemoveStatAfterDuration(StatType statType, float amount, float duration)  
    {  
        yield return new WaitForSeconds(duration);  
        currentStats[statType] -= amount;  
    }  
}

아이템 생성과 사용 시스템 구현

public class ItemData : ScriptableObject  
{  
    public string itemName;  
    public List<StatEntry> statModifiers;  
    public bool isTemporary;  
    public float duration;  
}
 
public class ItemHandler : MonoBehaviour  
{  
    [SerializeField] private ItemData itemData;  
    public ItemData ItemData => itemData;  
}
// EnemyManager
public void RemoveEnemyOnDeath(EnemyController enemy)  
{  
    activeEnemies.Remove(enemy);  
    CreateRandomItem(enemy.transform.position);  
    // 소환도 다 했는데 활성화 된 적이 없다 == 다 잡았다!  
    if (enemySpawnComplite && activeEnemies.Count == 0)   
    {  
        gameManager.EndOfWave();  
    }  
}  
  
public void CreateRandomItem(Vector3 position)  
{  
    GameObject item = Instantiate(itemPrefabs[Random.Range(0, itemPrefabs.Count)], position, Quaternion.identity);  
}
// PlayerController
public void UseItem(ItemData item)  
{  
    foreach (StatEntry modifier in item.statModifiers)  
    {  
        statHandler.ModifyStat(modifier.statType, modifier.baseValue, !item.isTemporary, item.duration);  
    }  
}  
  
private void OnTriggerEnter2D(Collider2D other)  
{  
    if (other.CompareTag("Item"))  
    {  
        Debug.Log("Item과 부딛힘");  
        if (other.TryGetComponent<ItemHandler>(out ItemHandler handler))  
        {  
            if (handler.ItemData == null) return;  
            UseItem(handler.ItemData);  
            Destroy(handler.gameObject);  
        }  
    }  
}

유니티 심화 (3D)

URP

  • 렌더링 파이프라인 종류

  • 렌더링 : 화면에 오브젝트를 그리는 과정

  • 렌더링 파이프라인 : 화면에 오브젝트를 그리는 과정을 제어하는 시스템

    1. Culling : 절두체에 걸리지 않는 건 그리지 않음.
    2. Rendering : 평면 화면에 그림
      1. Shader : 작은 단위의 처리. 각각의 색상을 계산하기 위한 수학적 계산 및 알고리즘 포함
      2. Mesh : 개체의 모양을 정의
      3. Texture : 비트맵 이미지.
        • Material : 텍스쳐에 대한 참조, 타일링 정보, 색상 등등 표면을 렌더링하는 방법을 정의. 쉐이더에 따라 옵션이 정해짐
    3. Post-processing : 필터처럼 작동함.
  • Unity에서의 렌더링 파이프라인

    • 빌트인 : 기본 렌더링 파이프라인. 커스텀 확장에 제한적
    • SRP : 스크립트로 렌더링을 제어하고 커스터마이징 할 수 있음
      • URP
      • HDRP (하이엔드용)
  • URP : Universal Render Pipeline. 스크립트 가능한 렌더 파이프라인(SRP)임

    1. Cross-Platform Compatibility : Unity가 지원하는 모든 플랫폼에서 동작하도록 설계됨
    2. Performance and Scalability : 성능 / 확장성 굿
    3. Modern Rendering Features
    4. Customizability
    5. Graphics Quality
    6. Simplicity : 기존 렌더링 시스템에 비해 쓰기 쉬움. 셰이더 그래프, VFX 그래프 제공

FSM

  • FSM : Finite Sate Machine, 유한 상태 기계

    • 유한한 갯수의 상태들로 구성된 기계 및 패턴
    • 상태와 상태 간의 전환을 기반으로 동작하는 동작 기반 시스템
  • FSM의 구성 요소

    • 상태 : 시스템이 취할 수 있는 상태
    • 조건 : 상태 간 전환을 결정하는 조건
    • 동작 : 상태에 따라 수행되는 동작 또는 로직
  • FSM의 동작 원리

    • 초기 상태에서 시작해 입력 또는 조건에 따라 상태 전환을 수행함
    • 상태 전환은 전환 조건을 충족할 때 발생
      • 전환 조근언 입력, 시간, 조건 등으로 결정됨
    • 상태 전환 시 이전 상태의 종료 동작과 새로운 상태의 진입 동작이 수행됨
  • FSM의 예시 : 플레이어 상태 관리

    • 상태 : 정지, 이동, 점프
    • 조건 : 이동 입력, 점프 입력, 충돌 등등
    • 동작 : 이동 애니메이션 재생, 점프 처리, 이동 속도 조정 등
  • Switch-Case 문을 활용한 FSM의 단점

    • 코드가 지나치게 길어짐
    • 유지보수가 어려움
    • 상태가 추가될 때마다 새로운 분기를 작성해야 하고, 중복 코드가 많아짐
  • State Pattern을 활용한 FSM의 장점

    • 객체지향의 다향성을 활용
    • 상태를 명확하게 정의, 상태 간 전환을 일관되게 관리 가능
    • 복잡한 동작을 상태와 전환 조건으로 나누어 구현하기 때문에 코드 유지 보수가 용이함
    • 다양한 동작을 유기적으로 조합해 원하는 동작을 구현할 수 있음

캐릭터 컨트롤러

  • 유니티에서 캐릭터나 플레이어의 움직임과 충돌을 관리하기 위해 사용되는 컴포넌트
  • 물리 엔진이 아닌 캐릭터의 움직임을 프레임 기반으로 처리하기 때문에, 3D 캐릭터를 제어하는 데 사용됨
  • 수평 방향이 좀 더 중요하면 캐릭터 컨트롤러를 쓰는게
  • 주요 특징
    1. 캐릭터 이동 : 단순 이동을 쉽게 구현할 수 있도록 메소드를 제공. 이동 방향 및 속력을 설정
    2. 중력 적용 : 캐릭터 컨트롤러는 Rigidbody 물리 도움을 못받음. 그래서 직접 만들어야함
    3. 충돌 처리 : 다른 콜라이더와의 충돌을 통제하고, 경사로와의 상호작용을 지원
    4. 바닥 검출 : 바닥 검출을 알아서 처리함.
    5. 움직임 제한 : 지정 영역 내에서만 움직이도록 하거나, 경사를 따라 이동할 수 있도록 설정 가능

스탠다드 반 강의 (주제 : 최적화 이론 및 이해)

최적화의 필요성

  • 최대한 많은 경우의 수를 열어두어야 함.
  • WebGL이 최적화 하기에 가장 까다로움
    • 웹은 메모리가 한정적.

CPU 와 GPU 의 역할 차이

  • CPU : 스크립트 실행
  • GPU : 쉐이더, 파티클 효과 실행

FPS

  • 1초 동안 화면이 갱신되는지를 나타내는 지표.
  • 모바일, 웹 : 보통 30~60fps
  • PC, 콘솔 : 60fps 이상
  • Application.targetFrameRate로 동적 조정 가능

드로우콜

  • GPU에게 이 오브젝트를 화면에 그리라고 지시하는 명령 1회
  • 드로우콜이 많을 수록 안좋음 (CPU-GPU 간 명령 전달이 많아져 성능 저하가 심함)
  • 배칭, 오큘루전 컬링, LOD, 텍스쳐 아틀라스를 사용해서 최적화 가능

배칭

  • 여러 오브젝트의 드로우콜을 묶어서 한번에 함
  • FPS와 배치는 반비례 관계.
  • 배치는 줄이고, FPS는 높여야 함!

오큘루전 컬링

  • 다른 오브젝트에 가려(오클루전된) 카메라에 보이지 않는 오브젝트의 렌더링을 비활성화 하는 기능

LOD (Level of Detail)

  • 3D 모델 표현의 복잡도를 거리에 따라 차등을 두어 부하를 줄이는 기술
  • 멀 수록 가벼운 재질로 씀
  • Geometry LOD : 객체의 기하학적인 세부 수준을 조절. 면 수 or 정점 수 줄이기 등등
  • Texture LOD : 객체에 적용되는 텍스쳐의 해상도를 조절
  • Shader LOD : 쉐이더 프로그램을 조절해 시각적 효과를 조절. 빛 반사 or 그림자 등등

스크립트 최적화

  • Find 하지마셈
  • GetComponent 반복 사용 금지.
  • 클래스 / 리스트 반복 재할당 금지.
    • 특히 리스트는 Clear 쓰셈!!
  • Awake, Start 가볍게 하셈
  • 그리고 코루틴 yield return 할때 new 로 너무 자주 만들지마셈

정리

  • 렌더링
    • 오큘루전 컬링 했니?
    • LOD 했니?
    • 정적 배칭 했니?
  • 빌드 설정
    • 플랫폼 별 품질 설정 했니?
    • 불필요한 에셋이 들어가진 않았니?
  • 스크립트
    • Update에서 불필요한거 뺐니?
    • 오브젝트 풀링 했니?
    • GC 과도하게 안나오지?
  • 리소스 관리
    • 텍스쳐 크기/포맷 최적화 했니?
    • 어드레서블이나 에셋번들 썼니?
    • 씬 로딩 전략 잘 짰니?

학습하며 겪었던 문제점 & 에러

문제 1

  • 문제&에러에 대한 정의

갑자기 충돌 처리가 안됐음

  • 해결 방법

리지드 바디를 넣었다가 뺐더니 됐음..