말감로그

[Unity] Valley - MonoBehaviour를 상속받지 않는 인스턴스 클래스에서 코루틴 사용하는 법 본문

TIL

[Unity] Valley - MonoBehaviour를 상속받지 않는 인스턴스 클래스에서 코루틴 사용하는 법

habbn 2024. 11. 23. 04:35
728x90

 

Unity에서 코루틴을 실행하려면 MonoBehaviour 컴포넌트가 있어야 하지만 SoundManager는 MonoBehaviour를 상속받지 않는 정적 메서드를 사용하는 싱글톤 패턴을 구현한 클래스이다.

 

private 생성자를 통해 외부에서 인스턴스를 생성하지 못하도록 제한한다. 싱글톤 패턴의 핵심인 "클래스의 인스턴스가 하나만 존재해야 한다'는 것을 보장하는 것이다.

private 생성자를 사용하면 외부 코드에서 new SoundManager()와 같이 새 인스턴스를 생성하는 것을 방지할 수 있다.

static 생성자는 클래스가 처음 로드 될 때 단 한 번만 실행된다.

이를 통해 SoundManager.Instance가 초기화되며, 멀티 스레드 환경에서도 안전하게 인스턴스를 생성할 수 있다.


public class SoundManager
{
    public static SoundManager Instance { get; private set; } // 외부에서 접근 가능, 변경 불가능

    private AudioSource[] audioSources = new AudioSource[(int)SoundType.MAXCOUNT];
    private Dictionary<string, AudioClip> audioClips = new Dictionary<string, AudioClip>(); // 사운드 파일을 저장할 딕셔너리 <경로, 해당 오디오 클립> -> Object Pooling

    //private 생성자 : 외부에서 생성 불가능
    private SoundManager() { }

    // 정적 생성자 : 클래스 로드 시 호출되며, 단 한 번 실행
    static SoundManager()
    {
        Instance = new SoundManager();
    }

 

그래서 별도의 CoroutineHandler를 생성하여 코루틴을 실행할 수 있는 환경을 주면 된다. 이렇게 하면 코루틴이 특정 MonoBehaviour에 의존하지 않고 독립적으로 실행할 수 있게 된다.

using System.Collections;
using UnityEngine;

// SoundManager에서 사용하기 위한 코루틴 핸들러
public class CoroutineHandler : MonoBehaviour
{
    private static CoroutineHandler instance;

    void Awake()
    {
        if(!instance)
        {
            instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public static void StartStaticCoroutine(IEnumerator coroutine)
    {
        if (!instance)
        {
            // 새로운 GameObject 생성
            GameObject obj = new GameObject("CoroutineHandler");
            // 새 GameObject에 CoroutineHandler 컴포넌트를 추가하고, 이를 instance로 설정
            instance = obj.AddComponent<CoroutineHandler>();
        }
        instance.StartCoroutine(coroutine);
    }
}

 

이 CoroutineHandler를 통해 SoundManager 클래스에서 코루틴을 사용할 수 있다.

이 코루틴은 오디오 소스의 볼륨을 서서히 줄이기 위해 생성하였다.

public void FadeOut(float duration)
{
    AudioSource audioSource = audioSources[(int)SoundType.BGM];
    {
        if (audioSource.isPlaying)
        {
            //볼륨을 서서히 줄이기 위한 코루틴 호출
            CoroutineHandler.StartStaticCoroutine(FadeOutCoroutine(audioSource, duration));
        }
    }
}

private IEnumerator FadeOutCoroutine(AudioSource audioSource, float duration)
{
    float startVolume = audioSource.volume;
    float elapsedTime = 0f;

    while (elapsedTime < duration)
    {
        elapsedTime += Time.deltaTime;
        audioSource.volume = Mathf.Lerp(startVolume, 0f, elapsedTime / duration);
        yield return null;
    }

    audioSource.Stop();
    audioSource.volume = startVolume;
}

 

객체 기반 클래스 뿐 아니라 정적 메서드에서도 이러한 방식으로 코루틴을 실행할 수 있는 환경을 보장하여 코루틴을 실행할 수 있게 할 수 있다.

 

 

* 정적 메서드 : 객체를 만들지 않고도 호출할 수 있는 메서드이다. 왜? 정적 메서드나 변수는 클래스 자체에 저장되기 때문이다. 인스턴스는 필요할 때마다 새로 생성되지만, 정적 멤버는 프로그램이 실행되는 동안 하나의 고정된 메모리 공간에만 저장되기 때문이다.

ex ) Math.Sqrt()와 같은 수학적 계산

public class Calculator
{
    // 정적 메서드
    public static int Add(int a, int b)
    {
        return a + b;
    }

    // 인스턴스 메서드
    public int Multiply(int a, int b)
    {
        return a * b;
    }
}

 

정적 메서드는 객체를 생성하지 않고 ClassName.MethodName() 형태로 호출 가능

int result = Calculator.Add(3, 5); // 출력: 8

 

인스턴스 메서드는 객체를 생성한 후, 그 객체를 통해 호출해야 함.

Calculator calculator = new Calculator();
int result = calculator.Multiply(3, 5); // 출력: 15

 

728x90