Unity에서의 C# 이벤트
Coddy Unity 여정의 Unity 게임 아키텍처 섹션에 포함된 레슨. 64개 중 12번째.
점수가 바뀌면 여러 요소가 영향을 받습니다. 점수 텍스트, 소리, 업적이 그렇습니다. ScoreKeeper가 각각을 호출한다면 요소마다 필드가 필요하고, 새로운 기능을 추가할 때마다 ScoreKeeper를 수정해야 합니다. 이벤트는 이 구조를 뒤집습니다. ScoreKeeper는 변경 사항을 알리고, 해당 변경에 관심 있는 쪽에서 구독합니다.
이 기능을 가능하게 하는 C# 용어 두 가지가 있습니다. Action<int>는 int 하나를 받아 아무것도 반환하지 않는 메서드의 형식입니다. 이 형식의 event는 이러한 메서드의 목록입니다. 다른 코드에서 +=를 사용해 메서드를 추가하고, 소유자가 Invoke로 모든 메서드를 호출합니다. 일반적인 C# 코드로 표현하면 다음과 같습니다.
using System;
class Alarm
{
public event Action<int> Rang;
public void Ring(int hour)
{
Rang?.Invoke(hour);
}
}
class Sleeper
{
public void WakeUp(int hour)
{
Console.WriteLine("Up at " + hour);
}
}
// 다른 곳에서
Alarm alarm = new Alarm();
Sleeper sleeper = new Sleeper();
alarm.Rang += sleeper.WakeUp;
alarm.Ring(7); // 7시에 일어남Unity에서는 게시자와 리스너가 컴포넌트이며, 보통 서로 다른 GameObject에 있습니다.
using System;
using UnityEngine;
public class ScoreKeeper : MonoBehaviour
{
public event Action<int> OnScoreChanged;
private int score;
public void AddPoints(int points)
{
score += points;
OnScoreChanged?.Invoke(score);
}
}// ScoreLabel.cs, HUD의 텍스트에 적용
public ScoreKeeper keeper;
public TMP_Text label;
void Start()
{
keeper.OnScoreChanged += ShowScore;
}
void ShowScore(int score)
{
label.text = "Score: " + score;
}Action<int>는 각 리스너가 int 하나를 받는 메서드라는 뜻입니다. 여기서 int는 새 점수입니다. Invoke는 구독한 순서대로 구독된 모든 메서드를 호출합니다. 리스너가 없으면 이벤트는 null이므로, 단순히 OnScoreChanged(score)라고 쓰면 NullReferenceException이 발생하지만, ?.를 사용하면 대신 호출을 건너뜁니다. event 키워드는 다른 스크립트에서 +=와 -=만 사용할 수 있도록 제한합니다. ScoreLabel에서 keeper.OnScoreChanged(5) 또는 keeper.OnScoreChanged = null을 사용하면 컴파일되지 않습니다.
ScoreKeeper에는 레이블을 위한 필드가 없고, 레이블의 존재도 알지 못합니다. 콤보 카운터나 사운드 플레이어도 같은 방식으로 구독하므로 ScoreKeeper는 그대로 유지되며, HUD가 없는 테스트 씬도 오류 없이 실행됩니다. 리스너는 여기처럼 Inspector 참조를 통해 게시자를 찾거나, GetComponent 또는 싱글턴처럼 알고 있는 다른 방법으로 찾습니다. 객체가 파괴되기 시작하면 -=로 리스너를 제거하는 것이 중요하며, 이는 다음 강의에서 다룹니다.
챌린지
쉬움파이터가 콤보를 알리도록 하세요.
Fighter.cs에using System;과public event Action<int> OnComboChanged;를 추가하세요.comboText와flame을 사용하는 줄을OnComboChanged?.Invoke(combo);로 바꾸고, 두 필드를 모두 삭제하세요.ComboText와ComboFlame의Start()에서fighter.OnComboChanged += Show;와+= Grow;를 작성하세요.
‘플레이’를 누르고 Space를 몇 번 누르세요. 콤보가 커졌다가 초기화됩니다. 그런 다음 ‘확인’을 누르세요.
직접 해보기
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class Fighter : MonoBehaviour
{
public float comboWindow = 1f;
public TMP_Text comboText;
public Transform flame;
private int combo;
private float sinceHit;
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
combo++;
sinceHit = 0f;
comboText.text = "Combo x" + combo;
flame.localScale = Vector3.one * (0.5f + 0.25f * combo);
}
else if (combo > 0)
{
sinceHit += Time.deltaTime;
if (sinceHit > comboWindow)
{
combo = 0;
comboText.text = "";
flame.localScale = Vector3.one * 0.5f;
}
}
}
}
Unity 게임 아키텍처의 모든 레슨
직접 연습해 보세요: Unity 플레이그라운드