스파르타 게임개발종합반(Unity)/기술 면접 대비 꾸준 실습

03. 콜백, delegate, event

테크러너 2024. 7. 4.

확인 문제

using System;

class Program
{
    // 델리게이트 정의
    public delegate int MyCallback(int a, int b);

    // 덧셈 메서드
    static int Add(int a, int b)
    {
        return a + b;
    }

    // 곱셈 메서드
    static int Multiply(int a, int b)
    {
        return a * b;
    }

    static void Main(string[] args)
    {
        MyCallback MathOperation;
        
        MathOperation = Add;
        
        Console.WriteLine(MathOperation(3, 4));
        
        MathOperation = Multiply;
        
        Console.WriteLine(MathOperation(7, 5));
    }
}

1. 위 코드의 출력 결과는 무엇인가요? 그 이유는 무엇인가요?

출력 결과▼

7
35

이유 : `MyCallback`이라는 델리게이트는 `int`형 반환값과 `int`형 두 개의 매개변수를 가지는 메서드를 대신 호출할 수 있습니다. 델리게이트는 메서드를 매개변수로 전달할 수 있게 해줍니다. `MathOperation`을 `Add` 메서드로 지정했으므로 `Add` 메서드를 호출하여 `3`과 `4`를 더해 결과가 `7`로 나왔고, 이후 `MathOperation`을 `Multiply` 메서드로 지정했으므로 `Multiply` 메서드를 호출하여 `7`과 `5`를 곱해 결과가 `35`가 나온 것입니다.

 

 

2. 위 코드의 Main 함수를 아래처럼 바꿔도 같은 방식으로 동작할까요? 그 이유는 무엇인가요?

static void Main(string[] args)
{
    Func<int, int, int> MathOperation;

    MathOperation = Add;

    Console.WriteLine(MathOperation(3, 4));

    MathOperation = Multiply;

    Console.WriteLine(MathOperation(7, 5));
}

네 같은 방식으로 동작합니다. `Func`는 반환형과 매개변수 타입을 정의하는 델리게이트로, 위에서 사용한 `MyCallback` 델리게이트와 동일한 역할을 수행합니다. 마지막 매개변수는 반환 형식을 정의합니다.

 

 

3. 델리게이트를 사용하여 메서드를 매개변수로 전달하는 장점은 무엇인가요?

  • 동일한 코드 블록이 다양한 메서드를 호출할 수 있으므로 유연성이 향상됩니다.
  • 코드의 중복을 줄이고 유지보수를 쉽게할 수 있습니다.
  • 비동기 작업이나 이벤트 처리 시 콜백 메서드를 쉽게 구현할 수 있습니다.
  • 호출자가 메서드의 구현 세부 사항을 몰라도 되므로, 코드의 캡슐화와 추상화가 향상됩니다.
  • 전략 패턴을 쉽게 구현할 수 있습니다.

 

개인적으로 확장해본 코드▼

더보기
using System;

class Program
{
    // 델리게이트 정의
    public delegate int MyCallback(int a, int b);

    // 덧셈 메서드
    static int Add(int a, int b)
    {
        return a + b;
    }

    // 곱셈 메서드
    static int Multiply(int a, int b)
    {
        return a * b;
    }

    // 델리게이트를 매개변수로 받는 메서드
    static void PerformOperation(MyCallback operation, int x, int y)
    {
        Console.WriteLine(operation(x, y));
    }

    static void Main(string[] args)
    {
        // 델리게이트를 사용하여 메서드를 매개변수로 전달
        PerformOperation(Add, 3, 4); // 출력: 7
        PerformOperation(Multiply, 7, 5); // 출력: 35
    }
}

 

 

설명 문제

1. 콜백이란 무엇인가요? 콜백을 사용해본 경험이 있을까요?

콜백이란 특정 작업이 완료된 후 호출되는 함수나 메서드를 말합니다. 주로 비동기 작업이나 이벤트 처리에서 사용됩니다. 콜백 함수는 다른 함수에 매개변수로 전달되어 작업이 완료된 후 실행됩니다.

저는 주로 이벤트에 대한 콜백 메서드 등록으로 사용해봤습니다.

 

2. 델리게이트(delegate; 대리자)란 무엇인가요?

함수 포인터라고 할 수 있습니다. 델리게이트는 메서드의 참조를 담을 수 있는 형식으로, 특정 메서드를 대신 호출할 수 있습니다. 델리게이트를 사용하면 메서드를 매개변수로 전달하고, 런타임에 메서드를 동적으로 변경할 수 있습니다.

델리게이트는 특히 이벤트 처리, 콜백 메서드, 동적 메서드 호출에 유용합니다.

 

3. C#의 event란 무엇인가요?

`event`는 특정 작업이나 상태 변화가 발생했을 때 이를 알리기 위해 사용하는 특별한 델리게이트입니다.

이벤트는 델리게이트를 기반으로 하며, `+=` 연산자를 사용하여 이벤트 핸들러를 추가하고(이벤트 구독) `-=` 연산자를 사용하여 제거합니다. 이벤트는 외부에서 직접 호출할 수 없으며, 이벤트를 소유한 클래스 내부에서만 호출할 수 있습니다.

 

4. Unity에서 사용하는 델리게이트 혹은 이벤트에는 어떤 것이 있나요?

  • `Action` 델리게이트 : 반환 값이 없는 메서드를 참조할 수 있습니다. 매개변수를 받을 수 있는 인자의 수는 0에서 16개까지 가능합니다.
  • `Func` 델리게이트 : 반환 값이 있는 메서드를 참조할 수 있습니다. 마지막 매개변수는 반환 형식을 정의합니다. 매개변수를 받을 수 있는 인자의 수는 0에서 16개까지 가능합니다.
  • `UnityEvent` : 유니티의 이벤트 시스템으로, 인스펙터에서 쉽게 설정할 수 있습니다. `UnityEngine.Events` 네임스페이스에서 제공됩니다.
  • `event`(이벤트 핸들러) : 특정 이벤트가 발생할 때 호출될 메서드를 등록하는 데 사용됩니다.

 

 

