Unity 본캠프 11일차 - C# 팀 프로젝트

한예준·2025년 4월 21일

오늘부터 팀원들과 함께 C# 팀 프로젝트를 진행하기로 했다.
프로젝트를 어떻게 진행할지에 대해 대략적인 구상을 하고 역할 분담을 했다. 회의 이후 C# 문자열 특강이 있어서 일단 이걸 먼저 진행하도록 하겠다.


문자열 처리

  1. 보간 문자열
string name = "예준";
int age = 27;
Console.WriteLine("이름: "+ name + ", 나이: " + age);
Console.WriteLine($"이름:{name}, 나이:{age}");
  1. Replace
    문자열의 특정 단어를 바꿀 때
//사용 예시: 채팅 필터링에서 욕설이나 금칙어를 가릴 때
string testString = "야 너 바보야?";
string filtered = testString.Replace("바보", "***");
Console.WriteLine(filtered);

//출력: 야 너 ***야?
  1. Trim/ToLower/ToUpper
    입력값 정리, 대소문자 처리
string testString = "      Hello      ";
string filtered = testString.Trim();
string filtered2 = testString.ToLower();
string filtered3 = testString.ToUpper();

Console.WriteLine(filtered);
Console.WriteLine(filtered2);
Console.WriteLine(filtered3);
  1. Contains
    포함 여부 확인
string testString = "오늘 점심은 닭가슴살입니다.";
Console.WriteLine(testString.Contains("치킨"));
//출력: False
  1. IndexOf
    특정 문자의 위치 찾기
string testString = "yj6662@naver.com";
int atIndex = testString.IndexOf("@");

Console.WriteLine($"인덱스번호 : {atIndex}");
//출력 : 6
  1. Split
    문자열 나누기

  2. Substring
    부분 문자열 추출

  3. StringBuilder
    많은 문자열을 효율적으로 조립

StringBuilder sb = new StringBuiler();
for (int i = 0; i < 50000; i++)
{
	sb AppendLine("로그 메시지" + i);
}
Console.WriteLine(sb.ToString());
  1. Join
    문자열 배열 결합
string[] item = { "a", "b", "c", "d" };
Console.WriteLine(string.Join("*",item));
  1. StartsWith/EndsWith
    문자열 시작/끝 확인

  2. Length/Insert/Remove
    문자열 길이 및 삽입/삭제

  3. Equals/Compare
    문자열 비교

  4. Regex
    정규 표현식으로 고급 패턴 처리


팀 프로젝트 시작

우리 팀원들은 어떤 방향으로 게임을 구현할지를 고민해보고, 각자 역할을 분담하였다. 나는 전투기능 전반을 담당하기로 했다.
우리 팀은 일단 전체적인 게임의 틀을 구현하여 Main branch에 올리고, 각 기능을 맡은 사람이 기능별 branch를 따로 만들어 구현한 뒤, 완성된 코드를 Main branch에 merge하는 방식으로 작업을 진행하기로 했다.

전투 기능

