I'm FanJae.

[My Turn Based Console RPG] Day 1. 기본 구상 및 메인 화면 구현 본문

Projects/My Turn Based Console RPG

[My Turn Based Console RPG] Day 1. 기본 구상 및 메인 화면 구현

FanJae 2026. 5. 14. 08:55

1. 시작에 앞서

- 부트캠프 과제 중 하나였지만, 퀄리티를 높일 수 있는 부분은 최대한 높여서 Toy Project로서 진행해 보면 의미가 있을 것 같았다.

 

2. 기본 명세 구현 및 정리

1) 턴제 전투 구현

- 과제의 기본 전제가 턴제 RPG의 구현이었다.

- 따라서, 턴제 시스템에 맞게 배틀 시스템을 처리하였고, 우선 다음과 같은 형태로 구상하였다.

namespace MyConsoleMapleRPG.Systems
{
    internal class BattleSystem // 기본 배틀 시스템
    {
        private Player player;
        private Monster monster;

        public BattleSystem(Player player, Monster monster) // 기본적으로 배틀은 1vs1. 추후 늘어날수도?
        {
            this.player = player;
            this.monster = monster;
        }

        public void Start() // 배틀 시작 메서드
        {
            while(player.Hp > 0 && monster.Hp > 0) // 한명이라도 체력이 0으로 내려가면 종료.
            {
                player.Action(monster); // 선공은 기본적으로 플레이어 먼저

                if (monster.Hp == 0)
                {
                    break;
                }

                monster.Action(player); // 몬스터 후공
            }

            if (player.Hp == 0) Console.WriteLine($"{monster.Name}과의 전투에서 패배하였습니다.");
            else if (monster.Hp == 0) Console.WriteLine($"{monster.Name}과의 전투에서 승리하였습니다.");
            else if (player.Hp == 0 && monster.Hp == 0) Console.WriteLine($"무승부로 싸움이 종료되었습니다.");
        }
    }
}

 

 

2) 캐릭터 구현

- 캐릭터라는 추상 클래스를 만들었고, 이 캐릭터는 Player(유저가 컨트롤 할 캐릭터)와 Monster를 모두 포함할 것이다.

- 내가 설계한 모든 캐릭터는 기본적으로 이름, 체력, 공격력, 방어력을 가지며, 데미지를 줄 수 있는 존재다.

- 이 캐릭터들은 모두 배틀 시스템에서 활용될 Action()을 가지는데 이는 Monster의 경우 공격, Player의 경우는 턴제 게임에서 흔히 볼 수 있는 행동 (전투, 아이템 사용, 도망) 등을 추가해보려고 한다.

namespace MyConsoleMapleRPG.Character
{
    abstract class Character
    {
        private string name;
        private int hp;
        private int attack;
        private int defense;

        public Character(string name, int hp, int attack, int defense) // 캐릭터 생성자
        {
            Name = name;
            Hp = hp;
            Attack = attack;
            Defense = defense; 
        }

        public string Name // 이름 프로퍼티
        {
            get { return name; }
            protected set { name = value; }
        }
        public int Hp // 체력 프로퍼티
        {
            get { return hp; }
            protected set
            {
                if (value <= 0) hp = 0;
                else hp = value;
            }
        }

        public int Attack // 공격력 프로퍼티
        {
            get { return attack; }
            protected set
            {
                if (attack <= 0) attack = 1;
                else attack = value;
            }
        }

        public int Defense // 방어력 프로퍼티
        {
            get { return defense; }
            protected set
            {
                if (defense <= 0) defense = 1;
                else defense = value;
            }
        }


        public virtual void TakeDamage(int damage) // 모든 Character는 Damage라는 것을 줄 수 있다.
        {

        }
        public abstract void Action(Character target); // 모든 Character는 Action을 상속받아 처리한다. (Battle시 사용)

    }
}

 

3) 플레이어, 몬스터 분리

namespace MyConsoleMapleRPG.Character
{
    internal abstract class Player : Character
    {
        public Player() : base("기본 캐릭터", 50, 10, 5) { }
        public Player(string name) : base(name, 50, 10, 5) { }
        public Player(string name, int hp) : base(name, hp, 10, 5) { }
        public Player(string name, int hp, int attack, int defense) : base(name, hp, attack, defense) { }

        public override void Action(Character target) // 추후 변경
        {
            Console.WriteLine($"{Name}이 {target.Name}을 공격합니다.");
            target.TakeDamage(Attack);
        }
        public abstract void UseSkill(Character target);
    }
}
namespace MyConsoleMapleRPG.Character
{
   internal class Monster : Character
   {
            public Monster() : base("기본 몬스터", 50, 10, 5) { }
            public Monster(string name) : base(name, 50, 10, 5) { }
            public Monster(string name, int hp) : base(name, hp, 10, 5) { }
            public Monster(string name, int hp, int attack, int defense) : base(name, hp, attack, defense) { }

            public override void Action(Character target) // 추후 변경
            {
                Console.WriteLine($"{Name}이 {target.Name}을 공격합니다.");
                target.TakeDamage(Attack);
            }
    }
}

- 아직 이 쪽에 대한 구체적인 시나리오는 나오지 않았다.

