이벤트 시스템 구조
유니티 공식 프로젝트에서 제공하는 이벤트 시스템 구조

이번 시간은 ScriptableObject가 무엇이고, 언제 사용하는 지와 이를 활용하여 이벤트를 처리하는 방법에 대해서 알아보겠습니다. 게임 개발에서 아키텍처를 얼마나 잘 설계 하느냐 에 따라서 유지보수와 확장성이 올라가게 되며 시스템을 구축하는 시간도 많이 단축되게 됩니다. 이 시스템은 정말 중요하며 이를 더 효율적으로 설계하는 방법을 알려드리겠습니다.

[ScriptableObject는 무엇인가?]

  • Unity에서 제공해주는 데이터 클래스
  • 컴포넌트가 아닌 Asset 형태로 제공
  • 라이플사이클을 따르지 않으며 Play 모드에 의존하지 않습니다.(외부에서도 존재가 가능한 데이터 컨테이너 입니다.)
  • 전역적으로 접근이 가능하며 Scene과 독립적입니다.

[이벤트 시스템은 무엇인가?]

  • 특정 이벤트가 발생하게 되면 보내는 곳과 받는 곳이 있고 그 사이를 중계하는 클래스가 있는 시스템을 말함.
  • 예시
    • 몬스터를 죽인다 -> 몬스터를 죽였다는 알림을 중계자에게 보냄
      -> 중계자는 등록한 곳에 알림
      -> 몬스터 처치 퀘스트가 알림을 받음 -> 처치 횟수를 1증가 시킴

[ScriptableObject의 여러 용도들]

  • 데이터 컨테이너
  • 에디터
  • 중계 시스템

[이벤트 시스템 응용 예시]

ScriptableObject 이벤트 시스템 응용
  • Action을 걷기 모션이라고 생각해보고 ScriptableObject를 중계자, 매니저는 오디오를 재생시키는 싱글톤 패턴의 클래스라고 생각해봅시다.
  • ScriptableObject: 이벤트 함수를 구성
  • Action: 캐릭터가 걷는 것에 따라서 이벤트 함수를 호출
  • Manager: 오디오 재생에 필요한 기능들을 담고 있으며, 알림을 받으면 이를 재생 시키는 역할.
// ScriptableObject
[System.Serializable]
public class AudioEvent : ScriptableObject
{
    public event System.Action<string> OnPlayAudio;

    public void PlayAudio(string audioName)
    {
        if (OnPlayAudio != null)
            OnPlayAudio.Invoke(audioName);
    }
}

// Manager (Singleton Pattern)
public class AudioManager : MonoBehaviour
{
    private static AudioManager instance;
    public static AudioManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<AudioManager>();
                if (instance == null)
                {
                    GameObject managerObject = new GameObject("AudioManager");
                    instance = managerObject.AddComponent<AudioManager>();
                }
            }
            return instance;
        }
    }

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

    public void PlayAudio(string audioName)
    {
        // 오디오 재생 로직 구현
        Debug.Log("Playing audio: " + audioName);
    }
}

// Character Controller
public class CharacterController : MonoBehaviour
{
    public AudioEvent audioEvent;

    private void Start()
    {
        audioEvent.OnPlayAudio += AudioManager.Instance.PlayAudio;
    }

    private void OnDestroy()
    {
        audioEvent.OnPlayAudio -= AudioManager.Instance.PlayAudio;
    }

    private void Update()
    {
        if (Input.GetKey(KeyCode.W))
        {
            // 걷기 모션 이벤트 호출
            audioEvent.PlayAudio("Walk");
        }
    }
}

[Delegate, Action, UnityEvent 소개]

Delegate
무엇인가?
  • 다른 메소드를 가리키는 포인터 역할
  • 리턴 타입과 매개변수를 가지고 있음
  • 다른 것에 비해서 가장 유연하게 사용할 수 있음
  • 콜백 함수에도 사용이 가능함.
  • 매개변수의 개수와 리턴타입의 제약이 없음
사용예시
using System;

public class EventPublisher
{
    public delegate void EventHandler(string message);
    public event EventHandler OnEventOccurred;

    public void DoSomething()
    {
        // 이벤트 발생
        OnEventOccurred?.Invoke("Event occurred!");
    }
}

