어제 만들었던 Text Rpg를 이어서 인벤토리를 구현해자

 

public interface Item
{
    public string Name { get; }
    public int State { get; }
    public string ItemType { get; }
    public string Description { get; }
}

public class Items
{
    public string Name { get; set; }
    public int State { get; set; }
    public string ItemType { get; set; }
    public string Description { get; set; }
    public bool IsEquip;

    public Items(string name, string itemType, int state, string description, bool isEquip)
    {
        Name= name;
        State = state;
        ItemType = itemType;
        Description = description;
        IsEquip = isEquip;
    }
}

어제 플레이어를 만들때 사용했던 인터페이스를 다시 사용하여 아이템의 틀을 만들어 준다.

 

이제 만들어진 아이템의 정보를 담을 리스트를 만들어 주고 게임을 시작할 때  아이템 리스트를 초기화기능을 호출 할수 있게 만들어 준다.

public class Inventory
{
    private List<Items> ItemList; //아이템 저장 리스트

    public Inventory()
    {
        ItemList = new List<Items>(); // 아이템 리스트 초기화
    }

    public void AddItems(Items items) //아이템을 인벤토리에 추가하는 메서드
    {
        ItemList.Add(items);
    }

    public void ShowInven() // 인벤토리에 있는 아이템 출력
    {
        for (int i = 0; i < ItemList.Count; i++)
        {
            if (ItemList[i].IsEquip == true) Console.Write("[E] ");
            Console.WriteLine($"{ItemList[i].Name} | {ItemList[i].ItemType} + {ItemList[i].State} | {ItemList[i].Description}");
        }
    }
    public void ItemEquipManger(Player player)
    {
        //아이템 장착 관리
    }
}

 

이렇게 만들어진 인벤토리는 Main에서 인벤토리 생성후 아이템 리스트를 초기화 해주고

class Program
{
    static void Main(string[] args)
    {
        
        Console.WriteLine("무한의 던전에 오신 여러분 환영합니다.");
        /*.... 처음 인트로 ...*/

        Player player = new Player(name, select);
        
        Inventory inventory = new Inventory();

        MainStage mainStage = new MainStage(player, inventory);
        mainStage.Start();
    }
}

생성된 인벤토리 객채는 메인스테이지로 넘겨준다

 

public class MainStage(Player player, Inventory inventory)
{
    bool game = true;
    
    public void Start()
    {
        Items items1 = new Items("초보자 나무 목검", "공격력", 5, "초보자도 쉽게 다룰수 있는 나무 목검", false);
        Items items2 = new Items("초보자 천갑옷", "방어력", 5, "초보자에게 처음 지급되는 천갑옷", false);
        inventory.AddItems(items1);
        inventory.AddItems(items2);
        MainMenu();
    }
    public void InventoryUI()
    {
        while (game)
        {
            Console.Clear();
            Console.WriteLine("인벤토리");
            Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.\n");
            Console.WriteLine("[아이템 목록]\n");
            inventory.ShowInven();
            Console.WriteLine("1. 장착 관리");
            Console.WriteLine("0. 나가기\n");
            Console.WriteLine("원하시는 행동을 입력해주세요.");
            Console.Write(">> ");

            string input = Console.ReadLine();
            if (input == "1") ItemManagerUI();
            else if (input == "0") MainMenu();
            else
            {
                Console.WriteLine("잘못된 입력입니다. 계속하려면 아무 키나 누르세요...");
                Console.ReadKey();
            }
        }

    }
}

 

 

간단하게 아이템을 생성해주고 생성된 아이템은 인벤토리 리스트에 저장을 해주면 인벤토리UI를 통해 출력이 된다.

 

 

이어서 바로 장착 관리를 만들어 보자

 

