이번 시간은 ScriptableObject가 무엇이고, 언제 사용하는 지와 이를 활용하여 이벤트를 처리하는 방법에 대해서 알아보겠습니다. 게임 개발에서 아키텍처를 얼마나 잘 설계 하느냐 에 따라서 유지보수와 확장성이 올라가게 되며 시스템을 구축하는 시간도 많이 단축되게 됩니다. 이 시스템은 정말 중요하며 이를 더 효율적으로 설계하는 방법을 알려드리겠습니다.
목차
[ScriptableObject는 무엇인가?]
- Unity에서 제공해주는 데이터 클래스
- 컴포넌트가 아닌 Asset 형태로 제공
- 라이플사이클을 따르지 않으며 Play 모드에 의존하지 않습니다.(외부에서도 존재가 가능한 데이터 컨테이너 입니다.)
- 전역적으로 접근이 가능하며 Scene과 독립적입니다.
[이벤트 시스템은 무엇인가?]
- 특정 이벤트가 발생하게 되면 보내는 곳과 받는 곳이 있고 그 사이를 중계하는 클래스가 있는 시스템을 말함.
- 예시
- 몬스터를 죽인다 -> 몬스터를 죽였다는 알림을 중계자에게 보냄
-> 중계자는 등록한 곳에 알림
-> 몬스터 처치 퀘스트가 알림을 받음 -> 처치 횟수를 1증가 시킴
- 몬스터를 죽인다 -> 몬스터를 죽였다는 알림을 중계자에게 보냄
[ScriptableObject의 여러 용도들]
- 데이터 컨테이너
- 에디터
- 중계 시스템
[이벤트 시스템 응용 예시]
![[Unity] ScriptableObject 활용한 이벤트 처리: Unity 개발에서의 효율적인 이벤트 시스템 구현 방법 7단계 2 ScriptableObject 이벤트 시스템 응용](https://programmingdev.com/wp-content/uploads/2023/06/image-12-optimized.png)
- 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);
}
}
이 시스템에 대한 개인적인 생각
사용에 따라서는 개발 효율이 극대화 될 수 있습니다. 또한 버그의 발생률이 감소하기도 합니다. 하지만 소규모 프로젝트에서 사용하다 보면 단점이 부각되기도 합니다. 병렬 개발을 위해서 채용을 했는데 정해진 규칙안에서 코딩을 해야 하기 때문에 그만큼 시간이 소모되게 되는 것 입니다. 시스템의 장단점을 확실히 파악하고 응용해서 사용하면 좋은 결과를 낼 수 있지만 오 사용 시 사용하는 것만 못하고 생각합니다.