2025.01.31 (금)

윤혜진·2025년 1월 31일
0

TIL

목록 보기
16/38

📍오늘의 학습 키워드

  • 4주차 과제
    • 간단한 텍스트 RPG

📍학습 내용

  • 본격적으로 개인 과제를 진행하기 전, 4주차 과제인 간단한 텍스트 RPG를 먼저 만들어보기로 했다.
  • 4주차 과제는 개인 과제와 달리 몬스터와 플레이어 간의 전투만 진행되는 구조였기 때문에 본격적으로 개인 과제에 들어가기 전 배운 문법들에 익숙해지는 기회로 삼아보기로 함!
  • 아래는 내가 생각한 이번 과제 (간단한 텍스트 RPG)의 흐름 :
    - 플레이어의 이름을 묻기
    - 게임 시작(전투시작)
    
    - 몬스터의 공격
    - 플레이어 데미지 입음
    
    - 플레이어의 공격
    - 몬스터 데미지 입음
    
    - 만약 플레이어의 Health가 0이 된다면
        - 게임 종료
        
    - 몬스터의 Health가 0이 된다면
        - 남은 몬스터가 있는지 체크
            - 남은 몬스터가 있다면
                - 보상 아이템을 선택할 수 있게됨
                    - 체력 포션을 받는다면
                        - 체력 올려줌
                    - 공격력 포션을 받는다면
                        - 공격력 올려줌
                - 보상을 선택한 뒤 다음 몬스터와 전투 시작
            - 남은 몬스터가 없다면
                - 게임 종료
  • 흐름을 쓸 때는 뭔가 구현해야 할 것들이 많아 보였는데, 각 클래스별로 메서드를 만들어주고 event를 이용해 연달아 일어나야 할 메서드들을 연결해 주니 생각보다 어렵지 않았다.
  • 플레이어와 몬스터들의 구조는 비교적 비슷하며 크게는 4개의 메서드로 나뉘어 있다.
    1. Appeared 메서드

      • 등장 메서드 (자기소개 메서드)
      • 클래스 주인의 이름, 체력, 공격력을 출력해준다.
      //몬스터
      public void Appeared()
      {
          Console.WriteLine($"적이 등장했습니다!\n");
      
          Console.WriteLine($"이름: {Name}");
          Console.WriteLine($"체력: {Health}");
          Console.WriteLine($"공격력: {Attack}\n");
          Thread.Sleep(1000);
      }
      
      //플레이어
      public void Appeared()
      {
          Console.WriteLine($"현재 {Name}의 체력: {Health}");
          Console.WriteLine($"현재 {Name}의 공격력: {Attack}\n");
          Thread.Sleep(1000);
      }
    2. TakeDamage 메서드

      • 상대에게 데미지를 받는 메서드
      • 상대의 GiveDamage가 실행되면 뒤이어 실행된다. (event로 이어줌)
      public void TakeDamage(int damage)
      {
          Console.WriteLine($"{Name}(이)가 {damage}의 데미지를 입었습니다.");
          Health -= damage;
          Console.WriteLine($"현재 {Name}의 체력: {Health}\n");
          Thread.Sleep(1000);
      
          if (Health <= 0)
          {
              IsDead = true;
              Die();
          }
      }
    3. GiveDamage 메서드

      • 상대에게 데미지를 주는 메서드
      • event를 통해 상대의 TakeDamage 메서드와 이어주었다.
      public void GiveDamage()
      {
          Console.WriteLine($"{Name}의 공격!\n");
          Thread.Sleep(1000);
          OnAttack?.Invoke(Attack);
      }
    4. Die 메서드

      • 사망 메서드
      • TakeDamage에서 데미지 처리를 한 뒤, 체력이 0 이하가 되었다면 실행되는 메서드
      public void Die()
      {
          Console.WriteLine($"{Name}(이)가 사망했습니다.\n");
          Thread.Sleep(1000);
      }

