728x90

1. Action

Action 타입은 입력과 출력이 없는 메서드를 가리킬 수 있는 델리게이트(delegate)입니다.

 

델리게이트는 '대리자'로 번역되며 메서드를 값으로 할당받을 수 있는 타입입니다.

 

Action 타입의 변수에는 void SomeFunction() 처럼 입력과 출력이 없는 메서드를 등록할 수 있습니다.

등록된 메서드는 원하는 시점에 매번 실행할 수 있습니다.

 

청소를 원하는 시점에 '대리' 실행하는 간단한 예시를 봅시다. 다음은 마우스를 클릭할 대마다 onClear에 등록된 메서드가 실행되는 예입니다.

 

public class Cleaner : MonoBehaviour {
	Action onClean;
    
    void Start(){
    	onClean += CleaningRoomA;
        onClean += CleaningRoomB;
    }
    
    void Update(){
    	if(Input.GetMouseButtonDown(0)){
        	onClean();
        }
    }
    
    void CleaningRoomA(){
    	Debug.Log("A방 청소");
    }
    
    void CleaningRoomB(){
    	Debug.Log("B방 청소")
    }
}

예시의 Start() 메서드는 onClean에 방을 청소하는 메서드를 등록합니다.

 

Action 타입의 변수에는 +=을 사용해 메서드를 등록할 수 있습니다. 이때 등록할 메서드 끝에는 괄호를 붙이지 않고 이름만 명시합니다.

괄호를 붙이면 메서드를 '등록'하는 것이 아니라'실행'하고 그 반환값을 할당하는 것이 됩니다.

 

다음 코드는 CleaningRoomA()를 먼저 실행하고 그 결괏값을 onClean에 더하는 형태가 되므로 에러가 발생합니다.

 

onClean += CleaningRoomA();

 

onClean에 메서드를 등록한 후 onClean();을 실행하면 등록된 메서드가 일괄 실행됩니다.

 

2. 이벤트

이벤트는 연쇄 동작을 이끌어내는 사건입니다. 이벤트 자체는 어떤 일을 실행하지 않지만 이벤트가 발생하면 이벤트를 구독하는 처리들이 연쇄적으로 일어납니다.

 

이벤트를 사용하면 어떤 클래스에서 특정 사건이 일어났을 때 다른 클래스에서 그것을 감지하고 관련된 처리를 실행할 수있습니다. 이벤트를 구현할 때는 이벤트와 이벤트에 관심이 있는 이벤트 리스너로 오브젝트를 구분합니다.

 

 

C#에서 이벤트를 구현하는 대표적인 방법은 델리게이트를 클래스 외부로 공개하는 것입니다.

외부로 공개된 델리게이트는 클래스 외부의 메서드가 등록될 수 있는 명단이자 이벤트가 됩니다.

그리고 이벤트가 발동(invoke)하면 이벤트에 등록된 메서드들이 모두 실행됩니다.

 

여기서 이벤트를 항상 듣고 있다가 이벤트가 발동될 때 실행되는 메서드들을 이벤트 리스너라고 합니다.

이벤트 리스너를 이벤트에 등록하는 것을 '이벤트 이스너가 이벤트를 구독한다'고 표현합니다.

 

이벤트는 자신을 구독하는 이벤트 리스너들이 어떤 처리를 실행하는지 상관하지 않는다는 점에 주목합니다.

이벤트는 자신의 명단에 등록된 메서드들의 내부 구현을 알지 못한 채 그들을 실행합니다.

 

이벤트의 장점 :

견고한 커플링 해소

 

이벤트는 자신을 구독하는 메서드의 구현과 상관없이 동작하므로 '견고한 커플링'문제를 해소합니다.

견고한 커플링은 어떤 클래스가 다른 클래스의 구현에 강하게 결합되어 코드를 유연하게 변경할 수 없는 상태입니다.

 

플레이어가 죽었을 때 게임 데이터를 저장하는 기능을 구현한다고 가정해봅시다.

public class Player : MonoBehaviour {
	public GameData gameData;
    
    public void Die(){
    	gameData.Save();
    }
}

public class GameData : MonoBehaviour{
	public void Save(){
    	Debug.Log("게임 저장..");
    }
}

위 코드는 Player 클래스가 자신과 상관없는 GameData 클래스와 강하게 결합되어 있습니다.

 

Player 클래스는 플레이어에 관한 기능만 처리하면 되므로 GameData 클래스의 기능이 필요하지 않습니다.

하지만 GameData 클래스는 Player 클래스의 Die() 메서드에서 '사망 사건'이 일어나면 Save() 메서드를 실행해야 합니다. 따라서 Player 클래스에서 GameData 클래스에 관한 코드를 추가해야 합니다.

 

이 경우 GameData의 구현이 변경되면(예를 들어 Save() 메서드의 이름이 변경되는 경우) Player 클래스의 구현도 변경해야 하므로 코드를 유연하게 유지보수하기 어렵습니다.

 

위 예시 코드는 Player 클래스에서 onDeath라는 이벤트를 제공하는 방식으로 개선할 수 있습니다.

 

public class Player : MonoBehaivour{
	public Action onDeath;
    
    public void Die(){
    	onDeath();
    }
}

public class GameData : MonoBehaviour{
	void Start(){
    	Player player = FindObjectOfType<Player>();
        player.onDead
    }
    
    public void Save(){
    	Debug.Log("게임 저장..");
    }
}

변경된 코드에서 Player 클래스는 GameData 타입의 오브젝트를 비롯해 자신의 사망 사건에 관심 있는 상대방 오브젝트를 파악할 필요가 없습니다. Player의 사망 사건에 관심 있는 오브젝트는 Player의 onDeath 이벤트를 구독하면 됩니다.

 

event

델리게이트 타입의 변수는 event 키워드를 붙여 선언할 수 있습니다. 어떤 델리게이트 변수를 event로 선언하면 클래스 외부에서는 해당 델리게이트를 실행할 수 없게 됩니다.

 

event를 사용하면 이벤트를 소유하지 않은 측에서 멋대로 이벤트를 발동하는 것을 막을 수 있습니다.

다음은 event를 사용해 Player 클래스 외부에서 이벤트를 실행하지 못하게 막는 예시입니다.

 

public class Player : MonoBehaviour {
	public event Action onDeath;
    
    public void Die(){
    	onDeath();
    }
}

public class GameData : MonoBehaviour{
	void Start(){
    	Player player = FindObjectOfType<Player>();
        player.onDeath += Save;
        player.onDeath(); //에러(Player 밖에서는 onDeath 발동 불가)
    }
    
    public void Save(){
    	Debug.Log("게임 저장..")
    }
}

 

 

출처 : 

레트로의 유니티 게임 프로그래밍 에센스 | 이제민 - 교보문고 (kyobobook.co.kr)

 

레트로의 유니티 게임 프로그래밍 에센스 | 이제민 - 교보문고

레트로의 유니티 게임 프로그래밍 에센스 | 독자분들로부터 수많은 찬사를 받았던 유니티 대표 도서 『소문난 명강의:레트로의 유니티 게임 프로그래밍 에센스』가 개정판으로 돌아왔습니다!

product.kyobobook.co.kr

 

 

728x90

+ Recent posts