70.내일배움캠프 62일차 TIL <Unity Unity 2D 팀프로젝트- MartialGod:Reborn - 11일차> 07/07

정광훈(Unity_9기)·2025년 7월 7일

TIL (Today I Learned)

목록 보기
71/110
post-thumbnail

잡지식

잡지식이라 한 이유는 이것저것 잡다하게 있어서 임. 복습 겸 새로 알게 된 사실을 적음.

<필드>

필드(Field)는 C# 프로그래밍 언어의 맥락에서 클래스나 구조체 내부에 선언된 변수를 의미.
이 변수들은 해당 클래스나 구조체의 데이터를 저장하는 데 사용.

인스펙터(Inspector) 창에 노출시켜 개발자가 코드를 직접 수정하지 않고도 값을 조정할 수 있게 하는 것.
이를 '직렬화(Serialization)'라고 함.

Public 필드: 인스펙터에 노출되어 에디터에서 값을 직접 설정할 수 있음.
Private 필드: private으로 선언된 필드는 기본적으로 인스펙터에 노출되지 않음.

public class Player : MonoBehaviour
{
    public float moveSpeed = 5f; // public 필드: 인스펙터에 노출됨
    private int health = 100;    // private 필드: 인스펙터에 노출되지 않음

    [SerializeField] private string playerName = "Hero"; // [SerializeField]를 사용하여 private 필드를 인스펙터에 노출
}

이걸 필드라고 표현하는지 몰랐음.


<클로저>

람다가 외부 스코프의 변수를 캡처할 때, 해당 변수들은 람다 객체(정확히는 람다로 인해 컴파일러가 생성한 익명 클래스의 인스턴스)의 필드로 복사. 이를 클로저(Closure)라고 부름.

만약 람다 객체가 오랫동안 살아남으면서 (예: 전역 리스트에 추가되는 경우) 동시에 매우 큰 객체들을 캡처한다면,
메모리 사용량이 증가하고 가비지 컬렉션 부하가 커질 수 있음.

캡처된 변수들도 람다 객체가 소멸될 때까지 메모리에 유지되기 때문에 메모리 누수(Memory Leak)로 이어질 수도 있음.

재사용되는 메서드 내 람다 사용의 고려사항:
재사용되는 메서드, 특히 게임의 Update() 루프처럼 프레임마다 또는
매우 빈번하게 호출되는 메서드 안에서 람다를 사용할 때는 몇 가지 주의할 점이 있음.

반복적인 람다 객체 생성 및 GC 부하:
Update()와 같이 매 프레임 실행되는 메서드 안에서 외부 변수를 캡처하는 람다를 생성하면,
프레임마다 새로운 람다(익명 클래스) 인스턴스가 생성.

인스턴스들은 해당 프레임이 끝나면
더 이상 참조되지 않는 "쓰레기"가 되어 가비지 컬렉션(GC)의 대상이 됨.

GC 스파이크 유발:
이 부하가 일정 수준을 넘어서면, GC가 작동하여 사용되지 않는 메모리를 회수하게 됨.

이 과정에서 "Stop-the-World" 현상이 발생하여 게임이 잠시 멈추거나 프레임 드랍이 일어날 수 있음.
이것이 바로 플레이어가 느끼는 "렉" 현상으로 나타남.

클로저(Closure)로 인한 메모리 유지:
람다가 외부 스코프의 변수를 캡처(클로저)한다면,
캡처된 변수들도 람다 객체와 함께 메모리에 남아있게 됨.

만약 캡처하는 변수들이 크거나 많다면,
이는 메모리 사용량을 더욱 늘리고 GC가 처리해야 할 대상을 증가시킴.



<마무리>

GlobalVolumeManager구현 마무리

using UnityEngine;
using UnityEngine.Rendering.Universal;
using DG.Tweening;
using UnityEngine.Rendering;

public class GlobalVolumeManager : MonoBehaviour
{
    private static GlobalVolumeManager instance;

    public static GlobalVolumeManager Instance
    {
        // 외부에서 사용할 때 Awake()가 아닌 Start()에서 사용해야 함
        get { return instance; }
    }

