C# Event | 게임 오브젝트 사이에 커플링 줄이기

유니티는 많은 게임 오브젝트를 생성할 수 있습니다. 스크립트를 통해 서로 연결되어 있는 오브젝트가 있을 건데요. A와 B 오브젝트가 서로 코드로 엮어있는 정도를 커플링이라고 합니다.

‘커플링이 높다’라는 말은 A와 B 오브젝트가 서로 심하게 엮어있어서 A 오브젝트가 없어지면 B 오브젝트가 영향을 받기 때문에 코드를 수정하기 쉽지 않을 때 불리는 말입니다.

하나를 수정함으로써 다른 여럿 코드도 수정해야 한다면 여간 복잡한 게 아니죠. 그래서 깔끔한 코드에 들어가는 조건 중 하나가 커플링이 적은 코드입니다.

유니티에서의 event


event는 원하는 순간에 event에 등록된 기능을 발동시키는데 그 기능이 어떤 기능인지 전혀 신경을 쓰지 않고 발동시킵니다.

만약 A 게임 오브젝트가 죽었을 때 B와 C가 어떤 기능을 발동시켜야 하면 A 게임 오브젝트는 C# event 기능을 활용할 수 있습니다. B와 C는 A 게임 오브젝트 event에 자신의 기능을 등록만 하고 A 게임 오브젝트도 event에 어떤 기능이 등록되어 있는지 신경을 안 씁니다.

그래서 event에서는 event를 발동시키는 쪽과 event에 기능을 등록하는 쪽을 구분하는 용어가 있습니다. event를 발동시키는 쪽을 publisher라 부르고 event에 기능을 등록하는 쪽을 subcriber라 부릅니다.

publisher와 subcriber의 관계를 간단히 설명하겠습니다. publisher는 subcriber를 모르는 상태이고 event를 감지해야 되는 쪽이 event에 등록하는 것입니다. 위 경우를 예로 들자면 B와 C는 A 오브젝트가 죽었을 때를 감지해야 기능을 발동시킬 수 있겠죠.

간단한 예제


게임을 하는데 플레이어가 라운드가 시작되기 전에 유료 아이템을 사용해서 자신의 능력치를 높일 수 있습니다. 그런데 유료 아이템은 여러 개를 중첩할 수 있습니다.

플레이어는 입장에서는 중첩된 아이템의 내용물을 아무것도 모르는 상태에서 그저 아이템을 발동시켜야 합니다. 이럴 때 event를 사용할 수 있습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Character : MonoBehaviour
{
    public delegate void Item(Character target);
    //delegate와 event는 함께 사용된다
    public event Item playerItem;

    public string playerName = "Angliss";

    public float hp = 100;
    public float defense = 50;
    public float damage = 30;

    void Start()
    {
        playerItem(this);
    }

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Space))
        {
            playerItem(this);
        }
    }
}

event는 delegate와 함께 사용됩니다. 유니티에서의 delegate는 여러 함수를 등록하고 쉽게 호출하기 위해 사용되는데 event의 특징에 딱 맞죠.

플레이어 캐릭터는 체력, 방어력과 공격력을 가지고 있고 Item의 기능을 대행해 줄 delegate를 만들었습니다. 캐릭터 입장에서는 playerItem을 발동만 시키면 event를 발동 시키는 것입니다.

게임이 시작되고 playerItem(this)를 통해 등록된 아이템이 사용될 것이고 테스트를 위해서 스페이스바를 누를 때마다 등록된 아이템이 사용되도록 하였습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Item : MonoBehaviour
{
    public void HealthItem(Character target)
    {
        Debug.Log(target.playerName + "의 체력을 강화했다!");
        target.hp += 10;
    }

    public void ShieldItem(Character target)
    {
        Debug.Log(target.playerName + "의 방어력을 강화했다!");
        target.defense += 10;
    }

    public void DamageItem(Character target)
    {
        Debug.Log(target.playerName + "의 공격력을 강화했다!");
        target.damage += 10;
    }

    void Awake()
    {
        Character player = FindObjectOfType<Character>();

        player.playerItem += HealthItem;
        player.playerItem += ShieldItem;
        player.playerItem += DamageItem;
    }
}

Item 스크립트는 캐릭터의 체력, 방어력 와 공격력을 올려주는 기능이 있습니다. 그리고 게임이 시작되자마자 플레이어 게임 오브젝트를 찾고 플레이어 스크립트에 있는 event에 발동되기 원하는 기능들을 등록하였습니다.

플레이어가 Item을 사용하기 위해선 플레이어가 가지는 스크립트에서 원하는 아이템의 기능을 원하는 순간에 사용되게 코드를 수정해야 했지만 이제는 Item에서만 코드를 수정하면 되는 것입니다. 이 덕분에 플레이어 스크립트는 매우 깔끔하게 작성되었죠.

그런데 사실 delegate만으로도 event 기능을 구현할 수는 있는데 delegate 만으로 작성되면 event에 등록된 기능들이 덮어씌워지거나 publisher가 아니라 subcriber가 이벤트를 발동시킬 수 있습니다. 원하지 않는 방향으로 작성될 수 있는 것이죠.

event 키워드는 delegate가 event가 아닌 방향으로 잘못 작성되거나 event에 맞춰 더 엄격하게 코드를 작성할 수 있습니다.

예제에 작성된 코드를 바탕으로 캐릭터와 ItemManager 게임 오브젝트를 만들면 게임이 시작되자마자 정상적으로 아이템이 사용되고 스페이스바를 누를 때마다 아이템이 다시 사용되는 것을 볼 수 있습니다.

댓글 달기

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