public class EventSubscriber
{
    public void Subscribe(EventPublisher publisher)
    {
        publisher.OnEventOccurred += HandleEvent;
    }

    public void Unsubscribe(EventPublisher publisher)
    {
        publisher.OnEventOccurred -= HandleEvent;
    }

    private void HandleEvent(string message)
    {
        Console.WriteLine("Event handled: " + message);
    }
}

public class Program
{
    public static void Main()
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        subscriber.Subscribe(publisher);

        publisher.DoSomething(); // 이벤트 발생

        subscriber.Unsubscribe(publisher);
    }
}
Action
무엇인가?
  • .NET 프레임워크에서 제공해주는 델리게이트의 한 종류
  • 최대 16개의 매개변수를 가짐
  • 리턴타입은 void
  • 람다식을 사용하여 익명 메소드를 처리 할 수 있음
  • 간단하고 명확한 이벤트 처리를 위해 사용함.
    • 익명 메소드, 람다식을 사용하여 간단하게 사용 가능
사용 예시
using System;

public class EventPublisher
{
    public Action<string> OnEventOccurred;

    public void DoSomething()
    {
        // 이벤트 발생
        OnEventOccurred?.Invoke("Event occurred!");
    }
}

public class EventSubscriber
{
    public void Subscribe(EventPublisher publisher)
    {
        publisher.OnEventOccurred += HandleEvent;
    }

    public void Unsubscribe(EventPublisher publisher)
    {
        publisher.OnEventOccurred -= HandleEvent;
    }

    private void HandleEvent(string message)
    {
        Console.WriteLine("Event handled: " + message);
    }
}

public class Program
{
    public static void Main()
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        subscriber.Subscribe(publisher);

        publisher.DoSomething(); // 이벤트 발생

        subscriber.Unsubscribe(publisher);
    }
}
UnityEvent
무엇인가?
  • Unity에서 제공하는 이벤트 시스템
  • SerializeField로 직렬화가 가능
    • Json으로 변환이 가능합니다.
  • Inspector에서 이벤트 핸들러를 연결하고 관리할 수 있음
  • 오브젝트간 상호작용에 사용이 많이 됨
    • 오브젝트 상태변화, 클릭 이벤트, 키 입력 등
사용 예시
using UnityEngine;
using UnityEngine.Events;

public class EventPublisher : MonoBehaviour
{
    public UnityEvent OnEventOccurred;

    public void DoSomething()
    {
        // 이벤트 발생
        OnEventOccurred?.Invoke();
    }
}

public class EventSubscriber : MonoBehaviour
{
    private void Start()
    {
        EventPublisher publisher = FindObjectOfType<EventPublisher>();

        publisher.OnEventOccurred.AddListener(HandleEvent);
    }

    private void OnDestroy()
    {
        EventPublisher publisher = FindObjectOfType<EventPublisher>();

        publisher.OnEventOccurred.RemoveListener(HandleEvent);
    }

    private void HandleEvent()
    {
        Debug.Log("Event handled");
    }
}

public class Program : MonoBehaviour
{
    private void Start()
    {
        EventPublisher publisher = FindObjectOfType<EventPublisher>();
        EventSubscriber subscriber = FindObjectOfType<EventSubscriber>();

        subscriber.Subscribe(publisher);

        publisher.DoSomething(); // 이벤트 발생

        subscriber.Unsubscribe(publisher);
    }
}

이 시스템에 대한 개인적인 생각

사용에 따라서는 개발 효율이 극대화 될 수 있습니다. 또한 버그의 발생률이 감소하기도 합니다. 하지만 소규모 프로젝트에서 사용하다 보면 단점이 부각되기도 합니다. 병렬 개발을 위해서 채용을 했는데 정해진 규칙안에서 코딩을 해야 하기 때문에 그만큼 시간이 소모되게 되는 것 입니다. 시스템의 장단점을 확실히 파악하고 응용해서 사용하면 좋은 결과를 낼 수 있지만 오 사용 시 사용하는 것만 못하고 생각합니다.

참고한 사이트

Sebastian의Event설명

stackoverflow 유니티 이벤트 차이

베르의 유니티이벤트 사용법

다른 글 보러가기

초보자들을 위한 깃허브 사용 방법

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다