public void ItemEquipManger(Player player) // 아이템 장착 관리
{
    bool game = true;
    while (game)
    {
        Console.Clear();
        Console.WriteLine("인벤토리 - 장착 관리");
        Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.\n");
        Console.WriteLine("[아이템 목록]\n");
        for (int i = 0; i < ItemList.Count; i++)
        {
            Console.Write($"- {i + 1} ");
            if (ItemList[i].IsEquip == true) Console.Write("[E] ");
            Console.WriteLine($"{ItemList[i].Name} | {ItemList[i].ItemType} + {ItemList[i].State} | {ItemList[i].Description}");
        }
        

        Console.WriteLine("0. 나가기\n");
        Console.WriteLine("원하시는 행동을 입력해주세요.");
        Console.Write(">> ");

        int select = int.Parse(Console.ReadLine());

        if (select == 0) game = false;
        else if (select > 0 && select <= ItemList.Count && ItemList[select - 1] != null )
        {
            if (ItemList[select - 1].IsEquip == true)//장비 해제
            {
                ItemList[select - 1].IsEquip = false;
                if (ItemList[select - 1].ItemType == "공격력") player.EquipAttack -= ItemList[select - 1].State;
                else if (ItemList[select - 1].ItemType == "방어력") player.EquipDefense -= ItemList[select - 1].State;
            }
            else //장비 장착
            {
                ItemList[select - 1].IsEquip = true;
                if (ItemList[select - 1].ItemType == "공격력") player.EquipAttack += ItemList[select - 1].State;
                else if (ItemList[select - 1].ItemType == "방어력") player.EquipDefense += ItemList[select - 1].State;

                if (ItemList[select - 1].ItemType == "공격력")// 선택한 아이템이 공격력이면
                {
                    //공격력 아이템은 모두 false로 변경
                    for (int i = 0; i < ItemList.Count; i++)
                    {
                        if (ItemList[i].ItemType == "공격력" && ItemList[i].IsEquip == true)
                        {
                            ItemList[i].IsEquip = false;
                            player.EquipAttack -= ItemList[i].State;
                        }
                    }
                    ItemList[select - 1].IsEquip = true;
                    player.EquipAttack += ItemList[select - 1].State;
                }
                else if (ItemList[select - 1].ItemType == "방어력")// 선택한 아이템이 공격력이면
                {
                    //공격력 아이템은 모두 false로 변경
                    for (int i = 0; i < ItemList.Count; i++)
                    {
                        if (ItemList[i].ItemType == "방어력" && ItemList[i].IsEquip == true)
                        {
                            ItemList[i].IsEquip = false;
                            player.EquipDefense -= ItemList[i].State;
                        }
                            
                    }
                    ItemList[select - 1].IsEquip = true;
                    player.EquipDefense += ItemList[select - 1].State;
                }
            }
        }
        else 
        {
            Console.WriteLine("잘못된 입력입니다. 계속하려면 아무 키나 누르세요...");
            Console.ReadKey();
        }
    }
}

현재 if문으로 도배를 해놔서 가독성이 엄청 떨어지고 중복으로 또 장착했다가 해제하는데 이렇게 구현된 이유가

장착된 아이템과 같은 타입의 장비가 장착 되어있으면 장착된 아이템을 해제하고 장착을 하기위해 이렇게 구현이 되었다.

 

사실 저도 이게 맞는지 모르겠지만 엄청 가독성이 떨어지고 문제가 많다는 것은 알고 있지만 일단 완성은 했으니....

 이 짤처럼 어쨋든 작동하니까... 아무튼 작동은 하니까....

C# 기본 문법을 배운지 4일차 

아직까지는 기본 문법만 공부해서 그런지 아직까지는 큰 무리는 없는것 같다(강의 숙제가 힘든건 함정.....) 

 

그래서 이번까지 배운 문법을 활용해서 TEXT RPG를 만드는 것이 목표이다.

 

우선 interface를 활용하여 플레이어의 인터페이스를 구현하자

public interface ICharacter
{
    string Name { get; set; }
    string ClassName { get; }
    int Health { get; set; }
    int Attack { get; }
    int Defence { get; set; }
    bool IsDead { get; }
    void TakeDamage(int damage);
}

public class Player : ICharacter
{
    public string Name { get; set; }
    public string ClassName { get; set; }
    public int Health { get; set; }
    public int AttackPower { get; set; }
    public int Defence { get; set; }
    public bool IsDead => Health <= 0;
    public int Attack => new Random().Next(10, AttackPower); // 공격력은 랜덤

    public int Level = 1;
    public int Gold = 1500;