    [Header("Global Volume")] [SerializeField]
    private Volume globalVolume;

    private FilmGrain filmGrain;
    private ChromaticAberration chromaticAberration;
    private Vignette vignette;

    private void Reset()
    {
        globalVolume = FindObjectOfType<Volume>();
    }

    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
            InitGlobalVolume();
        }
    }

    private void InitGlobalVolume() // 글로벌 볼륨 초기화
    {
        if (globalVolume == null)
        {
            LogHelper.LogError("GlobalVolumeManager에 Global Volume이 할당되지 않았습니다.", this);
            return;
        }

        if (!globalVolume.profile.TryGet<FilmGrain>(out filmGrain))
        {
            LogHelper.LogError("Global Volume에 FilmGrain이 없습니다.", this);
        }
        else
        {
            filmGrain.active = true;
            filmGrain.intensity.overrideState = true;
        }

        if (!globalVolume.profile.TryGet<ChromaticAberration>(out chromaticAberration))
        {
            LogHelper.LogError("Global Volume에 ChromaticAberration이 없습니다.", this);
        }
        else
        {
            chromaticAberration.active = true;
            chromaticAberration.intensity.overrideState = true;
        }

        if (!globalVolume.profile.TryGet<Vignette>(out vignette))
        {
            LogHelper.LogError("Global Volume에 Vignette가 없습니다.", this);
        }
        else
        {
            vignette.active = true;
            vignette.intensity.overrideState = true;
        }
    }

    /// <summary>
    /// Global Volume의 FilmGrain - intensity를 변경
    /// </summary>
    /// <param name="intensity">변화시킬 intensity의 목표치</param>
    /// <param name="fade">false를 입력하지 않으면 기본적으로 fade처리</param>
    public void SetFilmGrain(float intensity, bool fade = true)
    {
        if (filmGrain == null)
        {
            LogHelper.LogError("Global Volume에 FilmGrain이 초기화되지 않았습니다.", this);
            return;
        }

        if (fade)
        {
            DOTween.To(GetFilmGrainIntensity, SetFilmGrainIntensity, intensity, 1.0f);
        }
        else
        {
            filmGrain.intensity.value = intensity;
        }
    }

    /// <summary>
    /// Global Volume의 FilmGrain - intensity를 변경
    /// </summary>
    /// <param name="intensity">변화시킬 intensity의 목표치</param>
    /// <param name="duration">fade하는데 걸리는 시간</param>
    /// <param name="fade">false를 입력하지 않으면 기본적으로 fade처리</param>
    public void SetFilmGrain(float intensity, float duration, bool fade = true)
    {
        if (filmGrain == null)
        {
            LogHelper.LogError("Global Volume에 FilmGrain이 초기화되지 않았습니다.", this);
            return;
        }

        if (fade)
        {
            DOTween.To(GetFilmGrainIntensity, SetFilmGrainIntensity, intensity, duration);
        }
        else
        {
            filmGrain.intensity.value = intensity;
        }
    }

    private float GetFilmGrainIntensity()
    {
        return filmGrain.intensity.value;
    }

    private void SetFilmGrainIntensity(float x)
    {
        filmGrain.intensity.value = x;
    }

    /// <summary>
    /// Global Volume의 ChromaticAberration - intensity를 변경
    /// </summary>
    /// <param name="intensity">변화시킬 intensity의 목표치</param>
    /// <param name="fade">false를 입력하지 않으면 기본적으로 fade처리</param>
    public void SetChromaticAberration(float intensity, bool fade = true)
    {
        if (chromaticAberration == null)
        {
            LogHelper.LogError("Global Volume에 ChromaticAberration이 초기화되지 않았습니다.", this);
            return;
        }

        if (fade)
        {
            DOTween.To(GetChromaticAberrationIntensity, SetChromaticAberrationIntensity, intensity, 1f);
        }
        else
        {
            chromaticAberration.intensity.value = intensity;
        }
    }

    /// <summary>
    /// Global Volume의 ChromaticAberration - intensity를 변경
    /// </summary>
    /// <param name="intensity">변화시킬 intensity의 목표치</param>
    /// <param name="duration">fade하는데 걸리는 시간</param>
    /// <param name="fade">false를 입력하지 않으면 기본적으로 fade처리</param>
    public void SetChromaticAberration(float intensity, float duration, bool fade = true)
    {
        if (chromaticAberration == null)
        {
            LogHelper.LogError("Global Volume에 ChromaticAberration이 초기화되지 않았습니다.", this);
            return;
        }

        if (fade)
        {
            DOTween.To(GetChromaticAberrationIntensity, SetChromaticAberrationIntensity, intensity, duration);
        }
        else
        {
            chromaticAberration.intensity.value = intensity;
        }
    }

    private float GetChromaticAberrationIntensity()
    {
        return chromaticAberration.intensity.value;
    }

    private void SetChromaticAberrationIntensity(float x)
    {
        chromaticAberration.intensity.value = x;
    }

    /// <summary>
    /// Global Volume의 Vignette - color를 변경
    /// </summary>
    /// <param name="color">변화시킬 color의 목표치</param>
    /// <param name="fade">false를 입력하지 않으면 기본적으로 fade처리</param>
    public void SetVignette(Color color, bool fade = true)
    {
        if (vignette == null)
        {
            LogHelper.LogError("Global Volume에 Vignette가 초기화되지 않았습니다.", this);
            return;
        }

        if (fade)
        {
            DOTween.To(GetVignetteColor, SetVignetteColor, color, 1.0f);
        }
        else
        {
            vignette.color.value = color;
        }
    }

    /// <summary>
    /// Global Volume의 Vignette - color를 변경
    /// </summary>
    /// <param name="color">변화시킬 color의 목표치</param>
    /// <param name="duration">fade하는데 걸리는 시간</param>
    /// <param name="fade">false를 입력하지 않으면 기본적으로 fade처리</param>
    public void SetVignette(Color color, float duration, bool fade = true)
    {
        if (vignette == null)
        {
            LogHelper.LogError("Global Volume에 Vignette가 초기화되지 않았습니다.", this);
            return;
        }

        if (fade)
        {
            DOTween.To(GetVignetteColor, SetVignetteColor, color, duration);
        }
        else
        {
            vignette.color.value = color;
        }
    }

    private Color GetVignetteColor()
    {
        return vignette.color.value;
    }

    private void SetVignetteColor(Color x)
    {
        vignette.color.value = x;
    }

    /// <summary>
    /// Global Volume의 Vignette - intensity를 변경
    /// </summary>
    /// <param name="intensity">변화시킬 intensity의 목표치</param>
    /// <param name="fade">false를 입력하지 않으면 기본적으로 fade처리</param>
    public void SetVignette(float intensity, bool fade = true)
    {
        if (vignette == null)
        {
            LogHelper.LogError("Global Volume에 Vignette가 초기화되지 않았습니다.", this);
            return;
        }

        if (fade)
        {
            DOTween.To(GetVignetteIntensity, SetVignetteIntensity, intensity, 1.0f);
        }
        else
        {
            vignette.intensity.value = intensity;
        }
    }

    /// <summary>
    /// Global Volume의 Vignette - intensity를 변경
    /// </summary>
    /// <param name="intensity">변화시킬 intensity의 목표치</param>
    /// <param name="duration">fade하는데 걸리는 시간</param>
    /// <param name="fade">false를 입력하지 않으면 기본적으로 fade처리</param>
    public void SetVignette(float intensity, float duration, bool fade = true)
    {
        if (vignette == null)
        {
            LogHelper.LogError("Global Volume에 Vignette가 초기화되지 않았습니다.", this);
            return;
        }

        if (fade)
        {
            DOTween.To(GetVignetteIntensity, SetVignetteIntensity, intensity, duration);
        }
        else
        {
            vignette.intensity.value = intensity;
        }
    }

    private float GetVignetteIntensity()
    {
        return vignette.intensity.value;
    }

    private void SetVignetteIntensity(float x)
    {
        vignette.intensity.value = x;
    }
}

0개의 댓글