- 기본적인 구상을 잡아 놓은 상태에서 어떻게 뻗어나갈지를 더 생각해봐야 할 것 같다.

- 다만 Playerble Character는 전사, 마법사 클래스로 할 예정이고, 스킬 셋도 그것으로 가져올 예정이다.

 

4) 콘솔 렌더링 분리

using System.Drawing;

namespace MyConsoleMapleRPG.UI
{
    internal static class ConsoleRenderer
    {
        public  static void DrawWindow(int x, int y, int width, int height, string title = "") // 창 밖의 틀 표현
        {
            Console.SetCursorPosition(x, y);
            Console.Write("┌");

            for (int i = 0; i < width - 2; i++)
            {
                Console.Write("─");
            }

            Console.Write("┐");

            for (int row = 1; row < height - 1; row++)
            {
                Console.SetCursorPosition(x, y + row);
                Console.Write("│");

                for (int col = 0; col < width - 2; col++)
                {
                    Console.Write(" ");
                }

                Console.Write("│");
            }

            Console.SetCursorPosition(x, y + height - 1);
            Console.Write("└");

            for (int i = 0; i < width - 2; i++)
            {
                Console.Write("─");
            }

            Console.Write("┘");

            if (title != "")
            {
                Console.SetCursorPosition(x + 3, y);
                Console.Write(title);
            }
        }

        public static void DrawImageAsAscii(string Path, int targetWidth, int x = 0, int y = 0)
        {

            using Bitmap original = new Bitmap(Path);

            using Bitmap bitmap = new Bitmap(original, new Size(32, 22)); // 가로, 세로

            for (int row = 0; row < bitmap.Height; row++)
            {
                Console.SetCursorPosition(x, y + row);
                for (int col = 0; col < bitmap.Width; col++)
                {
                    Color color = bitmap.GetPixel(col, row);

                    if (color.A == 0) // 투명셀 무시
                    {
                        Console.SetCursorPosition(x + col + 1, y + row);
                        continue ;
                    }

                    Console.Write($"\x1b[48;2;{color.R};{color.G};{color.B}m ");

                }
            }

            Console.Write("\x1b[0m");

        }

    }
}

- 이전 프로젝트를 진행할 때는 처음에 분리 안하고 나중에 분리해 놓는 경우가 많았다.

- 이번에는 그냥 미리 분리 해놨고, 가능하면 콘솔 화면 렌더링은 이 클래스만 담당하게 하는 것이 목적이다.

- DrawWindows 는 화면의 겉 부분을 구현하는 쪽이고, DrawImageAsASCII는 이미지 파일의 픽셀 값을 가져와서 화면에 직접 찍는 방식이다. (도트를 찍을 때 그것과 유사하다.)

 

5) 게임 컨트롤러 처리

using MyConsoleMapleRPG.Enums;
using MyConsoleMapleRPG.UI;
using MyConsoleMapleRPG.UI.Map;

namespace MyConsoleMapleRPG.Systems
{
    internal class GameController
    {
        private readonly MainMenu mainMenu = new MainMenu();
        public void Run()
        {
            ConsoleSetting.Initialize();
            while (true)
            {
                MainMenuResult result = mainMenu.Show(); // 메뉴 창 선택
                                    
                switch(result)
                {
                    case MainMenuResult.StartGame:
                        StartGame();
                        break;

                    case MainMenuResult.LoadGame:
                        // LoadGame();
                        break;

                    case MainMenuResult.Credits:
                        // ShowCredits();
                        break;

                     case MainMenuResult.Exit:
                        return ;
                }
            }
        }
        private void StartGame()
        {
            CharacterSelectScreen characterSelectScreen = new CharacterSelectScreen();

            int selectedJob = characterSelectScreen.Show();

            if (selectedJob == -1)
            {
                return;
            }

            MapScreen mapscreen = new MapScreen();
            mapscreen.Show();
        }
    }
}

- 게임의 시작에서 종료까지 여기서 담당할 것이다.

- 다만, 기존 오목처럼 룰과 관련된 정보를 이곳에 포함 시켜야 할지 아니면, 별도로 나눠서 판단할 지에 대해서는 아직 조금 더 고민이 필요하다.

 

3. 진행 사항

1) 기본 화면 구현

- MapleStory의 로고를 아스키 아트로 변환하는 사이트를 통해서 아스키 아트로 변환해주었다.

- 아스키 아트의 변환은 해당 사이트를 이용하였다.

링크 : https://wepplication.github.io/tools/asciiArtGen/

 

아스키아트 변환 사이트

텍스트 및 이미지(URL,파일첨부)를 아스키코드로 만드는 아스키아트 생성 사이트

wepplication.github.io

- 원래는 캐릭터를 넣고 싶었지만, 인게임에서 활용 중인 도트가 상당히 커서 콘솔 화면에 담으려면 게임 화면이 상당히 커야 할 것 같아 해당 직업을 잘 나타내 주는 무기(검, 스태프)로 대체하였다.

 

4. 진행 회고

아직 정리가 되지 않은 부분이 많아서 많이 난잡하다. 기본 틀은 잡아놨으니까 여기서 어떻게 추가해야할지 생각해 봐야겠다.

 
Comments