    public Player(string name, string Select)
    {
        Name = name;

        if (Select == "1" || Select == "전사")
        {
            ClassName = "전사";
            Health = 120;
            AttackPower = 20;
            Defence = 10;
        }
        else if (Select == "2" || Select == "도적")
        {
            ClassName = "도적";
            Health = 100;
            AttackPower = 30;
            Defence = 5;
        }
    }
}

여기서 ICharacter 인터페이스를 만든 이유는 지금 당장은 플레이어 뿐이지만 나중에 몬스터를 추가 했을때 동일한 구조를 따르도록 설계하기 위해서 인터페이스로 만들었다.

 

다음으로 플레이어 이름과 직업 클래스를 사용자로 부터 입력 받기위해

class Program
{
    static void Main(string[] args)
    {
        
        Console.WriteLine("무한의 던전에 오신 여러분 환영합니다.");
        Console.WriteLine("원하시는 이름을 설정 해주세요.");
        Console.Write("이름 : ");
        string name = Console.ReadLine();

        //저장 시스템 만들기

        //직업 선택창 만들기
        Console.Clear();
        Console.WriteLine("스파르타 던전에 오신 여러분 환영합니다.");
        Console.WriteLine("원하시는 직업을 골라주세요.");
        Console.WriteLine("1. 전사 ");
        Console.WriteLine("2. 도적 ");
        Console.Write(">> ");
        string select = Console.ReadLine();
        
        Player player = new Player(name, select);
        
        MainStage mainStage = new MainStage(player);
        mainStage.Start();
    }
}

플레이어 객체를 생성하고 생성된 플레이어 객체를 통해 플레이어의 이름과 직업 클래스를 저장한다.

데이터가 저장된 플레이어 객체를 MainStage 클래스에 전달을 해줌으로서 앞으로 게임 진행을 할때 플레이어 데이터를 더 효과적으로 관리가 가능하다.

 

이제  MainStage 클래스에는 게임에 대한 메인 메뉴를 만들 것이다.

기본적으로 상태창, 인벤토리, 상점을 구연을 해보자

public class MainStage(Player player)
{
    bool game = true;
    public void Start()
    {
        MainMenu();
    }
    public void MainMenu()
    {
        while (game)
        {
            Console.Clear();
            Console.WriteLine("에데온 마을에 오신 여러분 환영합니다.");
            Console.WriteLine("이곳에서 던전으로 들어가기전 활동을 할 수 있습니다.\n");
            Console.WriteLine("1. 상태 보기");
            Console.WriteLine("2. 인벤토리");
            Console.WriteLine("3. 상점\n");
            Console.WriteLine("0. 게임 종료\n");
            Console.WriteLine("원하시는 행동을 입력해주세요.");
            Console.Write(">> ");
            string choice = Console.ReadLine();

            switch (choice)
            {
                case "1":
                    StateUI();
                    break;
                case "2":
                    InventoryUI();
                    break;
                case "3":
                    //ShopUI();
                    break;
                case "0":
                    game = false;
                    return;
                default:
                    Console.WriteLine("잘못된 입력입니다. 계속하려면 아무 키나 누르세요...");
                    Console.ReadKey();
                    break;
            }
        }
    }
}

 

 

스위치 문을 통해 입력 받은 숫자에 따라 상태창, 인벤토리, 상점으로 넘어간다.

이제 메인에서 전달 받은 플레이어 정보를 상테창에 표시를 하면 간단하게 상태창을 만들 수 있다.

public void StateUI()
{
    while (game)
    {
        Console.Clear();
        Console.WriteLine("상태 보기");
        Console.WriteLine("캐릭터의 정보가 표시됩니다.\n");
        Console.WriteLine($"Lv. {player.Level}");
        Console.WriteLine($"{player.Name} ( {player.ClassName} )");
        Console.WriteLine($"공격력 : {player.AttackPower}");
        Console.WriteLine($"방어력 : {player.Defence}");
        Console.WriteLine($"체 력 : {player.Health}");
        Console.WriteLine($"Gold : {player.Gold} G");
        Console.WriteLine("\n0. 나가기\n");
        Console.WriteLine("원하시는 행동을 입력해주세요.");
        Console.Write(">> ");


        string input = Console.ReadLine();
        if (input == "0") MainMenu();
        else
        {
            Console.WriteLine("잘못된 입력입니다. 계속하려면 아무 키나 누르세요...");
            Console.ReadKey();
        }
    }

}

