오늘 배운 내용
-
팀 과제 기능 구현 - 스킬
-
팀 과제 기능 구현 - 콘솔 UI 수정
팀 과제 기능 구현 - 스킬
팀 과제 스킬 기능을 구현했다. 생각보다 구현하는데 시간이 오래걸렸는데
뭔가 스킬을 만든다고 생각하니까 재미는 있었다.
먼저 스킬 클래스를 구현했다.
상위 클래스가 될 Skill 클래스는 abstract로 정의해서 Use() 메서드를 abstract로 정의하였고,
앞으로 이 Skill 클래스를 상속받은 모든 스킬들은 Use()를 사용해서 동작하도록 만들었는데
Use() 메서드들은 타겟과 공격력을 받아서 타겟들의 Attacked() 메서드를 호출해 데미지를 전달하는 역할을 했다.
public enum SkillType
{
Target,
Random,
}
[Serializable]
public abstract class Skill
{
public string name;
public string skillInfo;
public int mp;
public int targetCount;
public SkillType type;
public Skill(string name, string skillInfo, int mp, int targetCount, SkillType type)
{
this.name = name;
this.skillInfo = skillInfo;
this.mp = mp;
this.targetCount = targetCount;
this.type = type;
}
// 스킬 마다 클래스로 생성
// 플레이어의 Skill Ation에 등록해서 사용한다.
// Skill은 스킬마다 대상 몬스터가 달라지니까 Unit의 List를 받는다.
public abstract void Use(List<Unit> _units, int _attack);
}
[Serializable]
public class AlphaStrike : Skill
{
public AlphaStrike() : base("알파 스트라이크", "공격력 * 2로 하나의 적을 공격합니다.", 10, 1, SkillType.Target) { }
public override void Use(List<Unit> _units, int _attack)
{
// Scene에서 타겟 설정 다해서 player -> Skill.use로 넘김 Skill에서는 받은 타겟을 다처리하면됨
int damage = (int)(_attack * 2f);
foreach (Unit unit in _units)
{
unit.Attacked(damage);
}
}
}
[Serializable]
public class DoubleStrike : Skill
{
public DoubleStrike() : base("더블 스트라이크", " * 1.5로 2명의 적을 랜덤으로 공격합니다.", 15, 2, SkillType.Random) { }
public override void Use(List<Unit> _units, int _attack)
{
// Scene에서 타겟 설정 다해서 player -> Skill.use로 넘김 Skill에서는 받은 타겟을 다처리하면됨
int damage = (int)(_attack * 1.5f);
Random ran = new Random();
int count = Math.Min(_units.Count, targetCount);
for(int i =0; i< count; i++)
{
int randInt = ran.Next(0, _units.Count);
_units[randInt].Attacked(damage);
_units.RemoveAt(randInt);
}
}
}
스킬 클래스는 간단했는데.. 스킬을 선택하고 스킬의 대상을 선택하는 등의 장면을 만드는데
오래걸리게 됬고, BattleScene에 메서드가 너무 많아지게 되서, 복잡하고 어려워서
BattleScene을 PlayerPhaseScene, MosterPhaseScene, BattleResultScene으로 클래스를 분할했다,
그래도 PlayerPhaseScene은 메서드가 많아서 복잡했지만 다 분할하게되면 너무 많은 Scene클래스가 생기게 되어 한 클래스로 만들어 두었다.
class PlayerPhaseScene : Scene
{
Dungeon dungeon;
public PlayerPhaseScene() : base() { }
public override int DrawScene()
{
dungeon = DungeonGame.Instance.dungeon;
return PlayerPhase();
}
public int PlayerPhase()
{
Console.Clear();
DrawUI();
WriteText();
DungeonGame.Instance.PrintMessage();
options = new int[] { 1, 2 };
switch (InputKey(options))
{
case 1:
return PlayerPhaseAttack();
case 2:
return PlayerSelectSkill();
default:
return (int)SceneType.PlayerPhaseScene;
}
}
public int PlayerPhaseAttack()
{
//몬스터의 수 만큼 options를 추가
// 플레이어가 몬스터 번호를 입력하면
// 몬스터 리스트의 해당 인덱스에 있는 몬스터가
// 플레이어가 주는 데미지를 받음
Console.Clear();
DrawUI();
WriteBattleText();
DungeonGame.Instance.PrintMessage();
int key = InputKey(MakeOption(dungeon.Count()));
switch (key)
{
case 0:
return (int)SceneType.PlayerPhaseScene;
default:
Unit unit = dungeon.GetUnit(key - 1);
if (unit.IsDead == true)
{
DungeonGame.Instance.message.Append("이미 죽은 몬스터 입니다.\n");
return PlayerPhaseAttack();
}
_player.AttackUnit(unit);
return AttackResult();
// 몬스터 선택에 따른 결과 출력
}
}
public int PlayerSelectSkill()
{
Console.Clear();
DrawUI();
WriteSkillText();
DungeonGame.Instance.PrintMessage();
int key = InputKey(MakeOption(_player.SkillCount));
switch (key)
{
case 0:
return (int)SceneType.PlayerPhaseScene;
default:
if (_player.MP < _player.skillList[key - 1].mp)
{
DungeonGame.Instance.message.Append("MP가 부족합니다!\n");
return PlayerSelectSkill();
}
if (_player.skillList[key-1].type == SkillType.Random)
{
foreach (Unit i in dungeon.GetUnitList())
{
if (!i.IsDead)
{
_player.targetList.Add(i);
}
}
_player.UseSkill(key - 1);
return AttackResult();
}
else
{
return SelectSkillTarget(key - 1, _player.skillList[key - 1].targetCount);
}
}
}
public int SelectSkillTarget(int index, int count)
{
Console.Clear();
DrawUI();
WriteSkillTargetText();
DungeonGame.Instance.PrintMessage();
int key = InputKey(MakeOption(dungeon.Count()));
switch (key)
{
case 0:
return PlayerSelectSkill();
default:
Unit _unit = dungeon.GetUnit(key-1);
if (_player.targetList.Contains(_unit))
{
DungeonGame.Instance.message.Append("이미 선택한 대상입니다.\n");
return SelectSkillTarget(index, count);
}
else if (_unit.IsDead == true)
{
DungeonGame.Instance.message.Append("이미 죽은 몬스터 입니다.\n");
return PlayerPhaseAttack();
}
_player.targetList.Add(_unit);
if (--count > 0) return SelectSkillTarget(index, count);
_player.UseSkill(index);
// 스킬을 사용하기위해 내가 선택한 target들이 저장되야함
return AttackResult();
// 선택된 타겟을 저장하면서 count가 0이 될때까지 계속 선택
}
}
public int AttackResult()
{
options = new int[] { 0 };
Console.Clear();
DrawUI();
WriteAttackResultText();
switch (InputKey(options))
{
default:
return dungeon.DungeonClear() ? (int)SceneType.BattleResultScene : (int)SceneType.MonsterPhaseScene;
}
}
public void WriteText()
{
WriteLeftMessage($"[내정보]\n\n{_player.PlayerInfo()}");
WriteSelectMessage("1. 공격\n\n2. 스킬");
WriteRightMessage($"{dungeon.MonsterListInfo()}");
WriteMessage("Battle!!\n");
}
public void WriteAttackResultText()
{
WriteLeftMessage($"[내정보]\n\n{_player.PlayerInfo()}");
WriteSelectMessage("0. 다음");
WriteRightMessage($"{dungeon.MonsterSelectInfo()}");
WriteMessage("Battle!!\n");
}
public void WriteBattleText()
{
WriteLeftMessage($"[내정보]\n\n{_player.PlayerInfo()}");
WriteSelectMessage("0. 취소");
WriteRightMessage($"{dungeon.MonsterSelectInfo()}");
WriteMessage("Battle!!\n");
}
public void WriteSkillText()
{
WriteLeftMessage($"[내정보]\n\n{_player.PlayerInfo()}");
WriteSelectMessage($"{_player.PlayerSkillInfo()}0.취소");
WriteRightMessage($"{dungeon.MonsterListInfo()}");
WriteMessage("Battle!!\n");
}
public void WriteSkillTargetText()
{
WriteLeftMessage($"[내정보]\n\n{_player.PlayerInfo()}");
WriteSelectMessage($"0.취소");
WriteRightMessage($"{dungeon.MonsterSelectInfo()}");
WriteMessage("Battle!!\n");
}
}
class MonsterPhaseScene : Scene
{
Dungeon dungeon;
public MonsterPhaseScene() : base() { }
public override int DrawScene()
{
dungeon = DungeonGame.Instance.dungeon;
return MonsterPhase(0);
}
public int MonsterPhase(int i)
{
options = new int[] { 0 };
if (dungeon.DungeonClear())
{
return (int)SceneType.BattleResultScene;
}
while (i < dungeon.Count() && dungeon.GetUnit(i).IsDead)
{
i++;
}
if(i < dungeon.Count())
{
Console.Clear();
DrawUI();
dungeon.GetUnit(i).AttackUnit(_player);
WriteText();
switch (InputKey(options))
{
default:
if (_player.Health <= 0)
{
dungeon.Result = false;
return (int)SceneType.BattleResultScene;
}
return ++i < dungeon.Count() ? MonsterPhase(i) : (int)SceneType.PlayerPhaseScene;
}
}
return (int)SceneType.PlayerPhaseScene;
}
public void WriteText()
{
WriteLeftMessage($"[내정보]\n\n{_player.PlayerInfo()}");
WriteSelectMessage("0. 다음");
WriteRightMessage($"{dungeon.MonsterListInfo()}");
WriteMessage("Battle!!\n");
}
}
class BattleResultScene : Scene
{
Dungeon dungeon;
public BattleResultScene() { }
public override int DrawScene()
{
dungeon = DungeonGame.Instance.dungeon;
return BattleResult(dungeon.Result);
}
public int BattleResult(bool _result)
{
options = new int[] { 0 };
Console.Clear();
if (_result)
{
WriteVictoryText();
_player.GetExp(dungeon.Count() * 5); // 몬스터 한 마리당 5의 경험치
}
else
{
WriteLoseText();
}
dungeon.Clear();
dungeon.Init();
switch (InputKey(options))
{
case 0:
return (int)SceneType.StartScene;
default:
return (int)SceneType.StartScene;
}
}
public void WriteVictoryText()
{
WriteLeftMessage($"[던전 결과]\n몬스터 {dungeon.Count()}마리를 잡았습니다! 경험치 {dungeon.Count() * 5} 증가!\n");
WriteSelectMessage("0. 다음");
WriteMessage("[보상 정산]\n골드 + 300 G\n원하시는 행동을 입력해주세요");
}
public void WriteLoseText()
{
WriteLeftMessage($"{_player.PlayerInfo()}");
WriteSelectMessage("0. 다음");
WriteRightMessage($"Baltte!! - Result\nLose");
WriteMessage("[보상 정산]\n골드 + 300 G\n원하시는 행동을 입력해주세요");
}
}
팀 과제 기능 구현 - 콘솔 UI 수정
battle의 기능을 다 만들고나서 남는 시간에 콘솔 UI를 건드려 봤다.
조금만 바꿔봐야지 싶어서 건드렸는데 결국에는 모든 출력문의 코드를 바꾸게 됬다..
public void DrawUI()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Console.WindowHeight; i++)
{
Console.SetCursorPosition(0, i);
Console.WriteLine("l");
Console.SetCursorPosition(33, i);
Console.WriteLine("l");
Console.SetCursorPosition(Console.WindowWidth-1, i);
Console.WriteLine("l");
}
for (int i =0;i< Console.WindowWidth/2; i++)
{
sb.Append("ㅡ");
}
Console.SetCursorPosition(0, 0);
Console.Write(sb.ToString());
Console.SetCursorPosition(0, Console.WindowHeight - 16);
Console.Write(sb.ToString());
Console.SetCursorPosition(0, Console.WindowHeight -1);
Console.Write(sb.ToString());
}
public void WriteTopMessage(string str)
{
string[] strArray = str.Split('\n');
int xPos = 3;
int yPos = 3;
for(int i =0; i< strArray.Length; i++)
{
Console.SetCursorPosition(xPos, yPos++);
Console.WriteLine(strArray[i]);
}
}
public void WriteSelectMessage(string str)
{
string[] strArray = str.Split('\n');
int xPos = 3;
int yPos = Console.WindowHeight - 16;
for (int i = 0; i < strArray.Length; i++)
{
Console.SetCursorPosition(xPos, yPos+=2);
Console.WriteLine(strArray[i]);
}
}
public void WriteLeftMessage(string str)
{
string[] strArray = str.Split('\n');
int xPos = 3;
int yPos = 1;
for (int i = 0; i < strArray.Length; i++)
{
Console.SetCursorPosition(xPos, yPos += 1);
Console.WriteLine(strArray[i]);
}
}
public void WriteMessage(string str)
{
str += DungeonGame.Instance.PrintMessage();
string[] strArray = str.Split('\n');
int xPos = Console.WindowWidth / 2 - 23;
int yPos = Console.WindowHeight - 16;
for (int i = 0; i < strArray.Length; i++)
{
Console.SetCursorPosition(xPos, yPos+=2);
Console.WriteLine(strArray[i]);
}
Console.SetCursorPosition(xPos, yPos+=2);
}
public void WriteRightMessage(string str)
{
string[] strArray = str.Split('\n');
int xPos = Console.WindowWidth / 2 - 23;
int yPos = 1;
for (int i = 0; i < strArray.Length; i++)
{
Console.SetCursorPosition(xPos, yPos += 2);
Console.WriteLine(strArray[i]);
}
}
방법자체는 간단했는데 위치마다 메서드를 제작하고
한번에 모아서 출력하고 있던 string들을 위치별로 다른 메서드를 사용해서
출력하도록 만들었다.
그런데 진짜 문제는 Action을 통해 Consolw.Write를 호출하던 부분이였다..
Action을 중간 중간에 Console.SetCurPosition을 바꿔줘야 했는데
방법을 찾아보다가 결국 제대로 찾지못했다.
그래서 다른방법이 뭐있을까? 라고 생각해보니 어차피 지금은 action을 출력문을 출력하는
용도로만 사용하고 있었고 그래서 Action 대신 StringBuilder를 사용해서 string들을 모으고
그것을 아까 만들어둔 메서드로 출력을하고 출력을한 stringBuilder는 Clear()하여 사용했다.
public StringBuilder message = new StringBuilder();
public string PrintMessage()
{
string str = message.ToString();
message.Clear();
return str;
}