어제와 오늘은 C# TRPG 프로젝트를 마무리 하는 데에 집중하였다. 이틀 동안 진행한 내용을 정리하고, 돌아보고자 한다.
MaxHP, Atk 이 두가지 값만 할당해줬는데, MonGold, MonLevel을 추가로 할당해주었다. public Monster(Monsters type)
{
MonsterType = type;
(monMaxHp, monAtk, monGold, monLevel) = type switch
{
Monsters.고블린 => (15, 5, 75, 2),
Monsters.홉고블린 => (20, 7, 140, 4),
Monsters.오크 => (20, 8, 160, 6),
.
.
.
switch 생성자
전통적인 switch를 통한 생성자는 다음과 같다.public Monster(Monsters type) { MonsterType = type; switch (type) { case Monsters.고블린: monMaxHp = 15; monAtk = 5; monGold = 75; monLevel = 2; break; case Monsters.홉고블린: monMaxHp = 20; monAtk = 7; monGold = 140; monLevel = 4; break; case Monsters.오크: monMaxHp = 20; monAtk = 8; monGold = 160; monLevel = 6; break; . . .현재
switch식 문법은 C# 8.0부터 도입된 것으로,switch를 간결하게 쓸 수 있게 해주었다.
한 줄에 여러 값을 그룹으로 할당할 수 있어서, 필드 초기화 코드를 크게 줄여줄 수 있다.
case마다break없이=>로 바로 결과를 반환할 수 있다.
Mp, exp 필드를 추가하였다.monLevel값의 합만큼 player.exp가 상승하도록 처리하였다. int xpGain = monsters.Sum(m => m.monLevel);
player.exp += xpGain;
Console.WriteLine($"획득 경험치: {xpGain}");
exp값에 따라 플레이어의 레벨이 올라가는 기능을 구현해주셨다.Atk, Def도 상승하도록 처리해주셨다.quest.CheckCondition와 연결하여 퀘스트 조건을 확인하도록 처리하였다. if (monHP <= 0)
{
GameManager.quest.CheckCondition(Name);
}
BattleSystems에서 구현하던 스킬 기능을 GameCharacter에서 처리하였다.SelectMonster 메서드를 통해 중복코드 없이 처리하도록 만들었다.atk Mp critRate critDamage dodgeRate SkillCost SkillName health 등의 필드를 통해 전투 시스템에서 사용되는 수치를 조정한다.Attack, Skill1, Skill2Attack: TakeDamage에서 받은 데미지를 그대로 반환.Skill1: 단일 대상 스킬. Skill1Cost만큼의 Mp를 소모한다.Skill2: 범위 스킬. 살아있는 몬스터 중 랜덤 타겟팅한다. Skill2Cost만큼의 Mp를 소모한다.var rnd = GameManager.rd;double variation = rnd.NextDouble() * 0.2 + 0.9; // 0.9 ~ 1.1 사이의 데미지배율 생성int dmg = (int)Math.Round(playerAtk * variation); // 데미지 변동률 적용if (rnd.NextDouble()<job.dodgeRate)
return 0;dodgeRate(0.xx)보다 작으면 0을 반환한다.bool isCritical = rnd.NextDouble() < job.critRate;
else if (isCritical)
dmg = (int)(dmg * job.critDamage);critRate(0.xx)보다 작으면 크리티컬이 발동된다. 크리티컬 데미지는 critDamage만큼의 배율을 가진다.monHP = Math.Max(monHP - dmg, 0);if (monHP == 0)
GameManager.quest.CheckCondition(Name);플레이어는 공격, 스킬, 탈출 중 하나를 선택.
if (sel == 1) // 공격 else if (sel == 2) // 스킬else if (sel == 0) // 탈출Attack 또는 Skill로 호출.Attack
int idx = GameManager.SelectMonster(monsters);// 공격할 몬스터 선택
var target = monsters[idx]; // 선택한 몬스터
int dmg = player.Attack(target); // 몬스터에게 공격
Skill 예시
else if (skill == 2)
{
if (player.Mp < player.Skill2Cost || player.health < player.Skill2HpCost) // 스킬 사용 시 마나 또는 체력 확인
{
Console.WriteLine("MP 또는 체력이 부족합니다.");
Thread.Sleep(1000);
continue;
}
player.Skill2(monsters); // 스킬 사용
return true; // 스킬 사용 후 턴 종료
}
Job 클래스에 Archer 클래스를 상속시킨다.Attack, Skill1, Skill2 메서드는 가상 메서드로 만들어 하위 클래스에서 이 동작을 자유롭게 override할 수 있게 열어놓았다.Archer클래스는 Job이 갖고 있는 모든 필드와 메서드를 물려받는다.Attack, Skill1, Skill2 메서드를 override 키워드로 재정의해 궁수만의 공격/스킬 로직을 구현한다.//광전사의 스킬 코스트 ex) Skill1
public override int Skill1(Monster target)
{
health -= Skill1HpCost;
int dmg = target.TakeDamage(atk * 2, this);
Mathod.FontColorOnce($"{Skill1Name} 사용으로 HP{Skill1HpCost} 소모", ColorCode.Red);
Thread.Sleep(1000);
return dmg;
}
//광전사와 다른 직업의 전투 보상차이
Console.WriteLine("전투에서 승리했습니다!");
if (player.chad == "광전사")
{
player.health += 5 * monsters.Length;
Mathod.FontColorOnce($"체력이 {5 * monsters.Length}만큼 회복됩니다.\n", ColorCode.Red);
}
else
{
player.Mp += 10;
Mathod.FontColorOnce("마나가 10만큼 회복됩니다.\n", ColorCode.Blue);
}
public class Job
{
.
.
.
public double critRate;
public double critDamage;
public double dodgeRate;
.
.
.
}
// 전사 클래스 예시
public class Warrior : Job
{
.
.
.
critRate = 0.1; // 치명타 확률
critDamage = 1.5; // 치명타 배율
dodgeRate = 0.1; // 회피율
.
.
.
TakeDamage 메서드에서 받아 치명타/회피 로직을 작성하였다. public int TakeDamage(int playerAtk, Job job)
{
var rnd = GameManager.rd;
double variation = rnd.NextDouble() * 0.2 + 0.9; // 0.9 ~ 1.1 사이의 데미지 랜덤 배율 생성
int dmg = (int)Math.Round(playerAtk * variation); // 데미지 변동률 적용
bool isCritical = rnd.NextDouble() < job.critRate; // 크리티컬 확률 체크
if (rnd.NextDouble()<job.dodgeRate) // 회피일 때
{
Mathod.FontColorOnce($"→ {Name}이(가) 공격을 회피했습니다!\n", ColorCode.Cyan);
Thread.Sleep(1000);
return 0;
}
else if (isCritical) // 크리티컬일 경우
{
dmg = (int)(dmg * job.critDamage);
Mathod.FontColorOnce($"→ {Name}에게 ({dmg}) 크리티컬 데미지!", ColorCode.Yellow);
}
else // 일반 데미지
{
Mathod.FontColorOnce($"→ {Name}에게 {dmg} 데미지!" , ColorCode.Red);
}
monHP = Math.Max(monHP - dmg, 0); // 몬스터 체력 감소
Mathod.FontColorOnce($"남은 체력: {monHP}/{monMaxHp}\n", ColorCode.Red);
Thread.Sleep(1000);
if (monHP == 0)
GameManager.quest.CheckCondition(Name); // 퀘스트 조건 체크
return dmg; // 데미지 반환
}
Attack 메서드는 특별히 수정하지 않아도 기능은 의도대로 구현된다. int dmg = m.monAtk - player.def / 2;
if (dmg < 0)
dmg = 0;
던전 층수가 저장데이터에 저장되지 않았다. DungeonFloor 변수를 GameManager가 아닌 player에 넣어주었다. 이후 player 저장 데이터에 floor를 추가하였다. 마나, 경험치도 같이 처리해주었다.
광전사는 체력을 코스트로 하는데 Mp가 없어도 스킬을 쓰지 못하는 오류가 있었다. 광전사의 스킬 코스트를 SkillHpCost로 따로 지정해주고 출력로직을 수정해주었다.
//기존
if (player.Mp < player.Skill1Cost)
{
Console.WriteLine("MP가 부족합니다.");
}
//현재
if (player.Mp < player.Skill1Cost || player.health < player.Skill1HpCost)
{
Console.WriteLine("MP 또는 체력이 부족합니다.");
}