오늘 한 일
- 스파르타 클럽 진도 나가기 (~ 4-3)
- 팀프로젝트 계획 및 ERD 작성하기
저번 진도까지는 이미 파이썬을 경험한 바탕이 있기 때문에 빠르게 왔지만(3주차 숙제가 템플릿 없는 줄 알고 백지부터 시작하고 완성했다;), 4주차 강의부터는 여러 번의 이해 시도가 필요한 개념들이 있어서 한번 여기서 정리해보기로 했다.
- 델리게이트(Delegate)는 메서드를 참조하는 타입입니다.
^ 위와 같은 설명을 그냥 메서드의 리스트와 같은 개념으로 이해했다.
- 이해를 위해 그려놓은 그림
MyDelegate dele = Method1; // 첫 메서드 할당 (할당되지 않은 delegate는 +=가 불가능하다.) MyDelegate dele += Method2; // 이후로는 List.Add(Method2)와 비슷한 느낌이다. MyDelegate dele += Method3;
델리게이트를 이것저것 만져보면서 알게된 규칙
초기 할당은 =로 메서드를 추가해주고 || 그 이후 메서드 추가는 += || 메서드 제거는 -= 로 이루어진다.
- 메서드 추가
- 메서드 제거
호출 시 등록된 메서드부터 순차적으로 진행한다.
- 출력 장면 (맨 오른쪽 밑에 실행창이 있다.)
- 등록 순서를 바꾼다면 위와 같이 바뀐다.
event와 병합 사용하기
- 강의에서 나온 내용대로 event와 함께 사용되는 모습을 볼 수 있다.
- 해당 코드
// 델리게이트 선언 public delegate void EnemyAttackHandler(float damage); // 적 클래스 public class Enemy { // 공격 이벤트 public event EnemyAttackHandler OnAttack; // 적의 공격 메서드 public void Attack(float damage) { // 이벤트 호출 OnAttack?.Invoke(damage); // Invoke - 함수를 실행합니다. // null 조건부 연산자 - ? // null 참조가 아닌 경우에만 멤버에 접근하거나 메서드를 호출 } } // 플레이어 클래스 public class Player { // 플레이어가 받은 데미지 처리 메서드 public void HandleDamage(float damage) { // 플레이어의 체력 감소 등의 처리 로직 Console.WriteLine("플레이어가 {0}의 데미지를 입었습니다.", damage); } } // 게임 실행 static void Main() { // 적 객체 생성 Enemy enemy = new Enemy(); // 플레이어 객체 생성 Player player = new Player(); // 플레이어의 데미지 처리 메서드를 적의 공격 이벤트에 추가 enemy.OnAttack += player.HandleDamage; // 적의 공격 enemy.Attack(10.0f); }
- 솔직히 이거 처음보고 강의에서도 뭐라고 말하는지 전혀 이해가 안되서 애를 먹었다.
순차적으로 이해하는 과정
- 일단 델리게이트와 이벤트의 관계를 생각해보았다. 델리게이트는 메서드를 합쳐주는 기능이 있는데 그걸 이벤트가 할당 받았다.
public delegate void EnemyAttackHandler(float damage); // ... (생략) ... // 공격 이벤트 public event EnemyAttackHandler OnAttack;
! 참고로 event에 델리게이트를 붙여주지않으면 안된다. 코파일럿의 답변
- 그림과 같이 이해해보기 ( 대충 Event가 Delegate의 타입을 가져왔다. )
2. 그 다음에 Attack이라는 메서드에 이벤트 호출을 써놓았다.
// 적의 공격 메서드 public void Attack(float damage) { // 이벤트 호출 OnAttack?.Invoke(damage); // Invoke - 함수를 실행합니다. // null 조건부 연산자 - ? // null 참조가 아닌 경우에만 멤버에 접근하거나 메서드를 호출 } }
- 그림과 같이 이해 (이번에는 Method와 event가 붙었다.)
3. 각각의 객체가 생성된 이후에 Event 타입의 이름으로 player.HandleDamage 메서드를 저장했다.
// 게임 실행 static void Main() { // 적 객체 생성 Enemy enemy = new Enemy(); // 플레이어 객체 생성 Player player = new Player(); // 플레이어의 데미지 처리 메서드를 적의 공격 이벤트에 추가 enemy.OnAttack += player.HandleDamage; // 그렇다면 알 수 있는 것은 event가 실행시킬 메서드를 저장하는 역할임을 알 수 있다! }
- 그림과 같이 이해
4. 그 이후에 Enemy클래스의 Method를 실행시켜보았다.
enemy.Attack(10.0f);
- 출력 결과
- 그림과 같이 이해
- 실행이 어떻게 흘러갔는가
- Attack 메서드가 10의 인자를 받고 그것을 OnAttack에 전달
- OnAttack 이벤트는 저장된 메서드를 호출하면서 damage(10)을 전달
- player는 damage(10)을 받고 입력된 WriteLine 코드 실행
? 혹시나 이 과정을 보고도 이해가 안된다면 직접 코드를 이리저리 만져보는 것을 추천한다.
이해가 됐다면 이러한 변형이 가능해진다.
- 메서드에서 이벤트에게 값을 주기 전에 임의로 변동시켜서 줄 수 있고
public void Attack(float damage) { damage = 20; // 몬스터의 공격력이 20이라는 가정을 할 때! // + 또는 추가시키고 싶은 로직을 추가시킬 수도 있다. (치명타 판정, 회피 판정 등등) // 이벤트 호출 OnAttack?.Invoke(damage); }
- 추가적인 메서드를 넣어서 공격을 전체에게 가하는 느낌도 낼 수 있다.
// 플레이어 객체 생성 Player player1 = new Player("플레이어1"); Player player2 = new Player("플레이어2"); Player player3 = new Player("플레이어3"); Player player4 = new Player("플레이어4"); // 플레이어의 데미지 처리 메서드를 적의 공격 이벤트에 추가 enemy.OnAttack += player1.HandleDamage; enemy.OnAttack += player2.HandleDamage; enemy.OnAttack += player3.HandleDamage; enemy.OnAttack += player4.HandleDamage;
- 회피율 기능을 추가한다면 밑과 같이 출력이 된다.
그리고 이해를 하고나니 남은 부분들도 빠르게 이해가 되었다.
람다 - 델리게이트에 간단한 기능을 넣고자 할 때 사용됨.
// 표현식 람다 (한줄짜리) (해당 람다를 담을 수 있는 델리게이트) = x => x + x; // 문장 람다 (다줄짜리) (해당 람다를 담을 수 있는 델리게이트) = (x) => { x + x; }; enemy.OnAttack += (float a) => Console.WriteLine("적이 공격을 시도합니다!"); // 매개변수가 동일하다면 집어넣을 수 있다.
델리게이트를 간단하게 만들 수 있는 템플릿
델리게이트는 미리 선언해줘야할 필요가 있는 반면 제네릭 델리게이트인 Func과 Action은 바로 선언해서 사용 가능하다.
Func - 값을 반환하는 메서드를 위한 델리게이트
Func<매개변수1, 매개변수2, ... 마지막 매개변수, 반환값> addFunc = method;
Action - 값을 반환하지 않는 메서드를 위한 델리게이트
Func<매개변수> addFunc = method;
활용법 (강의에 나온 예제 코드입니다.)
class GameCharacter { private Action<float> healthChangedCallback; // Action 선언 private float health; public float Health { get { return health; } set { health = value; healthChangedCallback?.Invoke(health); // health가 set(변할) 때마다 호출된다. } } public void SetHealthChangedCallback(Action<float> callback) // Action을 외부로 가져갈 수가 없으니 메서드가 콜백함수들을 붙이는 걸 도와주자 { healthChangedCallback = callback; } } // 게임 캐릭터 생성 및 상태 변경 감지 GameCharacter character = new GameCharacter(); character.SetHealthChangedCallback(health => { if (health <= 0) { Console.WriteLine("캐릭터 사망!"); } }); // 체력이 0 이하가 되면 텍스트를 출력하는 람다를 Action에 추가했다.
저번 주에는 진도 외의 것을 TIL로 작성을 하려할 때 해석에 너무 어려운 느낌이 들었다. 아무래도 지금 상황에서는 빠르게 진도를 나가서 기초부터 빠르게 다진 다음에 진도 이외의 공부를 해나가는것이 올바른 절차일 것같다.
내일 할 일
- 스파르타 코딩클럽 강의 (~ 4주차 과제)
- 이번주 팀 프로젝트 코드 만들어보기 ( 전투 기능 만들기 )