<오늘의 계획>

1. 기능 수정

2. 콘솔 꾸미기

 

  • 기능 수정

인트로 멘트가 한 글자씩 출력되면 좋을 것 같아서 while문과 Thread.Sleep()을 통해 0.05초에 한 글자씩 나오도록 기본설정을 하고, 아무 키를 입력했을 때 스킵되도록 했다.Console.KeyAvailable은 bool값으로 어떤 키를 눌렀을 때 true를 출력한다. 이를 이용하여 값이 true가 되면 Thread.Sleep()의 속도를 다시 0으로 만들어주어 전체 멘트가 나오도록 했다.이 기능은 자주 사용할 것 같아서 메서드로 만들어주었다.

// 한글자씩 출력
static void OutputTxt(string txt) // string = Char(문자) 배열
{
    Console.ForegroundColor = ConsoleColor.Cyan;

    // 아무 키나 눌렀을 때 스킵
    int speed = 50;
    int txtCount = 0; // 글자수
    while (txtCount != txt.Length) // txt 글자수가 끝까지 나올 때까지
    {
        if (Console.KeyAvailable) // 아무 키나 눌렸을 때 true
        {
            speed = 0;
            Console.ReadKey(true);
        }
        Console.Write(txt[txtCount]); // 글자를 하나하나 가져올 인덱스
        Thread.Sleep(speed);
        txtCount++;
    }
}

// 적용
static void DisplayGameIntro()
{
	string startTxt = "스파르타 마을에 오신 여러분 환영합니다.\n이곳에서 던전으로 들어가기 전 활동을 할 수 있습니다.\n";
	OutputTxt(startTxt);
}

Console.Readkey( ) : 사용자가 눌린 키 한 문자 정보를 리턴하는 메소드

Console.KeyAvailable : 키 입력값의 참거짓 여부

 

  • 콘솔 꾸미기

콘솔 꾸미기 명령어

// 콘솔 배경
Console.BackgroundColor = ConsoleColor.색상;
Console.Clear(); // 화면 지우기

// 글씨색 변경
Console.ForegroundColor = ConsoleColor.색상;

// 글씨색 기본색으로 되돌리기
Console.ResetColor();

// 콘솔창 이름 설정
Console.Title = "콘솔창";

 

 

<추가>

- 코드에 필요한 클래스를 별도의 파일로 만들고 분리해보기

- Readme 작성하기

 

<마치며>

C#에 좀 더 친숙해지는 것에 중점을 맞췄어야했는데 많은 기능을 구현하고 싶은 욕심에 새로운 메서드를 찾아보고 탐구하는 것에 너무 시간을 할애했던 것 같다. 잘하는 분들은 시간이 남았기 때문에 여러 가지 추가기능을 구현했음을 항상 유의하고 초보자인 나는 문법 위주로 공부해야겠다.

 

 

<구상 및 설계>

기본적으로 게임 시작화면과 선택지를 눌렀을 때, 캐릭터의 상태를 볼 수 있는 상태 보기창과 인벤토리 창으로 구성할 예정이다.

능력이 된다면 던전까지 구현하고 싶지만 시간과 능력 부족으로 팀 프로젝트 때 함께 구현해야할 것 같다.

 

게임 시작

- 1. 상태 보기

- 2. 인벤토리 - 1. 장착 관리

- 3. 상점

- 4. 던전 입장

 

<구현>

게임 시작

// 선택지
static int CheckValidInput(int min, int max)
{
    while (true)
    {
        string input = Console.ReadLine();

        bool parseSuccess = int.TryParse(input, out var ret);
        if (parseSuccess)
        {
            if (ret >= min && ret <= max)
                return ret;
        }

        Console.WriteLine("잘못된 입력입니다.");
    }
}

// 메인화면
static void GameLogo()
{
    Console.WriteLine("= Sparta Dungeon =");
    Console.WriteLine("1. 게임시작");
    Console.WriteLine("0. 나가기");
    Console.WriteLine();
    Console.WriteLine("원하시는 행동을 입력해주세요.");
    Console.Write(">> ");

    int input = CheckValidInput(0, 1);
    switch (input)
    {
        case 0:
            break;
        case 1:
            DisplayGameIntro();
            break;
    }
}

