TIL 0111 TextRPG (팀) - 3 / 원형 큐 (Circular Queue)

강성원·2024년 1월 11일
0

TIL 오늘 배운 것

목록 보기
14/70

오늘 개발한 내용

전투 중 스킬 사용

이 부분은 고민에 은근 시간을 많이 썼다. 남의 코드에 내 코드 붙이는 것은 항상 어렵다.

고민의 중심 내용은 "기본 공격과 스킬 공격의 로직을 합치느냐, 따로 두느냐" 였다.

처음에는 스킬 번호를 선택 시 Skill 함수를 따로 빼서 몬스터에게 스킬을 사용하는 장면을 만들까 했었지만, Fight에서도 호출하는 작은 함수들이 조금 있어서 어떻게든 Fight에 내 스킬 공격을 합쳐보기로 했다. (써놓고 보니까 복잡시럽다.)


Skill 클래스 변경 내용

스킬이 몇 몬스터를 공격할지 구분해주기 위해서 대상 타겟의 수를 정하는 필드를 추가해줬다.

internal class Skill
{	
	,,,다른 필드 생략,,,
    
	public int NumberTargets        { get; set; }
    
    public Skill(string name, int cost, int atkMultiplier, string description, int numberTargets)
    {
        ,,,생략,,,
        NumberTargets = numberTargets;
        if (NumberTargets < 1) NumberTargets = 1;
        if (NumberTargets > 5) NumberTargets = 5;

    }
    
    ,,,메서드 생략,,,
}

타겟의 수는 생성자에서 최소1, 최대 5로 제한해두었다.


스킬 번호 전달

(스킬 목록 예시)
1. 빠르게 찌르기
2. 보통 찌르기
3. 세게 찌르기
if(!스킬 목록 보여줌)
{ 
	Fight(monsters, startHp, 0); 		//기본 공격
}
else
{
	Fight(monsters, startHp, skillNum);	//스킬 공격
}

스킬 선택여부를 어떻게 구분지을까 하다가
기본 공격으로 Fight를 호출할 때에는 인자를 0으로 전달해주고
스킬 공격의 경우에는 선택한 스킬의 번호(skillNum)를 Fight에 넘겨주도록 했다.
1. 빠르게 찌르기를 택했다면 Fight()의 마지막 인자는 1이다.


단일 공격과 다중 공격의 구분

원래라면 Fight()에서 스킬이냐 아니냐로 분기를 나누려고 했다.

하지만 생각을 해보니 스킬도 단일 공격이면 기본 공격과 방식에는 차이가 없다.
그래서 단일 공격과 다중 공격으로 구분해서 분기를 나누었다.

private void Fight(Monster[] monsters, int startHp, int skillNum)
{            
    bool invalid = false;
    bool isMultiTarget = false; // 선택한 스킬이 다중 타격인지
    if ((skillNum > 0) && (player.Skills[skillNum-1].NumberTargets > 1))
        isMultiTarget = true;

    while (true)
    {	
    	//,,, 생략 ,,,
        
        // 단일 타겟인 경우
        if (!isMultiTarget)
        {
            //,,, 공격 대상 선택 내용 생략 ,,,

            if (inputNum > 0 && inputNum <= monsters.Length)
            {
                if (monsters[inputNum - 1].IsDead) //몬스터 생존 여부
                {
                    invalid = true;
                    continue;
                }
                else
                {
                    invalid = false;
                    PlayerPhase(monsters[inputNum - 1], skillNum);
                }
            }
            //,,, 잘못된 입력 생략 ,,,
        }
        // 다중 타겟인 경우
        else
        {
            PlayerPhase(monsters, skillNum);
        }

        //,,, 전투 마무리 생략,,,

        return;
    }            
}
  • 메서드의 위쪽에 보면 isMultiTarget 변수에 조건문으로 bool값을 넣는 것을 볼 수 있다.
    조건문의 내용은
    매개 변수로 받은 skillNum이 0 이상이고, 스킬의 타겟의 수가 1 초과인 경우에 isMultiTarget에 true를 대입하는 것이다.

