전체 코드
using System.ComponentModel;
using System.Numerics;
using System.Threading;
namespace CSharp
{
class Program
{
static void Main(string[] args)
{
Game game = new Game();
while (true)
{
game.Process();
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CSharp
{
public enum GameMode
{
None,
Lobby,
Town,
Field
}
class Game
{
private GameMode mode = GameMode.Lobby;
private Player player = null;
private Monster monster = null;
private Random rand = new Random();
public void Process()
{
switch (mode)
{
case GameMode.Lobby:
ProcessLobby();
break;
case GameMode.Town:
ProcessTown();
break;
case GameMode.Field:
ProcessField();
break;
}
}
private void ProcessLobby()
{
Console.WriteLine("직업을 선택하세요");
Console.WriteLine("[1] 기사");
Console.WriteLine("[2] 궁수");
Console.WriteLine("[3] 법사");
string input = Console.ReadLine();
switch (input)
{
case "1":
player = new Knight();
mode = GameMode.Town;
break;
case "2":
player = new Archer();
mode = GameMode.Town;
break;
case "3":
player = new Mage();
mode = GameMode.Town;
break;
}
}
private void ProcessTown()
{
Console.WriteLine("마을에 입장 했습니다.");
Console.WriteLine("[1] 필드로 간다.");
Console.WriteLine("[2] 로비로 돌아가기.");
string input = Console.ReadLine();
switch (input)
{
case "1":
mode = GameMode.Field;
break;
case "2":
mode = GameMode.Lobby;
break;
}
}
private void ProcessField()
{
Console.WriteLine("필드에 입장 했습니다.");
Console.WriteLine("[1] 전투 모드 돌입");
Console.WriteLine("[2] 일정 확률로 마을로 도망");
CreateRandomMonster();
string input = Console.ReadLine();
switch (input)
{
case "1":
ProcessFight();
break;
case "2":
TryEscape();
break;
}
}
private void CreateRandomMonster()
{
int randValue = rand.Next(0, 3);
switch (randValue)
{
case 0:
monster = new Slime();
Console.WriteLine("슬라임이 스폰 되었습니다!");
break;
case 1:
monster = new Orc();
Console.WriteLine("오크가 스폰 되었습니다!");
break;
case 2:
monster = new Skeleton();
Console.WriteLine("스켈레톤이 스폰 되었습니다!");
break;
}
}
private void ProcessFight()
{
while (true)
{
int damage = player.GetAttack();
monster.OnDamaged(damage);
if (monster.IsDead())
{
Console.WriteLine("승리했습니다!");
Console.WriteLine($"남은 체력 : {player.GetHP()}");
break;
}
damage = monster.GetAttack();
player.OnDamaged(damage);
if (player.IsDead())
{
Console.WriteLine("패배했습니다!");
mode = GameMode.Lobby;
break;
}
}
}
private void TryEscape()
{
int randValue = rand.Next(0, 101);
if (randValue <= 33)
mode = GameMode.Town;
else
ProcessFight();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CSharp
{
public enum CreatureType
{
None,
Player = 1,
Monster = 2,
}
class Creature
{
CreatureType type;
protected int hp = 0;
protected int attack = 0;
protected Creature(CreatureType type)
{
this.type = type;
}
public void SetInfo(int hp, int attack)
{
this.hp = hp;
this.attack = attack;
}
public int GetHP() { return hp; }
public int GetAttack() { return attack; }
public bool IsDead() { return hp <= 0; }
public void OnDamaged(int damage)
{
hp -= damage;
if (hp < 0)
{
hp = 0;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CSharp
{
public enum PlayerType
{
None = 0,
Knight = 1,
Archer = 2,
Mage = 3
}
class Player : Creature
{
protected PlayerType type = PlayerType.None;
public PlayerType GetPlayerType() { return type; }
protected Player(PlayerType type) :base(CreatureType.Player)
{
this.type = type;
}
}
class Knight : Player
{
public Knight() : base(PlayerType.Knight)
{
SetInfo(100, 10);
}
}
class Archer : Player
{
public Archer() : base(PlayerType.Archer)
{
SetInfo(75, 12);
}
}
class Mage : Player
{
public Mage() : base(PlayerType.Mage)
{
SetInfo(50, 15);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CSharp
{
public enum MonsterType
{
None = 0,
Slime = 1,
Orc = 2,
Skeleton = 3
}
class Monster : Creature
{
protected MonsterType type = MonsterType.None;
protected Monster(MonsterType type) : base(CreatureType.Monster)
{
this.type = type;
}
public MonsterType GetMonsterType() { return type; }
}
class Slime : Monster
{
public Slime() : base(MonsterType.Slime)
{
SetInfo(10, 1);
}
}
class Orc : Monster
{
public Orc() : base(MonsterType.Orc)
{
SetInfo(20, 2);
}
}
class Skeleton : Monster
{
public Skeleton() : base(MonsterType.Skeleton)
{
SetInfo(15, 5);
}
}
}
아주 좋은 마무리네요! 이 TextRPG 프로젝트는 객체지향 프로그래밍의 핵심 개념을 한 번에 학습할 수 있는 대표적인 예제입니다.
아래는 전체 코드를 철저하게 분석하고, 각 클래스와 함수의 역할, 설계 의도까지 정리한 완벽 분석 자료입니다. 블로그에 그대로 올려도 좋을 정도로 구성해 드릴게요.
📌 프로젝트 개요
✔️ 프로젝트 목표
- 직업 선택, 마을/필드 이동, 몬스터 전투 등 Text 기반 RPG 구현
- 객체지향 문법의 핵심 (상속, 다형성, 캡슐화) 실습
- C#의 기본 문법부터 클래스 설계 방법까지 종합 학습
✔️ 프로젝트 구조
Program : 메인 진입점 (Game 클래스 실행)
Game : 게임 전체의 흐름 담당 (상태 머신 역할)
Creature : 생명체의 기본 속성 및 동작 정의 (공통 부모 클래스)
Player : 플레이어 기본 클래스 (Knight/Archer/Mage 파생)
Monster : 몬스터 기본 클래스 (Slime/Orc/Skeleton 파생)
1️⃣ Program 클래스 (진입점)
class Program
{
static void Main(string[] args)
{
Game game = new Game();
while (true)
{
game.Process();
}
}
}
💡 핵심 정리
- 게임 객체 생성 후 무한 루프로 게임 진행
Process()는 게임의 현재 상태에 맞는 처리 실행 (Lobby/Town/Field)
2️⃣ Game 클래스 (상태 머신)
💡 상태 관리
GameMode 열거형으로 상태 표현
- 각 상태마다 처리 함수 존재 (
ProcessLobby, ProcessTown, ProcessField)
💡 상태 전환 흐름
| 상태 | 설명 | 전환 조건 |
|---|
| Lobby | 직업 선택 | 직업 선택 완료 시 Town으로 이동 |
| Town | 마을에서 행동 선택 | 필드 이동 시 Field로, 로비 복귀 시 Lobby로 |
| Field | 몬스터 생성 및 전투/도망 선택 | 전투 승리/패배 시 Lobby로 |
💡 주요 메서드 분석
ProcessLobby() - 직업 선택
player = new Knight();
- Knight/Archer/Mage 인스턴스 생성 (Player 포인터로 관리)
- OOP의 다형성 활용: player는 부모 타입이지만 실제 객체는 직업별로 다름
ProcessField() - 몬스터 생성 및 전투/도망 처리
CreateRandomMonster();
- 랜덤 몬스터 생성 (Slime/Orc/Skeleton 중 하나)
ProcessFight() - 전투 처리
while (true) { ... }
- 플레이어와 몬스터가 번갈아가며 공격
- 서로의
OnDamaged() 호출로 체력 감소
- 사망 여부는
IsDead()로 판단
- 전투 승패에 따라 다음 상태 결정
3️⃣ Creature 클래스 (공통 부모)
class Creature
{
protected int hp = 0;
protected int attack = 0;
...
}
💡 핵심 역할
- 플레이어/몬스터 공통 속성 (체력, 공격력) 관리
- 공통 동작 (OnDamaged, IsDead 등) 제공
- 상속을 통한 코드 중복 제거
4️⃣ Player 클래스 (플레이어 기본)
class Player : Creature
{
protected PlayerType type = PlayerType.None;
...
}
💡 플레이어 타입 정의
PlayerType 열거형으로 직업 구분
- 생성자에서 초기 스탯 설정 (
SetInfo)
| 직업 | 체력 | 공격력 |
|---|
| 기사 | 100 | 10 |
| 궁수 | 75 | 12 |
| 법사 | 50 | 15 |
💡 직업별 클래스 상속 구조
| 클래스 | 상속 관계 |
|---|
| Knight | Player 상속 |
| Archer | Player 상속 |
| Mage | Player 상속 |
- 공통 기능은
Player에 구현
- 직업별 스탯만 다르게 초기화
5️⃣ Monster 클래스 (몬스터 기본)
class Monster : Creature
{
protected MonsterType type = MonsterType.None;
...
}
💡 몬스터 타입 정의
MonsterType 열거형으로 종류 구분
- 생성자에서 초기 스탯 설정 (
SetInfo)
| 몬스터 | 체력 | 공격력 |
|---|
| 슬라임 | 10 | 1 |
| 오크 | 20 | 2 |
| 스켈레톤 | 15 | 5 |
💡 몬스터별 클래스 상속 구조
| 클래스 | 상속 관계 |
|---|
| Slime | Monster 상속 |
| Orc | Monster 상속 |
| Skeleton | Monster 상속 |
- 공통 기능은
Monster에 구현
- 몬스터별 스탯만 다르게 초기화
🔗 전체 클래스 관계 다이어그램
Creature (공통 부모)
├── Player (플레이어 부모)
│ ├── Knight
│ ├── Archer
│ └── Mage
│
└── Monster (몬스터 부모)
├── Slime
├── Orc
└── Skeleton
- Creature : 기본 능력치 제공
- Player/Monster : 공통 로직 확장 및 타입별 특화
💻 핵심 문법 포인트
| 개념 | 설명 |
|---|
| 클래스 상속 | Creature를 공통 부모로 설정해 상속 구조 구성 |
| 다형성 | Player, Monster를 부모 포인터로 관리해 직업/몬스터 교체 가능 |
| 캡슐화 | 체력/공격력은 protected로 외부 노출 최소화 |
| 상태 머신 | GameMode로 현재 상태 관리 및 상태별 로직 분리 |
| 열거형 | PlayerType, MonsterType, GameMode로 상태 및 타입 표현 |