// 스타트
static void DisplayGameIntro()
{
    Console.Clear();
    Console.WriteLine("==================================================");
    Console.WriteLine("스파르타 마을에 오신 여러분 환영합니다.\n이곳에서 던전으로 들어가기 전 활동을 할 수 있습니다.");
    Console.WriteLine("==================================================");
    Console.WriteLine();
    Console.WriteLine("1. 상태 보기\n2. 인벤토리\n0. 메인화면");
    Console.WriteLine();
    Console.WriteLine("원하시는 행동을 입력해주세요.");
    Console.Write(">> ");

    int input = CheckValidInput(0, 2);
    switch (input)
    {
        case 0:
            Console.Beep();
            Main();
            break;
        case 1:
            DisplayMyInfo();
            break;
        case 2:
            DisplayInventory();
            break;
    }
}

 

상태 보기

class Program
{
	private static Character player;
    
    // 메인
    static void Main(string[] args)
    {
        GameDataSetting();
        DisplayGameIntro();
    }
    
    // 각종 데이터
    static void GameDataSetting()
    {
        // 캐릭터 정보 세팅
        player = new Character("Chad", "전사", 1, 10, 5, 100, 1500);

        // 아이템 정보 세팅
    }

    static void DisplayMyInfo()
    {
        Console.Clear();
        Console.Title = "= Information =";

        ChooseTextColor("= 상태 보기 =");
        LineTextColor("캐릭터의 정보가 표시됩니다.");
        Console.WriteLine();
        StatTextColor("Lv. ", player.Level.ToString("00")); // 00, 07 등 한자릿수도 두자릿수로 표현하기 위해 string 타입으로 변환
        Console.WriteLine();
        Console.WriteLine("{0} ( {1} )", player.Name, player.Job);
        StatTextColor("공격력 : ", player.Atk.ToString());
        Console.WriteLine();
        StatTextColor("방어력 : ", player.Def.ToString());
        Console.WriteLine();
        StatTextColor("체 력 : ", player.Hp.ToString());
        Console.WriteLine();
        StatTextColor("Gold : ", player.Gold.ToString());
        Console.WriteLine();
        Console.WriteLine();
        ChooseTextColor("0. 나가기");
        Console.WriteLine();
        Console.WriteLine("원하시는 행동을 입력해주세요.");
        Console.Write(">> ");

        int input = CheckValidInput(0, 0);
        switch (input)
        {
            case 0:
                DisplayGameIntro();
                break;
        }
    }
}

// 플레이어의 캐릭터 상태 클래스
public class Character
{
    public string Name { get; }
    public string Job { get; }
    public int Level { get; }
    public int Atk { get; }
    public int Def { get; }
    public int Hp { get; }
    public int Gold { get; }

    public Character(string name, string job, int level, int atk, int def, int hp, int gold)
    {
        Name = name;
        Job = job;
        Level = level;
        Atk = atk;
        Def = def;
        Hp = hp;
        Gold = gold;
    }
}

 

인벤토리

class Program
{
	private static Character player;
	private static Item[] items;
    
    // 각종 데이터
    static void GameDataSetting()
    {
        // 캐릭터 정보 세팅
        player = new Character("Chad", "전사", 1, 10, 5, 100, 1500);

        // 아이템 정보 세팅
        items = new Item[10]; // 최대 아이템 10개
        AddItem(new Item("무쇠갑옷", "무쇠로 만들어져 튼튼한 갑옷입니다.", 0, 0, 5, 0));
        AddItem(new Item("낡은 검", "쉽게 볼 수 있는 낡은 검입니다.", 1, 2, 0, 0));
        AddItem(new Item("고양이 수염", "고양이 수염은 행운을 가져다 줍니다. 야옹!", 1, 7, 7, 7));
    }

    // 아이템 추가
    static void AddItem(Item item)
    {
        if (Item.ItemCount == 10) return; // 아이템이 꽉차면 아무일도 일어나지 않는다
        items[Item.ItemCount] = item; // 0개 -> items[0], 1개 -> items[1] ...
        Item.ItemCount++; // AddItem이 될 때마다 ItemCount가 올라간다
    }

    // 인벤토리
    static void DisplayInventory()
    {
        Console.Clear();
        Console.Title = "= inventory =";

        Console.WriteLine("인벤토리");
        Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.");
        Console.WriteLine();
        Console.WriteLine("[아이템 목록]");

        for (int i = 0; i < Item.ItemCount; i++)
        {
            items[i].ItemStat();
        }

        Console.WriteLine("");
        Console.WriteLine("1. 장착 관리\n0. 나가기");
        Console.WriteLine();
        Console.WriteLine("원하시는 행동을 입력해주세요.");
        Console.Write(">> ");

        int input = CheckValidInput(0, 1);
        switch (input)
        {
            case 0:
                DisplayGameIntro();
                break;
            case 1:
                DisplayerEquip();
                break;
        }
    }
}