오늘은 TEXT RPG 게임의 기본틀과 상태창까지 만들어 보았다. 아직까지는 큰 어려움이 없었지만 이제 만들 인벤토리와 상점을 만들 생각을 하니 살짝 두려움이 몰려온다........

유니티에서 Silder를 이용해서 오디오 볼륨을 조절하는 기능을 구현해 보았다.

 


우선 GameObject를 만들어 AudioManager라고 이름을 지어주고 AudioManger.cs 스크립트를 만들어 붙여준다.

 

 

AudioManager.cs 스크립트를 열어 코드를 작성해준다.

using UnityEngine;
using UnityEngine.UI;

public class AudioManager : MonoBehaviour
{
    public AudioSource bgmSource;
    public Slider bgmVolumeSlider;


    public AudioClip clip;

    private void Awake()
    {
        bgmSource = GetComponent<AudioSource>();
    }

    void Start()
    {
        bgmVolumeSlider.value = bgmSource.volume;
        bgmSource.clip = this.clip;
        bgmSource.loop = true;
        bgmSource.Play();
    }

    void Update()
    {
        bgmSource.volume = bgmVolumeSlider.value;
    }
}

 

코드 작성을 완료했으면 슬라이더 UI를 추가해 적당한 크기로 설정후 

AudioManager에 Audio Source 컴포넌트, bgm clip, 슬라이더 UI를 붙여준다

 

 

이제 시작버튼을 눌러 잘 작동하는지 확인을 해주면 완성

어제 딕셔너리(Dictionary)에 대해 배웠는데 리스트(List), 배열(Array)와 차이가 궁금하여 구글링도 하고 ChatGPT에 물어보기도하고 김재경 튜터님에게도 물어 보았다.

 

김재경 튜터님의 말씀으로 딕셔너리를 이해를 하려면 리스트와 배열을 알아야한다. 리스트와 배열에서 필요한 A데이터를 찾는다면 리스트와 배열 내부에 있는 데이터들을 순차적으로 비교를 하면서 A데이터를 찾기 때문에 시간이 오래 걸릴 수 있다. 하지만 딕셔너리는 Key값과 Value값이 1대1 대응하기 때문에 Key 값으로 바로바로 필요한 데이터를 빠르게 찾을 수 있다고 하셨다. 

 

 


리스트 (List <T>)

  • 특징
    • 저장 공간 크기가 가변적
    • 내부적으로 배열을 사용하지만, 데이터가 추가되면 자동으로 크기를 늘려 관리
    • 순서를 유지하며 데이터에 접근 가능
  • 장점
    • 저장 공간 크기 가변성 : 데이터 추가/삭제가 자유롭고, 자동으로 크기 조정.
    • 순서 유지 : 데이터 삽입 순서를 보존
    • 유연성 : 검색, 정렬 등 다양한 메서드 제공
  • 단점
    • 중간 삽입/삭제 성능 저하 : 요소를 이동 해야 하므로 O(n)의 시간 소모
    • 탐색 기능 저하 : 특정 값을 찾으려면 순차적으로 탐색 O(n)의 시간 소모
    • 메모리 재할당 비용 : 내부 배열의 크기를 늘릴 때, 새 배열로 복사
  • 사용 예시
    • 대기열 : 사이트에 접속을 위해 대기열을 기다리는 상황 사람들은 순서대로 대기하며, 새로 온 사람은 뒤에 서고 기존 사람은 앞에서 차례대로 접속
    • 장바구니 : 온라인 쇼핑몰에서 장바구니에 상품을 추가/제거 하는 경우 물건