isMultiTarget의 값에 따라서 PlayerPhase() 메서드에 전달하는 인자의 타입이 달라진다.

  • isMultiTargetfalse라면(단일 공격) 사용자는 공격할 몬스터를 하나 선택하고
    PlayerPhase()에 선택된 공격 대상인Monster 객체 하나만 스킬 번호와 함께 넘겨준다.

  • isMultiTargettrue라면(다중 공격) PlayerPhase()Monster객체 배열 전체를 스킬 번호와 함께 넘겨준다.

이를 가능케 하기 위해서 Monster 배열을 인자로 받는PlayerPhase()를 추가로 오버로딩했다.

아래에서는 다중공격의 경우에만 다루겠다.


다중 공격 흐름

너무 길기 때문에 이 메서드를 위에서부터 토막으로 설명하겠다.

private void PlayerPhase(Monster[] monsters, int skillNum) 
{
				,,,아래에서 설명,,,
}

우선 매개변수로도 보이듯이 존재하는 몬스터 전부를 받아왔다.
순서대로 따다닥 공격하면 편하겠지만 요구사항에는 랜덤하게 공격을 해달라고 하니 맞춰본다.

다중 공격에 신경 썼던 조건

다중 공격에 신경 써야 할 경우는 2가지이다.

  1. 다중 공격 타겟 수가 살아있는 몬스터 수 이상일 때
  2. 다중 공격 타겟 수가 살아있는 몬스터 수 미만일 때

이렇게 2 가지의 상태로 나뉜다.
1번의 경우에는 전부 공격하면 되고,
2번은 몬스터 수N 중에서 다중 공격 타겟 수M을 골라내서 공격해준다.

먼저 살아있는 몬스터의 수와 스킬로 다중 타격 가능한 수를 변수화 시킨다.

int MonsterNum = monsters.Count(x => !x.IsDead);          		// 생존 몬스터 수
int TargetNum = player.Skills[skillNum - 1].NumberTargets;		// 스킬의 타겟 수

Linq 메서드 Count를 사용하면 배열 중 원하는 조건에 부합하는 요소의 개수를 얻을 수 있다.

□다중 타격 수 >= 살아있는 몬스터 수

foreach (Monster monster in monsters.Where(x => !x.IsDead)) 
{
    string prevHp = monster.Hp.ToString();
    monster.GetDamage(damage);
    string currentHp = monster.IsDead ? "Dead" : monster.Hp.ToString();
                       
    ,,,출력 로직 생략,,,

    Console.ReadLine();
}

Linq의 Where 메서드를 호출해서 죽지 않은 몬스터들만 순회해서 모든 살아있는 몬스터를 공격한다.

몬스터 2마리를 랜덤으로 때리는 스킬로 살아있는 몬스터 총 2마리를 공격하는 장면이다.

□다중 타격 수 < 살아있는 몬스터 수

다중 타격 가능한 수가 살아있는 몬스터 수 미만인 경우에는 준비 과정이 필요했다.

몬스터 수 m 중에서 n마리의 몬스터를 랜덤으로 공격해야한다는 것인데,
(m > n)
그리하기 위해서는 우선 몬스터 배열을 섞어줄 필요가 있었다.
하지만 원본 배열을 건드리지 않아야 했기에 임시 배열을 만들어 복사해주고 임시 배열을 섞어주었다.

Monster[] shuffledMonsters = monsters.ToArray();    //원본 보존을 위한 임시 배열
for(int i = shuffledMonsters.Length - 1; i > 0; i--)
{
    int j = random.Next(0, i + 1);
    Monster tempMonster = shuffledMonsters[i];
    shuffledMonsters[i] = shuffledMonsters[j];
    shuffledMonsters[j] = tempMonster;
}

Fisher-Yates Shuffle 알고리즘을 사용해서 배열을 섞어주었다.

그 후에 섞은 배열 내부에서 스킬의 타격 가능 수만큼 몬스터를 공격한다.

foreach (Monster monster in shuffledMonsters.Where(x => !x.IsDead).Take(TargetNum))
{
    string prevHp = monster.Hp.ToString();
    monster.GetDamage(damage);
    string currentHp = monster.IsDead ? "Dead" : monster.Hp.ToString();

    ,,,출력 및 입력은 생략,,,
}

