1vdlrwnsv1 님의 블로그
팀프로젝트 textrpg 본문
일단 내 역할은 전투씬 구현이다
게임 상태를 관리하고 플레이어 정보를 저장하며 다른 클래스들이 이를 참조 할 수 있도록 하는 GameState클래스
using TextRPG_Team.Objects;
namespace TextRPG_Team;
public class GameState
{
//씬전환간에 필요한 인스터스들
//Objects
readonly Player _player = new Player("Payer", new Stats(100, 10, 5), 1000);
public Player GetPlayer() => _player;
}
Player객체인 _player를 읽기전용으로 선언하여 플레이어의 상태를 저장. 어떤 변수를 저장하는지 Player클래스를 보면
생성자에 나와있다 초기화시 이름 스탯 골드가 설정된다
GetPlayer()메서드를 통해 게임내 Player객체를 다른 클래스들이 참조 가능하게 해줌
Stats 구조체
플레이어의 주요 스탯을 저장함
MaxHp, Atk, Def, Lv 속성을 통해 각각 최대 체력, 공격력, 방어력, 레벨을 정의.
생성자를 통해 해당 스탯을 초기화할 수 있다.
+ 연산자 오버로드가 구현되어 있어 두 개의 Stats 객체를 더할 수 있음. 이 기능은 아이템이나 효과로 인한 스탯 증가를 계산할 때 유용함
Player클래스
namespace TextRPG_Team.Objects;
public struct Stats // 체,공,방
{
public float MaxHp { get; set; } //최대 체력
public float Atk { get; set; } //공격력
public float Def { get; set; } //방어력
public int Lv { get; set; } //레벨
public Stats(float maxHp, float atk, float def, int lv = 1)
{
MaxHp = maxHp;
Atk = atk;
Def = def;
Lv = lv;
}
// + 연산자 오버로드
public static Stats operator +(Stats s1, Stats s2)
{
return new Stats
{
MaxHp = s1.MaxHp + s2.MaxHp,
Atk = s1.Atk + s2.Atk,
Def = s1.Def + s2.Def
};
}
}
public class Player : ICharacter
{
// 필드
private int _gold;
// 속성
public string Name { get; private set; } //이름
public int Gold //골드 그냥 골드에 값만 대입하시면 자동으로 로그뜹니다.
{
get => _gold;
set
{
if (value < _gold) // 골드가 줄어들 때만 메시지 출력
{
if (value < 0)
TryBuyAction?.Invoke("골드가 부족합니다.", ConsoleColor.Red);
else
TryBuyAction?.Invoke("구매에 성공했습니다.", ConsoleColor.Blue);
}
_gold = value; // 항상 골드 값을 설정
}
}
public float Health { get; private set; } //현재 체력
public float Power => GetStats().Atk; //최종 데미지(추후에 치명타
// 이벤트
public Action<ICharacter, float>? AttackAction { get; set; } //공격 시 동작을 정의하는 Action
public Action<string, ConsoleColor>? TryBuyAction { get; set; } //구매 시도 Action
// 스탯 관련 변수 및 메서드
private Stats PlayerStats { get; set; } //기본 스텟
private Stats AddStats { get; set; } //추가 스텟
public Stats GetStats() => PlayerStats + AddStats; //최종 스텟
// 생성자
public Player(string name, Stats stats, int gold)
{
Name = name;
PlayerStats = stats;
_gold = gold;
Health = PlayerStats.MaxHp;
}
// 메서드
public void Attack(ICharacter target) //공격
{
// Action이 정의돼 있다면 실행
AttackAction?.Invoke(this, Power);
}
public void TakeDamage(float damage) //피해
{
Health = Math.Max(0, Health - damage);
}
public bool IsDead() => Health <= 0f;
public override string ToString() //플레이어 정보
{
return $"Lv.{GetStats().Lv} : {Name} " +
$"HP : {Health} / {GetStats().MaxHp}" + (AddStats.MaxHp > 0 ? $"(+{AddStats.MaxHp})" : "")+
$"공격력 : {GetStats().Atk}" + (AddStats.Atk > 0 ? $"(+{AddStats.Atk})" : "")+
$"방어력 : {GetStats().Def}"+ (AddStats.Def > 0 ? $"(+{AddStats.Def})" : "")+
$"Gold : {Gold} G";
}
}
플레이어 관리. 플레이어의 이름, 스탯, 골드, 체력, 공격력 등을 관리하고, 공격 및 피해를 처리하는 메서드를 포함
Name, Gold, Health, Power(공격력) 등의 속성을 정의.
Gold 속성에는 값이 변경될 때마다 메시지를 출력.
Stats 클래스의 PlayerStats와 AddStats를 통해 최종 스탯을 계산하고 관리.
GetStats() 메서드를 통해 두 스탯을 더한 결과를 반환.
Attack() 메서드는 공격 시 동작을 처리하고, TakeDamage()는 피해를 입을 때 체력을 갱신.
ToString() 메서드는 플레이어의 상태를 출력할 때 사용됨
BattleScene 클래스
namespace TextRPG_Team.Scenes;
using System.Collections.Generic;
using System.Linq;
using TextRPG_Team.Manager;
using TextRPG_Team.Objects;
public enum Turn
{
Player,
Enemy
}
public class BattleScene : IScene
{
static Random random = new Random();
private readonly GameState _gameState;
private List<Monster> battleMonsters = new List<Monster>();
//private Player player = new Player("Chad", "전사", 1, 100, 10);
private EnemySpawner enemySpawner = new EnemySpawner();
private Turn currentTurn = Turn.Player;
private class Monster
{
public string Name { get; }
public int Level { get; }
public float MaxHp { get; }
public float Attack { get; }
public float HP { get; private set; }
public bool IsDead => HP <= 0;
public Monster(string name, float hp, float attack)
{
Name = name;
MaxHp = hp;
HP = hp;
Attack = attack;
}
public void TakeDamage(float damage)
{
HP = Math.Max(0, HP - damage);
}
public override string ToString()
{
string status = IsDead ? "Dead" : $"HP {HP}";
return $"Lv.{Level} {Name} {status}";
}
}
// public class Player
// {
// public string Name { get; }
// public string Class { get; }
// public int Level { get; }
// public int Hp { get; set; }
// public int MaxHp { get; }
// public int Attack { get; }
// public Player(string name, string playerClass, int level, int hp, int attack)
// {
// Name = name;
// Class = playerClass;
// Level = level;
// Hp = hp;
// MaxHp = hp;
// Attack = attack;
// }
public void PlayerInfo()
{
Console.WriteLine("\n[내정보]");
Console.WriteLine($"Lv. {_gameState.GetPlayer().Name}");
Console.WriteLine($"HP {_gameState.GetPlayer().Health}/{_gameState.GetPlayer().GetStats().MaxHp}");
Console.WriteLine($"power {_gameState.GetPlayer().GetStats().Atk}");
}
//}
public BattleScene(GameState gameState)
{
_gameState = gameState;
}
public void Run()
{
Console.Clear();
var enemyList = enemySpawner.GetEnemies();
// {
// new Monster("미니언", 2, 15, 5),
// new Monster("공허충", 3, 10, 9),
// new Monster("대포미니언", 5, 25, 8),
// };
int monsterCount = random.Next(1, 5);
battleMonsters.Clear();
for (int i = 0; i < monsterCount; i++)
{
// battleMonsters.Add(new Monster(
// monsterPool[random.Next(monsterPool.Count)].Name,
// monsterPool[random.Next(monsterPool.Count)].Level,
// monsterPool[random.Next(monsterPool.Count)].MaxHp,
// monsterPool[random.Next(monsterPool.Count)].Attack
// ));
var enemy = enemyList[random.Next(enemyList.Count)];
battleMonsters.Add(new Monster(enemy.Name, enemy.GetStats().MaxHp, enemy.GetStats().Atk));
}
DisplayBattleState();
NextTurn();
}
private void NextTurn()
{
// 모든 몬스터가 죽었는지 확인
if (battleMonsters.All(m => m.IsDead))
{
DisplayBattleResult();
return;
}
if (currentTurn == Turn.Player)
{
currentTurn = Turn.Enemy;
PlayerTurn();
}
else
{
Console.Clear();
currentTurn = Turn.Player;
EnemyTurn();
}
}
private void DisplayBattleState()
{
Utility.ColorWriteLine("\nBattle!", ConsoleColor.Yellow);
for (int i = 0; i < battleMonsters.Count; i++)
{
var monster = battleMonsters[i];
string status = monster.IsDead ? "Dead" : $"HP {monster.HP}";
ConsoleColor textColor = monster.IsDead ? ConsoleColor.DarkGray : ConsoleColor.White;
Utility.ColorWriteLine($"{i + 1} Lv.{monster.Level} {monster.Name} {status}", textColor);
}
PlayerInfo();
}
private void DisplayBattleResult()
{
int deadMonsters = battleMonsters.Count(m => m.IsDead);
Console.Clear();
Utility.ColorWriteLine($"Battle! - Result", ConsoleColor.DarkCyan);
Utility.ColorWriteLine("Victory", ConsoleColor.Green);
Console.WriteLine($"Lv.{_gameState.GetPlayer().GetStats().MaxHp} {_gameState.GetPlayer().Name}");
Console.WriteLine($"HP {_gameState.GetPlayer().GetStats().MaxHp} -> {_gameState.GetPlayer().Health}");
Console.WriteLine($"몬스터 {deadMonsters}마리 잡았음");
Console.WriteLine("\n0. 다음");
while (Console.ReadLine() != "0") { }
}
private void EnemyTurn()
{
Utility.ColorWriteLine("\nEnemy Turn!", ConsoleColor.Red);
float oldHp = _gameState.GetPlayer().Health;
foreach (var monster in battleMonsters)
{
if (monster.IsDead) continue;
float damage = monster.Attack;
_gameState.GetPlayer().TakeDamage(damage);
// 공격 후 출력
Console.WriteLine($"Lv.{monster.Level} {monster.Name} 의 공격! [데미지: {damage}]");
}
// 플레이어 HP가 갱신된 후 출력
Console.WriteLine($"Lv.{_gameState.GetPlayer().GetStats().MaxHp} {_gameState.GetPlayer().Name} HP {oldHp} -> {_gameState.GetPlayer().Health}");
// 플레이어가 쓰러졌다면 전투 종료
if (_gameState.GetPlayer().IsDead())
{
Utility.ColorWriteLine("\n당신은 쓰러졌습니다...", ConsoleColor.Red);
return; // 전투 종료
}
Console.WriteLine("\n0. 다음");
while (Console.ReadLine() != "0") { }
// 전투 상태 출력
DisplayBattleState();
NextTurn();
}
private void PlayerTurn()
{
while (true)
{
Console.WriteLine("0. 취소\n");
Console.Write("대상을 선택해주세요.\n>> ");
if (int.TryParse(Console.ReadLine(), out int choice))
{
if (choice == 0) return;
int index = choice - 1;
if (index >= 0 && index < battleMonsters.Count)
{
Monster target = battleMonsters[index];
if (target.IsDead)
{
Console.WriteLine("잘못된 입력입니다.\n");
continue;
}
AttackMonster(target);
break;
}
}
}
}
private void AttackMonster(Monster target)
{
Console.Clear();
float attackPower = _gameState.GetPlayer().Power;
float oldHP = target.HP;
target.TakeDamage(attackPower);
Console.WriteLine("\nChad 의 공격!");
Console.WriteLine($"Lv.{target.Level} {target.Name} 을(를) 맞췄습니다. [데미지 : {attackPower}]");
Console.Write($"Lv.{target.Level} {target.Name}");
Console.WriteLine($"HP {oldHP} -> {(target.IsDead ? "Dead" : target.HP.ToString())}\n");
Console.WriteLine("0. 다음");
while (Console.ReadLine() != "0") { }
DisplayBattleState();
NextTurn();
}
// private int ApplyAttackVariation(int baseAttack)
// {
// double variation = baseAttack * 0.1;
// int minAttack = (int)Math.Ceiling(baseAttack - variation);
// int maxAttack = (int)Math.Ceiling(baseAttack + variation);
// return random.Next(minAttack, maxAttack + 1);
// }
public IScene? GetNextScene()
{
int input = Utility.GetInput(1, 2);
return input switch
{
1 => this,
2 => new ExampleScene(_gameState),
_ => null
};
}
}
전투 관리. 플레이어와 몬스터 간의 전투를 처리하며, 턴제 전투 시스템을 구현하고 전투의 결과를 출력
battleMonsters 리스트에 몬스터들을 저장하고, enemySpawner는 몬스터를 생성하는 역할
Turn 열거형은 현재 턴이 플레이어 차례인지 몬스터 차례인지를 나타냄
Run() 메서드는 전투를 시작하고, 전투에 참여할 몬스터들을 랜덤으로 생성 몬스터의 정보를 화면에 출력
DisplayBattleState()는 전투 상태(플레이어와 몬스터의 HP 등)를 출력
NextTurn()은 턴을 넘기는 메서드로, 플레이어의 턴과 몬스터의 턴을 교대로 진행
EnemyTurn()과 PlayerTurn()은 각각 몬스터와 플레이어의 턴을 처리함 몬스터는 플레이어에게 피해를 주고, 플레이어는 몬스터를 공격
DisplayBattleResult()는 전투 결과를 출력
플레이어가 몬스터를 공격할 때 AttackMonster() 메서드가 실행되어, 공격력에 맞춰 몬스터의 체력이 감소하고, 결과를 출력
몬스터가 죽었는지 체크하고, 죽지 않았다면 계속 전투를 진행함
모든 몬스터가 죽으면 전투가 종료되고 결과를 출력합니다. 플레이어가 죽으면 전투가 종료됩니다.
BattleScene클래스를 만들었고 GameState객체를 전달받음
이제 몬스터의 값도 똑같이 전달받게 코드 수정하면 된다.