오늘은 전투시스템을 마저 완성하겠다.
전투시스템에 추가적으로 던전 클리어 이후 던전의 층이 올라가고 층이 올라갈 때마다 상대 몬스터들이 강해지는 시스템을 적용할 생각이다.
가능하다면 스킬 시전 기능까지 구현해 볼 생각이다.
먼저 기능을 구현해보기 전에 테스트 코드를 넣어보고 실행해보았다. 이후 몇 가지 출력 오류를 바로잡았다.
Dungeon.Floor 변수를 도입하고, 층별로 등장 가능한 몬스터 타입을 분기했다. public static class Dungeon
{
public static int Floor { get; private set; } = 1;
public static void NextFloor()
{
Floor++;
if (Floor > 10)
{
Console.WriteLine("던전 클리어!");
Console.WriteLine("축하합니다!");
Console.ReadKey();
}
else
{
Console.WriteLine($"던전 {Floor}층으로 이동합니다...");
Thread.Sleep(1000);
Console.Clear();
BattleSystem.Start();
}
}
}
private static Monster[] SpawnMonsters(int Floor)
{
int monsterCount;
if (Floor == 5 || Floor == 10)
monsterCount = 1; // 보스 몬스터
else
monsterCount = GameManager.rd.Next(1, 5);
Monsters[] types;
if (Floor >= 1 && Floor <= 4)
types = new[] { Monsters.고블린, Monsters.홉고블린, Monsters.오크, Monsters.하이오크 };
else if (Floor == 5)
types = new[] { Monsters.해츨링 };
else if (Floor >= 6 && Floor <= 9)
types = new[] { Monsters.와이번, Monsters.워울프, Monsters.만티코어 };
else if (Floor == 10)
types = new[] { Monsters.드래곤 };
else
types = new[] { Monsters.고블린 };
var monsters = new Monster[monsterCount];
for (int i = 0; i < monsterCount; i++)
{
monsters[i] = new Monster(types[GameManager.rd.Next(types.Length)]);
}
return monsters;
}
5층과 10층은 보스 몬스터 한 마리만 나오도록 처리하였다.
몬스터 ID/이름 프로퍼티 추가
public enum Monsters
{
고블린= 1101,
홉고블린= 1102,
오크= 1103,
하이오크= 1104,
해츨링= 1105,
와이번= 1106,
워울프= 1107,
만티코어= 1108,
드래곤= 1109,
}
public class Monster
{
public Monsters MonsterType { get; set; }
public int ID => (int)MonsterType;
public string Name => MonsterType.ToString();
public int monMaxHp { get; set; }
public int monHP { get; private set; }
public int monAtk { get; set; }
public int monGold { get; set; }
public bool IsAlive => monHP > 0;
public Monster(Monsters type)
{
MonsterType = type;
(monMaxHp, monAtk) = type switch
{
Monsters.고블린 => (15, 5),
Monsters.홉고블린 => (20, 7),
Monsters.오크 => (20, 8),
Monsters.하이오크 => (25, 10),
Monsters.해츨링 => (50, 20),
Monsters.와이번 => (30, 13),
Monsters.워울프 => (17, 17),
Monsters.만티코어 => (25, 20),
Monsters.드래곤 => (100, 40),
};
monHP = monMaxHp;
}
public int TakeDamage(int playerAtk, Job job)
{
double damageMultiplier = job is Wizard ? 0.1 : 1.0;
Random random = new Random();
// 0.9 ~ 1.1 사이의 랜덤 배율 생성
double variation = random.NextDouble() * 0.2 + 0.9;
int dmg = (int)Math.Round(playerAtk * variation * damageMultiplier);
monHP = Math.Max(monHP - dmg, 0); // HP가 0보다 작아지지 않도록 조정
return dmg;
}
}
public int TakeDamage(int playerAtk, Job job)
{
double damageMultiplier = job is Wizard ? 0.1 : 1.0;
Random random = new Random();
// 0.9 ~ 1.1 사이의 랜덤 배율 생성
double variation = random.NextDouble() * 0.2 + 0.9;
int dmg = (int)Math.Round(playerAtk * variation * damageMultiplier);
monHP = Math.Max(monHP - dmg, 0); // HP가 0보다 작아지지 않도록 조정
return dmg;
}
private static bool Attack(int damage, Job player, Monster[] monsters)
{
int sel;
while (true)
{
Console.WriteLine("공격할 몬스터를 선택하세요.");
for (int i = 0; i < monsters.Length; i++)
{
var m = monsters[i];
if (m.IsAlive)
Mathod.ChangeFontColor(ColorCode.Red);
else
Mathod.ChangeFontColor(ColorCode.DarkGray);
string status = m.IsAlive ? $"{m.monHP}/{m.monMaxHp}" : "사망";
Console.WriteLine($"{i + 1}. {m.Name} ({status})");
Mathod.ChangeFontColor(ColorCode.None);
}
Console.Write(">> ");
if (!Mathod.CheckInput(out sel))
{
Console.WriteLine("잘못된 입력입니다.");
continue;
}
if (sel < 1 || sel > monsters.Length || !monsters[sel - 1].IsAlive)
{
Console.WriteLine("잘못된 입력입니다.");
continue;
}
break;
}
var target = monsters[sel - 1];
int dmg = target.TakeDamage(damage, player);
Mathod.ChangeFontColor(ColorCode.Green);
Console.WriteLine($"→ {target.Name}에게 {dmg} 데미지!\n (남은 HP {target.monHP}/{target.monMaxHp})\n\n");
Mathod.ChangeFontColor(ColorCode.None);
Thread.Sleep(1000);
return true;
}
private static class Skill
{
public static bool AlphaStrike(int damage, Job player, Monster[] monsters)
{
int Skillcost = 10; //스킬 사용 시 소모되는 MP
if (player.Mp < Skillcost)
{
Console.WriteLine("MP가 부족합니다.");
Thread.Sleep(1000);
return false;
}
//공격력 * 2로 공격
Attack(player.atk*2, player, monsters);
player.Mp -= Skillcost; //스킬 사용 시 MP 소모
return true;
}
public static bool DoubleStrike(int damage, Job player, Monster[] monsters)
{
int Skillcost = 15; //스킬 사용 시 소모되는 MP
if (player.Mp < Skillcost)
{
Console.WriteLine("MP가 부족합니다.");
Thread.Sleep(1000);
return false;
}
//공격력 * 1.5로 2명의 적을 랜덤으로 공격
var Rdtarget = GameManager.rd;
int targetDamage = (int)(damage * 1.5);
for (int i = 0; i < 2; i++)
{
int targetindex;
do
{
targetindex = Rdtarget.Next(0, monsters.Length);
}
while (!monsters[targetindex].IsAlive);
var target = monsters[targetindex];
int dmg = target.TakeDamage(targetDamage, player);
Mathod.ChangeFontColor(ColorCode.Red);
Console.WriteLine($"→ {target.Name}에게 {dmg} 데미지! (남은 HP {target.monHP}/{target.monMaxHp})");
Mathod.ChangeFontColor(ColorCode.None);
Thread.Sleep(1000);
}
player.Mp -= Skillcost; //스킬 사용 시 MP 소모
return true;
}
}
GameService.cs에 있는 메서드를 사용하였다. else
{
Mathod.ChangeFontColor(ColorCode.Red);
Console.WriteLine($"\n{m.MonsterType}의 공격");
Console.WriteLine($"→ {m.monAtk-player.def/2} 데미지!");
player.health -= m.monAtk-player.def/2;//방어력의 절반만큼 데미지 감소
Mathod.ChangeFontColor(ColorCode.Green);
Console.WriteLine($"남은 체력: {player.health}");
Console.WriteLine("");
Thread.Sleep(1000);
Mathod.ChangeFontColor(ColorCode.None);
if (player.health <= 0)
return; // 플레이어가 사망하면 전투 종료
}
Console.WriteLine($"\n{m.MonsterType}의 공격");
Console.WriteLine($"→ {m.monAtk-player.def/2} 데미지!");
player.health -= m.monAtk-player.def/2;//방어력의 절반만큼 데미지 감소
Console.WriteLine($"남은 체력: {player.health}");
Skill.AlphaStrike사용 시 공격력만큼만 데미지를 입히는 버그 수정퀘스트 연동 및 경험치·레벨업 기능 추가, UI 보강
오늘은 게임의 기능들을 구현하느라 정신이 없었지만 어제보다 훨씬 작업이 수월하게 진행되었다.
먼저 작업 흐름이 조금 매끄러워졌다. 미리 어떤 부분을 작업할지 구조를 구상하고, 작은 단위로 나누어 작업하니 불안감이 많이 사라졌다.
또한 협업 경험이 쌓였다. 팀원들이 만들어 놓은 코드를 확인하고, 내 코드에 연결짓는 과정이 자연스러워졌다.
실행 중 문제가 발생하는 일은 아직 막을 수 없지만, '이 부분이 원인이구나'하고 직관하는 능력이 늘어나는 것 같다. 각각의 모듈이 어떤 역할을 하는지가 점점 잘 보이기 시작한다.