전체 글 (206)

  • 2024.01.04
  • 2024.01.03
  • 2024.01.02
  • 2023.12.30
  • 2023.12.30
  • 2023.12.30
  • 2023.12.29
  • 2023.12.28
  • 2023.12.27
  • 2023.12.26
  • 2023.12.22
  • 2023.12.21
  • 01
    04

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 9일차

    부분진행도 : Chapter2.1 - 4일차

    작성일자 : 2024.01.04(목)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    간단한 던전 RPG 텍스트 콘솔 게임의 개인과제 제출이 내일 오후 6시이다.

    팀원들 모두 각자 열심히 진행을 하는 중.

    나는 선택 기능 구현 목록을 오늘 마무리 하였고, 현재 디버그를 하며 코드 상의 어색한 부분을 계속 수정중이다.

    선택 기능 구현 모두 무게가 있는 기능이라 모든 부분에서 힘을 쓰다 보니, 800줄 가까이 되는 코드를 작성했다.

    오늘의 기록을 위해 영상을 찍으면서도 글자 색 지정을 안했거나 하는 자잘한 미스도 보였다.

     

    2. 오늘 학습에 대해

    기능 구현을 모두 마친 후 돌아보니, 기존 작성했던 코드에서는 존재하는 아이템을 모두 shopItems 리스트에 Item 클래스 형태의 객체로 추가 해 두고, inventory 리스트에 추가하거나 빼는 방식을 사용하고 있었는데, 앞으로 개발이 계속된다면 퀘스트로 얻는 아이템이나 몬스터 드랍템 등 Shop에서 취급하지 않는 아이템도 많이 생길 것이기 때문에, 전체 아이템을 관리하는 리스트인 gameItems를 사용하는 구조로 바꾸는데에 많은 시간을 쏟았다.

     

    게임 구조

    기본적으로 모든 씬은 while(true) 안에 두어 플레이어에게 잘못된 입력값을 받으면 그 씬이 다시 로드되도록 하였다.

    main(){
    	ShowMainMenu(); // 메인 씬
    }
    ShowMainMenu(){
        while(true){
            (if 입력값 == 1){ 상태; }
            (if 입력값 == 2){ 상점; }
            (if 입력값 == 3){ 던전; }
            (if 입력값 == 0){ 게임종료; }
        }
    }
    
    상태(){
        while(true){
        
        	// 상태 씬 구성
            
        	(if 입력값 == 0){ break; // 뒤로가기 }
        }
    }
    
    상점(){
        while(true){
        
        	// 상점 씬 구성
            
        	(if 입력값 == 1){ 아이템구매 }
        	(if 입력값 == 2){ 아이템판매 }
        	(if 입력값 == 0){ break; // 뒤로가기 }
        }
    }
    ...

    먼저 계산을 마칠 필요성이 있는 구문들은 while문 위에 작성하거나, 아래의 코드처럼 같은 화면을 로드하지만 경고문을 같이 보여주는 경우도 자주 있다.

    아이템구매(){
    	alertMessage = ""
    	while(true){
        
        	// 기존 아이템 구매 씬의 내용
            
            // Write를 사용하면 alertMessage가 빈 문장이어도 티가 나지 않는다
            Console.Write(alertMessage)
        
        	(if 골드부족){ alertMessage = "골드가 부족해요\n\n" }
        }
    }

     

    기능 구현에 관해서는 전체적으로 비슷한 무게를 두고 구현했던 기분이기 때문에, 주석을 적은 전체 코드를 아래와 같이 적어놓았다.

    더보기

     

    using System;
    using System.Collections.Generic;
    
    namespace Chapter2
    {
        internal class Program
        {
    
            enum ItemType
            {
                Armor,
                Weapon,
                Potion
            }
            enum DungeonDifficulty
            {
                Easy,
                Normal,
                Hard
            }
    
            // 아이템 클래스 정의
            class Item
            {
                public string Name { get; set; }
                public int Attack { get; set; }
                public int Defense { get; set; }
                public string Description { get; set; }
                public bool IsEquipped { get; set; }
                public int Price { get; set; }
                public bool IsPurchased { get; set; }
                public ItemType Type { get; set; }
                public bool IsAvailableInShop { get; set; }
            }
    
            static int level = 1;
            static string name = "말랑단단장";
            static string job = "전사";
            static float baseAttack = 10;
            static int baseDefense = 5;
            static int health = 100;
            static int gold = 2500;
    
            static int currentDungeonClears = 0; // 현재 레벨에서의 던전 클리어 횟수
    
    
            // 인벤토리 리스트
            static List<Item> inventory = new List<Item>();
    
            // 상점 아이템 목록
            static List<Item> gameItems = new List<Item>
            {
                new Item { Name = "수련자 갑옷", Defense = 5, Type = ItemType.Armor, Description = "수련에 도움을 주는 갑옷입니다.", Price = 1000, IsPurchased = false, IsAvailableInShop = true },
                new Item { Name = "무쇠갑옷", Defense = 9, Type = ItemType.Armor, Description = "무쇠로 만들어져 튼튼한 갑옷입니다.", Price = 2000, IsPurchased = false, IsAvailableInShop = true },
                new Item { Name = "스파르타의 갑옷", Defense = 15, Type = ItemType.Armor, Description = "스파르타의 전사들이 사용했다는 전설의 갑옷입니다.", Price = 3500, IsPurchased = false, IsAvailableInShop = true },
    
                new Item { Name = "낡은 검", Attack = 2, Type = ItemType.Weapon, Description = "쉽게 볼 수 있는 낡은 검 입니다.", Price = 600, IsPurchased = false, IsAvailableInShop = true },
                new Item { Name = "청동 도끼", Attack = 5, Type = ItemType.Weapon, Description = "어디선가 사용됐던거 같은 도끼입니다.", Price = 1500, IsPurchased = false, IsAvailableInShop = true },
                new Item { Name = "스파르타의 창", Attack = 7, Type = ItemType.Weapon, Description = "스파르타의 전사들이 사용했다는 전설의 창입니다.", Price = 2500, IsPurchased = false, IsAvailableInShop = true },
    
                new Item { Name = "힘의 비약", Attack = 1, Type = ItemType.Potion, Description = "마을 뒷편의 폭포수가 담기면 영험한 기운이 솟아납니다.", Price = 200, IsPurchased = false, IsAvailableInShop = true },
                new Item { Name = "방어의 비약", Defense = 1, Type = ItemType.Potion, Description = "마을 우물의 물이 담기면 수호신의 기운이 깃듭니다.", Price = 200, IsPurchased = false, IsAvailableInShop = true }
            };
    
            static void Main(string[] args)
            {
                ShowMainMenu();
            }
    
            static void ShowMainMenu()
            {
                bool isRunning = true;
                string alertMessage = "";
    
                while (isRunning)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[스파르타 마을]");
                    Console.ResetColor();
                    Console.WriteLine("스파르타 마을에 오신 여러분 환영합니다.");
                    Console.WriteLine("이곳에서 던전으로 들어가기 전 활동을 할 수 있습니다.");
                    Console.WriteLine("");
                    Console.WriteLine("1. 상태 보기");
                    Console.WriteLine("2. 인벤토리");
                    Console.WriteLine("3. 상점");
                    Console.WriteLine("4. 던전 입장");
                    Console.WriteLine("5. 휴식하기");
                    Console.WriteLine("");
                    Console.WriteLine("6. 저장하기");
                    Console.WriteLine("7. 불러오기");
                    Console.WriteLine("8. 초기화");
                    Console.WriteLine("");
                    Console.WriteLine("0. 종료");
                    Console.WriteLine("");
                    Console.WriteLine("");
                    Console.Write(alertMessage);
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (input == "1")
                    {
                        ShowCharacterStatus();
                    }
                    else if (input == "2")
                    {
                        ShowInventory();
                    }
                    else if (input == "3")
                    {
                        ShowShop();
                    }
                    else if (input == "4")
                    {
                        EnterDungeon();
                    }
                    else if (input == "5")
                    {
                        Rest();
                    }
                    else if (input == "6")
                    {
                        alertMessage = SaveGame();
                    }
                    else if (input == "7")
                    {
                        alertMessage = LoadGame();
                    }
                    else if (input == "8")
                    {
                        alertMessage = ResetGame();
                    }
                    else if (input == "0")
                    {
                        isRunning = false; // 게임 종료
                        Console.WriteLine("");
                        Console.WriteLine(" ¯\\_(ツ)_/¯ 게임을 종료합니다 ¯\\_(ツ)_/¯");
                    }
                }
            }
    
            static void ShowCharacterStatus()
            {
                float totalAttack = baseAttack;
                int totalDefense = baseDefense;
    
                foreach (var item in inventory)
                {
                    if (item.IsEquipped)
                    {
                        totalAttack += item.Attack;
                        totalDefense += item.Defense;
                    }
                }
    
                while (true)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[상태 보기]");
                    Console.ResetColor();
                    Console.WriteLine("캐릭터의 정보가 표시됩니다.\n");
                    Console.WriteLine($"Lv. {level.ToString("D2")}");
                    Console.WriteLine($"{name} ( {job} )");
                    Console.WriteLine($"공격력 : {totalAttack} (+{totalAttack - baseAttack})");
                    Console.WriteLine($"방어력 : {totalDefense} (+{totalDefense - baseDefense})");
                    Console.WriteLine($"체 력 : {health}");
                    Console.WriteLine($"Gold : {gold} G");
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (input == "0")
                    {
                        break; // [스파르타 마을]로 돌아감
                    }
                }
            }
    
            static void ShowInventory()
            {
                while (true)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[인벤토리]");
                    Console.ResetColor();
                    Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.");
                    Console.WriteLine("");
                    Console.WriteLine("[아이템 목록]");
    
                    if (inventory.Count == 0)
                    {
                        Console.WriteLine("보유한 아이템이 없습니다.");
                    }
                    else
                    {
                        for (int i = 0; i < inventory.Count; i++)
                        {
                            string stats = GetItemStats(inventory[i]);
                            string equipped = inventory[i].IsEquipped ? "[E]" : "";
                            Console.WriteLine($"({i + 1}) {equipped}{inventory[i].Name} | {stats} | {inventory[i].Description}");
                        }
                    }
    
                    Console.WriteLine("");
                    Console.WriteLine("1. 장착 관리");
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (input == "1")
                    {
                        ManageEquipment();
                    }
                    else if (input == "0")
                    {
                        break; // [스파르타 마을]로 돌아감
                    }
                }
            }
    
            static void ManageEquipment()
            {
                while (true)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[인벤토리 - 장착 관리]");
                    Console.ResetColor();
                    Console.WriteLine("보유 중인 아이템을 관리할 수 있습니다.");
                    Console.WriteLine("");
                    Console.WriteLine("[아이템 목록]");
    
                    for (int i = 0; i < inventory.Count; i++)
                    {
                        string stats = GetItemStats(inventory[i]);
                        string equipped = inventory[i].IsEquipped ? "[E]" : "";
                        Console.WriteLine($"{i + 1}. {equipped}{inventory[i].Name} | {stats} | {inventory[i].Description}");
                    }
    
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (input == "0")
                    {
                        break; // [인벤토리]로 돌아감
                    }
                    else if (int.TryParse(input, out int itemIndex) && itemIndex > 0 && itemIndex <= inventory.Count)
                    {
                        Item selectedItem = inventory[itemIndex - 1];
                        if (selectedItem.Type != ItemType.Potion)
                        {
                            // 같은 타입의 다른 아이템을 해제
                            foreach (var item in inventory)
                            {
                                if (item.Type == selectedItem.Type && item != selectedItem)
                                {
                                    item.IsEquipped = false;
                                }
                            }
                        }
                        // 선택한 아이템 장착 상태 토글
                        selectedItem.IsEquipped = !selectedItem.IsEquipped;
                    }
                }
            }
    
            static void ShowShop()
            {
                while (true)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[상점]");
                    Console.ResetColor();
                    Console.WriteLine("필요한 아이템을 얻을 수 있는 상점입니다.");
                    Console.WriteLine("");
                    Console.WriteLine("[보유 골드]");
                    Console.WriteLine($"{gold} G");
                    Console.WriteLine("");
    
                    Console.WriteLine("[아이템 목록]");
                    foreach (var item in gameItems.Where(item => item.IsAvailableInShop))
                    {
                        string stats = GetItemStats(item);
                        string purchaseStatus = item.IsPurchased ? "구매완료" : $"{item.Price} G";
                        Console.WriteLine($"- {item.Name} | {stats} | {item.Description} | {purchaseStatus}");
                    }
    
                    Console.WriteLine("");
                    Console.WriteLine("1. 아이템 구매");
                    Console.WriteLine("2. 아이템 판매");
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (input == "1")
                    {
                        PurchaseItem();
                    }
                    else if (input == "2")
                    {
                        SellItem();
                    }
                    else if (input == "0")
                    {
                        break; // [스파르타 마을]로 돌아감
                    }
                }
            }
    
    
            static void PurchaseItem()
            {
                string alertMessage = "";
    
                while (true)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[상점 - 아이템 구매]");
                    Console.ResetColor();
                    Console.WriteLine("필요한 아이템을 얻을 수 있는 상점입니다.");
                    Console.WriteLine("");
                    Console.WriteLine("[보유 골드]");
                    Console.WriteLine($"{gold} G");
                    Console.WriteLine("");
    
                    Console.WriteLine("[아이템 목록]");
                    for (int i = 0; i < gameItems.Count; i++)
                    {
                        string stats = GetItemStats(gameItems[i]);
                        string purchaseStatus = gameItems[i].IsPurchased ? "구매완료" : $"{gameItems[i].Price} G";
                        Console.WriteLine($"{i + 1}. {gameItems[i].Name} | {stats} | {gameItems[i].Description} | {purchaseStatus}");
                    }
    
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
    
                    Console.Write(alertMessage);
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (int.TryParse(input, out int itemIndex) && itemIndex > 0 && itemIndex <= gameItems.Count)
                    {
                        Item selectedItem = gameItems[itemIndex - 1];
                        if (selectedItem.IsPurchased)
                        {
                            alertMessage = "이미 구매한 아이템입니다.\n\n";
                        }
                        else if (gold >= selectedItem.Price)
                        {
                            selectedItem.IsPurchased = true;
                            inventory.Add(selectedItem);
                            gold -= selectedItem.Price;
                            alertMessage = "구매를 완료했습니다.\n\n";
                        }
                        else
                        {
                            alertMessage = "Gold가 부족합니다.\n\n";
                        }
                    }
                    else if (input == "0")
                    {
                        break; // [상점]으로 돌아감
                    }
                }
            }
    
            static void SellItem()
            {
                string alertMessage = "";
                while (true)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[상점 - 아이템 판매]");
                    Console.ResetColor();
                    Console.WriteLine("필요한 아이템을 판매할 수 있는 상점입니다.\n");
    
                    Console.WriteLine("[보유 골드]");
                    Console.WriteLine($"{gold} G\n");
    
                    Console.WriteLine("[아이템 목록]");
                    for (int i = 0; i < inventory.Count; i++)
                    {
                        string stats = GetItemStats(inventory[i]);
                        int sellPrice = (int)(inventory[i].Price * 0.85);
                        Console.WriteLine($"{i + 1}. {inventory[i].Name} | {stats} | {inventory[i].Description} | {sellPrice} G");
                    }
    
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
                    Console.Write(alertMessage);
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (int.TryParse(input, out int itemIndex) && itemIndex > 0 && itemIndex <= inventory.Count)
                    {
                        Item selectedItem = inventory[itemIndex - 1];
                        int sellPrice = (int)(selectedItem.Price * 0.85);
                        gold += sellPrice;
                        selectedItem.IsEquipped = false; // 장착 해제
    
                        // 상점에서 구매 여부 업데이트
                        var shopItem = gameItems.FirstOrDefault(item => item.Name == selectedItem.Name);
                        if (shopItem != null)
                        {
                            shopItem.IsPurchased = false;
                        }
    
                        inventory.RemoveAt(itemIndex - 1); // 아이템 목록에서 제거
                        alertMessage = $"{selectedItem.Name}를 {sellPrice} G에 판매했습니다.\n\n";
                    }
                    else if (input == "0")
                    {
                        break; // [상점]으로 돌아감
                    }
                }
            }
    
            static void EnterDungeon()
            {
                float totalAttack = baseAttack;
                int totalDefense = baseDefense;
    
                foreach (var item in inventory)
                {
                    if (item.IsEquipped)
                    {
                        totalAttack += item.Attack;
                        totalDefense += item.Defense;
                    }
                }
    
                while (true)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[던전 입장]");
                    Console.ResetColor();
                    Console.WriteLine("");
    
                    string healthStr = $"│ 체력  : {health}";
                    string attackStr = $"│ 공격력 : {totalAttack}";
                    string defenseStr = $"│ 방어력 : {totalDefense}";
                    string goldStr = $"│ 골드  : {gold}";
                    Console.WriteLine("┌──────────────────────┐");
                    Console.WriteLine(PadRightToLength(healthStr, 20) + "│");
                    Console.WriteLine(PadRightToLength(attackStr, 20) + "│");
                    Console.WriteLine(PadRightToLength(defenseStr, 20) + "│");
                    Console.WriteLine(PadRightToLength(goldStr, 20) + "│");
                    Console.WriteLine("└──────────────────────┘");
    
                    if (health <= 0) Console.WriteLine("\n※ 던전 입장을 위해 체력이 1 이상 필요합니다");
                    Console.WriteLine("");
                    Console.WriteLine("1. 쉬운 던전   | 방어력 5 이상 권장");
                    Console.WriteLine("2. 일반 던전   | 방어력 11 이상 권장");
                    Console.WriteLine("3. 어려운 던전 | 방어력 17 이상 권장");
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
                    
    
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
                    string input = Console.ReadLine();
    
                    if (input == "1" && health>0)
                    {
                        ProcessDungeon(DungeonDifficulty.Easy, 5, 1000);
                    }
                    else if (input == "2" && health > 0)
                    {
                        ProcessDungeon(DungeonDifficulty.Normal, 11, 1700);
                    }
                    else if (input == "3" && health > 0)
                    {
                        ProcessDungeon(DungeonDifficulty.Hard, 17, 2500);
                    }
                    else if (input == "0")
                    {
                        break; // [스파르타 마을]로 돌아감
                    }
                }
            }
    
            static void ProcessDungeon(DungeonDifficulty difficulty, int recommendedDefense, int baseReward)
            {
                float totalAttack = baseAttack;
                int totalDefense = baseDefense;
    
                foreach (var item in inventory)
                {
                    if (item.IsEquipped)
                    {
                        totalAttack += item.Attack;
                        totalDefense += item.Defense;
                    }
                }
    
                bool isSuccess = true; // 던전 성공 여부
                int healthLoss = new Random().Next(20, 36); // 기본 체력 감소량
                int reward = baseReward; // 기본 보상
    
                // 방어력이 권장 수치보다 낮은 경우 실패 확률 적용
                if (totalDefense < recommendedDefense)
                {
                    isSuccess = new Random().NextDouble() >= 0.4;
                    healthLoss += recommendedDefense - totalDefense; // 체력 감소량 증가
                }
                else
                {
                    healthLoss -= totalDefense - recommendedDefense; // 체력 감소량 감소
                }
    
                // 체력 감소 적용
                health -= Math.Max(healthLoss, 0);
    
                // 보상 계산
                if (isSuccess)
                {
                    int attackBonus = new Random().Next((int)totalAttack, (int)(totalAttack * 2 + 1));
                    reward += (int)(baseReward * (attackBonus / 100.0));
                }
    
                // 결과 출력
                while (true)
                {
                    Console.Clear();
                    if (isSuccess)
                    {
    
                        Console.ForegroundColor = ConsoleColor.Yellow;
                        Console.WriteLine("[던전 클리어]");
                        Console.ResetColor();
                        Console.WriteLine($"축하합니다!!\n{GetDifficultyString(difficulty)} 던전을 클리어 하였습니다.");
                        Console.WriteLine($"\n[탐험 결과]\n체력 {health + healthLoss} -> {health}\nGold {gold} G -> {gold + reward} G");
                        gold += reward;
    
                        // 레벨업 로직
                        currentDungeonClears++;
                        if (currentDungeonClears >= level)
                        {
                            LevelUp();
                            Console.WriteLine($"\n축하합니다! 레벨이 {level}로 상승했습니다.");
                        }
                    }
                    else
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine("[던전 실패]");
                        Console.ResetColor();
                        Console.WriteLine($"아쉽게도 {GetDifficultyString(difficulty)} 던전을 클리어하지 못했습니다.");
                        Console.WriteLine($"\n[탐험 결과]\n체력 {health + healthLoss / 2} -> {health}");
                    }
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (input == "0")
                    {
                        break; // [던전 입장]으로 돌아가기
                    }
                }
            }
    
            static void Rest()
            {
                string alertMessage = "";
                while(true)
                {
                    Console.Clear();
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine("[휴식하기]");
                    Console.ResetColor();
                    Console.WriteLine($"500 G를 내면 체력을 회복할 수 있습니다. (보유 골드 : {gold} G)");
                    Console.WriteLine("");
                    Console.WriteLine("1. 휴식하기");
                    Console.WriteLine("");
                    Console.WriteLine("0. 나가기");
                    Console.WriteLine("");
                    Console.Write(alertMessage);
                    Console.Write("원하시는 행동을 입력해주세요.\n>> ");
    
                    string input = Console.ReadLine();
    
                    if (input == "1")
                    {
                        if (health == 100)
                        {
                            alertMessage = "체력이 이미 최대치입니다.\n\n";
                        }
                        else if (gold < 500)
                        {
                            alertMessage = "보유 골드가 부족합니다.\n\n";
                        }
                        else
                        {
                            gold -= 500;
                            health = 100;
                            alertMessage = "휴식을 완료했습니다. 체력이 100이 되었습니다.\n\n";
                        }
                    }
                    else if (input == "0")
                    {
                        break; // [스파르타 마을]로 돌아가기
                    }
    
                }
            }
    
            // 아이템 능력치 반환
            static string GetItemStats(Item item)
            {
                List<string> stats = new List<string>();
                if (item.Attack > 0)
                {
                    stats.Add($"공격력 +{item.Attack}");
                }
                if (item.Defense > 0)
                {
                    stats.Add($"방어력 +{item.Defense}");
                }
                return string.Join(" ", stats);
            }
    
            // 던전 Difficulty에 대한 문자열 반환
            static string GetDifficultyString(DungeonDifficulty difficulty)
            {
                switch (difficulty)
                {
                    case DungeonDifficulty.Easy:
                        return "쉬운";
                    case DungeonDifficulty.Normal:
                        return "일반";
                    case DungeonDifficulty.Hard:
                        return "어려운";
                    default:
                        return "알 수 없는";
                }
            }
    
            // 문자열의 우측을 공백으로 채움
            static string PadRightToLength(string str, int totalLength)
            {
                return str.PadRight(totalLength);
            }
    
            static void LevelUp()
            {
                level++;
                baseAttack = 10 + (level - 1) * 0.5f; // 레벨에 따른 기본 공격력 재계산
                baseDefense = 5 + (level - 1); // 레벨에 따른 기본 방어력 재계산
                currentDungeonClears = 0; // 클리어 횟수 초기화
            }
    
            static string SaveGame()
            {
                using (StreamWriter file = new StreamWriter("savegame.txt"))
                {
                    file.WriteLine(level);
                    file.WriteLine(name);
                    file.WriteLine(job);
                    file.WriteLine(baseAttack);
                    file.WriteLine(baseDefense);
                    file.WriteLine(health);
                    file.WriteLine(gold);
                    file.WriteLine(currentDungeonClears);
    
                    // 인벤토리 저장 (아이템 이름과 장착 여부)
                    foreach (var item in inventory)
                    {
                        file.WriteLine($"{item.Name},{item.IsEquipped}");
                    }
                    // 상점템 구매 여부 저장
                    foreach (var item in gameItems)
                    {
                        if (item.IsPurchased)
                        {
                            file.WriteLine($"ShopItem,{item.Name}");
                        }
                    }
                }
                return "진행도를 저장하였습니다.\n\n";
            }
    
            static string LoadGame()
            {
                if (File.Exists("savegame.txt"))
                {
                    using (StreamReader file = new StreamReader("savegame.txt"))
                    {
                        level = int.Parse(file.ReadLine());
                        name = file.ReadLine();
                        job = file.ReadLine();
                        baseAttack = float.Parse(file.ReadLine());
                        baseDefense = int.Parse(file.ReadLine());
                        health = int.Parse(file.ReadLine());
                        gold = int.Parse(file.ReadLine());
                        currentDungeonClears = int.Parse(file.ReadLine());
    
                        // 상점 아이템 구매 여부 초기화
                        foreach (var item in gameItems)
                        {
                            item.IsPurchased = false;
                        }
    
                        // 인벤토리 로드
                        inventory.Clear();
                        string line;
                        while ((line = file.ReadLine()) != null)
                        {
                            string[] parts = line.Split(',');
                            if (parts[0] == "ShopItem")
                            {
                                // 상점 아이템 구매 여부 로드
                                var itemToPurchase = gameItems.FirstOrDefault(item => item.Name == parts[1]);
                                if (itemToPurchase != null)
                                {
                                    itemToPurchase.IsPurchased = true;
                                }
                            }
                            else
                            {
                                // 인벤토리 아이템 로드
                                var itemToAdd = gameItems.FirstOrDefault(item => item.Name == parts[0]);
                                if (itemToAdd != null)
                                {
                                    inventory.Add(itemToAdd);
                                    itemToAdd.IsEquipped = bool.Parse(parts[1]);
                                }
                            }
                        }
                    }
    
                    return "진행도가 로드되었습니다.\n\n";
                }
                else
                {
                    return "저장된 데이터가 없습니다.\n\n";
                }
            }
    
            static string ResetGame()
            {
                level = 1;
                name = "말랑단단장"; 
                job = "전사"; 
                baseAttack = 10;
                baseDefense = 5;
                health = 100;
                gold = 2500;
                currentDungeonClears = 0;
    
                inventory.Clear(); // 인벤토리 초기화
                gameItems.ForEach(item => item.IsPurchased = false); // 상점 아이템 초기화
    
                return "진행도가 초기화되었습니다.\n\n";
            }
        }
    }

     

    위 코드에 대한 설명

    아이템 클래스 정의
    - Item 클래스에 아이템의 구성요소를 필드로 두어 관리

    캐릭터 관련 기본적인 스테이터스들
    - 굳이 Player 클래스로 둘 필요가 있을까 고민하다가 굳이 그렇게 할 필요성이 없어 보여 전역 필드로 사용

    전체적인 게임아이템을 관리하는 List와, 인벤토리 List
    - Item 클래스 형태를 멤버로 갖는 리스트 형태로 관리
    - 초기화를 진행하지 않은 필드는 ["", 0, False] 등의 값을 가진다.
    - 즉 IsPurchased 필드는 현재 작성된 코드처럼 굳이 false로 지정해줄 필요는 없음.

    Main()
    - ShowMainMenu() 메서드를 호출하여 게임 시작

    ShowMainMenu()
    - 모든 씬(Show메서드)은 while문을 사용하여 그리며, 잘못된 입력을 받을 시 현재 메뉴를 다시 보여줌.

    ShowCharacterStatus()
    - while문 안에는 최소한의 코드를 두어 자원을 절약
    - $문자열 적극 사용

    ShowInventory()
    - 필요 시, alertMessage에 문구를 저장하여 화면 갱신 시 보여줌. 
    - inventory.Count만큼 반복하여 아이템의 정보를 표시.
    - GetItemStats() 메서드를 통해 아이템의 모든 능력치를 문자열로 반환받는다.
    - string equipped = inventory[i].IsEquipped ? "[E]" : "";
    - inventory[i].IsEquipped 가 참일 경우, "[E]"를, 거짓일 경우 ""를 equipped 에 저장한다.

    ManageEquipment()
    - 장비 장착 목적의 입력값에 대해
    - int형으로 Parse가 가능하면 true를 반환하고, else if 문 내에서 itemIndex 변수에 저장
    - 또한 itemIndex가 인벤토리 크기 범위 내인지 확인하여 만족한다면 장비를 장착하거나 해제

    ShowShop(), PurchaseItem(), SellItem()
    - 아이템목록 표시 시, IsPurchased 가 true일 경우, "구매완료" 문구를 함께 표시
    - 아이템 구매를 성공할 경우, 해당 Item 객체는 inventory 리스트에 추가하여 관리할 수 있도록 함. 복사본이 아닌 객체 참조 형태.
    - 아이템 판매를 성공할 경우, 85% 가격을 돌려받으며, IsEquipped을 false로, inventory.RemoveAt(index)를 통해 List에서 제거한다.

    EnterDungeon(), ProcessDungeon()
    - 던전 관련 장면을 구현. 선택한 난이도의 던전 입장 시, 캐릭터 스테이터스와 확률에 따라 성공과 실패를 한다.
    - 캐릭터 스테이터스를 요구하기 때문에, 구현 기능목록에는 없었지만 캐릭터 스테이터스를 기본적으로 보여주도록 함.
    - 여러가지 계산 관련하여 코드가 길어졌다.
    - 체력이 0 이하일 경우, 던전에 진입 불가. 이것도 요구사항에는 없었지만 기본적으로 이 정도 제한은 당연하다고 생각하여 구현.

    Rest()
    - 500골드를 지불하고 체력을 100으로 만든다

    string GetItemStats(Item item)
    - item의 공격력, 방어력 중 0보다 높은 능력치를 집계하여 문자열로 반환

    string GetDifficultyString(DungeonDifficulty difficulty)
    - 던전 Difficulty에 대한 한글로 된 문자열 반환

    string PadRightToLength(string str, int totalLength)
    - 문자열의 우측을 공백으로 채움

    LevelUp()
    - 던전 탐험 시 레벨업 로직을 작성
    - 게임데이터를 로드 하였을 경우에도 올바르게 적용되어야하기 때문에, '레벨에 따라' 기본 스텟을 연산하는 로직을 작성하였다.

    SaveGame()
    - 레벨, 이름, 직업, 기본공격력, 기본방어력, 현재체력, 골드, 경험치, 인벤토리목록+장착여부, 상점템구매여부
    - savegame.txt로 저장하여 보안요소는 없다.

    LoadGame()
    - 인벤토리 및 장착여부와 상점아이템 구매 여부를 반영하는 데에 코드가 길어졌다.
    - inventory.Clear(); 로 쉽게 리스트 비우기가 가능
    - 아이템들은 모두 객체참조를 하여 아이템 내 필드를 쉽게 관리할 수 있도록 하였다.

    ResetGame()
    - 진행도를 초깃값으로 초기화

     

    3. 시연 영상(gif, mp4)

    길이가 3분정도라서, 가능하면 동영상으로 보는 게 좋을 듯 하다.

     

     

     

    4. 과제에 대해

    - 주어진 요구사항에는 모두 만족하지만, 플레이어 입장에서 불친절한 화면 구성이 많다. 당장 제출이 내일이기 때문에, 현재 캠프에서 이 프로젝트의 앞으로의 방향성을 듣고 난 이후 개선할 지 검토를 해봐야 한다.

    - '=>' 같은 기호는 쓰기는 하는데 볼 때마다 무슨 기능인지 계속 헷갈린다. 파이썬에서도 데코레이트라던가 람다 함수라던가 필요하면 찾아는 쓰는데 볼 때마다 까먹는 요소들이 엄청 많은데 그야말로 과제요소가 아닌가 싶다. 숙련도 부족 이슈.

     

    5. 참고자료

    반응형
    COMMENT
     
    01
    03

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 8일차

    부분진행도 : Chapter2.1 - 3일차

    작성일자 : 2024.01.03(수)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    개인 과제 주간의 3일차이다.

    C# 문법강의는 모두 빠르게 보기는 하였지만, 제네릭과 out 및 ref 키워드, 알고리즘 연습 외 몇 가지를 이후 복습 할 예정이다. 오늘

    오늘은 개인 과제의 필수 구현 요소를 확인하며, 선택 구현 요소 중 세 가지를 구현하였다.

     

    2. 오늘 학습에 대해

    오늘자 강의 중, 사용자 정의 예외 처리를 하는 예시

    // Exception을 상속받아 예외 클래스를 생성한다.
    public class NegativeNumberException : Exception
    {
        public NegativeNumberException(string message) : base(message)
        {
        }
    }
    try
    {
        int number = -10;
        if (number < 0)
        {
        	// 매개변수 message로 사용할 문자열을 전달하며, 예외를 발생
            throw new NegativeNumberException("음수는 처리할 수 없습니다.");
        }
    }
    catch (NegativeNumberException ex)
    {
        Console.WriteLine(ex.Message); // "음수는 처리할 수 없습니다."
    }
    catch (Exception ex)
    {
    	// NegativeNumberException 이외의 예외처리
        Console.WriteLine("예외가 발생했습니다: " + ex.Message);
    }

     

    개인 과제에 대해서는 내일 선택 요구사항까지 구현을 마친 후, 코드의 분석을 별도의 게시물로 작성할 예정이다.

     

    3. 앞으로의 과제에 대해

    - 개인 과제 완성, GPT를 통한 리뷰와 리팩토링

    - 강의 중 이해가 부족한 부분 재학습

     

    4. 참고자료

    - 없음

    반응형
    COMMENT
     
    01
    02

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 7일차

    부분진행도 : Chapter2.1 - 2일차

    작성일자 : 2024.01.02(화)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    강의 듣기 및 개인 프로젝트의 2일차다. 개인 프로젝트를 살짝 진행 해 두었고, 강의의 진도를 나갔다.

    C# 문법 강의를 듣고 있는데 처음 보는 내용들이 있어 시간이 걸리고 있다.

    오늘은 C# 학습 내용에 대해 살짝 정리하고, 개인 프로젝트에 대해서는 이후 작성할 것.

     

    2. 오늘 학습에 대해


    Visual Studio의 디버깅 관련

    - F9로 코드상의 브레이크 포인트 지정

    - F5로 디버깅 시작

    - F10으로 코드 디버깅 순차적으로 진행

    - 확인 원하고자 하는 변수 위에 마우스 올려 고정버튼 가능

     

    foreach문

    - Python의 for i in ['a','b','c']과 같은 역할을 한다. 예제는 아래와 같음

    string[] inventory = { "검", "방패", "활", "화살", "물약" };
    
    foreach (string item in inventory)
    {
        Console.WriteLine(item);
    }

     

    Random 사용 예시

    string[] choices = { "가위", "바위", "보"};
    string computerChoice = choices[new Random().Next(0,3)] // 0,1,2중에 랜덤한 값 하나

     

    캐스트 관련 참고사항

    double average = sum / scores.Length; // int형인 sum과 scores.Length 둘 중 하나는 double로 캐스팅 해야 맞는 average가 나옴
    double average = (double)sum / scores.Length;

     

    다차원 배열의 선언과 초기화

    int[,] array2da = new int[2,3]; // 2행 3열
    array2da[0,0] = 1; // 초기화
    int[,] array2db = new int[2,3]{{1,2,3},{4,5,6}}; // 선언과 초기화


    컬렉션(Collection)


    - 배열과는 다르게 크기가 가변적
    - System.Collections.Generic 네임스페이스 추가 필요

     

    컬렉션:List 의 예시

    List<int> list = new List<int>();
    list.Add(1);
    list.Add(2);
    list.Add(3);
    list.Remove(2);
    foreach(int i in list){Console.WriteLine(i);}
    for(int i=0; i<list.Count; i++){Console.WriteLine(list[i]);}
    // 1 3

     

    컬렉션:Dictionary 의 예시

    Dictionary<string, int> scores = new Dictionary<string, int>();
    scores.Add("Alice",100);
    foreach(DeyValuePair<string, int> pair in scores){Console.WriteLine(pair.Key + ": " + pair.Value);

     

     

    컬렉션:Stack 의 예시

    Stack<int> stack1 = new Stack<int>();
    stack1.Push(1);
    stack2.Push(2);
    int value = stack1.Pop(); // value = 2



    컬렉션:Queue 의 예시

    Queue<int> queue1 = new Queue<int>();
    queue1.Enqueue(1);
    queue1.Enqueue(2);
    int value = queue1.Dequeue(); // value = 1


    컬렉션:HashSet(중복되지 않은 요소로 이루어진 집합) 의 예시

    HashSet<int> set1 = new HashSet<int>();
    set1.Add(1);
    set1.Add(2);
    foreach(int element in set1){ Console.WriteLine(element); }


    구조체

    여러 개의 데이터를 묶어 하나의 사용자 정의 형식으로 만듦

    struct Person
    {
        public string Name;
        public int Age;
    
        public void PrintInfo()
        {
            Console.WriteLine($"Name: {Name}, Age: {Age}");
        }
    }
    Person person1;
    person1.Name = "John";
    person1.Age = 25;
    person1.PrintInfo();


    클래스:용어메모와 구조체와의 비교

    - 특징 : 캡슐화/상속/다형성/추상화/객체 
    - 구성요소 : 필드/메서드/생성자/소멸자
    - 구조체와 클래스는 모두 사용자 정의 형식을 만드는 데 사용될 수 있다.
    - 구조체는 값 형식으로, 스택에 할당되고 복사될 때 값이 복사된다.
    - 클래스는 참조 형식으로, 힙에 할당되고 참조로 전달되어 성능 측면에서 구조체와 차이가 있다.
    - 구조체는 상속을 받을 수 없지만, 클래스는 단일 상속 및 다중 상속이 가능하다.
    - 구조체는 작은 크기의 데이터 저장이나 단순한 데이터 구조에 적합하며, 클래스는 더 복잡한 객체를 표현하고 다양한 기능을 제공하기 위해 사용한다.


    접근제한자

    - public : 외부에서도 자유 접근 가능
    - private : 같은 클래스 내부에서만 접근 가능
    - protected : 같은 클래스 내부와 상속받은 클래스에서 접근 가능

    프로퍼티(Property)

    - 객체의 필드에 직접 접근하지 않고 간접적으로 필드값을 읽거나 설정하는 데에 사용되는 접근자(Accessor) 메서드의 조합.
    - 필드에 대한 접근제어와 데이터 유효성 검사 등을 수행
    - 필드와 마찬가지로 객체의 상태를 나타내는 데이터 역할을 하지만, 외부에서 접근할 때 추가적인 로직을 수행 할 수 있음.
    - get과 set 접근자를 사용하여 동작을 정의한다
    - get 접근자는 프로퍼티의 값을 반환하고, set 접근자는 프로퍼티의 값을 설정한다.
    - 필요에 따라 두 접근자 중 하나를 생략하여 읽기 전용 혹은 쓰기 전용 프로퍼티를 정의할 수 있다.

    class Person
    {
        private string name;
        private int age;
    
        public string Name
        {
            get { return name; }
            private set { name = value; }
        }
    
        public int Age
        {
            get { return age; }
            set
            {
                if (value >= 0)
                    age = value;
            }
        }
    }
    
    Person person = new Person();
    person.Name = "John";     // 컴파일 오류: Name 프로퍼티의 set 접근자는 private입니다.
    person.Age = -10;         // 유효성 검사에 의해 나이 값이 설정되지 않습니다.
    
    Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");  // Name과 Age 프로퍼티에 접근하여 값을 출력합니다.

     

    또한, 다음과 같은 자동 프로퍼티 구문으로 간단하게 정의하고 사용이 가능

    [접근 제한자] [데이터 타입] 프로퍼티명 { get; set; }
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    Person person = new Person();
    person.Name = "John";     // 값을 설정
    person.Age = 25;          // 값을 설정
    
    Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");  // 값을 읽어 출력

     

     

    상속

    - 여러 부모의 클래스를 하나에 자식에 상속받는 다중상속은 C#에서 불가능
    - 단, 하나의 클래스와 동시에 여러 개의 인터페이스를 상속받는 것은 가능

    public class Animal
    {
        public string Name { get; set; }
        public int Age { get; set; }
    
        public void Eat() { Console.WriteLine("Animal is eating."); }
        public void Sleep() { Console.WriteLine("Animal is sleeping."); }
    }
    
    public class Dog : Animal
    {
        public void Bark() { Console.WriteLine("Dog is bark."); }
    }

     

     

    다형성 : 가상 메서드

    - 자식 클래스에서 재정의 할 수 있는 메서드

    public class Unit{
        public virtual void Move(){
            Console.WriteLine("두발로 걷기");
        }
    }
    public class Marine:Unit{
        public override void Move(){
            Console.WriteLine("네발로 걷기");
        }
    }
    
    Marine marine = new Marine();
    marine.Move(); // virtual/override 를 사용하지 않았어도, 문제 없이 "네 발로 걷기"를 출력
    
    List<Unit> list = new List<Unit>();
    list.Add(new Marine()); // 부모의 형태로 관리하는 경우, virtual/override를 사용하여 실형태인 자식을 탐색할 수 있도록 함
    foreach(Unit unit in list) { unit.Move(); } // 즉, virtual/override를 사용하지 않았다면, "두발로 걷기"를 출력

     

     

    추상 클래스와 메서드

    - 추상 클래스는 직접적인 인스턴스를 생성 불가
    - 상속을 위한 뼈대 클래스로 사용
    - 추상 클래스는 abstract 키워드를 사용하여 선언되고, 추상 메서드를 포함 할 수 있다.
    - 추상 메서드는 구현부가 없는 메서드이며, 자식 클래스에서 의무적으로 구현을 요구.

    abstract class Shape{ public abstract void Draw(); }
    class Circle : Shape{ public override void Draw() { Console.WriteLine("Drawing a Circle"); } }

     

     

    용어 혼동 주의

    - 오버라이딩(Overriding) : 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 재정의 하는 것
    - 오버로딩(Overloading) : 매개변수의 갯수와 타입에 따라 동일한 이름의 여러개의 메서드를 정의하는 것


    제너릭 / out, ref 키워드에 대한 학습과 정리는 내일 중 진행

     

    3. 과제에 대해

    - 개인 과제의 필수 항목은 1차적으로 완성하였고, UI 개편과 선택 항목 구현의 진행이 필요.

    - 3주차 강의까지 학습 완료, 5주차 강의까지 마치기.

    반응형
    COMMENT
     
    12
    30
    ['./lala_data/searchImg/defaultDelay10/test.png']
    Exception in thread ./lala_data/searchImg/defaultDelay10/test.png:
    Traceback (most recent call last):
      File "threading.py", line 950, in _bootstrap_inner
      File "buff_alarm_tray.py", line 94, in run
      File "lalatools\DigitalImageProcessing\ImageProcess.py", line 44, in match
      File "lalatools\DigitalImageProcessing\ImageProcess.py", line 22, in __init__
    cv2.error: OpenCV(4.6.0) D:\a\opencv-python\opencv-python\opencv\modules\imgproc\src\templmatch.cpp:1164: error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 in function 'cv::matchTemplate'

    아마 실행파일(exe) 뿐 아니라 터미널에서 실행하였어도 같은 문제가 발생했을 것으로 예상된다.

    본인의 경우에는 아래와 같이 Pyinstaller로 작성한 실행파일을 실행하였을 경우, 콘솔창에 위와 같은 로그가 남았다.

     

    작성했던 코드에서는 특별한 문제가 없었지만 위와 같은 에러와 함께 cv2가 이미지를 불러오지 못하는 경우

    관리자 권한으로 실행하면 문제를 해결 할 수 있었다.

    매번 관리자 권한으로 실행을 선택하기 귀찮은 경우 아래와 같이 파일 속성에서 설정 해 둘 수도 있다.

     

    반응형
    COMMENT
     
    12
    30

    위와 같은 Unhandled exception in script 에러창이 팝업되며 이하 전문은 아래와 같다.

    Traceback (most recent call last):
      File "buff_alarm_tray.py", line 27, in <module>
      File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
      File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
      File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
      File "PyInstaller\loader\pyimod02_importers.py", line 419, in exec_module
      File "autoit\__init__.py", line 6, in <module>
      File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
      File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
      File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
      File "PyInstaller\loader\pyimod02_importers.py", line 419, in exec_module
      File "autoit\autoit.py", line 26, in <module>
    OSError: Cannot load AutoItX from path: C:\Users\happy\AppData\Local\Temp\_MEI493962\autoit\lib\AutoItX3_x64.dll

    즉 해당 경로에서 AutoItX3_x64.dll 파일을 찾을 수 없다는 내용.

     

    이 경우, pyinstaller를 실행할 때, --add-data 옵션으로 경로를 직접 지정해 줄 수 있다.

    본인의 경우 해당 라이브러리 경로에 AutoItX3_x64.dll 파일이 위치했다.

    (happy는 사용자 이름이다)

    C:\Users\happy\AppData\Local\Programs\Python\Python39\Lib\site-packages\autoit\lib\

    아래와 같이 수정하여 pyinstaller 로 다시 실행파일을 만들어준다.

    # 수정 전
    pyinstaller --onefile --icon='./lala_data/exeIcon.ico' .\buff_alarm_tray.py
    
    # 수정 후
    pyinstaller --onefile --add-data "C:\Users\happy\AppData\Local\Programs\Python\Python39\Lib\site-packages\autoit\lib\AutoItX3_x64.dll;autoit\lib" --icon='./lala_data/exeIcon.ico' .\buff_alarm_tray.py

    새로 만들어진 실행파일은 처음과 같은 에러가 발생하지 않는다.

    반응형
    COMMENT
     
    12
    30

    Pyinstaller 을 사용하여 실행파일을 만들 경우 아래와 같은 OSError: [WinError 225] 에러가 발생하였을 경우, 

    - OSError: [WinError 225] 파일에 바이러스 또는 기타 사용자 동의 없이 설치된 소프트웨어가 있기 때문에 작업이 완료되지 않았습니다
    
    - win32ctypes.pywin32.pywintypes.error: (225, '', '파일에 바이러스 또는 기타 사용자 동의 없이 설치된 소프트웨어가 있기 때문에 작업이 완료되지 않았습니다')

     

    Windows Defender에서 해당 프로젝트 폴더를 검사 제외 목록에 추가하여 해결한다.

    설정 > 업데이트 및 보안 > Windows 보안 > 바이러스 및 위협 방지 > 바이러스 및 위협 방지 설정 > 제외

     

     

     

    반응형
    COMMENT
     
    12
    29

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 6일차

    부분진행도 : Chapter2.1 - 1일차

    작성일자 : 2023.12.29(금)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    캠프에서 새로운 팀 편성을 하였다. 1인 개발자, 회사생활 등 경험이 많은 팀원분들이 많으셨다. 약간 쭈글이 모드.

    이번 팀은 2주간 유지된다. 첫 주는 C# 강의와 함께 개인 과제를 위주로 학습하고, 후반 주차는 첫 주에 한 내용을 바탕으로 팀 프로젝트를 진행하여 더 심화된 결과물을 만든다. 이번 과정에서는 Unity는 사용하지 않고 C#만을 사용하여 텍스트로 진행하는 RPG게임이다.

     

    2. 오늘 학습에 대해

    오늘은 학습 시간을 활용하여 C# 강의를 들었다. Unity에서 사용하던 메서드와도 미묘하게 다르고, 다른 언어와도 혼동되는 코드들이 많기 때문에 참고가 될 만한 내용을 정리한다.

    개발 IDE 환경에 대해

    - Visual Studio Installer에서 설치 시에, C#과 동시에 .NET 프레임워크도 설치한다.

    - 솔루션에서 프로젝트 생성 시 [최상위 문 사용 안함]에 체크하여도 무방하다.

    C# 코딩에 대해

    - 나중에 기억하기 힘들 것 같은 literal의 예시 몇 가지

    - 정수형 리터럴
        - 0x10 (16진수 int)
        - 0b10 (2진수 int)
        - 10L (long)
        - 10UL (unsigned long)
    - 부동소수점형 리터럴
        - 3.14 (double)
        - 3.14f (float)
        - 3.14m (decimal)
    - 문자형 리터럴
        - '\u0022' (유니코드 문자)
    - 문자열 리터럴
        - @"문자열 내 개행 문자
        사용하기"

    - 의외로 되는 변수 관련 코드

    int num1, num2, num3 = 10; // num1과 num2는 선언만 된 상태, num3에는 10을 저장
    
    num1 = num2 = num3 = 30; // num3에 30, num2에 30, num1에 30을 저장

    - 코드 컨벤션 몇 가지

    식별자(Identifiers) : 변수, 메서드, 클래스, 인터페이스 등에 사용되는 이름
    식별자 표기법
    - PascalCase: 클래스, 메서드, 프로퍼티 등에 사용
    - camelCase: 변수, 매개변수, 로컬변수 등에 사용
    - 대문자 약어: ID, HTTP, FTP 등 특수한 경우에 사용
    - snake_case : Python에서는 변수나 함수명으로 사용했으나 C#에서는 사용하지 않는 것으로 보임

    - 문자열 관련 메서드 몇 가지

    // 구분자를 통해 문자열 나누기
    string input = Console.ReadLine();
    string[] strs = input.Split(' ');
    
    // 문자 'H'를 5개로 구성된 문자열 생성
    string str1 = new string('H', 5);
    
    // 문자열에서 검색하여 인덱스 반환
    string str2 = "Hello, World!";
    int index = str2.IndexOf("World"); // 7
    
    // 문자열 치환
    string str3 = "Hello, World!";
    string newStr3 = str3.Replace("World", "Universe");
    
    // 문자열을 숫자로 변환 시에는 Parse() 사용
    string str4 = "123";
    int num1 = int.Parse(str4);
    
    // 숫자를 문자열로 변환 시에는 ToString() 사용
    int num2 = 123;
    string str5 = num2.ToString();
    
    // 문자열 대소 비교
    // ASCII 비교가 되며, -1/0/1 중 하나를 반환.
    // 첫 매개변수에서 둘째 매개변수를 뺀 값이라고 기억하면 좋음
    string str6 = "Apple";
    string str7 = "Banana";
    int compare = string.Compare(str6, str7); // -1
    
    // 문자열 포맷팅 : 문자열 형식화
    string name = "John";
    int age = 30;
    string message1 = string.Format("My name is {0} and I'm {1} years old.", name, age);
    
    // 문자열 포맷팅 : 문자열 보간
    string message2 = $"My name is {name} and I'm {age} years old.";

    이상 기억하면 좋을만한 내용 정리.

     

    3. 과제에 대해

    주어진 C# 문법 강의의 1/3정도를 완료하였다. 강의 수강을 하루이틀 정도에 마친 후, 이번주차에 주어진 개인과제를 확인할 예정이다. 개인과제 제출의 기한은 7일 뒤인 23/1/5(금) 19:00까지. RPG게임 하나를 만드는 과제이기 때문에 꽤 시간이 걸릴지도 모르겠다.

    반응형
    COMMENT
     
    12
    28

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 5일차

    부분진행도 : Chapter1 - 5일차

    작성일자 : 2023.12.28(목)

    개발일지 목록 : 클릭


    0. 글 작성에 앞서

    카드 뒤집어 매칭하는 게임에 대해 5인으로 구성된 팀 활동으로 4일간 여러가지 기능 구현을 마친 후 오늘 프로젝트 발표회를 하였다. 팀 내에 불화는 없었고 모두 열심히 참여하여 나름 순조로운 엔딩이 되었다.

     

    1. 진행중인 프로젝트에 대해

    어제 발표시연 녹화영상과 레파지토리 주소를 제출한 후, 어제 남겨졌던 한개의 버그를 오후 2시의 발표 전까지 고치는 작업을 하였다. 대부분의 시간을 소비하여 버그는 고쳐졌고, 그 외 난이도별로 주어지는 시간 설정을 다르게 하거나, 결과로 얻어지는 점수의 스펙트럼을 계산하는 등 발표자에게 참고가 되면 좋을 내용들에 대해 작업하였다.

    발표회에서는 총 11개 팀이 발표를 하였는데, 게임에 구현된 참신한 기능부터 임팩트 있는 발표자료까지 배울 점이 많았다.

     

    2. 오늘 학습에 대해

    발표를 보며 추가했으면 좋았다고 생각한 기능들

    • 저장된 점수 초기화 버튼
    • 사운드바로 음량 조절(데이터 저장하여 게임 재시작 시에도 사용)
    • 시연 발표 시에도 안 그래도 너무 큰 사운드 작게 조절 할 수 있어서 좋은듯
    • 잠긴 스테이지는 자물쇠 아이콘, 다음 난이도 해금 조건은 이전난이도의 ‘클리어’로 하는 게 좋았을 듯
    • 엔딩 크레딧
    • 게임을 진행하여 모으는 재화 시스템
    • (튜터 코멘트) : 남은 시간 표시는 자릿수를 맞추면 좋겠다고 하는데, 남은 시간이 008.97 이렇게 뜨면 보기 좀 별로일 것 같아서 동의는 못하지만 다른 항목의 디자인적 부분으로는 참고 OK.

    발표준비에 도움이 될 내용들

    • 본인이 발표 담당이 아니더라도 따로 녹화 해 보기
    • 발표 연습도 되고, 본인 채널에 따로 업로드하여 포폴로 쓰기 좋을 듯
    • 좋은 느낌의 임팩트 있는 PPT
    • 피드백을 즉각 메모하는 습관
    • 시연을 할 때 보이스 채우기
    • (튜터 코멘트) : 겪은 문제와 그에 대한 해결에 무게를 두어 발표하는 것을 추천. 면접관들 입장에서의 면접자들의 결과물이 그게 그거임. 물음표를 던질 수 있고 성장할 수 있는 개발자를 선호.

     

    3. 과제에 대해

    내일 새로운 팀을 구성하고, 앞으로 2주 간 심화 강의를 들으며 학습을 한다고 들었는데 팀 프로젝트도 병행하는 듯 하다. 첫 주차의 프로젝트를 잘 정리 해 두고 남은 학습 기간 중에도 많이 배워가는 것이 과제.

     

    4. 참고자료

    - 어제 팀원이 녹화한 시연 영상 : https://www.youtube.com/watch?v=HXZUXY8e5dA

    반응형
    COMMENT
     
    12
    27

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 4일차

    부분진행도 : Chapter1 - 4일차

    작성일자 : 2023.12.27(수)

    개발일지 목록 : 클릭


    0. 글 작성에 앞서

    아침에 일어났다가 다시 잠들어 20분 지각하였다. 하루종일 피곤해서 머리가 잘 안돌아갔기 때문에 팀 소통에 영향을 끼쳤을지 모르겠다. 어제까지는 그래도 순조로웠는데 오늘은 버그 픽스 하나에 여러명이 몰려 좀 미묘한 상황이 되어버려 에너지를 많이 써버렸다. 캠프 시작 후 작은 첫 내적 위기.

     

    1. 진행중인 프로젝트에 대해

    디자인적인 개선은 전혀 기대하지 않았었는데, 팀에 그림 능력자가 두 명이나 있어 배경과 카드 이미지 등을 새로운 리소스로 교체하였다. 발표 담당의 팀원도 혼자 척척 준비하시는 듯. 나는 코딩 열심히 해야겠다.

    기능 구현 중 카드를 뒤집는 상황에서, 생각할 수 있는 다양하고 참신한 버그가 많이 터져 두 세명이 달라붙어 버그수정을 하였는데, 같은 코드파일을 만지고 있어서 그런건지 뭔가 심리적 거부감이 심했다. 내가 괜한 오지랖을 부리는건가 싶은 느낌도 자주 들고 그랬기 때문에 결과에 크게 영향을 끼치는 사안이 아니면 그냥 조용히 두고 보는 게 낫다고 생각했다. 그리고 분명 여러 테스트케이스로 테스트를 해보는데도 main에 병합하면 왜인지 모르겠는데 의문의 또 다른 버그가 생기고 그랬던 것도 피로도 상승에 한몫 했다.

    개인 프로젝트라면 사소한 버그도 불을 켜고 잡아낼텐데, 소통이라는 행위 자체에 너무 기가 빨리는 내 모습을 보면 역시 1인개발자가 맞지 않나 싶은 생각이 또 들었다. 물론 회사에 입사하고나면 잘 마련된 체계가 있을 것이기 때문에 이렇게까지 에너지를 쓸 것 같지는 않다.

    이번 주차의 프로젝트는 사실상 3~4일짜리였고 완벽한 게임을 요구하지는 않을 것이기 때문에 결과물 제출은 크게 문제가 없다. 발표 시연 영상의 준비는 한 명이 자진해 맡아주셔 잘 제작해주고 있는 것 같아 나머지 인원은 현재 숨을 돌리는 중.

     

    2. 오늘 학습에 대해

    코딩 부분은 오늘도 새로운 것을 배우진 않았다. 팀 활동과 Git 관련해서 경험치를 쌓은 편.

    팀 프로젝트의 역할 분담

    파트(분야)를 확실하게 분리한 곳에서는 트러블이 일어날 일이 매우 적다는 것을 다시 느꼈다. 그림이나 BGM, 그리고 발표준비는 인원배치가 확실히 되어 문제가 생겨도 독립적인 공간에서 해결을 하면 되는데, 코딩과 씬 관리는 전체가 관리하고 기능구현을 적당적당하게 분배해서 작성했기 때문에 이런 점에서 어려움이 컸다. 조금 더 구체적으로 역할분배를 하면 결과물이 좋을 것 같다고는 생각이 들지만, 코딩교육이기 때문에 어쩔 수 없는 부분이긴 하다.

     

    3. 과제에 대해

    - 내일 시연발표까지 남은 시간동안 가능한 버그 고치기

     

    4. 참고자료

    - 없음

    반응형
    COMMENT
     
    12
    26

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 3일차

    부분진행도 : Chapter1 - 3일차

    작성일자 : 2023.12.26(화)

    개발일지 목록 : 클릭


    글 작성에 앞서

    팀 프로젝트를 하며 깃 사용법과 병렬 개발을 하며 생기는 트러블에 대한 경험치를 열심히 쌓고있다. 특이사항은 없음.

     

    1. 진행중인 프로젝트에 대해

    카드를 뒤집어 같은 카드를 맞추는 게임을 개발 중, 느긋하지만 순조로운 분위기로 5인 팀 프로젝트중이다.

    이미지 리소스를 한번 갈고, 카드 애니메이션에 관한 기능구현을 마친 후, 마지막으로 시연 녹화를 한다면 일단 최종제출의 요구사항은 종료이다.

     

    2. 오늘 배운 내용에 대해

    게임 내적 코딩에 대해 새로운 내용은 없었고, 자잘한 버그픽스를 많이 했다. 모두 코드 내적으로 새로운 변수나 조건문을 쓰거나 라인 순서를 바꾸거나 등으로 해결 가능했다.

    Github와 Github Desktop 사용이 아직 미숙하다. main 브런치에 merge를 끝낸 팀원이 작업한 내용을 내 브런치로 가져오는 기능을 오늘에서야 발견하였다. 그리고 Conflict가 난 부분을 어제는 제대로 해결하지 못하고 결국 스크립트파일을 새로 작성하기도 했었는데, 오늘은 제대로 Atom Browser를(왜인지는 모르겠지만 기본 브라우저로 Atom이 설정되어 있었다) 열어 필요한 코드만을 남기고 저장 및 종료하여 Conflict를 해결하였다.

    깃허브에서는 Pull Request 만 계속 요청하고 기다렸는데, Conflict 안내가 따로 없다면 merge까지 한 후에 팀원들한테 알리면 된다고 하여 밀렸던 commit들을 모두 merge하였다. 레파지토리에 문제가 생길까봐 조심조심 하는 중.

    일단 발견한 버그들은 모두 fix하였고 내일 마무리 작업을 하게 될 것 같다.

     

    3. 과제에 대해

    - 카드 뒤집기 게임 최종확인

    - 시연 발표에 대한 회의와 준비

     

    4. 참고자료

    - 없음

    반응형
    COMMENT
     
    12
    22

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 2일차

    부분진행도 : Chapter1 - 2일차

    작성일자 : 2023.12.22(금)

    개발일지 목록 : 클릭


    글 작성에 앞서

    1주간의 팀 프로젝트인데 다시 보니 시간이 넉넉치 않았다. 월요일은 성탄절이라 쉬는날이어서, 오늘, 화요일, 수요일 중에 기능개발을 완료하고 수요일 21시까지 시연 녹화본을 포함하여 최종 제출을 해야한다.

    생각보다 오늘 진척이 잘 되었다. 오전은 다들 비몽사몽인 것 같긴 했는데, 11시에 있었던 git 실시간 강의를 듣고, 모여서 바로 github 레파지토리를 만들고 각자 기능개발에 착수했다. 팀장 맡으신 분이 경험이 있으신지 가이드를 잘 해주신다.

    노션 팀 스페이스도 열심히 갱신중

     

    1. 진행중인 프로젝트에 대해

    기본적인 카드 뒤집기 게임에 14가지 개선사항을 적용하는 것이 우선 목표이고, 5명이 약 3개정도씩의 기능구현을 맡았다.

    - 난이도별 최고점수를 보여주는 스테이지 선택 씬 구현, 순차적인 난이도 해금 시스템 구현

    - 난이도가 높아질수록 많은 카드가 나오게 하기

    - 카드 섞는 알고리즘 새로 짜기

    새로운 씬 구성은 예상은 했지만 생각했던 것보다 훨씬 많은 시간을 소모했다. 그 외의 알고리즘 관련된 내용은 열배는 더 빠르게 끝낸 듯.

    새롭게 사용한 유형의 코드는 아래와 같다.

    씬이 전환되어도 오브젝트를 유지

    DontDestroyOnLoad(gameObject);

    난이도 선택 씬에서 선택한 난이도를 오브젝트에 저장하고, 해당 오브젝트는 다른 씬으로 이동해도 파괴되지 않는다.

    데이터 불러오기와 저장

    easyScore = PlayerPrefs.GetFloat("EasyScore", 0f); // 로드
    PlayerPrefs.SetFloat("EasyScore", 100f); // 저장

    EasyScore라는 데이터가 저장되어있지 않다면 0f를 사용한다. 해당 데이터는 게임을 종료해도 보존된다.

    카드의 선정과 배치는 난이도 조건에 따라 반복문 등을 다르게 구사하여 구현한다.

    맡은 기능 개발은 모두 구현하고 Pull Request를 하였다. 팀으로 Github를 사용하는 것은 처음이었기 때문에 계속 헤맸다. 다른 팀원의 코드를 Pull 한 뒤에 Conflict 난 부분에서 뭔가 잘 안되어 저장이 이상하게 되어버려 그냥 충돌 난 그 파일을 새로 작성했다. 아까 보았던 Git 강의 다시 한 번 확인하고 익혀야 할 듯.

    아래는 오늘 작성한 난이도 선택 씬이다.

     

    2. 과제에 대해

    - 팀원들이 남은 기능구현을 마치고 모두 Merge 끝내기

    - 세세한 코드 수정과 디자인적 요소 개선

    - 시연 준비(시연할 팀원 정하기, 녹화와 발표)

     

    3. 참고자료

    - 오늘 특별히 찾아본 중요한 자료는 없는 듯

    반응형
    COMMENT
     
    12
    21

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 1일차

    부분진행도 : Chapter1 - 1일차

    작성일자 : 2023.12.21(목)

    개발일지 목록 : 클릭


    글 작성에 앞서

    본 캠프 첫째 날, 아침 9시에 OT를 진행하고, 점심시간 전에 팀 배정이 확정되어 활동을 시작했다.

     

    OT 내용 정리 : 커리큘럼에 대해

    2023.12.21 ~ 2024.05.02 약 5개월간의 훈련 일정. 훈련 2개월째에 접어들면 알고리즘 학습도 병행한다.

    매일 팀 개발을 하며 배운 내용을 정리하는 TIL(Today I Learned)과, 매주 WIL(Week I Learned)을 기록한다.

    하루일정은 오전 9시 ~ 오후 9시이며, 진행 중 두 시간은 식사시간.

    출결 기준은 ~5:59 결석, ~11:59 조퇴, 12:00~ 출석이며 3회의 외출/조퇴/지각을 할 경우 1회 결석이 된다.

    부정출결 또는, 총 결석일수가 과정의 20%를 초과 할 경우 제적이 된다. 

    커리큘럼 중의 중도하차 1회는 20만원 차감, 2회는 50만원 차감, 3회 이상은 100만원 차감. 향후 5년 간 K-Digital Training 과정 수강 불가라고 한다. 또한 한 달 마다 훈련 장려금 신청하기(31만원가량 지급)도 잊지 않도록 하자.

    안내의 자세한 내용은 캠프의 Docs에서 자세히 확인 가능. 

     

    OT 내용정리 : 첫 주차 팀 프로젝트에 대해

    사전캠프 4주차 과정에서 만들었던, 늘어놓은 카드를 순차적으로 뒤집어 같은 카드 두개를 뒤집으면 삭제하는 카드게임의 연장선이다. 새로운 이미지 리소스와 개선 기능을 15개정도 더 구현한다.

    가이드라인에 맞춰 노션 팀 스페이스를 열심히 가꾸었다. 최대한 필요한것만 간략하게 남기도록 노력했다.

    가장 걱정되는 것은, 오는 주 수요일까지 시연 발표를 녹화하는 것과 목요일에 있을 진짜 발표이다.

     

    1. 진행중인 프로젝트에 대해

    사전캠프 4주차에 진행했던 '카드 뒤집기 게임'의 결과는 위와 같다.(테스트를 위해 15초 내에 성공 못할 시 종료 판정)

    5주차에 게임 시작 씬도 넣고, 효과음과 배경음도 넣고, 광고도 넣어 복잡해졌다.

    오늘은 팀 스페이스를 되도록 자세하게 작성하는 것으로 거의 하루를 보냈다. 조원간 분업이 정말 구체적으로 되어있던 다른 팀도 보였는데, 그렇게까지 전문적으로 나누기에는 우리 팀원의 스타일이 모두 비슷비슷했다.

    협업을 위해 팀원이 모두 같은 버전을 맞추고, 한 주간 진행해야 할 흐름을 작성했다. 앞으로 구현할 개선 기능들의 난이도는 크게 어렵지 않아, 대부분의 시간을 협업이라는 것에 소모하게 될 것 같다. 처음에는 머리가 새하얘져서 아무말도 못했는데, 점점 익숙해지는중.

    프로젝트의 진척에 대해서는, 리소스로 사용할 이미지를 팀원 전원이 3장씩 제출하여 모아두었고, 내일부터 코딩을 본격적으로 할 것 같다.

     

    2. 과제에 대해

    - 굳이 적을만한 구체적인 프로젝트 관련 과제는 없는데, 웹버전 노션에서, 첨부한 이미지를 삭제할 수 있는 방법을 못찾겠다. 팀 전원 헤메는 중.

     

    3. 참고자료

    - 없음

    반응형
    COMMENT