Where 메서드로 우선 죽지 않은 몬스터만 골라오고, Take 메서드로 스킬 타격 가능 수 만큼의 요소를 가져온다.

이렇게 5마리의 몬스터에게 2마리를 공격하는 스킬을 사용하면
랜덤으로 2마리만 공격한다.


완성 후 문제점

스킬 처치 클리어시 무한 반복 버그

스킬을 사용해서 모든 몬스터를 무찌르면 몬스터는 전부 죽어있는 표시지만 공격이나 스킬을 사용하라고 메뉴가 뜬다.

몬스터가 죽어서 이름은 회색이지만 공격이랑 스킬은 계속 써보란다. 싸패인가보다.

해결

완전 간단하고 사소한 부분에서 문제가 있었다.

while(true)
{
	,,, 생략 ,,,
    
  //입력된 스킬 번호를 Fight로 전달
  if (int.TryParse(input,out skillNum) && skillNum >= 1 && (skillNum - 1) < player.Skills.Count)
  {
      // Fight 메서드에 스킬 번호 전달
      Fight(monsters, startHp, skillNum);
      isSkillShow = false;
  }
  else
      invalid = true;
  continue;
}
  • Fight() 를 진행하며 몹을 다 잡고난 후에 메서드가 반환되면 break로 while 문을 끝내지 않았던 것이 문제였다.
while(true)
{
	,,, 생략 ,,,
    
  //입력된 스킬 번호를 Fight로 전달
  if (int.TryParse(input,out skillNum) && skillNum >= 1 && (skillNum - 1) < player.Skills.Count)
  {
      // Fight 메서드에 스킬 번호 전달
      Fight(monsters, startHp, skillNum);
      isSkillShow = false;
      break;
  }
  else
      invalid = true;
  continue;
}
  • break를 추가하니 스킬 사용해서 클리어해도 던전 입구로 잘 나가진다.

내일 개발할 부분

스킬 사용 시 MP 소모

생각해보니 스킬 사용하면 MP 소모가 있어야하는데 완전 까먹었다.
Skill 클래스에 Use 메서드를 만들어서 로직을 구현하면 좋을 것 같다.

마나 부족 시 스킬 사용 불가

스킬을 선택 시 판단하는 로직을 추가해주면 될 것 같다.

직업별 클래스 구성

플레이어로부터 하위 클래스로 전사, 궁수, 마법사, 도적을 만들어주고
각 하위 클래스 생성자에서 스킬을 추가해주는 방식이 될 수 있도록 할 것이다.


원형 큐

알고리즘 특강 때 선형 큐의 대안인 원형 큐에 대해서 잠깐 언급이 있었다.

특강 후에 따로 자료를 찾아보고 원형 큐에 대해서 글을 정리했다.

원형 큐 정리


오늘 회고

어제 막혔던 부분이 오늘은 생각보다 쉽게 풀려버렸다. 어제는 정말로 몸이 안좋아서 코딩이 안됐었나보다.
어서 빨리 독감을 전부 털어내고 정상 컨디션으로 돌아가고싶다. 건강이 최고다..ㅠㅠ

우리 조에서는 19시에 하루동안 개발한 내용을 서로에게 알려주는 시간을 가지는데,
이 시간은 내가 개발하느라 신경쓰지 못했던 다른 분들의 개발 파트에 대해서 자세히 알 수 있는 유익한 시간이다.
나도 처음에는 이런 작은 발표도 머리가 하얘지고 말을 못했는데, 팀장님 주도로 조금씩 하다보니 몇 번 안했지만 좀 더 편해진 느낌이다. (팀장님께 감사..)

그리고 내배캠 유니티 트랙에 들어오길 잘했다고 생각한 부분이 또 있는데, 그것은 오늘부터 시작한 알고리즘 문제풀이 시간이다.
전공자이지만 항상 자료구조, 알고리즘은 약하다고 생각했었다. 아침의 코드 카타 시간과 추가 공부를 통해서 이 부분이 많이 보완됐으면 좋겠다.

오늘도 보람찼고 내일도 보람차게 보내고 상쾌한 주말을 맞이할 수 있도록하자.

profile
개발은삼순이발

0개의 댓글