📍겪은 어려움

  • 모두가 체력 0이 될 때까지 전투가 계속되는 버그 :

    • while문 안의 조건을 아래와 같이 잘못 설정해주었기 때문에 일어났던 버그 (피곤했나…)
      while(!player.IsDead || !goblin.IsDead)
      {
          player.GiveDamage();
          goblin.GiveDamage();
      }
    • 해결: while문의 조건을 player.IsDead || goblin.IsDead로 바꿔주었다.
  • 적이 죽은 뒤에도 플레이어를 때리는 버그 발생 :

    • TakeDamage 메서드에 Health가 0이하로 떨어지면 IsDead를 true로 만들어주는 코드를 구현해 놓았는데, while문의 true/false 확인은 {} 안쪽의 코드를 모두 실행한 뒤에 확인하는 구조라 죽은 고블린이 플레이어를 한 대 더 때리고 나서야 게임이 끝나는 버그가 발생했다.
    • 해결: 데미지를 주는 메서드 밑에 IsDead를 체크하고, 반복문을 빠져나오는 조건문을 추가함
      while (true)
      {
          player.GiveDamage();
          if (goblin.IsDead) break;
      
          goblin.GiveDamage();
          if (player.IsDead) break;
      }
  • 메모리 오버헤드인 줄 알았던 문제 :
    • 과제 요구사항에 “각 턴이 진행될 때 천천히 보여지도록 Thread.Sleep을 사용하여 1초의 대기시간을 추가해주세요” 라는 부분이 있었기에 메서드 안 출력문 아래마다 Thread.Sleep(1000)를 붙여 1초의 딜레이를 주었다.
    • 그런데 만들고 보니 전투 부분만 들어가면 묘하게 1초보다 느려지는 현상이 발생함. (체감상 1.5~2초쯤?)
    • 전투 부분은 event를 사용해서 구현했고, 나는 event를 처음 적용해 보았으므로 ‘이게 바로 어제 배웠던 메모리 오버헤드?’ 같은 생각이나 하며 신기해했는데… TIL 초고를 작성하며 정리하다 보니 뭔가 이상하다는 생각이 들기 시작했다.
    • 의심의 이유:
      • 나는 어제자 TIL에 메모리 오버헤드“프로그램의 실행 흐름 도중에 동떨어진 위치의 코드를 실행시켜야 할 때, 추가적으로 시간, 메모리, 사원이 사용되는 현상”이라고 정리해 두었는데, 아무리 event를 사용했다지만 같은 클래스에서 필드를 참조하는 문구와 이렇게까지 차이가 날 필요가 있나? 싶은 생각이 드는 것이다.
      • 물론 필드를 참조하는 것과 메서드를 참조하는 게 어떻게 같겠냐마는… 그걸 차치해 두더라도 이런 player.OnAttack += goblin.TakeDamage; 같이 메서드를 붙여주는 코드 사이에서도 공격 메서드와 같은 딜레이가 일어나지 않는 게 조금 이상하다는 생각이 들었다.
    • 그래서 메서드 어딘가에서 Thread.Sleep 이 두 번 실행되는 게 아닌가 의심하며 코드를 다시 한번 살펴보았는데… 아니나 다를까, 붙여준 OnAttack 뒤에서 Thread.Sleep 을 실행시켜주고 있었다.
      public void TakeDamage(int damage)
      {
          Console.WriteLine($"{Name}(이)가 {damage}의 데미지를 입었습니다.");
          Health -= damage;
          Console.WriteLine($"현재 {Name}의 체력: {Health}\n");
          Thread.Sleep(1000);
      
          if (Health <= 0)
          {
              IsDead = true;
              Die();
          }
      }
      
      public void GiveDamage()
      {
          Console.WriteLine($"{Name}의 공격!");
          OnAttack?.Invoke(Attack); //TakeDamage가 실행된 뒤 Thread.Sleep이 또 실행됨
          Thread.Sleep(1000);
      }
    • 해결: Thread.Sleep 의 위치를 OnAttack(델리게이트) 앞으로 바꾸니 딜레이 없이 잘 돌아갔다…

📍회고 및 반성

  • 과제 조건이 인터페이스를 만들고 적용시키는 것이었기 때문에 자연스럽게 인터페이스를 사용하게 되었는데, 점점 메서드가 늘어나자 내가 손수 입력해야 하는 중복 메서드들이 너무 많아져서 불편했다.
  • 아직 구현할 기능이 적어 그 필요성을 제대로 느끼지 못하는 걸 수도 있지만, 일단 이번 코드만큼은 구현할 때 그냥 클래스를 상속받았으면 더 편하지 않았을까?라는 생각이 들었음.
  • 반대로 과제 조건은 아니었지만, 배운 걸 써먹어보고 싶은 욕심에 델리게이트(event, Action) 기능을 사용해 봤는데 확실히 편리했다!!
  • 이 기능이 없었다면 내가 isAttack이라는 bool 변수를 만들어서 메서드 간에 신호를 주고받을 수 있도록 손수 구현해야 했을 텐데, 델리게이트를 통해 메서드를 연결해 두면 [몬스터 공격 - 플레이어 공격받음]이 자동으로 실행되는 점이 정말, 진짜, 진심, 너무!!! 편리하고 신기했음. (덕분에 구현 시간도 생각보다 오래 걸리지 않았다!!)
  • 다만 아무래도 공격과 공격받는 행동이 한 메서드에서 이루어지는 게 아난 각 메서드에서 신호를 주고받는 형태라, 세부적인 타이밍이 잘 안 맞는 문제가 있긴 했다. (죽은 고블린이 플레이어를 기어코 한 대 때리고서야 죽는다던지)
  • 하지만 감안해도 정말 좋은 기능인 듯! 개인 과제를 할 때 꼭 써먹어야겠다👍

0개의 댓글

관련 채용 정보