기획을 담당한 팀원이 전투 기능을 기획한 바는 다음과 같았다.

  • 전투

    • 전투 시작 시 1~4마리 몬스터가 랜덤 스폰
      몬스터는 총 3종류

      LV2 미니언
      HP 15
      ATK 5
      LV3 공허충
      HP 15
      ATK 9
      LV5 대포 미니언
      HP 25
      ATK 8

    • 내 턴에 할 수 있는 것은 공격 밖에 없음

  • 공격

    • 공격을 선택 시 몬스터 앞에 숫자가 표시 됨
    • 일치하지 않은 몬스터나 이미 죽은 몬스터를 잘못된 입력입니다 출력
    • 몬스터를 알맞게 선택했다면 해당 몬스터 공격
    • 계산 공식 → (몬스터 HP) - (플레이어 ATK *(90%~110%) 만큼 변동 적용 후, 몬스터 HP에서 차감
    • 만약 오차가 소수점일 경우 반올림 처리
    • 몬스터가 죽었다면 체력 대신 Dead 표시
    • 몬스터가 죽었다면 해당 몬스터의 텍스트는 전부 어두운 색으로 표시
  • 적 차례

    • 플레이어 차례 종료 후 0 을 입력하면 적 차례 진입
    • 빠른 번호 순으로 몬스터가 공격함(죽은 몬스터는 공격 X)
    • 다음을 누르면 그 다음 몬스터의 공격이 계속 됨
    • 몬스터의 차례가 끝나면 플레이어의 차례로 돌아옴
  • 전투 결과

    • 모든 몬스터가 Dead 상태가 된다면 게임이 종료 → 승리
    • 내 체력이 0이 되면 게임이 종료 → 패배

기능 구현 과정

  • 먼저 몬스터의 종류 및 종류별 능력치를 지정해준다.
public enum Monsters { Minion, VoidInsect, CannonMinion }
  • Monsters 열거형(enum)을 통해 몬스터의 종류를 세 가지로 정의

  • Monster 클래스를 통해 몬스터가 가져야 할 능력치를 설정.

      public class Monster
      {
          public Monsters MonsterType { get; set; }
          public int monMaxHp { get; set; }
          public int monHP { get; private set; }
          public int monAtk { get; set; }
          public int monLevel { get; set; }
          public bool IsAlive => monHP > 0; // IsAlive 불리언을 통해 생존 여부 확인
    
          public Monster(Monsters type)
          {
              MonsterType = type;
              // switch 식으로 타입별 기본 스탯 정의
              (monMaxHp, monAtk, monLevel) = type switch
              {
                  Monsters.Minion => (15, 5, 2),
                  Monsters.VoidInsect => (15, 9, 3),
                  Monsters.CannonMinion => (25, 8, 5),
    
              };
              monHP = monMaxHp;
          }
    
          public int TakeDamage(int playerAtk)
          {
              Random random = new Random();
              // 0.9 ~ 1.1 사이의 랜덤 배율 생성
              double variation = random.NextDouble() * 0.2 + 0.9;
              int dmg = (int)Math.Round(playerAtk * variation);
              monHP = Math.Max(monHP - dmg, 0); // HP가 0보다 작아지지 않도록 조정
              return dmg;
          }
      }
    • 생성자 public Monster
      • Monsters type만 넘겨도 switch 식을 통하여 체력, 공격력, 레벨을 할당.
      • 설정한 monHP = monMaxHP 를 통해 현재 체력도 설정해준다.
    • TakeDamage(int playerAtk)메서드
      • Random을 통해 0.9~1.1 사이의 배율을 생성한다.
      • playerAtk * 배율 결과를 Math.Round 하고, 그만큼 monHP에서 차감한다.
      • 몬스터의 체력이 0보다 작아지지 않게 조정한다.
      • 실제 깎인 dmg 값을 반환한다.
  • BattleSystem

    • Start(Job Player)
      전투 진입점, 전투 씬, 승패 판정,
          public static void Start(Job player)
          {
              var monsters = SpawnMonsters();
    
              while (player.health > 0 && monsters.Any(m => m.IsAlive))
              {
                  PlayerTurn(player, monsters);
                  if (!monsters.Any(m => m.IsAlive)) break;
                  EnemyTurn(player, monsters);
              }
    
              Console.Clear();
              Console.WriteLine(player.health > 0 ? " 승리! " : " 패배...");
              Console.ReadKey();
          }
    • SpawnMonsters()
      몬스터 1~4마리 랜덤 소환, 종류 랜덤 소환.
            public static Monster[] SpawnMonsters()
          {
              int monsterCount = _rand.Next(1, 5); // 1~4마리 랜덤 생성
              Monsters[] monsterTypes = (Monsters[])Enum.GetValues(typeof(Monsters));
              Monster[] monsters = new Monster[monsterCount];
              for (int i = 0; i < monsterCount; i++)
              {
                  Monsters randomType = monsterTypes[_rand.Next(monsterTypes.Length)];
                  monsters[i] = new Monster(randomType);
              }
              return monsters;
          }
    • RenderStatus(Job player, Monster[] monsters)
            public static void RenderStatus(Job player, Monster[] monsters)
          {
              Console.Clear();
              Console.WriteLine("플레이어 상태");
              Console.WriteLine($"체력: {player.health}");
              Console.WriteLine($"공격력: {player.atk}");
              for (int i = 0; i < monsters.Length; i++)
              {
                  var m = monsters[i];
                  if (m.IsAlive)
                  {
                      Console.WriteLine($"몬스터 {i + 1} ({m.MonsterType})");
                      Console.WriteLine($"체력: {m.monHP}/{m.monMaxHp}");
                      Console.WriteLine($"공격력: {m.monAtk}");
                  }
                  else
                  {
                      Console.WriteLine($"몬스터 {i + 1} ({m.MonsterType}) 사망");
                  }
              }
          }
    • 플레이어 상태 및 몬스터 상태 (사망 여부) 표시
    • `PlayerTurn(Job player, Monster[] monsters)
            public static void PlayerTurn(Job player, Monster[] monsters)
          {
              while (true)
              {
                  RenderStatus(player, monsters);
                  Console.Write("공격할 몬스터 번호(0: 차례 넘기기) > ");
                  if (!Mathod.CheckInput(out int sel)) continue;
                  if (sel == 0) break;
                  if (sel < 1 || sel > monsters.Length || !monsters[sel - 1].IsAlive)
                  {
                      Console.WriteLine("잘못된 입력입니다."); continue;
                  }
    
                  var target = monsters[sel - 1];
                  int dmg = target.TakeDamage(player.atk);
                  Console.WriteLine($"→ 몬스터 {sel}번에 {dmg} 데미지! (남은 HP {target.monHP}/{target.monMaxHp})");
                  Console.ReadKey();
                  break;  // 한 번 공격 후 적 차례로
              }
          }
    • 화면 갱신 및 입력 처리
    • 공격 실행
  • `EnemyTurn(Job player, Monster[] monsters)

        public static void EnemyTurn(Job player, Monster[] monsters)
        {
            Mathod.BufferClear();
            Console.WriteLine("적 턴");
            Console.WriteLine($"플레이어 체력: {player.health}\n");
            for (int i = 0; i < monsters.Length; i++)
            {
                var m = monsters[i];
                if (!m.IsAlive) 
                    continue; //죽은 몬스터는 건너뛰기
                else
                {
                    Console.WriteLine($"몬스터 {i + 1} {m.MonsterType}의 공격");
                    Console.WriteLine($"→ {m.monAtk} 데미지!");
                    player.health -= m.monAtk;
                    Console.WriteLine($"남은 체력: {player.health}");

                    Console.WriteLine("계속하시려면 아무 키나 누르세요.");
                    Console.ReadKey();

                    if (player.health <= 0)
                    {
                        Console.WriteLine("플레이어가 사망했습니다.");
                        Console.WriteLine("전투가 종료됩니다.");
                        Console.WriteLine("게임 오버");
                        Console.WriteLine("다시 시작하려면 아무 키나 누르세요.");
                        Console.ReadKey();
                        return; // 플레이어가 사망하면 전투 종료
                    }
                }
            }
        }
  • 플레이어 체력 출력
  • 몬스터 순차 공격 및 데미지 반영, 플레이어 사망 시 종료

오늘 구현은 여기까지 하도록 하겠다. 내일은 몬스터의 층 별 스탯 강화나 보스 몬스터와 같은 추가 기능을 구현해 볼 생각이다.


회고 및 느낀점

  • 일주일만에 팀 프로젝트를 시작했다. 팀원 분들 중에 Git을 많이 다뤄보셨던 분이 계셔서 Git 활용이 조금 더 효율적이게 되었다.
  • 저번 팀에서 Git을 활용할 때는 팀원 별로 Branch를 나눠서 한꺼번에 merge하는 과정을 겪으면서 상당히 많은 오류가 발생했다.
  • 하지만 현재는 팀원이 아닌 기능 별로 Branch를 나눠서 기능이 구현될 때마다 main에 merge하기 때문에 오류 발생 가능성이 현저히 적어질 것 같다.
  • Git 사용은 수월해졌지만, 코드 작성에는 여전히 어려움이 있었다.
  • 전체적인 프로젝트의 구조를 짜면서 코딩에 능숙하신 팀원분께서 코드 작성을 편리하게 하는 기능을 넣어주셨는데, C#의 기본 기능에도 익숙하지 않은 입장에서 이러한 기능까지 응용하는 것은 쉬운 작업은 아니었다.
  • 하지만 익숙해지니 활용에 있어서 편리한 점이 많았다.
  • 코딩을 하면서 자주 겪은 일은 아니지만, 팀원들이 작성한 코드와 규칙에 맞게 코딩을 하는 게 나만 알아볼 수 있던 코딩을 하던 내 입장에서는 쉽지 않았는데, 앞으로의 협업과정에서 필수적으로 해내야 하는 일이라고 생각한다.
  • 코드 작성에 있어서 많이 헤매기도 했지만, 시간이 지나면서 점점 익숙해졌다. 내일은 조금 더 빠르고 효율적인 팀 작업을 할 수 있을거라 기대해본다.

0개의 댓글