오늘는 스파르타 코딩 클럽 unity 게임 개발 과정 16일차(팀과제 C# 콘솔 폰트 변경 및 표 형식으로 보여주기 설명)

2023. 11. 21. 01:14스파르타코딩클럽 게임개발

반응형

오늘 내가 한 것..

폰트와 크기가 기본의 것과 다르다. 진중한 분위기가 난다.
정갈한 테이블을 통해 직업과 캐릭터 선택을 할 수있다.

오늘 내가 힘들었던 점.

콘솔은 익숙하지 않아서 실수가 많았는데 테이블을 만드는 코드는 같은 스파르타 동기인 세진님의 코드를 참고하여 .. 처음에는 이해 되지 않아 챗gtp와 함께 코드를 분석하고 내가 사용할 수 있게 변형했다.

그런데 한글을 입력하면 영어와 자리를 차지하는 비트수가 다르다 보니 계속 열이 달라져 표가 정갈하지 못했다.

열이 틀어져있다.

이를 고치기 위해 무엇을 해야할까 고민을 했다.

세진님에게 물어보니 한글을 입력받으면 열을 조정시키는 코드가 있다길래 나의 코드에 맞춰 사용했다.

세진님의 도움으로 만들어진 코드

// 문자열의 출력 길이를 계산하는 메서드입니다.
private static int GetPrintingLength(string line) => line.Sum(c => IsKorean(c) ? 1 : 0);

// 주어진 문자가 한글인지 확인하는 메서드입니다.
private static bool IsKorean(char c) => '가' <= c && c <= '힣';

이 코드를 DrawTable 메서드에 사용하여 이 문제를 해결했다.

1. 렌더러 클래스

Renderer 클래스는 배경과 테이블 그리고 데이터값으로 받은 문자열을 테이블 안에 출력해주는 역할을 한다.

클래스 소개

using System.Text;

// Renderer class responsible for drawing the game screen on the console.
public static class Renderer
{
    // 변수: 콘솔 창의 높이와 길이를 저장합니다.
    private static readonly int inputAreaHeight = 1;
    private static readonly string inputAreaString = ">> ";
    private static readonly int printMargin = 2;

    private static int width;
    private static int height;

    // 메서드: 게임 화면 초기화
    public static void Initialize(string gameName)
    {
        Console.Title = gameName;
        Console.ForegroundColor = ConsoleColor.White;
        Console.BackgroundColor = ConsoleColor.Black;
        Console.Clear();
        Console.OutputEncoding = Encoding.UTF8;
    }

    // 메서드: 게임 화면에 테두리 그리기
    public static void DrawBorder(string title = "")
    {
        Console.Clear();
        width = Console.LargestWindowWidth;
        height = Console.LargestWindowHeight;

        Console.SetCursorPosition(0, 0);
        Console.Write(new string('=', width));

        for (int i = 1; i < height - inputAreaHeight - 2; i++)
        {
            Console.SetCursorPosition(0, i);
            Console.Write('║');
            Console.SetCursorPosition(width - 1, i);
            Console.Write('║');
        }

        if (!string.IsNullOrEmpty(title))
        {
            Console.SetCursorPosition(0, 2);
            Console.Write(new string('=', width));
            int correctLength = GetPrintingLength(title);
            int horizontalStart = (width - correctLength) / 2;
            if (horizontalStart < 0) horizontalStart = 3;
            Console.SetCursorPosition(horizontalStart, 1);
            Console.WriteLine(title);
        }

        Console.SetCursorPosition(0, height - inputAreaHeight - 2);
        Console.Write(new string('=', width));
    }

    // 메서드: 문자열 출력 길이 계산
    private static int GetPrintingLength(string line) => line.Sum(c => IsKorean(c) ? 1 : 0);

    // 메서드: 주어진 문자가 한글인지 확인
    private static bool IsKorean(char c) => '가' <= c && c <= '힣';

    // 메서드: 특정 라인에 문자열 출력
    public static void Print(int line, string content)
    {
        Console.SetCursorPosition(printMargin, line);
        Console.WriteLine(content);
    }

    // 메서드: 화면에 테이블 그리기
    public static void DrawTable(Table table, int startLine, int selectedRow)
    {
        var types = table.GetTypes();
        int currentLine = startLine;

        // 헤더 행 그리기
        StringBuilder header = new StringBuilder("| ");
        foreach (var type in types)
        {
            header.Append(type.name.PadRight(type.length));
            header.Append(" | ");
        }
        Print(currentLine++, header.ToString());

        // 수평 선 그리기
        StringBuilder horizontalLine = new StringBuilder("+--");
        foreach (var type in types)
        {
            horizontalLine.Append(new string('-', type.length + 2));
            horizontalLine.Append("+");
        }
        Print(currentLine++, horizontalLine.ToString());

        int dataCount = table.GetDataCount();
        for (int i = 0; i < dataCount; i++)
        {
            StringBuilder row = new StringBuilder("| ");
            if (i == selectedRow)
            {
                row.Append("▶");
            }
            else
            {
                row.Append(" ");
            }
            var rowData = table.GetRow(i);
            for (int j = 0; j < types.Length; j++)
            {
                string data = rowData[j];
                int dataLength = GetPrintingLength(data);
                row.Append(data.PadRight(types[j].length - dataLength));
                row.Append(" | ");
            }
            Print(currentLine++, row.ToString());
        }
    }

    // 메서드: 화면에 입력 영역 그리기
    public static void DrawInputArea()
    {
        Console.SetCursorPosition(printMargin, height - inputAreaHeight - 1);
        Console.Write(inputAreaString);
    }
}

이 클래스는 게임 화면 초기화, 경계 그리기, 문자열 출력, 테이블 그리기 등의 기능을 제공하여 게임 화면을 구성한다.

Renderer 클래스의 각 부분을 살펴보자.

변수 선언

// 변수: 콘솔 창의 높이와 길이를 저장합니다.
private static readonly int inputAreaHeight = 1;
private static readonly string inputAreaString = ">> ";
private static readonly int printMargin = 2;

private static int width;
private static int height;
  • inputAreaHeight: 콘솔 창 하단의 입력 영역의 높이를 정의합니다.
  • inputAreaString: 입력 영역의 프롬프트 문자열을 정의합니다.
  • printMargin: 출력할 때 왼쪽 여백을 설정합니다.
  • width, height: 콘솔 창의 현재 너비와 높이를 저장하는 변수입니다.

메서드: 게임 화면 초기화

// 메서드: 게임 화면 초기화
public static void Initialize(string gameName)
{
    Console.Title = gameName;
    Console.ForegroundColor = ConsoleColor.White;
    Console.BackgroundColor = ConsoleColor.Black;
    Console.Clear();
    Console.OutputEncoding = Encoding.UTF8;
}
  • Initialize: 게임 화면을 초기화하는 메서드입니다.
  • gameName: 게임 제목을 전달받아 콘솔 창의 타이틀을 설정하고, 글자 색과 배경 색을 지정합니다.

메서드: 게임 화면에 테두리 그리기

// 메서드: 게임 화면에 테두리 그리기
public static void DrawBorder(string title = "")
{
    // ... (중략)
}
  • DrawBorder: 게임 화면에 테두리를 그리는 메서드입니다.
  • title: 테두리 상단에 출력할 제목을 선택적으로 받습니다.

메서드: 문자열 출력 길이 계산

// 메서드: 문자열 출력 길이 계산
private static int GetPrintingLength(string line) => line.Sum(c => IsKorean(c) ? 1 : 0);
  • GetPrintingLength: 문자열의 출력 길이를 계산하는 메서드입니다.
  • line: 출력 길이를 계산할 문자열을 받습니다.
  • IsKorean: 문자열에 한글이 포함되어 있는지 확인하는 내부 메서드입니다.

메서드: 화면 중앙에 문자열 출력

// 메서드: 화면 중앙에 문자열 출력
public static void PrintCenter(string[] lines)
{
    // ... (중략)
}
  • PrintCenter: 화면 중앙에 여러 줄의 문자열을 출력하는 메서드입니다.
  • lines: 출력할 문자열 배열을 받습니다.

메서드: 특정 라인에 문자열 출력

// 메서드: 특정 라인에 문자열 출력
public static void Print(int line, string content)
{
    // ... (중략)
}
  • Print: 특정 라인에 문자열을 출력하는 메서드입니다.
  • line: 출력할 라인 번호를 받습니다.
  • content: 출력할 내용을 받습니다.

메서드: 화면에 테이블 그리기

// 메서드: 화면에 테이블 그리기
public static void DrawTable(Table table, int startLine, int selectedRow)
{
    // ... (중략)
}
  • DrawTable: 화면에 테이블을 그리는 메서드입니다.
  • table: 출력할 테이블 데이터를 담은 객체를 받습니다.
  • startLine: 테이블을 그릴 시작 라인을 지정합니다.
  • selectedRow: 선택된 행을 강조하기 위한 인덱스를 받습니다.

메서드: 화면에 입력 영역 그리기

// 메서드: 화면에 입력 영역 그리기
public static void DrawInputArea()
{
    // ... (중략)
}
  • DrawInputArea: 화면 하단에 입력 영역을 그리는 메서드입니다.

2. 테이블을 나타내는 클래스

// 테이블을 나타내는 클래스
public class Table
{
    private Dictionary<string, TableDataType> dataTypes = new();
    private Dictionary<string, List<string>> datas = new();

    // 데이터 타입 추가 메서드
    public bool AddType(string name, int length, bool center = false)
    {
        if (dataTypes.ContainsKey(name))
            return false;

        dataTypes[name] = new TableDataType(name, length, center);
        return true;
    }

    // 데이터 추가 메서드
    public void AddData(string name, string content)
    {
        if (!datas.TryGetValue(name, out List<string>? list))
        {
            list = new List<string>();
            datas[name] = list;
        }

        list.Add(content);
    }

    // 모든 데이터 타입을 가져오는 메서드
    public TableDataType[] GetTypes() => dataTypes.Values.ToArray();

    // 특정 행의 데이터를 가져오는 메서드
    public string[] GetRow(int row)
    {
        string[] result = new string[dataTypes.Count];
        int index = 0;

        foreach (string key in dataTypes.Keys)
        {
            result[index] = datas[key][row];
            index++;
        }

        return result;
    }

    // 데이터 행 수를 가져오는 메서드
    public int GetDataCount() => datas.First().Value.Count;
}

// 테이블 데이터 타입을 정의하는 구조체
public struct TableDataType
{
    public string name;
    public int length;
    public bool center;

    public TableDataType(string name, int length, bool center = false)
    {
        this.name = name;
        this.length = length;
        this.center = center;
    }
}

클래스: 테이블

  • Table 클래스는 테이블을 나타내는데 사용됩니다.
  • dataTypes: 각 열의 데이터 타입을 저장하는 딕셔너리입니다.
  • datas: 각 열에 대한 실제 데이터를 저장하는 딕셔너리입니다.

메서드: 데이터 타입 추가

// 데이터 타입 추가 메서드
public bool AddType(string name, int length, bool center = false)
{
    if (dataTypes.ContainsKey(name))
        return false;

    dataTypes[name] = new TableDataType(name, length, center);
    return true;
}
  • AddType: 특정 열의 데이터 타입을 추가하는 메서드입니다.
  • name: 열의 이름을 나타냅니다.
  • length: 열의 길이를 나타냅니다.
  • center: 데이터가 가운데 정렬되어야 하는지 여부를 나타냅니다.

메서드: 데이터 추가

// 데이터 추가 메서드
public void AddData(string name, string content)
{
    if (!datas.TryGetValue(name, out List<string>? list))
    {
        list = new List<string>();
        datas[name] = list;
    }

    list.Add(content);
}
  • AddData: 특정 열에 데이터를 추가하는 메서드입니다.
  • name: 데이터를 추가할 열의 이름을 나타냅니다.
  • content: 추가할 데이터 내용을 나타냅니다.

메서드: 모든 데이터 타입 가져오기

// 모든 데이터 타입을 가져오는 메서드
public TableDataType[] GetTypes() => dataTypes.Values.ToArray();
  • GetTypes: 테이블의 모든 데이터 타입을 반환하는 메서드입니다.

메서드: 특정 행의 데이터 가져오기

// 특정 행의 데이터를 가져오는 메서드
public string[] GetRow(int row)
{
    string[] result = new string[dataTypes.Count];
    int index = 0;

    foreach (string key in dataTypes.Keys)
    {
        result[index] = datas[key][row];
        index++;
    }

    return result;
}
  • GetRow: 테이블의 특정 행의 데이터를 반환하는 메서드입니다.
  • row: 가져올 행의 인덱스를 나타냅니다.

메서드: 데이터 행 수 가져오기

// 데이터 행 수를 가져오는 메서드
public int GetDataCount() => datas.First().Value.Count;
  • GetDataCount: 테이블의 데이터 행 수를 반환하는 메서드입니다.

구조체: 테이블 데이터 타입

// 테이블 데이터 타입을 정의하는 구조체
public struct TableDataType
{
    public string name;
    public int length;
    public bool center;

    public TableDataType(string name, int length, bool center = false)
    {
        this.name = name;
        this.length = length;
        this.center = center;
    }
}
  • TableDataType 구조체는 테이블의 각 열에 대한 데이터 타입을 정의합니다.
  • name: 데이터 타입의 이름을 나타냅니다.
  • length: 데이터 타입의 길이를 나타냅니다.
  • center: 데이터 타입이 가운데 정렬되어야 하는지 여부를 나타냅니다.

렌더러와 테이블 실제 사용 예제

  // 게임 화면 초기화 및 테이블 설정  
  Renderer.Initialize("게임 제목");  
  // 테두리와 테이블 그리기  
  Renderer.DrawBorder("캐릭터 선택");  
  Renderer.Print(3, "플레이할 캐릭터를 선택해주세요!");  
  // 입력 영역 그리기  
  Renderer.DrawInputArea();  
  // 테이블에 데이터 추가  
  Table table = new Table();  
  table.AddType("index", 7, false);  
  table.AddType("name", 20, false);  
  table.AddType("class", 20, false);  
  table.AddType("hp", 15, false);  
  table.AddType("damage", 15, false);  
  table.AddType("defense", 15, false);  

  table.AddData("index", "1");  
  table.AddData("name", $"{character.Name}");  
  table.AddData("class", $"{character.Job}");  
  table.AddData("hp", $"{character.Health}");  
  table.AddData("damage", $"{character.Attack}");  
  table.AddData("defense", $"{character.Defense}");

출력화면

3. 콘솔 폰트와 폰트크기 바꾸기

폰트 변경 및 크기 변경 클래스

using System.Runtime.InteropServices;

// ConsoleHelper 클래스는 콘솔 창 설정을 도와주는 정적 클래스입니다.

public static class ConsoleHelper
{
    // 고정폭 트루타입 글꼴 상수
    private const int FixedWidthTrueType = 54;
    // 표준 출력 핸들 상수
    private const int StandardOutputHandle = -11;

    // 표준 출력 핸들을 가져오는 외부 메서드
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetStdHandle(int nStdHandle);

    // 콘솔 글꼴 설정을 변경하는 외부 메서드
    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern bool SetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool MaximumWindow, ref FontInfo ConsoleCurrentFontEx);

    // 현재 콘솔 글꼴 설정을 가져오는 외부 메서드
    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern bool GetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool MaximumWindow, ref FontInfo ConsoleCurrentFontEx);

    // 콘솔 화면 버퍼 크기를 설정하는 외부 메서드
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool SetConsoleScreenBufferSize(IntPtr hConsoleOutput, COORD size);

    // COORD 구조체 정의
    [StructLayout(LayoutKind.Sequential)]
    public struct COORD
    {
        public short X;
        public short Y;
    }

    // ConsoleOutputHandle을 저장하는 변수
    private static readonly IntPtr ConsoleOutputHandle = GetStdHandle(StandardOutputHandle);

    // 글꼴 정보를 저장하는 구조체
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct FontInfo
    {
        internal int cbSize;
        internal int FontIndex;
        internal short FontWidth;
        public short FontSize;
        public int FontFamily;
        public int FontWeight;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string FontName;
    }

    // 현재 콘솔 글꼴 정보를 가져오는 메서드
    public static FontInfo GetCurrentFont()
    {
        FontInfo before = new FontInfo
        {
            cbSize = Marshal.SizeOf<FontInfo>()
        };

        // GetCurrentConsoleFontEx 메서드 호출
        if (!GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref before))
        {
            var er = Marshal.GetLastWin32Error();
            throw new System.ComponentModel.Win32Exception(er);
        }
        return before;
    }

    // 콘솔 글꼴을 변경하고 변경 전, 후의 정보를 반환하는 메서드
    public static FontInfo[] SetCurrentFont(string font, short fontSize = 0)
    {
        FontInfo before = new FontInfo
        {
            cbSize = Marshal.SizeOf<FontInfo>()
        };

        // GetCurrentConsoleFontEx 메서드 호출
        if (GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref before))
        {
            FontInfo set = new FontInfo
            {
                cbSize = Marshal.SizeOf<FontInfo>(),
                FontIndex = 0,
                FontFamily = FixedWidthTrueType,
                FontName = font,
                FontWeight = 400,
                FontSize = fontSize > 0 ? fontSize : before.FontSize
            };

            // SetCurrentConsoleFontEx 메서드 호출
            if (!SetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref set))
            {
                var ex = Marshal.GetLastWin32Error();
                throw new System.ComponentModel.Win32Exception(ex);
            }

            // Reset console buffer size and window size
            SetConsoleScreenBufferSize(ConsoleOutputHandle, new COORD { X = 80, Y = 300 });

            FontInfo after = new FontInfo
            {
                cbSize = Marshal.SizeOf<FontInfo>()
            };
            GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref after);

            return new[] { before, set, after };
        }
        else
        {
            var er = Marshal.GetLastWin32Error();
            throw new System.ComponentModel.Win32Exception(er);
        }
    }
}

