1. 유니티 이벤트
프로그램에서 이벤트(Event)는 시스템 내에서 발생하는 특정 동작이나 상태 변화를 처리하는 메커니즘이다. 예를 들어, 사용자가 웹 페이지에서 버튼을 클릭하거나 화면을 터치하는 것과 같은 행동이 이벤트로 간주된다. 이벤트 시스템에서 "제공자"는 이와 같은 사용자 행동을 감지하고, 이를 구독자에게 알리는 역할을 한다. 제공자는 버튼 클릭, 마우스 이동, 키보드 입력 등 다양한 이벤트를 발생시키며, 구독자는 이러한 이벤트를 미리 "구독"하고 있다가, 이벤트가 발생할 때마다 반응을 한다. 예를 들어, 자바스크립트에서 addEventListener 메서드는 구독자 역할을 하며, 버튼 클릭 이벤트를 처리할 때마다 제공자가 발생시킨 클릭 정보를 받아서 지정된 함수를 실행한다.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
public class ButtonClickExample : MonoBehaviour
{
public Button myButton; // 버튼 객체
void Start()
{
// 구독자: 버튼 클릭 시 호출될 메서드를 이벤트에 구독
myButton.onClick.AddListener(OnButtonClicked);
}
// 이벤트 핸들러 메서드 (구독자)
private void OnButtonClicked()
{
Debug.Log("버튼이 클릭되었습니다!"); // 버튼 클릭 시 실행될 동작
}
}
유니티 UI의 버튼도 일종의 이벤트 시스템의 예시라고 볼 수 있다.
Event 구조
이벤트 기반 시스템에서 제공자는 구독자가 누구인지 알 필요 없이, 특정 이벤트가 발생하면 해당 이벤트를 처리할 구독자들에게만 알리면 된다. 이는 제공자와 구독자 간의 참조 관계를 깔끔하게 유지할 수 있는 장점이 있다. 구독자는 제공자와 독립적으로 동작할 수 있기 때문에, 제공자가 무엇을 하는지에 대한 정보를 알 필요가 없다. 그러나 중요한 점은 제공자 객체가 삭제되면 그 객체에 구독한 모든 구독자들이 더 이상 유효하지 않게 되어, 구독자들이 "미아" 상태가 된다는 것이다. 따라서 제공자의 생명 주기를 잘 관리해야 구독자들이 정상적으로 동작할 수 있다.
이벤트 사용법
.NET에서 이벤트를 사용하려면 먼저 System 네임스페이스에 포함된 EventHandler나 EventArgs 클래스를 사용하는 것이 일반적이다. 이벤트는 주로 클래스 내에서 발생한 상태 변화나 동작을 외부에 알리기 위해 사용되며, 이를 위해 이벤트를 선언할 때 event 키워드를 사용한다. 이벤트는 델리게이트와 밀접하게 연결되며, 특정 메서드가 이벤트의 리스너로 등록되면, 이벤트가 발생할 때 해당 메서드가 호출된다.
이벤트 제공자는 event 키워드를 사용하여 이벤트를 정의하며, 보통 이벤트 이름은 On으로 시작하고, 그 뒤에 동사나 시제를 붙여서 어떤 동작을 나타내도록 한다. 예를 들어, OnSpacePressed는 "스페이스 키가 눌렸을 때"라는 동작을 나타내는 이벤트이다. 이 이벤트는 EventHandler 델리게이트를 타입으로 지정해, 이 이벤트가 발생했을 때 호출될 메서드를 지정할 수 있도록 한다. 일반적으로 EventHandler와 같은 핸들러는 사용자 인터페이스에서 발생하는 입력 이벤트를 처리하는 데 사용되며, 다음은 버튼 클릭이나 키보드 입력을 관찰하는 역할을 한다. 아래는 OnSpacePressed 이벤트를 정의하고, 해당 이벤트가 발생했을 때 호출될 메서드를 연결하는 예시이다.
public class KeyPressHandler
{
// 이벤트 정의
public event EventHandler OnSpacePressed;
// 키 입력을 처리하는 메서드
public void HandleKeyPress(ConsoleKey key)
{
if (key == ConsoleKey.Spacebar)
{
// 이벤트 발생
OnSpacePressed?.Invoke(this, EventArgs.Empty);
}
}
}
// 이벤트를 구독하는 코드
var handler = new KeyPressHandler();
handler.OnSpacePressed += (sender, e) => Console.WriteLine("스페이스 바가 눌렸습니다!");
handler.HandleKeyPress(ConsoleKey.Spacebar); // "스페이스 바가 눌렸습니다!" 출력
이벤트를 호출하는 방법은 이벤트가 발생했을 때 구독자들에게 알리는 방식으로, OnSpacePressed(this, EventArgs.Empty)와 같이 호출한다. 이때, this는 이벤트 제공자가 현재 객체임을 나타내며, EventArgs.Empty는 이벤트에 추가적인 데이터를 전달하지 않음을 의미한다. 이벤트를 호출하기 전에 OnSpacePressed != null 검사를 하는 이유는, 이벤트가 아직 구독되지 않았을 수 있기 때문이다. 이벤트를 호출하려고 할 때 구독자가 없으면 null로 평가되어 예외가 발생할 수 있기 때문에 이를 방지하기 위한 안전장치이다. 예를 들어, 다음과 같이 스페이스바 입력을 감지하고 이벤트를 발생시키는 코드에서 이벤트 핸들러가 없을 경우 안전하게 호출되지 않도록 할 수 있다.
이벤트를 호출할 때 ?.Invoke를 사용하면, 이벤트가 null인지 확인하는 코드를 더 간결하게 작성할 수 있다. 이 구문에서 ?.는 널 조건부 연산자로, OnSpacePressed가 null이 아니면 Invoke 메서드를 호출하고, 그렇지 않으면 아무 작업도 수행하지 않는다. 즉, 이벤트를 호출할 때마다 null 검사를 명시적으로 작성할 필요 없이, 안전하게 구독된 핸들러들을 호출할 수 있다. 이는 코드의 가독성을 높이고, 불필요한 조건문을 줄여주는 장점이 있다.
이벤트 구독자 시그니처에서 시그니처란, 이벤트 핸들러 메서드가 갖춰야 할 매개변수의 타입과 순서를 의미한다. 즉, 이벤트가 발생할 때 해당 이벤트를 처리하는 메서드가 어떤 매개변수를 받아야 하는지, 그 타입과 순서를 정의하는 것이다. 표준적인 이벤트 구독자의 시그니처는 일반적으로 object sender와 EventArgs eventArgs 두 가지 매개변수를 포함한다.
sender는 이벤트를 발생시킨 객체를 나타내며, eventArgs는 이벤트와 관련된 추가 데이터를 전달하는 역할을 한다. 이 시그니처는 이벤트 제공자가 발생시킨 이벤트에 대해 일관된 방식으로 응답할 수 있도록 보장하며, 이벤트 구독자가 해당 시그니처를 따르지 않으면, 구독할 수 없다. 예를 들어, Test_OnSpacePressed 메서드는 object sender와 EventArgs eventArgs를 받아, 스페이스바가 눌렸을 때 적절히 처리할 수 있도록 설계된다.
이벤트 구독자의 표준 반환값은 void로 정의되며, 이는 이벤트가 발생했을 때 아무 값도 반환하지 않고, 단순히 처리만 하는 방식이다. 반환값을 다른 타입으로 설정할 수도 있지만, 그러면 구독자 호출을 구분하기 어려워져서 일반적으로 void가 사용된다. 또한, 표준 매개변수로는 object sender와 EventArgs eventArgs가 사용된다. object sender는 이벤트를 발생시킨 객체를 나타내며, EventArgs는 이벤트와 관련된 데이터를 담고 있는 상자 역할을 한다. EventArgs에는 이벤트 발생 시 필요한 추가 정보나 상태가 담겨 있을 수 있으며, 이 데이터를 구독자들이 활용하여 특정 동작을 수행할 수 있다. 예를 들어, 이벤트와 함께 전달된 정보는 여러 구독자가 공유하면서 활용할 수 있기 때문에, 구독자는 EventArgs를 통해 동적인 데이터를 처리할 수 있다.
이벤트를 구독하고 취소하는 과정은 델리게이트를 사용하는 것과 유사하다. 이벤트 제공자에 구독자가 특정 메서드를 등록하는 방식은 델리게이트의 += 연산자와 비슷한 방식으로 동작한다. 구독 시에는 OnSpacePressed += Test_OnSpacePressed;와 같이 구독할 메서드를 이벤트에 등록하며, 이를 통해 해당 이벤트가 발생했을 때 Test_OnSpacePressed 메서드가 호출된다. 반대로 구독 취소는 OnSpacePressed -= Test_OnSpacePressed;와 같이 -= 연산자를 사용하여 이전에 등록한 메서드를 이벤트에서 제거할 수 있다. 이는 델리게이트에서 메서드를 추가하거나 제거하는 것과 매우 유사하다.
public class SpaceBarListener
{
// 이벤트 정의
public event EventHandler OnSpacePressed;
private void Start()
{
// 이벤트 구독
OnSpacePressed += Test_OnSpacePressed;
// 이벤트 발생 시 Test_OnSpacePressed가 호출된다.
OnSpacePressed?.Invoke(this, EventArgs.Empty);
// 이벤트 구독 취소
OnSpacePressed -= Test_OnSpacePressed;
}
// 구독자 메서드
private void Test_OnSpacePressed(object sender, EventArgs eventArgs)
{
Console.WriteLine("스페이스 바 눌림!");
}
}
이벤트 제공자는 자신이 발생시킨 이벤트에 대해 구독자가 누구인지 알 필요 없이, 등록된 모든 구독자에게 자동으로 알림을 보낸다. 즉, 이벤트는 구독자들의 목록을 관리하고, 이벤트가 발생하면 그 목록에 있는 메서드들을 차례대로 실행한다. 구독자는 이벤트 제공자의 클래스를 참조하여 이벤트를 구독할 수 있으며, 같은 오브젝트 내에서 이 작업이 이루어진다면 쉽게 구독할 수 있다. 예를 들어, Unity 에디터에서는 이벤트 구독 시 구독자 시그니처가 자동완성되어 편리하게 작성할 수 있다. 또한, 이벤트는 구독자가 특정 구체적인 클래스나 메서드를 알지 못해도, 단지 그 메서드가 이벤트 시그니처와 일치하는지에만 의존하여 구독자를 실행한다.구독자는 제공자가 누구인지 알지 못해도, 제공자가 발생시키는 이벤트에만 반응하게 된다.
이벤트에서 EventHandler를 사용할 때, 제네릭을 통해 더 구체적인 EventArgs 클래스를 정의하면, 이벤트를 발생시킬 때 전달할 수 있는 데이터를 더욱 세밀하게 다룰 수 있다. 제네릭 타입 <OnSpacePressedEventArgs>는 커스텀 EventArgs 클래스를 지정하는 데 사용되며, 이를 통해 이벤트에 필요한 추가 데이터를 정의할 수 있다. 예를 들어, OnSpacePressedEventArgs 클래스는 spaceCount와 같은 속성을 정의하여, 스페이스바가 눌릴 때마다 몇 번째로 눌렸는지와 같은 정보를 이벤트와 함께 전달할 수 있다. 이때, new 키워드를 사용하여 EventArgs의 새로운 인스턴스를 생성하는 것이 중요하다. 이벤트가 발생하면, OnSpacePressed?.Invoke(this, new OnSpacePressedEventArgs { spaceCount = 1 });와 같이 새 객체를 생성하여 이벤트에 전달한다.
이 커스텀 EventArgs를 다른 클래스에서 사용할 때는 구독자의 메서드 시그니처에서 TestingEvents.OnSpacePressedEventArgs처럼 명확하게 지정해주면 된다. 예를 들어, 구독자의 Test_OnSpacePressed 메서드는 object sender와 OnSpacePressedEventArgs를 매개변수로 받아 이벤트가 발생할 때 전달된 데이터(spaceCount)를 처리할 수 있다. 델리게이트를 사용하고 싶다면 EventHandler 대신에 델리게이트를 직접 정의하고, 그 델리게이트 타입을 이벤트에 지정할 수도 있다.
public class TestingEvents
{
// 이벤트 정의
public event EventHandler<OnSpacePressedEventArgs> OnSpacePressed;
// 커스텀 EventArgs 클래스
public class OnSpacePressedEventArgs : EventArgs
{
public int spaceCount;
}
public void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// 이벤트 발생
OnSpacePressed?.Invoke(this, new OnSpacePressedEventArgs { spaceCount = 1 });
}
}
// 구독자 메서드
private void Test_OnSpacePressed(object sender, OnSpacePressedEventArgs e)
{
Console.WriteLine("구독자의 스페이스바! 눌린 횟수: " + e.spaceCount);
}
}
유니티 이벤트 사용법
유니티에서 이벤트를 사용하려면 UnityEngine.Events 네임스페이스를 정의해야 한다. 이 네임스페이스에는 UnityEvent 클래스가 포함되어 있으며, 이를 통해 Unity에서 자주 사용하는 이벤트 시스템을 쉽게 구현할 수 있다. UnityEvent는 이벤트를 등록하고 호출하는 작업을 더 직관적으로 만들며, 특히 Unity의 인스펙터 창에서 직접 이벤트를 연결할 수 있어 유용하다. UnityEngine.Events 네임스페이스를 통해 제공되는 이벤트 시스템은 게임 오브젝트 간의 상호작용을 간편하게 설정할 수 있게 해준다.
유니티 이벤트 제공자를 정의하는 방법
public UnityEvent OnSpacePressed;
public UnityEvent OnSpacePressed;처럼 public으로 이벤트를 선언하면, 유니티 에디터의 인스펙터 창에서 다른 객체들이 해당 이벤트에 구독자를 수동으로 추가할 수 있다. 이는 유용할 수 있지만, 개인적으로 코드의 가독성이나 관리 측면에서 선호하지 않는 경우가 많다. 이런 상황에서는 [HideInInspector] 속성을 사용하여 해당 이벤트가 인스펙터에 표시되지 않도록 숨길 수 있다. 이 방법을 사용하면, 코드에서만 이벤트를 관리할 수 있고, 인스펙터에서 불필요한 수동 구독을 방지할 수 있다. 예를 들어, OnSpacePressed 이벤트는 코드 내에서만 구독하고 실행되며, 유니티 에디터에서의 시각적 관리 없이 깔끔하게 유지할 수 있다.
유니티이벤트(제공자)를 호출하는 법
public void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
//스페이스바 눌리면
if (OnSpacePressed != null)
OnSpacePressed.Invoke(); // 이벤트 핸들러들을 호출
}
}
유니티이벤트(제공자)를 호출하는 법
public void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
//스페이스바 눌리면
OnSpacePressed?.Invoke(); // 이벤트 핸들러들을 호출
}
}
?. 연산자는 null 조건부 연산자로, 객체가 null이 아닌 경우에만 해당 객체의 메서드나 속성에 접근할 수 있게 해준다. 이벤트 호출 시 이 연산자를 사용하면, 이벤트에 구독자가 없거나 null일 때 발생할 수 있는 오류를 방지할 수 있다. 예를 들어, OnSpacePressed?.Invoke(this, EventArgs.Empty);와 같이 작성하면, OnSpacePressed가 null이 아닐 때만 Invoke 메서드가 호출되므로, 구독자가 없을 때도 안전하게 실행할 수 있다.
다른 클래스에서 유니티 이벤트(제공자)를 구독
private void Start() {
TestingEvents testingEvents = GetComponent<TestingEvents>();
testingEvents.OnSpacePressed.AddListener(Test_OnSpacePressed); //구독 꾹
}
//구독자로 쓸 함수
private void Test_OnSpacePressed()
{
Debug.Log("구독자의 스페이스바!");
}
유니티 이벤트에서는 일반적인 이벤트와 달리 AddListener 메서드를 사용하여 구독자를 추가한다. 이를 통해 이벤트가 발생했을 때 호출될 메서드를 등록할 수 있으며, 구독자 시그니처를 명시적으로 구현할 필요가 없어서 코드가 간결하고 깔끔하다. 예를 들어, OnSpacePressed.AddListener(OnSpacePressedHandler)와 같이 메서드를 구독할 수 있다. 또한 구독 취소는 RemoveListener 메서드를 사용하여, 더 이상 이벤트가 발생할 때 구독자가 호출되지 않도록 할 수 있다. 이 방식은 코드의 관리와 수정이 용이하고, 인스펙터를 통해 구독을 쉽게 설정할 수 있다는 장점이 있다.
유니티이벤트(제공자) 제너릭 매개변수 지정
public UnityEvent<int> OnOnSpacePressed;
유니티 이벤트에서 다른 클래스의 구독자 함수에 매개변수를 전달하려면, UnityEvent 뒤에 제네릭 형식을 지정해줘야 한다. 예를 들어, 구독자 함수가 int s와 같은 매개변수를 받는 경우, UnityEvent<int>와 같이 제네릭 타입을 지정해야 해당 이벤트를 구독할 수 있다. 이렇게 하면 이벤트를 호출할 때 int 타입의 인수를 함께 전달할 수 있으며, 구독자의 함수에서 이를 받을 수 있다. 예를 들어, OnSpacePressed.AddListener(OnSpacePressedHandler)와 같이 구독할 수 있으며, 이벤트를 발생시킬 때는 OnSpacePressed.Invoke(10)처럼 int 값을 전달하면, 구독자의 OnSpacePressedHandler(int s) 함수에서 이 값을 처리할 수 있게 된다.
//구독자로 쓸 함수
private void Test_OnSpacePressed(int s)
{
Debug.Log("구독자의 스페이스바!");
}
유니티 액션 사용 방법
UnityAction은 유니티 이벤트에서 사용할 수 있는 델리게이트로, UnityEvent의 AddListener 메서드에 구독자를 추가할 때 유용하게 사용된다. UnityAction을 사용하면, 메서드를 이벤트에 직접 추가할 수 있는데, 이는 AddListener 메서드를 통해 이루어진다. 예를 들어, UnityAction을 사용하여 OnSpacePressed.AddListener(OnSpacePressedHandler)와 같이 구독자를 추가할 수 있다. UnityAction은 인수와 반환값이 없는 메서드를 구독할 때 사용되며, 매개변수를 전달하고 싶은 경우 제네릭 형식을 지정하여 UnityAction<int>처럼 사용할 수 있다. 이 방법을 사용하면, UnityEvent와 함께 더 유연하게 이벤트를 관리하고, 메서드를 이벤트에 연결할 수 있다.
public UnityEvent OnSpacePressed; //요 이벤트에다가 유니티액션 넣기 가능
public UnityAction unityAction;
private void Start() {
OnSpacePressed.AddListener(unityAction);
}
유니티 이벤트와 UnityAction은 .NET의 기본적인 이벤트와 유사하지만, 주로 +=와 -= 연산자를 사용하여 구독과 구독 취소를 관리한다는 점에서 약간의 차이가 있다. 특히 유니티에서는 같은 스크립트 내에서 이벤트를 관리할 때 이러한 연산자가 유용하게 사용되며, 코드를 더 간결하고 직관적으로 만들 수 있다. UnityEvent와 UnityAction은 유니티에서 제공하는 강력한 이벤트 시스템으로, 이벤트를 발생시키고 구독자를 쉽게 연결할 수 있어, 다른 스크립트나 클래스 간의 상호작용을 간편하게 처리할 수 있다. 따라서 유니티 이벤트와 액션을 활용하여 코드 구조를 간소화하고, 관리하기 용이하게 만드는 것이 좋은 접근법이라고 생각된다.
C# 언어에는 Namespace(네임 스페이스) 라는 기능을 제공하여 강력한 방법으로 이 문제를 해결할 수 있습니다. 네임 스페이스는 클래스 이름에 접두어로 사용되는 클래스의 집합입니다. 아래의 샘플에서 P 와 B 는 HelpClass 라는 네임 스페이스의 멤버입니다. 라고합니다.
사용방법
구현목표는 P클래스와 B클래스를 만들고 Help라는 클래스를만들어서 P와 B의 기능을 namespace를사용하여
Help구현하여 P,B클래스에서 사용하는것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
namespace HelpClass
{
public class PlayerHelp : MonoBehaviour
{
public static void Help()
{
Debug.Log("플레이어 도와줘");
}
}
public class BossHelp : MonoBehaviour
{
public static void Help()
{
Debug.Log("보스 도와줘");
}
}
}
|
Class를 하나만들어서 namespace HelpClass라고 정의하였습니다. 또한 아래에는 PClass와 BClass를도와줄
2개의 클래스를 만들었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HelpClass;
public class B : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
}
|
B Class를 하나 만들어서 4번째줄에 using HelpClass를 넣어줍니다. namespace를 가져왔습니다.
4번째 줄을 스킵할경우에는 11번째줄을 HelpClass.BossHelp.Help(); 이런식으로 수정하여 사용할수있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HelpClass;
public class P : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
}
|
P Class또한 똑같습니다.
NameSpace의 사용이유
만약 자신의 아들과 딸에게 장난감박스를 만들어줄려고할때 SonToyBox, DaughterToyBox라는 2개의 클래스를 선언할수있지만
ToyBox라는 Namespces를 생성후 Son, Daughter의 2개의 클래스를 아래에 선언해 이름의 혼용을 막을수있습니다.
패키지로 묶인 스크립트는 수정해도, 원복된다
그래서 패키지 폴더를 Libary 밖으로 뺴야함
1.Library - Package Cache - (스크립트 수정하고싶은 폴더 복사)
2.Packages - 폴더붙여넣기
3. Library에 있던 폴더 삭제 (중복방지)
'Development > 3D Engine Programming' 카테고리의 다른 글
Unity Programming [5] : 유니티 소프트웨어 설계 (Attribute, Gizmos) (1) | 2024.12.27 |
---|---|
Unity Programming [4] : AI 통합 게임 엔진 (AI-Integrated Game Engine) (3) | 2024.11.19 |
Unity Programming [2] : C# 입출력 연산 (Input/Output (I/O) operations) (2) | 2024.11.08 |
Unity Programming [1]: 병행성 추상화 프레임워크 (Concurrency Abstraction Framework) (0) | 2024.10.28 |
Unity Programming [0] : C# 프로그래밍 (1) | 2024.10.11 |