배열 (Array)

  • 특징
    • 고정된 저장 공간 크기 구조로, 메모리를 효율적으로 사용
    • 요소를 순서대로 저장하며, 빠른 인덱스 접근을 제공
  • 장점
    • 빠른 인덱스 접근 : 특정 위치의 데이터를 즉시 읽고 쓸 수 있음 O(1)의 시간 소모
    • 메모리 효율성 : 고정된 크기로 생성되어 추가 메모리를 사용하지 않음
    • 성능 우수 : 단순 구조로 반복 작업이나 연산에서 빠름.
  • 단점
    • 저장 공간 크기 고정 : 생성 시 크기를 지정해야 하며, 이후 변경 불가
    • 데이터 추가/삭제 불편 : 크기를 변경하려면 새로운 배열을 생성하고 데이터를 복사해야 함
    • 택색 성능 저하 : 특정 값을 찾을려면 순차 탐색이 필요 O(n)의 시간 소모
  • 사용 예시
    • 좌석 배치 : 대형 공연장이나 영화과에서 좌석 배치는 고정된 배열로 설정. 한 번 설정된 좌석 수는 변경 되지 않으며, 각 좌석은 고유한 번호로 접근 가능
    • 날씨 데이터 : 7일간의 날씨 데이터를 저장할 때. 한 주 동안의 날씨 예보를 배열로 저장하고, 날짜별 날씨 데이터를 빠르게 찾음

 


