![](https://t1.daumcdn.net/keditor/emoticon/friends1/large/013.gif)
오늘은 야식으로 불닭 ㅋ
팀 프로젝트 기간에 들어왔고 내가 맡은 부분은 [전투 시작] 부분이었다.
요구사항을 찬찬히 보니 몬스터가 등장하는 부분이 있었고 기존 개인과제에서는 없었기에
몬스터를 추가하는 부분을 써보기로 했다.
Monster Class
몬스터의 정보를 총괄하는 Monster class 를 새로 만들어 보자.
- 몬스터가 가지고 있어야 하는 정보
- 이름
- 레벨
- 체력
- 공격력
- 사망여부
먼저 내부에서 정보를 저장할 private 변수들을 생성해준다.
다만 몇 가지 차이점이 있다.
몬스터가 가지고 있는 정보 중 처음 생성 후, 변하지 않는 변수 [이름, 레벨, 공격력] 이 셋은 readonly 로 선언한다.
private readonly string _name;
private readonly int _lv;
private readonly int _atk;
공격을 받을 때 마다 변하는 [체력] 은 일반 private 변수로 선언한다.
private float _hp;
[사망여부]를 알 수 있는 bool 변수는 public 프로퍼티 get; private set; 하나로 만든다.
public bool IsDead { get; private set; } = false;
나머지 네 변수도 외부에서 볼 수 있는 public 프로퍼티로 private 변수를 get 하지만
[이름, 레벨, 공격력]은 set 하지 않으므로 람다로 get 기능만 허용토록 한다.
public string Name => _name;
public int Lv => _lv;
public int Atk => _atk;
Hp 프로퍼티는 런타임동안 몬스터가 데미지를 받으면 계속해서 변한다.
게다가 체력이 0 이하로 떨어지면 사망 상태가 되기에 프로퍼티의 set 부분에서 사망하도록 조건문을 쓰도록 하자.
public float Hp
{
get => _hp;
private set
{
_hp = value;
if(Hp <= 0)
{
_hp = 0;
IsDead = true;
}
}
}
그러나 이 Hp를 아무데서나 set 시키면 안 되니까 private으로 선언하고,
데미지를 받는 부분을 GetDamage 메서드를 통해 수행하도록 하자.
그런데 GetDamage는 몬스터 뿐만 아니라 플레이어 및 기타 데미지를 받을 수 있는 모든 객체의 공통사항이다.
그러니 GetDamage 메서드를 가지는 [ IDamagable ] interface를 생성하도록 하자.
// 데미지를 받을 수 있는 객체에게 상속할 인터페이스
internal interface IDamagable
{
public void GetDamage(float damage);
}
그 후, Monster 클래스에서 상속 받고 해당 기능을 구현하자.
internal class Monster : IDamagable
{
// 생성 시 할당 이후 변화 없는 변수 name, lv, atk 는 readonly로 선언.
private readonly string _name;
private readonly int _lv;
private readonly int _atk;
public string Name => _name;
public int Lv => _lv;
public int Atk => _atk;
private float _hp;
/// <summary>
/// get 시, Hp 반환
/// set 시, Hp = value 값 및 0 이하 시 Hp = 0, Dead 메서드 호출
/// </summary>
public float Hp
{
get => _hp;
private set
{
_hp = value;
if(Hp <= 0)
{
_hp = 0;
IsDead = true;
}
}
}
public bool IsDead { get; private set; } = false;
public void GetDamage(float damage) => Hp -= damage;
}
그리고 몬스터를 어떤 방식으로 구별할지 고민하다가 enum 으로 구별하는것으로 결정했다.
Define 클래스에서 모든 enum을 담당하고 있었으므로 이곳에 추가 해주도록했다.
public enum MonsterType
{
SkeletonWorrior,
SkeletonArcher,
SkeletonWizard,
GoblinWorrior,
GoblinArcher,
GoblinWizard,
}
그리고 Monster 생성자를 통해 해당 타입에 따라 [이름, 데미지, 체력] 을 달리 해야한다.
데미지와 체력은 고정값으로 하기위해 Monster 클래스 내부에 readonly int[], float[] 를 사용해 할당 해주기로 했다.
레벨은 랜덤한 값을 받기위해 생성자의 매개변수로 할당하도록 하자.
private Define.MonsterType _type;
private readonly int[] ATKs = [5, 10, 15, 7, 12, 18];
private readonly float[] HPs = [15f, 10f, 5f, 20f, 15f, 10f];
public Monster(int lv, Define.MonsterType type)
{
_lv = lv;
_atk = ATKs[(int)type];
_hp = HPs[(int)type];
_type = type;
}
※ 공격력을 int / 체력을 float 로 설정했는데, 지금 생각해보니 반대로 해야 하는게 맞는거 같다. 내일 수정해야겠다.
이름은 한글로 표현하기 위해 switch문을 사용하였다.
private Define.MonsterType _type;
// 타입에 따라 부여할 공격력/체력 (고정값)
private readonly int[] ATKs = [5, 10, 15, 7, 12, 18];
private readonly float[] HPs = [15f, 10f, 5f, 20f, 15f, 10f];
public Monster(int lv, Define.MonsterType type)
{
_lv = lv;
_atk = ATKs[(int)type];
_hp = HPs[(int)type];
_type = type;
// 몬스터 타입에 따라 이름 할당.
switch (_type)
{
case Define.MonsterType.SkeletonWorrior:
_name = $"Lv.{Lv} 스켈레톤 전사";
break;
case Define.MonsterType.SkeletonArcher:
_name = $"Lv.{Lv} 스켈레톤 궁수";
break;
case Define.MonsterType.SkeletonWizard:
_name = $"Lv.{Lv} 스켈레톤 마법사";
break;
case Define.MonsterType.GoblinWorrior:
_name = $"Lv.{Lv} 고블린 전사";
break;
case Define.MonsterType.GoblinArcher:
_name = $"Lv.{Lv} 고블린 궁수";
break;
case Define.MonsterType.GoblinWizard:
_name = $"Lv.{Lv} 고블린 마법사";
break;
}
}
마지막으로 인 게임 상에서 보여줄 몬스터의 정보들은 ShowText 메서드를 사용하였고,
이를 각 메뉴에 따라 달리 보여야 했기에 오버로드를 하여 사용처를 두 개로 분할하였다.
// 인 게임 상에서 출력할 Text 함수, 오버로딩으로 전투씬 전,후 구별 사용
public void ShowText()
{
if (IsDead)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($"{Name} \tDead");
Console.ResetColor();
}
else
Console.WriteLine($"{Name} \tHP {Hp}");
}
public void ShowText(int index)
{
if (IsDead)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine($"({index}) {Name} \tDead");
Console.ResetColor();
return;
}
Console.WriteLine($"({index}) {Name} \tHP {Hp}");
}
아래는 Monster 클래스의 전체 코드이다.
internal class Monster : IDamagable
{
// 생성 시 할당 이후 변화 없는 변수 name, lv, atk 는 readonly로 선언.
private readonly string _name;
private readonly int _lv;
private readonly int _atk;
public string Name => _name;
public int Lv => _lv;
public int Atk => _atk;
private float _hp;
/// <summary>
/// get 시, Hp 반환
/// set 시, Hp = value 값 및 0 이하 시 Hp = 0, Dead 메서드 호출
/// </summary>
public float Hp
{
get => _hp;
private set
{
_hp = value;
if(Hp <= 0)
{
_hp = 0;
IsDead = true;
}
}
}
public bool IsDead { get; private set; } = false;
private Define.MonsterType _type;
// 타입에 따라 부여할 공격력/체력 (고정값)
private readonly int[] ATKs = [5, 10, 15, 7, 12, 18];
private readonly float[] HPs = [15f, 10f, 5f, 20f, 15f, 10f];
}
이제 몬스터를 인게임 상에서 만들어 주도록한다.
몬스터가 생성되는 시기는 던전에서 전투를 시작했을 때 이다.
- 몬스터가 생성되기 전 미리 정해야 하는 값
- 스폰 되는 몬스터의 수
- 레벨
- 몬스터 타입
몬스터의 수는 Random().Next() 를 사용하여 임의적으로 1~4마리가 스폰되도록 설정하고
int spawnCount 지역변수에 할당한다.
그리고 몬스터들의 정보를 담을 배열 Monster[] monsters 도 만들도록 한다.
만들어진 배열에 새로운 몬스터들을 생성한다. 이 때, 각 개체마다 레벨과 타입이 다르므로
for문 내부에 랜덤 레벨 및 타입을 선언 후 몬스터를 생성한다.
레벨은 임의적으로 lv 1~5 까지 범위로 설정한다.
int spawnCount = new Random().Next(1, 5);
Monster[] monsters = new Monster[spawnCount];
for (int i = 0; i < monsters.Length; i++)
{
int randLv = new Random().Next(1, 6);
int randomType = new Random().Next(0, Enum.GetValues(typeof(MonsterType)).Length);
monsters[i] = new Monster(randLv, (MonsterType)randomType);
}
그 후 Monster 클래스에서 만들어 둔 ShowText 메서드를 이용해 몬스터들의 정보를 콘솔창에 출력 해주도록 하자.
for (int i = 0; i < monsters.Length; i++)
monsters[i].ShowText();
오버로드로 추가한 기능은 공격할 대상 선택 메뉴 진입시에 보여주는 것이다. 다음과 같이 보이게 된다.
for (int i = 0; i < monsters.Length; i++)
monsters[i].ShowText(i + 1);
'스파르타 내배캠' 카테고리의 다른 글
스파르타 내배캠 Unity 3기 15일차 (1) | 2024.01.12 |
---|---|
스파르타 내배캠 Unity 3기 14일차 (0) | 2024.01.11 |
스파르타 내배캠 Unity 3기 12일차 (2) | 2024.01.09 |
스파르타 내배캠 Unity 3기 11일차 (2) | 2024.01.08 |
스파르타 내배캠 Unity 3기 10일차 (0) | 2024.01.05 |