C# 머드 게임 프로젝트 (2) - 구조 변경, 기능 추가
지난 번에는 대충 어떤 식으로 콘솔을 입력하고 머드 게임을 구성하는지 테스트 해보던 단계라 기능이 분리되어 있지 않았다. 그래서 이번엔 클래스를 조금 더 잘게 분리한 뒤 몇 가지 기능을 추가하였다.
먼저 GameCore의 코드이다.
using C_Study;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace C_Study
{
public enum LevelType
{
Start,
Setting,
Menu,
Play,
}
public class GameCore
{
public void Start()
{
_player = new Player();
_levelType = LevelType.Start;
_levels = new Dictionary<LevelType, GameLevel>();
_levels.Add(LevelType.Start, new StartLevel(this));
_levels.Add(LevelType.Setting, new SettingLevel(this));
_levels.Add(LevelType.Menu, new MenuLevel(this));
}
public void Update()
{
while (_player != null)
{
_levels[_levelType].Update();
}
}
public void End()
{
}
public LevelType CurrentLevel
{
set { _levelType = value; }
}
public Player CurrentPlayer
{
get { return _player; }
}
private Player _player;
private LevelType _levelType;
private Dictionary<LevelType, GameLevel> _levels;
}
}
지난번엔 GameCore 내부에 모든 기능을 구현해서 업데이트 함수를 돌리고 있었지만, Level관련 인스턴스를 따로 만들어서 Dictionary 자료구조에 삽입하였고 GameCore는 update함수만 호출하도록 하였다.
각 레벨은 생성자에서 GameCore를 인자로 받으면, 해당 Core를 자료구조에 보유하도록 하였다.
Level에서 Core의 옵션을 변경하면서 레벨을 왔다갔다 하기 위해서이다.
모든 Level클래스는 GameLevel이라는 추상 클래스를 상속받도록 구현하였다.
아래는 GameLevel 클래스이다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace C_Study
{
public abstract class GameLevel
{
protected GameLevel() { }
public abstract void Update();
protected GameCore _parentCore;
}
}
아래는 이를 상속받은 클래스들이다.
시작 화면을 담당하는 StartLevel.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace C_Study
{
public class StartLevel : GameLevel
{
public StartLevel(GameCore parrentCore)
{
_parentCore = parrentCore;
}
public override void Update()
{
Console.Clear();
Console.WriteLine("*************************************************");
Console.WriteLine("*************************************************");
Console.Write("*****************");
DevFunctions.WriteColored("전사의 모험 RPG", ConsoleColor.Yellow);
Console.WriteLine("*****************");
Console.Write("******************");
DevFunctions.WriteColored("제작자 오의현", ConsoleColor.Yellow);
Console.WriteLine("******************");
Console.WriteLine("*************************************************");
Console.WriteLine("*************************************************");
Console.WriteLine();
Console.WriteLine("모험을 시작하시겠습니까?");
DevFunctions.WriteLineColored("1 : YES", ConsoleColor.Blue);
DevFunctions.WriteLineColored("2 : NO", ConsoleColor.Red);
string input = Console.ReadLine();
if (DevFunctions.IsNumeric(input) == false)
{
return;
}
int toInt = int.Parse(input);
switch (toInt)
{
case 1:
_parentCore.CurrentLevel = LevelType.Setting;
break;
case 2:
Environment.Exit(0);
break;
}
}
}
}
캐릭터 선택을 담당하는 SettingLevel
(아직은 무기 선택만 있지만, 나중에는 뭐 여러가지 추가할 수도 있다. 안할수도 있고..)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace C_Study
{
public class SettingLevel : GameLevel
{
public SettingLevel(GameCore parrentCore)
{
_parentCore = parrentCore;
}
public override void Update()
{
WeaponSelect();
WeaponFixOrReselect();
}
private void WeaponSelect()
{
while (true)
{
Console.Clear();
Console.WriteLine("-------------------------------------------------");
Console.WriteLine("-------------------------------------------------");
Console.WriteLine("----------------캐릭터 설정 단계-----------------");
Console.WriteLine("-------------------------------------------------");
Console.WriteLine("-------------------------------------------------");
Console.WriteLine();
DevFunctions.WriteLineColored("무기 선택 단계입니다.", ConsoleColor.Cyan);
Console.WriteLine();
Console.WriteLine("검은 기본 공격력이 낮지만 크리티컬 확률이 가장 높습니다.");
Console.WriteLine("공격시 일정 확률로 출혈을 발생시키기도 합니다.");
Console.WriteLine();
Console.WriteLine("창은 적당한 공격력과 크리티컬 확률을 보유하고 있습니다.");
Console.WriteLine("피격시 일정 확률로 적의 공격을 회피합니다.");
Console.WriteLine();
Console.WriteLine("망치는 높은 공격력을 보유하고 있지만 크리티컬 확률이 낮습니다.");
Console.WriteLine("공격시 일정 확률로 적을 기절 상태로 만듭니다.");
Console.WriteLine();
Console.WriteLine("어떤 무기를 선택하시겠습니까?");
DevFunctions.WriteColored("1.오래된 검 ", ConsoleColor.Blue);
DevFunctions.WriteColored("2.오래된 창 ", ConsoleColor.Red);
DevFunctions.WriteColored("3.오래된 망치", ConsoleColor.Green);
Console.WriteLine();
string input = Console.ReadLine();
if (DevFunctions.IsNumeric(input) == false)
{
continue;
}
if (input.Length > 1)
{
continue;
}
int toInt = int.Parse(input);
switch (toInt)
{
case 1:
_parentCore.CurrentPlayer.EquipedWeapon = new OldSword();
break;
case 2:
_parentCore.CurrentPlayer.EquipedWeapon = new OldSpear();
break;
case 3:
_parentCore.CurrentPlayer.EquipedWeapon = new OldHammer();
break;
}
break;
}
}
private void WeaponFixOrReselect()
{
while (true)
{
Console.Clear();
Console.WriteLine("-------------------------------------------------");
Console.WriteLine("-------------------------------------------------");
Console.WriteLine("----------------캐릭터 설정 단계-----------------");
Console.WriteLine("-------------------------------------------------");
Console.WriteLine("-------------------------------------------------");
Console.WriteLine();
Console.Write("선택하신 무기는 ");
DevFunctions.WriteColored(_parentCore.CurrentPlayer.EquipedWeapon.WPName, ConsoleColor.Cyan);
Console.Write("입니다.");
Console.WriteLine();
Console.WriteLine("선택하신 무기로 게임을 시작하시겠습니까?");
DevFunctions.WriteLineColored("1. 네", ConsoleColor.Blue);
DevFunctions.WriteLineColored("2. 다시 선택할래요", ConsoleColor.Red);
string input = Console.ReadLine();
if (DevFunctions.IsNumeric(input) == false)
{
continue;
}
if (input.Length > 1)
{
continue;
}
int toInt = int.Parse(input);
if(toInt != 1 && toInt != 2)
{
continue;
}
switch (toInt)
{
case 1:
_parentCore.CurrentLevel = LevelType.Menu;
break;
case 2:
_parentCore.CurrentLevel = LevelType.Setting;
break;
}
break;
}
}
}
}
아래는 인벤토리 보기, 캐릭터 스탯 보기, 전투하기 등의 메뉴를 선택하는 MenuLevel이다.
_parentCore에서 멤버변수를 사용하는 코드가 너무 길어서, 별도의 get를 따로 만들어야 할 듯 하다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace C_Study
{
public class MenuLevel : GameLevel
{
private delegate void UpdateFunc();
private delegate void SelectFunc();
public MenuLevel(GameCore parrentCore)
{
_parentCore = parrentCore;
selectfunc = new SelectFunc(SelectMenu);
}
public override void Update()
{
Console.Clear();
if (updatefunc != null)
{
updatefunc();
}
if (selectfunc != null)
{
selectfunc();
}
}
private void WriteStatus()
{
DevFunctions.WriteLineColored("착용중인 무기 정보", ConsoleColor.DarkCyan);
Console.WriteLine("이름 : {0} ", _parentCore.CurrentPlayer.EquipedWeapon.WPName);
Console.WriteLine("공격력 : {0} ", _parentCore.CurrentPlayer.EquipedWeapon.AttPower);
Console.WriteLine("크리티컬 확률 : {0}% ", _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb);
Console.WriteLine();
DevFunctions.WriteLineColored("플레이어 스탯 정보", ConsoleColor.DarkCyan);
Console.WriteLine("레벨 : {0} ", _parentCore.CurrentPlayer.Level);
Console.WriteLine("잔여 스탯 포인트 : {0} ", _parentCore.CurrentPlayer.StatPoint);
Console.WriteLine("공격력 : {0} ({1} + {2}) ", _parentCore.CurrentPlayer.AttPower + _parentCore.CurrentPlayer.EquipedWeapon.AttPower, _parentCore.CurrentPlayer.AttPower, _parentCore.CurrentPlayer.EquipedWeapon.AttPower);
Console.WriteLine("크리티컬 확률 : {0} ({1} + {2})% ", _parentCore.CurrentPlayer.CriticalProb + _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb, _parentCore.CurrentPlayer.CriticalProb, _parentCore.CurrentPlayer.EquipedWeapon.CriticalProb);
Console.WriteLine("크리티컬 데미지 : {0}% ", _parentCore.CurrentPlayer.CriticalPower);
Console.Write("무기 숙련도 : {0} ", _parentCore.CurrentPlayer.WeaponSkilled);
DevFunctions.WriteLineColored("*무기 숙련도가 높을수록 출혈, 회피, 기절 확률이 증가합니다.", ConsoleColor.DarkRed);
Console.WriteLine("방어력 : {0} ", _parentCore.CurrentPlayer.DefPower);
Console.WriteLine("체력 : {0} / {1} ", _parentCore.CurrentPlayer.CurrentHP, _parentCore.CurrentPlayer.MaxHP);
Console.WriteLine("경험치 : {0} / {1} ", _parentCore.CurrentPlayer.CurrentEXP, _parentCore.CurrentPlayer.MaxEXP);
Console.WriteLine();
}
private void WriteInventory()
{
DevFunctions.WriteLineColored("보유 아이템 정보", ConsoleColor.DarkCyan);
Console.WriteLine("아이템 개수 : {0} ", _parentCore.CurrentPlayer.Inventory.Count);
Console.WriteLine();
}
private void SelectMenu()
{
while (true)
{
Console.WriteLine("1. 캐릭터 정보를 확인한다.");
Console.WriteLine("2. 인벤토리를 확인한다.");
Console.WriteLine("3. 전투를 시작한다.");
Console.WriteLine("4. 게임을 종료한다.");
string input = Console.ReadLine();
if (DevFunctions.IsNumeric(input) == false)
{
continue;
}
if (input.Length > 1)
{
continue;
}
int toInt = int.Parse(input);
switch (toInt)
{
case 1:
updatefunc = new UpdateFunc(WriteStatus);
break;
case 2:
updatefunc = new UpdateFunc(WriteInventory);
break;
case 3:
updatefunc = null;
selectfunc = new SelectFunc(SelectMonster);
break;
}
break;
}
}
private void SelectMonster()
{
while (true)
{
DevFunctions.WriteLineColored("전투하고 싶은 몬스터를 선택하세요.", ConsoleColor.DarkCyan);
Console.WriteLine();
Console.WriteLine("1. 고블린 (적정 레벨 : 1)");
Console.WriteLine("2. 오우거 (적정 레벨 : 5)");
Console.WriteLine("3. 드래곤 (적정 레벨 : 10)");
Console.WriteLine("4. 이전으로 돌아간다.");
string input = Console.ReadLine();
if (DevFunctions.IsNumeric(input) == false)
{
continue;
}
if (input.Length > 1)
{
continue;
}
int toInt = int.Parse(input);
switch (toInt)
{
case 4:
selectfunc = new SelectFunc(SelectMenu);
break;
}
break;
}
}
private UpdateFunc updatefunc;
private SelectFunc selectfunc;
}
}
레벨은 일단 여기까지 구현하였고, Player 클래스 내부에도 몇가지 속성을 추가하였다.
아래는 Player 클래스이다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;
namespace C_Study
{
public class Player
{
public Player()
{
_attPower = 10;
_defPower = 10;
_maxHP = 100;
_currentHP = 100;
_maxEXP = 100;
_currentEXP = 0;
_criticalPower = 150.0f;
_criticalProb = 5.0f;
_level = 1;
_statPoint = 0;
_weaponSkilled = 1;
_inventory = new List<Item>();
}
public Weapon EquipedWeapon
{
get { return _equipedWeapon; }
set { _equipedWeapon = value; }
}
public int AttPower
{
get { return _attPower; }
}
public int DefPower
{
get { return _defPower; }
}
public int Level
{
get { return _level; }
}
public int StatPoint
{
get { return _statPoint; }
}
public int WeaponSkilled
{
get { return _weaponSkilled; }
}
public int MaxHP
{
get { return _maxHP; }
}
public int CurrentHP
{
get { return _currentHP; }
}
public int MaxEXP
{
get { return _maxEXP; }
}
public int CurrentEXP
{
get { return _currentEXP; }
}
public float CriticalPower
{
get { return _criticalPower; }
}
public float CriticalProb
{
get { return _criticalProb; }
}
public List<Item> Inventory
{
get { return _inventory; }
}
private Weapon _equipedWeapon;
private int _attPower;
private int _defPower;
private float _criticalPower;
private float _criticalProb;
private int _level;
private int _statPoint;
private int _weaponSkilled;
private int _maxHP;
private int _currentHP;
private int _maxEXP;
private int _currentEXP;
List<Item> _inventory;
}
}
코드를 보면 멤버변수에 인벤토리가 있는데, item 클래스를 만들어서 이를 상속받은 아이템들을 저장할 것이다.
Item클래스는 아래와 같다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace C_Study
{
public class Item
{
protected Item()
{
}
public int AttPower
{
get { return _attPower; }
protected set { _attPower = value; }
}
public int DefPower
{
get { return _defPower; }
protected set { _defPower = value; }
}
public int WeaponSkilled
{
get { return _weaponSkilled; }
protected set { _weaponSkilled = value; }
}
public int HealHP
{
get { return _healHP; }
protected set { _healHP = value; }
}
public float CriticalPower
{
get { return _criticalPower; }
protected set { _criticalPower = value; }
}
public float CriticalProb
{
get { return _criticalProb; }
protected set { _criticalProb = value; }
}
public int RemainTurn
{
get { return _remainTurn; }
protected set { _remainTurn = value; }
}
private int _attPower;
private int _defPower;
private float _criticalPower;
private float _criticalProb;
private int _weaponSkilled;
private int _healHP;
private int _remainTurn;
}
public class Apple : Item
{
public Apple()
{
HealHP = 50;
}
}
}
소비 아이템만 만들 것이기 때문에, 이 아이템으로 인해 상승될 수 있는 옵션들을 저장하였다.
무기 클래스도 조금 개선하였다.
검 종류에는 출혈 확률을 추가하였고, 창 종류에는 회피 확률, 망치 종류에는 기절 확률을 추가하였다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
namespace C_Study
{
public enum WeaponType
{
None,
Sword,
Spear,
Hammer,
}
public class Weapon
{
protected Weapon()
{
}
public WeaponType WPType
{
get { return _wpType; }
protected set { _wpType = value; }
}
public string WPName
{
get { return _wpName; }
protected set { _wpName = value; }
}
public int AttPower
{
get { return _attPower; }
protected set { _attPower = value; }
}
public float CriticalProb
{
get { return _criticalProb; }
protected set { _criticalProb = value; }
}
private int _attPower;
private float _criticalProb;
private string _wpName;
private WeaponType _wpType;
}
public class Sword : Weapon
{
protected Sword()
{
WPType = WeaponType.Sword;
CriticalProb = 25.0f;
}
protected float BleedingProb
{
get { return _bleedingProb; }
set { _bleedingProb = value; }
}
private float _bleedingProb;
}
public class Spear : Weapon
{
protected Spear()
{
WPType = WeaponType.Spear;
CriticalProb = 15.0f;
}
protected float EvadingProb
{
get { return _evadingProb; }
set { _evadingProb = value; }
}
private float _evadingProb;
}
public class Hammer : Weapon
{
protected Hammer()
{
WPType = WeaponType.Hammer;
CriticalProb = 5.0f;
}
protected float StunProb
{
get { return _stunProb; }
set { _stunProb = value; }
}
private float _stunProb;
}
public class OldSword : Sword
{
public OldSword()
{
WPName = "오래된 검";
AttPower = 10;
BleedingProb = 5.0f;
}
}
public class OldSpear : Spear
{
public OldSpear()
{
WPName = "오래된 창";
AttPower = 15;
EvadingProb = 10.0f;
}
}
public class OldHammer : Hammer
{
public OldHammer()
{
WPName = "오래된 망치";
AttPower = 30;
StunProb = 5.0f;
}
}
}
아래는 지금까지 구현된 것을 영상으로 찍은 것이다.
C++의 문법에 점점 익숙해지는중..