// 아이템 클래스
public class Item
{
    public string Name { get; }
    public string Description { get; }
    public int Type { get; }
    public int AtkOption { get; }
    public int DefOption { get; }
    public int HpOption { get; }
    public bool IsEquipped { get; set; }

    public static int ItemCount = 0; // static을 붙임으로 Item이라는 클래스에 귀속된다

    public Item(string name, string description, int type, int atkOption, int defOption, int hpOption, bool isEquipped = false)
    {
        Name = name;
        Description = description;
        Type = type;
        AtkOption = atkOption;
        DefOption = defOption;
        HpOption = hpOption;
        IsEquipped = isEquipped;
    }
	
    // 아이템 착용여부
    public void ItemStat(bool wearItem = false, int index = 0)
    {
        Console.Write("- ");
        if (IsEquipped)
        {
            Console.Write("[");
            Console.Write("E");
            Console.Write("]");
        }
        Console.Write(Name);
        Console.Write(" | ");

        if (AtkOption != 0) Console.Write($"AtkOption {(AtkOption >= 0 ? "+" : "")}{AtkOption} "); // 삼항연산자
        if (DefOption != 0) Console.Write($"DefOption {(DefOption >= 0 ? "+" : "")}{DefOption} "); // 옵션이 0이 아니라면 옵션수치를 내보내라
        if (HpOption != 0) Console.Write($"HpOption {(HpOption >= 0 ? "+" : "")}{HpOption} "); // [조건 ? 참 : 거짓]

        Console.Write(" | ");

        Console.WriteLine(Description);
    }
}

 

장착관리

// 장착 관리
static void DisplayerEquip()
{
    Console.Clear();
    Console.Title = "= Item Equip =";

    Console.WriteLine("인벤토리 - 장착 관리");
    Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.");
    Console.WriteLine();
    Console.WriteLine("[아이템 목록]");
    for (int i = 0; i < Item.ItemCount; i++)
    {
        items[i].ItemStat(true, i + 1);
    }
    Console.WriteLine();
    Console.WriteLine("0. 나가기");
    Console.WriteLine();
    Console.WriteLine("원하시는 행동을 입력해주세요.");
    Console.Write(">> ");

    int input = CheckValidInput(0, Item.ItemCount);
    switch (input)
    {
        case 0:
            DisplayInventory();
            break;
        default:
            ToggleEquipStatus(input - 1); // 유저가 입력하는건 1, 2, 3...  실제 배열에는 0, 1, 2...
            DisplayerEquip();
            break;
    }
}

// 장비 장착 여부
private static void ToggleEquipStatus(int index)
{
    items[index].IsEquipped = !items[index].IsEquipped; // ! : 불형의 변수를 반대로 만들어주는 것
}

// 장비 능력치 더하기
private static int itemStatSumAtk()
{
    int sum = 0;
    for (int i = 0; i < Item.ItemCount; i++)
    {
        if (items[i].IsEquipped) sum += items[i].AtkOption;
    }
    return sum;
}
private static int itemStatSumDef()
{
    int sum = 0;
    for (int i = 0; i < Item.ItemCount; i++)
    {
        if (items[i].IsEquipped) sum += items[i].DefOption;
    }
    return sum;
}
private static int itemStatSumHp()
{
    int sum = 0;
    for (int i = 0; i < Item.ItemCount; i++)
    {
        if (items[i].IsEquipped) sum += items[i].HpOption;
    }
    return sum;
}

 

 

오늘의 계획

1. 마지막 수정 및 점검

2. 발표

3. 개인 알고리즘 공부

 

  • 발표

전날에 발표 대본은 써놨지만 발표에 대한 경험이 거의 없고 열심히 팀원들과 준비한 게임을 내 발표로 망칠까봐 걱정이 많이 됐다. 하지만 이때 아니면 언제 발표를 연습해보겠어! 라는 생각과 팀원분들의 격려로 ppt자료와 대본을 한 번 더 체크했다.

미니프로젝트이지만 처음으로 팀원들과 협동하며 하나의 게임을 완성한 경험은 아쉬우면서도 뿌듯했다. 시간이 좀 더 있었다면 구현하면 좋았을텐데 싶은 부분과 발표 때 긴장해서 말을 제대로 못한게 아쉽기도 했지만 새로운 경험이라 재밌었다.

 

  • 알고리즘 문제 풀이

C# 코딩테스트 알고리즘 코드타카 문제를 풀던 중, 두 수의 나눗셈 부분에서 Convert 함수와 (double), (int) 등의 형변환 방식의 차이점이 무엇일까 궁금증이 생겼다. Convert 함수는 String처럼 숫자형이 아닌 자료형도 변환시켜줄 수 있지만 (double), (int) 등의 형변환은 숫자형끼리만 변환시킬 수 있었다.

 