딕셔너리 (Dictionary <Tkey, Tvalue>)

  • 특징
    • 키-값 쌍으로 데이터를 저장하며, 키를 통해 값을 빠르게 검색
    • 키는 고유해야 하며 중복 키를 허용하지 않음
  • 장점
    • 빠른 검색 속도 : 키를 사용해 평균 O(1)의 시간 소모
    • 키 기반 데이터 관리 : 데이터의 고유성을 보장하며 관리 용이
    • 유연성 : 다양한 키와 값 타입 지원
  • 단점
    • 메모리 사용량 증가 : 해시 테이블 구조로 인해 추가 메모리 소모
    • 값 기반 검색 비효율 : 값을 통해 키를 찾으려면 순차 탐색 필요 O(n)의 시간 소모
    • 순서 보장 없음 : 기본적으로 삽입 순서가 유지되지 않음(C# 7.3 이하 버)
  • 사용 예시
    • 전화번호부 : 사람들의 이름(키)과 전화번호(값)을 매칭하여 빠르게 찾음. 이름을 검색하면 해당 전화번호가 즉시 나옴
    • 도서관 책 관리 시스템 : 책의 ISBN(국제 표준 도서 번호)번호를 키로 사용하고 책의 제목, 저자, 출판사 등의 정보를 값으로 저장하여 빠르게 책을 검색 가능

비교

데이터 구조 특징 장점 단점
리스트 가변적인 크기, 순서 유지 -데이터 추가/삭제 용이
-데이터 삽입 순서 보존
-유연한 메서드 제공
-탐색/삽입 시 성능 저하
-메모리 재할당 발생 가능
배열 고정된 크기, 빠른 인덱스 접근 -고정된 크기로 메모리 효율적
-빠른 인덱스 접근
-단순 구조로 반복 작업이나 연산 빠름
-크기 변경 불가
-데이터 추가/삭제 불편
-탐색 성능 저하
딕셔너리 키-값 쌍으로 저장, 빠른 검색 -데이터 고유성 보장
-관계 표현 용이
-키 기반으로 빠른 검색
-해시 테이블 구조로 인해 추가 메모리 소모
-값 기반 검색 비효율

 

 

사용 예시 및 추천 사용처

데이터 구조 사용 예시 추천 사용처
리스트 온라인 쇼핑몰 장바구니, 대기열 -동적으로 데이터 추가/삭제가 필요한 경우
-순서가 중요한 경우
배열 좌석 배치, 날씨 데이터 -고정 크기 데이터
-빠른 인덱스 기반 접근이 필요한 경우
딕셔너리 전화번호부, 도서관 책 관리 시스템 -고유 식별자를 통해 빠르게 데이터를 검색해야 하는 경우
-데이터 관계 관리

 

오늘은 미니 프로젝트로 사전 캠프때 만들었던 카드 뒤집기 게임을 가지고 팀원과 역할을 나누어 기능 추가 및 UI 변경을 하였다. 게임의 기능이 어느 정도 완성이 되서 GitHub로 서로 공유하고 기능을 합치는 과정에서 조금 이해가 늦어 지는 부분이 있었는데 

public static Dictionary<int, string> names = new Dictionary<int, string>();
public static Dictionary<int, bool> clears = new Dictionary<int, bool>();

void Start()
{
    names.Add(1, "name_LJG");
    names.Add(2, "name_BSY");
    names.Add(3, "name_CJH");
    names.Add(4, "name_JHG");
    names.Add(5, "name_PSH");

    for(int i = 0; i < names.Count; i++)
        clears[i] = false;
}

이 부분이 이해가 잘 되지 않았다. 처음 봤을 때는 리스트와 같은 것인가? 리스트로 간단하게 만들면 되는 것 아닌가? 했지만 팀원에게 물어보니 리스트는 키 값으로 int로 접근이 가능한데 Dictionary는 int 뿐만 아니라 문자열이나 다양한 변수형을 넣을 수 있다는 장점이 있다고 설명을 해주었다. 물론 간단한 내용에는 리스트로 사용하는 것이 좋을 때도 있다고 했다.

 

다른 설명으로 Dictionary를 직역하면 사전이라는 뜻인데 사전에서는 키워드를 기준으로 원하는 정보를 찾는다 그래서 Dictionary에는 Key와 Value를 통해 데이터를 저장하고 찾는다.라고 설명을 해주셨다.


그래서 이런식으로 Key값을 문자열 Value값을 문자열로 설정하여 이름과 학과를 저장 가능하고

Dictionary<string, string> StudentData = new Dictionary<string, string>();

StudentData.Add("김철수", "컴퓨터공학과");
StudentData.Add("최민수", "기계공학과");
StudentData.Add("박진철", "전기전자공학과");

foreach (var student in StudentData)
{
    Console.WriteLine($"이름: {student.Key}, 학과: {student.Value}");
}

 

Key값 문자열 Value값 정수형으로 하여 이름과 나이를 저장할 수도 있다.

Dictionary<string, int> StudentData = new Dictionary<string, int>();

StudentData.Add("김철수", 25);
StudentData.Add("최민수", 26);
StudentData.Add("박진철", 40);

foreach (var student in StudentData)
{
    Console.WriteLine($"이름: {student.Key}, 나이: {student.Value}");
}

 

또한 정수형 정수형, 정수형 문자열도 가능하여 다양한 방법으로 데이터를 효율적으로 저장이 가능하다.

오늘 팀 프로젝트를 위해 GitHub 사용 방법에 대해 강의를 들었다.

 

GitHub 파일 업로드

우측 상단에 File -> New repository 순서대로 눌러 Create a new repository 창을 열어준다

 

Name은 GitHub 내에서 표시되는 repository 이름을 적어준다.

 

Local path는 내 컴퓨터 내에서 업로드할 파일 위치를 지정해준다.

 

Git ignre는 Git에서 제외시킬 수 있는 설정 파일을 말하는데 설정 파일은 Unity에서 작업을 하니까 Unity로 설정 해준다.

 

이런 식으로 설정을 완료 해주면 Create repository를 눌러 만들어 준다

 

 

완료가 되면 이런 식으로 되는데 현재는 내 데스크탑에만 파일이 있는 것이라 중앙 상단에 Publish repository를 눌러 GitHub에 업로드 준비를 해준다

 

버튼을 누르면 Publish repository창이 뜨면서 공유될 이름과 Keep this code private 버튼으로 공개, 비공개를 선택해준다.

 

설정을 완료 후 Publish repository를 눌러준다. 

 

그러면 GitHub에 파일을 업로드 가능하다. 만약 GitHub내에 파일을 수정한다면 위에 Local path로 지정된 파일내 파일을 수정하고 

저는 Unity Game File을 폴더에 넣어주었습니다.

Fetch origin을 눌러 수정된 파일을 업로드 준비를 해준다

 

 

이렇게 Changes탭을 누르면 바뀐 파일들(Unity project file)이 있는데 좌측 하단 Summary 탭에 간단한 설명을 기입후 Commit to main을 눌러 업로드 준비를 해준다.

 

업로드가 완료되면 Fetch origin이 Push origin으로 바뀌면서 마지막으로 업로드를 해준다

 

업로드가 완료되면 History창에서 업로드된 파일들을 확인 할 수 있다.

 

 

GitHub를 처음 사용해봐서 강의를 들어도 긴가민가 하는 부분이 많았지만 팀원들과 서로 소통을 하여 게속 사용해보니 어느정도는 사용 할 수 있게 되었다.

+ Recent posts