ConsoleHelper클래스:

ConsoleHelper클래스는 콘솔 창의 글꼴 설정을 변경하고 현재 설정을 가져오는 역할을 수행하는 정적 클래스입니다. 이 클래스는 Win32 API를 이용하여 콘솔 창의 글꼴을 조작합니다.

GetStdHandle 메서드:

표준 출력 핸들을 가져오는 외부 메서드입니다.

SetCurrentConsoleFontEx 메서드:

콘솔 글꼴 설정을 변경하는 외부 메서드입니다.

GetCurrentConsoleFontEx 메서드:

현재 콘솔 글꼴 설정을 가져오는 외부 메서드입니다.

SetConsoleScreenBufferSize 메서드:

콘솔 화면 버퍼 크기를 설정하는 외부 메서드입니다.

COORD 구조체:

콘솔 화면 버퍼 크기를 나타내는 구조체입니다.

FontInfo 구조체:

글꼴 정보를 나타내는 구조체로, 현재 콘솔 글꼴 설정의 정보를 저장합니다.

ConsoleOutputHandle 변수:

표준 출력 핸들을 저장하는 변수입니다.

GetCurrentFont 메서드:

현재 콘솔 글꼴 설정을 가져오는 메서드입니다.

SetCurrentFont 메서드:

콘솔 글꼴을 변경하고 변경 전, 후의 정보를 반환하는 메서드입니다.

간단한 사용법

ConsoleHelper.SetCurrentFont("EBS주시경 Medium", 30);

 

폰트 이름과 사이즈를 넣으면 작동된다.

만약 원래 폰트로 돌아오고 싶다면 사전에 폰트를 저장시킨 후 다시 ConsoleHelper을 사용하면 된다.

FontInfo originalFont = ConsoleHelper.GetCurrentFont();
ConsoleHelper.SetCurrentFont(originalFont, 30);

 

GetCurrentFont() 는 현재의 폰트를 저장한다.

 

오늘 배운것.

콘솔를 사용하여 문자열을 정열하고 표형식으로 보여줄수 있다.

문자열의 폰트를 바꿀 수 있다.   

반응형