
잡지식
잡지식이라 한 이유는 이것저것 잡다하게 있어서 임. 복습 겸 새로 알게 된 사실을 적음.
필드(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;
}
}