나의 첫 코드는 이랬는데

public class Solution {
    public int solution(int num1, int num2) {
        double answer = Convert.ToDouble(num1 / num2 * 1000);
        return Convert.ToInt32(answer);
    }
}

 

int형의 num1과 num2가 계산되어 정수로 반환되기 때문에 소수점을 반환할 수 없었다.

 

고로 이렇게 num1을 double형으로 변환시켜서 int형과 계산해도 더 상위의 형식인 double형으로 나오도록 했다.

public class Solution {
    public int solution(int num1, int num2) {
        double answer = Convert.ToDouble(num1) / num2 * 1000;
        return Convert.ToInt32(answer);
    }
}
public class Solution {
    public int solution(int num1, int num2) {
        double answer = (double)num1 / num2 * 1000;
        return (int)answer;
    }
}

 

 

+ C#은 파이썬과 달리 중첩 비교연산자 사용이 안된다는 사실을 알게 되었다.

틀린 예)

if (0 < angle < 90)

 

올바른 예)

if (0 < angle && angle < 90)

 

 

오늘의 계획

1. EndScene 수정

2. Clear/GameOver 로고 추가

3. QA

4. 발표 준비

 

  • EndScene 수정
private void Start()
{
    // 조 이름 회전 및 컬러 변경
    StartCoroutine(MoveInCircle());
    DOTween.To(() => _creditText.color, x => _creditText.color = x, new Color(Random.value, Random.value, Random.value, 1), colorChangeDuration)
        .SetLoops(-1, LoopType.Yoyo)
        .OnStepComplete(() =>
        {
            DOTween.To(() => _creditText.color, x => _creditText.color = x, new Color(Random.value, Random.value, Random.value, 1), colorChangeDuration);
        });
}

private IEnumerator MoveInCircle()
{
    Vector3 originalPosition = circleCenter;
    float timer = 0.0f;
    float circleDuration = 5.0f;

    while (true)
    {
        float angle = timer / circleDuration * 360 * Mathf.Deg2Rad;
        Vector3 offset = new Vector3(Mathf.Cos(angle) * radius, Mathf.Sin(angle) * radius, 0);
        endingCredit.transform.position = originalPosition + offset;

        timer += Time.deltaTime;
        if (timer > circleDuration) timer -= circleDuration;

        yield return null;
    }
}

 

DOTween과 코루틴(Coroutine)을 이용하여 EndScene에 조 이름이 원을 그리며 움직이고 색이 변하도록 하는 코드를 추가했다.

 

팀원분께 피드백을 받으며 private와 public의 차이는 알지만 각각 언제 쓰는지 궁금해졌다.

public은 다른 곳에서 매서드를 주고 받을 때 사용하며 기본적으로 보안 등의 문제로 동일한 클래스나 struct의 코드에서만 받을 수 있는 private을 지향하는게 좋다고 한다.

 

  • Clear/GameOver 로고 추가

Clear와 GameOver 로고를 간단하게 만들어서 PlayScene 담당분들께 넘겨드렸다.

Clear 로고는 애니메이션으로 색 변화를 주었다.

 

  • QA

맴버의 카드를 맞췄을 때, 본인의 프로필과 다른 사람의 프로필이 바뀐 현상이 있다. 맴버 카드와 프로필이 잘못 매치되어있어 금방 수정하였다.

또한 오픈한 카드 여러 번 클릭이 가능한 버그를 발견했다. 카드 한 장 오픈한 뒤, 다른 카드를 오픈하지 않고 오픈된 카드 연속 클릭하면 프로필이 열리면서 남은 횟수와 시도 횟수 카운트 변하는 현상과 카드를 여러 번 눌렀을 때 카드가 한 장만 사라지는 버그가 있다.

카드가 180도 돌아가는데 90도 돌아갔을 때 앞면을 보여주는 형식으로 카드를 뒤집는 코드를 짰는데 카드가 90도 돌아갔을 때 여러 번 클릭하면 순간적으로 다른 카드로 변화되어 생긴 버그로 그것을 방지하면 되는 것이었다.

 

그 외에도 버그 수정하면 또다른 버그가 터지고 수정하면 또 터지고 반복이었다. 어쨌든 해냈다.(나 말고 팀원분들이)

 

+ 난이도 시스템을 추가하기로 했다.

GameManager에서 CardCount 횟수를 조절하여 Easy, Normal, Hard로 나눈 뒤, 각각 30회, 20회, 10회로 제한하였다.

+ Recent posts