실습 문제

💡 [캐릭터 피격 기능 연결]

레이어가 데미지를 받을 때 여러 기능이 한번에 작동하도록 시스템을 만드려고 합니다.

상태 메시지가 출력되고, 피격 이펙트가 나오며, 피격 사운드가 재생되는 메서드가 구현되어 있는 상황입니다.

  • `DisplayStatus` : 데미지 상태를 출력하는 메서드입니다.
  • `DisplayHitEffect` : 피격 이펙트를 표시하는 메서드
  • `PlayHitSound` : 피격 사운드를 재생하는 메서드

1) TakeDamage 함수 내에서 플레이어가 데미지를 받을 때 이벤트를 발생(Publish)시키는 코드를 작성합니다.

2) DisplayAndSound 클래스의 생성자에서 이벤트 발생 시 호출될 메서드(`DisplayStatus`, `DisplayHitEffect`, `PlayHitSound`)를 연결합니다.

 

⚠️주의!!!)

조건 : 데미지를 받는 로직을 처리하는 TakeDamage 메서드 안에서 `DisplayStatus`, `DisplayHitEffect`, `PlayHitSound`를 직접 호출하지 않고 코드를 완성해봅시다.

using System;

namespace GameEventExample
{
    // 이벤트 데이터를 담는 EventArgs 파생 클래스
    public class PlayerDamagedEventArgs : EventArgs
    {
        public int Damage { get; set; }
    }

    // 플레이어 클래스
    public class Player
    {
        public int HP { get; private set; } = 100;

        // 플레이어가 데미지를 받을 때 발생하는 이벤트를 정의하세요.
        public event EventHandler<PlayerDamagedEventArgs> PlayerDamaged;

        // 플레이어가 데미지를 받는 메서드
        public void TakeDamage(int damage)
        {
            HP -= damage;

            PlayerDamagedEventArgs e = new PlayerDamagedEventArgs { Damage = damage };
            // TODO: 이벤트가 등록되어 있는지 확인하고 이벤트를 발생
            
            //
        }
    }

    public class DisplayAndSound
    {
        public DisplayAndSound(Player player)
        {
            // TODO : 각각의 이벤트 핸들러를 등록하세요.
            
            //
        }

        // 데미지 상태를 출력하는 메서드
        void DisplayStatus(object sender, PlayerDamagedEventArgs e)
        {
            Player player = sender as Player;
            Console.WriteLine($"Player took {e.Damage} damage!");
            Console.WriteLine($"Remaining HP: {player.HP}");
        }

        // 피격 이펙트를 표시하는 메서드
        void DisplayHitEffect(object sender, PlayerDamagedEventArgs e)
        {
            Console.WriteLine("Displaying hit effect...");
        }

        // 피격 사운드를 재생하는 메서드
        void PlayHitSound(object sender, PlayerDamagedEventArgs e)
        {
            Console.WriteLine("Playing hit sound...");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Player player = new Player();
            DisplayAndSound displayAndSound = new DisplayAndSound(player);

            // 플레이어가 데미지를 받아 이벤트를 발생
            player.TakeDamage(20);
            Console.WriteLine($"Player HP: {player.HP}");
        }
    }
}

 

구현한 코드▼

더보기
namespace GameEventExample
{
    // 이벤트 데이터를 담는 EventArgs 파생 클래스
    public class PlayerDamagedEventArgs : EventArgs
    {
        public int Damage { get; set; }
    }

    // 플레이어 클래스
    public class Player
    {
        public int HP { get; private set; } = 100;

        // 플레이어가 데미지를 받을 때 발생하는 이벤트를 정의하세요.
        public event EventHandler<PlayerDamagedEventArgs> PlayerDamaged;

        // 플레이어가 데미지를 받는 메서드
        public void TakeDamage(int damage)
        {
            HP -= damage;

            PlayerDamagedEventArgs e = new PlayerDamagedEventArgs { Damage = damage };
            // TODO: 이벤트가 등록되어 있는지 확인하고 이벤트를 발생
            PlayerDamaged?.Invoke(this, e);
        }
    }

    public class DisplayAndSound
    {
        public DisplayAndSound(Player player)
        {
            // TODO : 각각의 이벤트 핸들러를 등록하세요.
            player.PlayerDamaged += DisplayStatus;
            player.PlayerDamaged += DisplayHitEffect;
            player.PlayerDamaged += PlayHitSound;
        }

        // 데미지 상태를 출력하는 메서드
        void DisplayStatus(object sender, PlayerDamagedEventArgs e)
        {
            Player player = sender as Player;
            Console.WriteLine($"Player took {e.Damage} damage!");
            Console.WriteLine($"Remaining HP: {player.HP}");
        }

        // 피격 이펙트를 표시하는 메서드
        void DisplayHitEffect(object sender, PlayerDamagedEventArgs e)
        {
            Console.WriteLine("Displaying hit effect...");
        }

        // 피격 사운드를 재생하는 메서드
        void PlayHitSound(object sender, PlayerDamagedEventArgs e)
        {
            Console.WriteLine("Playing hit sound...");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Player player = new Player();
            DisplayAndSound displayAndSound = new DisplayAndSound(player);

            // 플레이어가 데미지를 받아 이벤트를 발생
            player.TakeDamage(20);
            Console.WriteLine($"Player HP: {player.HP}");
        }
    }
}
반응형

댓글