목차
1.Stream / StreamWriter
2. 디렉터리 존재 여부
3. 디렉토리 생성 / 삭제
4. 파일 존재 여부
5. 파일 생성 / 삭제 
6. 파일 읽기 

 

📝 System.IO 네임스페이스

using System.IO;

: 파일 시스템 작업 ( 디렉터리 / 파일 생성, 삭제, 수정 )에 관한 기능 제공 

: 텍스트 파일 작업 ( 파일 데이터 읽기 쓰기 )에 관한 기능 제공 

 

→ 더 많은 Syatem.IO 네임스페이스 기능

https://learn.microsoft.com/ko-kr/dotnet/standard/io/common-i-o-tasks

 

공통적인 I/O 작업 - .NET

.NET의 System.IO 네임스페이스에 있는 클래스와 메서드를 사용하여 공통적인 파일 작업 및 공통적인 디렉터리 작업을 수행하는 방법을 알아봅니다.

learn.microsoft.com

 

📝 1. Stream / StreamWriter

□ Stream 클래스

    ■ 마이크로소프트의 홈페이지의 설명에는 " 바이트 시퀀스의 제네릭 뷰를 제공"라는 표현을 사용한다.

    ■ 바이트 시퀀스란?

          □ 연속된 바이트들의 나열 

           텍스트파일/이미지파일/음악파일 이 저장될 때 바이트들의 시퀀스들로 저장된다. 

    ■ 제네릭 뷰?

           데이터가 무엇이든(텍스트, 이미지, 음악 등) 모두 바이트로 보고 처리할 수 있게 해주는 제네릭을 사용한다.

 

예시 )

타입이 다른 파일을 다룰때, 동일한 steam을 사용한다. 

// 텍스트 파일을 다룰 때도 Stream 사용 가능
FileStream textFile = File.OpenRead("text.txt");

// 이미지 파일을 다룰 때도 동일한 Stream 사용 가능
FileStream imageFile = File.OpenRead("image.jpg");

 

□ TextWriter

    ■ 일련의 문자를 순차적으로 작성할 수 있는 작성기 ( 추상 클래스)

  StreamWriter

    ■ TextWriter을 상속받아 실제로 구현한 클래스 

    ■ 문자를 바이트로 변환해서 Stream에 쓰는 실제 작업을 수행 

 

📝 2. 디렉토리 존재 여부 

Directory.Exists(savepath)

□ Bool 값을 반환

    ■ 해당 경로에 디렉터리가 존재하면 → true

    ■ 해당 경로에 디렉토리가 존재하지 않으면 → false 

 

📝 사용

using System.IO;

internal class Program
{
    static void Main(string[] args)
    {
        string savePath = @"D:\SpartaCodingClub\TextRPGSaveFile\";
        string saveFileName = "SaveFile";

        // 해당 디렉토리 (폴더) 가 없으면 생성
        if (!Directory.Exists(savePath))
        {
            Console.WriteLine("세이브 파일이 있습니다. ");
        }
        else 
        {
            Console.WriteLine("세이브 파일이 없습니다. ");
        }    
        }
    }
}

 

📝 3. 디렉토리 생성 / 삭제

📝 생성

Directory.CreateDirectory(savePath);

□ DirectoryInfo값을 반환

    ■ 지정된 경로에서 디렉터리를 나타내는 개체

📝 삭제

diretoryInfo.Delete();

□ DirectoryInfo에 저장된 디렉터리를 삭제 

 

📝 사용

using System.IO;

internal class Program
{
    static void Main(string[] args)
    {
        string savePath = @"D:\SpartaCodingClub\TextRPGSaveFile\";
        string saveFileName = "SaveFile";

        // 디렉토리 생성 
        DirectoryInfo di = Directory.CreateDirectory(savePath);

        // 디렉토리 삭제
        di.Delete();
    }
}

 

📝 4. 파일 존재 여부

File.Exists(savePath + saveFileName)

□ Bool 값을 반환 

    ■ 해당 경로에 파일이 존재하면 → true

    ■ 해당 경로에 파일이 존재하지 않으면 → false 

 

📝 사용

    internal class Program
    {
        static void Main(string[] args)
        {
            string savePath = @"D:\SpartaCodingClub\TextRPGSaveFile\";
            string saveFileName = "SaveFile";

            if (File.Exists(path))
            {
                 Console.WriteLine("파일이 존재합니다.");
            }
        }
    }

 

📝 5. 파일 생성 / 삭제

📝 생성

File.CreateText(파일명이 포함된 경로)

 StreamWriter 을 반환

이미 해당경로에 파일이 있는 경우 해당 내용이 바뀐다. 

 

📝 삭제

File.Delete(savePath + saveFileName);

해당 경로에 있는 파일을 삭제

 

📝 사용 

    internal class Program
    {
        static void Main(string[] args)
        {
            string savePath = @"D:\SpartaCodingClub\TextRPGSaveFile\";
            string saveFileName = "SaveFile";

            // 저장경로에 생성
            using (StreamWriter sw = File.CreateText(savePath + saveFileName))
            {
                sw.WriteLine("Hello ");
                sw.WriteLine("I'm ");
                sw.WriteLine("a talking potato.");
            }

        }
    }

 

📝 5. 파일 읽기 

File.ReadAllText(파일명이 포함된 경로);

□ string을 반환

    ■  파일의 모든 텍스트를 포함하는 문자열

 

📝 사용 

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
        //GameManger.Instance.EnterGame();

        string savePath = @"D:\Window11_TextRPG\";
        string saveFileName = "TestFile";

        string desString = File.ReadAllText(savePath + saveFileName);
    }
}

 


📝 Json 타입의 문자열을 저장하고 불러오는 과정 

https://youcheachae.tistory.com/32

 

[c#][TextRPG] 2. NewtonJson으로 직렬화&역직렬화

목차1. 저장 시스템    □ 설계 2. 저장    □ NuGet     □ Newtonsoft.Json    □ Json    □ 직렬화 / 역직렬화    □ visual Studio에서 사용방법    □ 실제 사용 예시 📝 저장 시스템: nuget의 NewtonJ

youcheachae.tistory.com


 

📝 참고한 링크 

https://learn.microsoft.com/ko-kr/dotnet/api/system.io.directory.createdirectory?view=net-8.0

 

Directory.CreateDirectory 메서드 (System.IO)

지정된 경로에 모든 디렉터리를 만듭니다.

learn.microsoft.com

https://learn.microsoft.com/ko-kr/dotnet/api/system.io.file.createtext?view=net-9.0

 

File.CreateText(String) 메서드 (System.IO)

UTF-8로 인코딩된 텍스트를 쓰기 위해 파일을 만들거나 엽니다. 파일이 이미 있는 경우 해당 내용이 바뀝니다.

learn.microsoft.com

https://learn.microsoft.com/ko-kr/dotnet/api/system.io.stream?view=net-8.0

 

Stream 클래스 (System.IO)

바이트 시퀀스의 제네릭 뷰를 제공합니다. 추상 클래스입니다.

learn.microsoft.com

https://learn.microsoft.com/ko-kr/dotnet/api/system.io.streamreader.-ctor?view=net-8.0

 

StreamReader 생성자 (System.IO)

지정된 스트림에 대한 StreamReader 클래스의 새 인스턴스를 초기화합니다.

learn.microsoft.com

 


📝 깃허브

https://github.com/kimYouChae/SpartaCodingClub

 

GitHub - kimYouChae/SpartaCodingClub

Contribute to kimYouChae/SpartaCodingClub development by creating an account on GitHub.

github.com

 

목차
1. 저장 시스템
    □ 설계 
2. 저장
    □ NuGet 
    □ Newtonsoft.Json
    □ Json
    □ 직렬화 / 역직렬화
    □ visual Studio에서 사용방법
3. 실제 사용 예시

 

📝 1. 저장 시스템

: nuget의 NewtonJson을 사용하여 클래스를 직렬화 한 뒤 텍스트파일로 저장

: 저장된 텍스트 파일을 불러와서 역직렬화 

 

1. 설계

 

 

 

📝 2. 저장 

1. Nuget

: c# 저장방식을 검색하다 보면  nuget의 NewtonJson을 사용하는 글이 많이 보인다. nuget이 뭘까?

 

https://www.nuget.org/

 

NuGet Gallery | Home

The NuGet Gallery is the central package repository for NuGet, the package manager for .NET.

www.nuget.org

:  " NuGet is the package manager for. NET." 즉 닷넷의 패키지 매니저이다.

 

https://learn.microsoft.com/ko-kr/nuget/quickstart/install-and-use-a-package-in-visual-studio

 

빠른 시작: Visual Studio에서 NuGet 패키지 설치 및 사용(Windows에만 해당)

이 빠른 시작에서는 Windows용 Visual Studio 프로젝트에서 NuGet 패키지를 설치하고 사용하는 방법을 알아봅니다.

learn.microsoft.com

: NuGet 패키지에는 다른 개발자가 프로젝트에서 사용할 수 있도록 만든 재사용 가능한 코드가 포함되어 있다. 

 

2. Newtonsoft.Json

: JSON 관련 작업을 할 때 편리한 기능을 제공하는 JSON 라이브러리 

 

3.Json

📝 what is Json 

     Java Script Object Notation의약자 

    데이터를 저장하고 전송하기 위한 형식

📝Json Data

      이름/값 쌍( 키/값 쌍 )으로 구성된다 

       JSON에서는 키는 큰따옴표로 묶인 문자열이어야 한다

{
  "name": "John",
  "age": 30
}

 

→ 더 자세한 내용은 w3 school 홈페이지 참고!

https://www.w3schools.com/js/js_json_syntax.asp

 

W3Schools.com

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

4. 직렬화 / 역직렬화 

📝Serialization (직렬화)

  문자열/숫자/객체/배열/bool/null 등의 타입을 json 문자열로 변환하는 과정

  JsonConvert.SerializeObject 사용

Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Sizes = new string[] { "Small" };

string json = JsonConvert.SerializeObject(product);

 

📝 Deserializaion (역직렬화)

  json 문자열을 문자열/숫자/객체/배열/bool/null 등의 타입으로 재변환하는 과정, 직렬화의 반대과정 

JsonConvert.DeserializeObject <T> 사용

string json = @"{
  'Name': 'Bad Boys',
  'ReleaseDate': '1995-4-7T00:00:00',
  'Genres': [
    'Action',
    'Comedy'
  ]
}";

Movie m = JsonConvert.DeserializeObject<Movie>(json);

 


5. visual Studio에서 사용방법

□ 솔루션 탐색기 우클릭 → Nuget 패키지 관리 선택

□ newton 검색 후 다운로드

 

📝 3. 실제 사용 

1. 직렬화 & 저장

 // 직렬화 된 string 반환
 private string SerializedObject() 
 {
     try 
     {
         PlayerSaveData data = new PlayerSaveData();
        
        // 직렬화
         string json = JsonConvert.SerializeObject(data);

         //Console.WriteLine(json);
         return json;
     }
     catch (Exception e) 
     {
         Console.WriteLine($"직렬화 중 오류 발생 : {e.Message}");
         return string.Empty;
     }
   
 }
public void SaveData() 
{
    Console.WriteLine("데이터를 저장합니다.");

    // 직렬화
    string jsonString = SerializedObject();

    // 해당 디렉토리 (폴더) 가 없으면 생성
    if (!Directory.Exists(savePath))
        Directory.CreateDirectory(savePath);

    // string을 textFile로 저장
    // 파일이 이미 있으면 덮어씀
    using (StreamWriter sw = File.CreateText(savePath + saveFileName))
    {
        sw.WriteLine(jsonString);
    }
}

 

2. 역직렬화 / 불러오기 

 public void LoadData() 
 {
     // 경로 + 파일명에 파일이 있으면 ?
     if (File.Exists(savePath + saveFileName))
     {
         Console.WriteLine(" 저장파일이 있습니다.");

         // 불러오기 
         string desString = File.ReadAllText(savePath + saveFileName);

         // 역 직렬화
         try
         {
             PlayerSaveData data = JsonConvert.DeserializeObject<PlayerSaveData>(desString);
         }

         catch (Exception e) { Console.WriteLine(e); }
     }
     else 
     {
         Console.WriteLine(" 저장파일이 없습니다.");
     }
 }

 


📝디렉토리/텍스트파일을 저장하고 불러오는 방법

https://youcheachae.tistory.com/33

 

[c#][TextRPG] 3. c#의 System.IO를 이용한 파일 시스템 작업

목차1.Stream / StreamWriter2. 디렉터리 존재 여부3. 디렉토리 생성 / 삭제4. 파일 존재 여부5. 파일 생성 / 삭제 6. 파일 읽기  📝 System.IO 네임스페이스using System.IO;: 파일 시스템 작업 ( 디렉터리 / 파

youcheachae.tistory.com


📝 깃허브

https://github.com/kimYouChae/SpartaCodingClub

 

GitHub - kimYouChae/SpartaCodingClub

Contribute to kimYouChae/SpartaCodingClub development by creating an account on GitHub.

github.com

 

목차
1. Scene 관리 시스템
    □ 의도
    □ 설계
2. Scene 관리 구현방법과 상세 설명
    □ IScene 인터페이스
    □ GameManager
    □ 전환 관리
    □ 실제 사용 예시
3. 싱글톤 시스템
    □ 의도
    □ 설계
    □ 개선사항
4. 싱글톤 구현방법과 상세설명

 

📝 Scene 관리 시스템

1. 의도

: 게임의 각 상태 (로비, 인벤토리, 상점 등)를 Scene으로 관리하고, Scene 간의 전환을 관리하기 위해서 

 

2. 설계

□ SceneType라는  Enum을 통해 게임 Scene 타입 정의

□ IScene 인터페이스를 통해 각 Scene을 공통된 타입으로 관리

□ 씬 전환을 GameManager 를 통해 중앙에서 관리

 

📝 Scene 관리 구현방법과 상세 설명 

1. IScene 인터페이스

interface IScene
{
    // 씬에 들어갔을 때 
    public void SceneEntry();

    // 씬에서 메인으로 동작할 로직
    public void SceneMainFlow();
}

: 각 Scene이 필수로 구현해야할 동작을 정의한다.

          □ SceneEntry()

                     ■ Scene에서 초기 화면 구성 등의 기능이 실행된다.

          □ SceneMainFlow()

                       각 Scene에서 필요한 플레이어 입력처리, 상태 업데이트, 화면 출력 등의 기능이 실행된다.

 

2. 📝 GameManager.cs

enum SceneType 
{
    LobbyScene,
    PlayerScene,
    InventoryScene,
    StoreScene,
    RestScene,
    DungeonScene
}

class GameManger : Singleton<GameManger>    
{
    // 씬 저장 리스트 
    private IScene[] gameScene;
    private IScene nextScene;
    private IScene preScene;

    public GameManger() 
    {
        int temp = Enum.GetNames(typeof(SceneType)).Length;
        gameScene = new IScene[temp];

        gameScene[(int)SceneType.LobbyScene]        = LobbyScene.Instance;
        gameScene[(int)SceneType.PlayerScene]       = PlayerManager.Instance;
        gameScene[(int)SceneType.InventoryScene]    = InventoryManger.Instance;
        gameScene[(int)SceneType.StoreScene]        = StoreManager.Instance;
        gameScene[(int)SceneType.RestScene]         = RestScene.Instance;
        gameScene[(int)SceneType.DungeonScene]      = DungeonScene.Instance;

        nextScene = gameScene[0];
    }
}

□  Scene관리를 위한 배열 

           ■ IScene 인터페이스 타입의 배열을 선언 -> 모든 Scene을 하나의 컨테이너에서 관리

            nextScene과 preScene 변수를 통해 Scene 전환 추적 가능 

□   Scene 등록

           ■ SceneType enum의 개수 만큼 배열 크기 동적할당

            enum 값을 인덱스로 활용하여 Scene 인스턴스를 매핑 

 

3. 씬 전환 관리

public void ChangeScene(SceneType type) 
{
    // 지금 씬 = 예전 씬으로
    preScene = nextScene;

    // 현재 씬 지정 
    nextScene = gameScene[(int)type];

    // 현재씬 실행 
    if (preScene != nextScene)
        nextScene.SceneEntry();

    nextScene.SceneMainFlow();
}

□ 전환 로직  

           ■  이전 Scene 기록을 통해 히스토리 관리             

           ■  SceneType enum의 인덱스에 해당하는 Scene을 현재 Scene으로 지정

□ Scene Entry() 실행

           ■  이전 씬과 다른 Scene Type의 Scene인 경우에만 SceneEntry()를 실행

           ■   불필요한 출력 방지 

□ Scene MainFlow() 실행 

           ■  해당 Scnen의 주요 기능 실행 

           ■  입력처리 / 상태 업데이트 / 화면 출력 등을 실행 

 

4. 실행 예시 

 

-📝 LobbyScene.cs

private void ChangeScene(int input) 
{  
    switch (input) 
    {
        case 0:
            // 플레이어 출력 
            PlayerManager.Instance.printPlayer();
            // 로비로 씬 전환 
            GameManger.Instance.ChangeScene(SceneType.LobbyScene);
            break;
        case 1:
            // 인벤토리로 씬 전환 
            GameManger.Instance.ChangeScene(SceneType.InventoryScene);
            break;
        case 2:
            // 상점으로 씬 전환 
            GameManger.Instance.ChangeScene(SceneType.StoreScene);
            break;
        case 3:
            // 던전 씬 전환
            GameManger.Instance.ChangeScene(SceneType.DungeonScene);
            break;
        case 4:
            // 휴식
            GameManger.Instance.ChangeScene(SceneType.RestScene);
            break;
        case 5:
            // 저장 
            GameManger.Instance.SaveData();
            break;
        default:
            Console.WriteLine("잘못된 접근입니다. 다시 로비로 돌아갑니다");

            // Lobby로 돌아가기
            GameManger.Instance.ChangeScene(SceneType.LobbyScene);
            break;

    }
}

□  ChangeScene(int input) 

           ■   사용자 입력(input)에 따라 적절한 Scene으로 이동


📝 Singleton 관리 

1. 의도

: 유니티 프로젝트를 하면서 싱글톤을 상위 클래스로 만들어서 관리한 적이 있는데 편리했던 기억이 있어서 c#에서도 편리할 거라고 예상했다. 

 

2. 설계

□ 제네릭을 사용하여 다양한 타입의 클래스를 생성할 수 있도록 선언

 

3. 개선사항

: 상속을 사용하지 않고 클래스에 static을 붙여 전역에서 사용하는 방법도 가능할 것 같다.

 

📝 Singleton관리 구현방법과 상세 설명 

 

1. 📝 Singleton<T>.cs

class Singleton<T>
    where T : class , new()
{
    private static T instance;

    public static T Instance
    {
        get 
        {
            if (instance == null)
                instance = new T();

            return instance;
        }
    }

}

□ 제네릭 <T>

           ■  다양한 참조 타입의 인스턴스 생성이 가능

           ■  where 제약조건을 통해 <T>는 참조타입 class와 매개변수가 없는 생성자를 가진 클래스만 허용

□ Instance 프로퍼티

           ■  최초 호출 시 인스턴스를 생성하며, 이후에는 동일한 인스턴스를 반환 

 


📝 깃허브

https://github.com/kimYouChae/SpartaCodingClub

 

GitHub - kimYouChae/SpartaCodingClub

Contribute to kimYouChae/SpartaCodingClub development by creating an account on GitHub.

github.com

 


📝 추후

: 저장시스템

: 아이템 구조 or 관리 방법

Interface란 

- 무엇을 해야 하는가에 대한 정의

     - 어떻게 구현할지는 명시하지 않는다.

- 실제로 "어떻게 구현하는가"는 하위 클래스에서 구현.

 

인터페이스의 특징 

1. 접근 제한 한정자를 가질 수 없다. public으로만 선언 가능하다.

: private으로 선언할 수 없다. 

 

2. 필드(변수)를 선언할 수 없다.

 

3. 인터페이스에 선언되는 메서드는 구현부를 가지지 않는다.

internal interface Interface1
{

    public 반환형 메서드이름();
    public 반환형 메서드이름();

    public int myProperty { get; set; }
}

 

c# 8.0 버전은 조금 다르다 그냥 참고만 하자! 

더보기

c# 8.0 버전에는 인터페이스의 메서드가 구현부를 가질 수 있다.

internal interface Interface1
{

    public 반환형 메서드이름 ();

    반환형 메서드이름()
    {
        Console.WriteLine("기본 구현입니다.");
    }

    public int myProperty { get; set; }
}

 

 

4. 클래스는 여러 인터페이스를 상속받을 수 있다.

interface Outerface2 
{
    public void OuterfaceMethod();
}

// 인터페이스 두개를 상속받은 클래스 
public class abcClase : Interface1 , Outerface2
{
        
}

 

5. 상속받은 인터페이스의 메서드를 반드시 구현해야 한다.

: 구현하지 않으면 "인터페이스 멤버를 구현하지 않습니다"라는 오류가 뜬다.

 

6. 인터페이스를 구현한 클래스의 인스턴스는 모두 같은 인터페이스 타입이다.

internal interface Interface1
{

    public void TestFunction();
}

// 인터페이스를 상속받는 클래스 1
public class temp1 : Interface1
{
    public void TestFunction()
    {
        
    }
}

// 인터페이스를 상속받는 클래스 2 
public class temp2 : Interface1
{
    public void TestFunction()
    {

    }
}

 

    static void Main(string[] args)
    {
        Interface1[] inter = new Interface1[2];
        inter[0] = new temp1();
        inter[1] = new temp2();
    }

: temp1과 temp2 클래스는 모두 Interface1이라는 인터페이스를 상속받았으므로 같은 타입이다. 

 

7. 인터페이스 안에는 메서드뿐만 아니라 프로퍼티 / 인덱서 / 이벤트도 가능하다.

출처 : microsoft 공식 c# interface 문서

 

 

8. 인터페이스는 인스턴스를 만들 수 없다.

 

 


추상클래스 vs interface

  추상클래스 abstract class 인터페이스 interface
공통점  
메서드 오버라이딩 하위 클래스는 반드시 메서드를 오버라이딩 해야한다.
인스턴스 인스턴스화 ( ex)new 키워드 사용 ) 불가능 
차이점    
필드 가질 수 있다. 가지지 못한다.
상속 인터페이스와 클래스 모두 상속가능 인터페이스 밖에 상속받지 못함.
생성자 가질 수 있다. 가지지 못한다.
메서드 구현  메서드 구현가능 메서드 구현 불가능

 


인터페이스 명명 규칙 

: 인터페이스 이름은 대문자 "I"로 시작한다.

public interface IAttack
{
    void IAttackMethod();
}

public interface ITracking 
{
    void ITrackinMethod();
}

도움이 된 링크
<interface>

https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/interface

 

interface 키워드 - C# reference

'인터페이스' 키워드를 사용하여 모든 구현 형식이 지원해야 하는 계약을 정의합니다. 인터페이스는 관련되지 않은 형식 집합 간에 공통 동작을 만드는 수단을 제공합니다.

learn.microsoft.com

 

<명명규칙>

https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/coding-style/identifier-names

 

식별자 이름 - 규칙 및 관례 - C#

C# 프로그래밍 언어의 유효한 식별자 이름에 대한 규칙을 알아봅니다. 또한 .NET 런타임 팀과 .NET 문서 팀에서 사용하는 일반적인 명명 규칙을 알아봅니다.

learn.microsoft.com

 

 

🔥 DontDestroyOnLoad를 할 때 오브젝트가 missing이 되어서

🔥 어떤 경우에 missing 되는지 알아보는 글


- Manager스크립트

public class Manager : MonoBehaviour
{
    // 싱글톤
    private static Manager instance;

    [Header("===Asset 폴더에서 드래그한 오브젝트===")]
    [SerializeField]
    private Sprite _assetSprite;
    [SerializeField]
    private GameObject _assetPrefab;

    [Header("===Canvas상의 ui 컴포넌트===")]
    [SerializeField]
    private Button _button;
    [SerializeField]
    private TextMeshProUGUI _Text;

    [Header("===씬 상의 오브젝트===")]
    [SerializeField]
    private GameObject _sceneObject;

    private void Awake()
    {
        // 싱글톤
        // 중복방지
        if (instance != null && instance != this)
        {
            Destroy(gameObject);  // 객체 전체를 파괴
            return;
        }
        instance = this;

        // 삭제 x
        DontDestroyOnLoad(this.gameObject);

        // 버튼에 이벤트 추가
        _button.onClick.AddListener(test);
    }

    private void test()
    {
        SceneManager.LoadScene("TestScene2");
    }
}

어떤 오브젝트가 missing 되는지 알기 위해서 

 

1. Asset폴더에서 드래그한 오브젝트
2. Canvas상의 UI 컴포넌트
3. 씬 상의 오브젝트

 

3 분류로 나누어봤다. 

 

- 초기 인스펙터창

: 각각에 맞게 오브젝트를 할당한 모습이다.

- 하이어러키 창에서  DontDestroyOnLoad가 잘 되는 것도 볼 수 있다.

 

- 씬 전환 시!!!!!

- 씬 상에 있던 모든 오브젝트가 missing이 되는 상황이다

1. Asset폴더에서 드래그한 오브젝트
2. Canvas상의 UI 컴포넌트
3. 씬 상의 오브젝트

- Asset폴더에서 드래그한 오브젝트만 할당되어 있는 것을 볼 수 있다.

 


그럼 씬의 스크립트에 할당된 오브젝트들은 어떻게 되지?

- 씬 전환 전

- 씬 전환 후 

! 당연히 영향 안 받는다

 


이 글을 쓰게 된 계기

: 싱글톤을 검색하면 항상 연관되어 있는 것이 DontDestroyOnLoad이다.
나는 이때까지 UI 오브젝트나 맵 상에 있는 오브젝트를 관리하는 Manager 스크립트에 Singleton을 사용하였기 때문에

DontDestroyOnLoad 하면 missing오류가 날 수밖에 없었다.

: 스크립트의 역할에 맞춰서 DontDestroyOnLoad 해야 한다

 

예를 들어서)

DontDestroyOnLoad로 관리해야 할 데이터
1. 재화 데이터
2. 플레이어 해금 데이터
3. 사운드 관리 
4. 선택한 플레이어 데이터

정도가 있을 것 같다.

 

+) 만약 오브젝트를 DontDestroyOnLoad 해야 할 때

Resource.Load로 동적로딩을 해도 된다. 


 

개인프로젝트의 DontDestroyOnLoad문제를 해결하게 되면 싱글톤 관련해서 글을 작성하겠다.....

그리고 확실하지 않은 게 있으면 꼭 테스트프로젝트에서 테스트해 보고 적용하자...!!!!

🔥 메서드 오버로딩과 오버라이딩의 용어가 헷갈려서 작성하는 포스트 🔥


메서드 오버로딩  (overloading)

같은 이름의 메서드를 중복하여 정의하는 것
: 매개변수의 개수나 타입이 달라야 함
: 객체 지향 프로그래밍의 특징 중 하나인 "다형성"의 특징

public class Monster
{
    protected int hp;
    protected string name;
    protected float damage;

    public void SettingMonster() 
    {
            
    }
    public void SettingMonster(int hp)
    {

    }
    public void SettingMonster(int hp , string name)
    {

    }
    public void SettingMonster(int hp, string name , float damage)
    {

    }
}

: 함수이름은 "SettingMonster"로 같다

1. 첫 번째 메서드는 매개변수가 없다.

2. 두 번째 메서드는 int를 매개변수로 가진다.

3. 세 번째 메서드는 int, string을 매개변수로 가진다

4. 네 번째 메서드는 int, string, float를 매개변수로 가진다.

=> 메서드 오버로딩

 

- 생성자 오버로딩

public class Monster
{
    protected int hp;
    protected string name;
    protected float damage;

    public Monster() 
    {
    
    }
    public Monster(int hp)
    {

    }
    public Monster(int hp , string name)
    {

    }
    public Monster(int hp, string name , float damage)
    {

    }
}

: 생성자 또한 오버로딩 할 수 있다. 

 

- 생성자 오버로딩 사용 

    public Monster()
    {
        Console.WriteLine("매개변수가 없는 생성자입니다.");
    }
    public Monster(int hp)
    {
        this.hp = hp;
        Console.WriteLine($"하나의 매개변수를 가지는 생성자 입니다 {this.hp}");
    }
    public Monster(int hp, string name)
    {
        this.hp = hp;
        this.name = name;
        Console.WriteLine($"두개의 매개변수를 가지는 생성자 입니다 {this.hp} / {this.name}");
    }
    public Monster(int hp, string name, float damage)
    {
        this.hp = hp;
        this.name = name;
        this.damage = damage;
        Console.WriteLine($"세개의 매개변수를 가지는 생성자 입니다 {this.hp} / {this.name} / {this.damage}");
    }

: 출력 시 해당 메서드가 실행되었는지 확인하기 위해서 출력문을 작성하였다.

 

- 실행

        static void Main(string[] args)
        {
            // 1. 매개변수가 없는 생성자 
            Monster monster = new Monster();

            // 2. 매개변수가 한개인 생성자
            Monster monster2 = new Monster(10);

            // 3. 매개변수가 두개인 생성자
            Monster monster3 = new Monster(10 , "SLIME");

            // 4. 매개변수가 세개인 생성자
            Monster monster4 = new Monster(10 , "SLIME" , 20f);
        }

: 입력된 매개변수와 유형에 따라 적절한 생성자가 매칭된다.

 

- 출력

 

- 그래서 오버로딩을 왜 사용할까?

1. 메서드에 사용되는 이름을 절약할 수 있다. 
2. 같은 기능을 하는 메서드를 하나의 이름으로 정의할 수 있다.

 

- 만약 오버로딩을 사용하지 않고 SettingMonster() 메서드를 사용해 보겠다.

        public class Monster
        {
            protected int hp;
            protected string name;
            protected float damage;

            public void SettingMonster_NoneParemeter()
            {

            }
            public void SettingMonster_OneParemeter(int hp)
            {
               
            }
            public void SettingMonster_TwoParemeter(int hp, string name)
            {

            }
            public void SettingMonster_TreeParemeter(int hp, string name, float damage)
            {
              
            }
        }

: 파라미터가 없는 메서드, 한 개만 있는 메서드, 두 개 있는 메서드, 세 개 있는 메서드 이름을 다 다르게 해야 한다.

: 해당 메서드는 Monster의 필드를 세팅해 주는 같은 역할을 하는데, 이름이 다 다르다. 

-> 사용할 때 불편하다! 


메서드 오버라이딩  (overriding)

: 상속받은 부모 클래스의 메서드를 '자식클래스'에서 재정의 하여 사용하는 것
: 자식클래스에서 오버라이딩할 메서드와 이름, 매개변수의 개수와 타입, 반환값이 같아야 함

 

- 함수이름만 같다면? 

public class Monster
{
    protected int hp;
    protected string name;
    protected float damage;

    public void Attack() 
    {
        Console.WriteLine("Monster의 Attack");
    }

}

public class Orc : Monster 
{
    public void Attack() 
    {
        Console.WriteLine("Orc의 Attack");
    }
}

static void Main(string[] args)
{
    Orc orc = new Orc();
    orc.Attack();
}

: 부모 메서드와 이름이 같은 메서드를 작성했을 때

 

-출력

: Orc클래스의 Attack() 메서드에 커서를 가져다 대면

: " 상속된 Attack() 메서드를 숨깁니다 "라는 경고문구가 뜬다. 

: new 키워드를 사용하라고 한다.

        public class Orc : Monster 
        {
            public new void Attack() 
            {
                Console.WriteLine("Orc의 Attack");
            }
        }

: new 키워드를 사용함으로써, 상위 메서드와는 다른 새로운 메서드를 정의한다고 할 수 있다.

 

- virtual , override 키워드 사용하기 

public class Monster
{
    protected int hp;
    protected string name;
    protected float damage;

    public virtual void Attack() 
    {
        Console.WriteLine("Monster의 Attack");
    }
}

public class Orc : Monster 
{
    public override void Attack() 
    {
        Console.WriteLine("Orc의 Attack");
    }
}

static void Main(string[] args)
{
    Orc orc = new Orc();
    orc.Attack();

}

: 부모클래스에서 virtual, 자식클래스에서 override 키워드를 사용함으로써 "오버라이딩"을 명시적으로 표현한다.

 

- return 타입이 같아야 한다

: 오버라이딩 하는 메서드의 반환타입이 같아야 한다. 

: 상위 Attack()은 void일 때 오버라이딩 한 메서드의 return 타입을 바꾸게 되면 오류가 난다. 

 

- base 키워드

        public class Orc : Monster 
        {
            public void Attack() 
            {
                base.Attack();
                Console.WriteLine("Orc의 Attack");
            }

        }

: base 키워드를 사용하게 되면 부모의 Attack() 메서드도 실행한다.

 

-출력

 


오버로딩한 메서드를 오버라이딩

: 코드를 작성하다 보면 오버로딩과 오버라이딩을 같이 사용하는 경우가 많은 것 같다.

 

- Monster 클래스

public class Monster
{
    protected int hp;
    protected string name;
    protected float damage;

    public virtual void Attack() 
    {
        
    }
    public virtual void Attack(float stage) 
    { 
    
    }
    public virtual void Attack(float stage, float critical) 
    {
    
    }
}

: 메서드 오버로딩을 사용하여 같은 이름의 메서드를 3개 정의했다. 

 

- Orc 클래스

 public class Orc : Monster 
 {
     public Orc(int h , string n, float d) 
     {
         this.hp = h;
         this.name = n;
         this.damage = d;
     }

     public override void Attack()
     {
         Console.WriteLine($"오크의 기본 공격: {damage * 1.5f} 데미지");
     }

     public override void Attack(float stage)
     {
         Console.WriteLine($"오크의 스테이지 {stage} 기반 공격: {damage * stage * 1.5f} 데미지");
     }

     public override void Attack(float stage, float critical)
     {
         Console.WriteLine($"오크의 크리티컬 공격: {damage * stage * critical * 1.5f} 데미지");
     }
 }

: 메서드 오버라이딩을 통해서 하위 Orc 클래스에서 재정의했다.

: 오버로딩한 Attack() 메서드를 오버라이딩 했다! 

 

- Main

        static void Main(string[] args)
        {
            Orc orc = new Orc(100, "오크", 10f);

            orc.Attack();                   // 기본 공격
            orc.Attack(2.0f);               // 스테이지 기반 공격
            orc.Attack(2.0f, 1.5f);         // 크리티컬 공격
        }

 

- 출력

 


<마치며>

가상함수에 대해 찾아보던 중, 정적바인딩과 동적바인딩에 관해서 알게 되었다. 
추후 공부 후 정적/동적바인딩에 대해서도 블로그글을 작성하고 싶다. 


<도움이 되었던 링크>

https://www.tcpschool.com/java/java_usingMethod_overloading

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

https://www.tcpschool.com/java/java_inheritance_overriding

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

https://school.programmers.co.kr/learn/courses/30/lessons/131127

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 


❗풀이방법

: 살 수 있는 품목을 저장하고 또 해당 품목이 몇개가 있는지 저장해야 함

=> map 사용 

1. 입력받은 wantnumber 이 각각 key와 value가 되어 map에 저장됨

map<string, int> shoppingCart;
for (int i = 0; i < want.size(); i++) 
{
    shoppingCart.insert( {want[i], number[i]});
}

 

2. discount 배열을 돌면서 10개씩 map에 저장후, 위에서 저장한 map과 비교함

for (int i = 0; i <= discount.size() - 10; i++) 
{
	map<string, int> innerMap;

	// 1. 저장
	for (int j = i; j < i + 10 ; j++) 
	{
		innerMap[discount[j]]++;
	}

	// 2. shoppingCart와 비교 , 다른게 있으면 flag를 false로 변경
	bool flag = true;
	for (map<string, int>::iterator it = innerMap.begin(); it != innerMap.end(); it++)
	{
		string key = it->first;
		int value = it->second;
		
		if (shoppingCart[key] != value)
			flag = false;
	}

	// 3. want만큼 구매 가능하면 answer을 + 해줌
	if (flag)
		answer++;

}

- 문제에서 "10일 연속으로 일치" 라고 적혀져 있었는데 이 부분을 못보고 number 를 더하고 있었다. 

문제를 잘 읽자 ! 

 

- for문 안에서 break조건

for (map<string, int>::iterator it = innerMap.begin(); it != innerMap.end(); it++)
{
	string key = it->first;
	int value = it->second;
	
	// 1. shoppingCart안에 key가 있는지 검사 
	if (shoppingCart.find(key) == shoppingCart.end())
	{
		flag = false;
		break;
	}

	// 2. key에 대한 value가 같은지 검사 
	if (shoppingCart[key] != value)
	{
		flag = false;
		break;
	}
}

(1) map에 key가 있는지 검사하는 조건을 넣어도 제출된다.

(2) 조건을 만족했을 때 break를 넣고 안 넣고는 문제 제출에 큰 영향을 받지 않는다.!

 

- 원래 생각한 로직

(1) 처음부터 10개를 map에 넣는다.

(2) 기준이 되는 map과 비교를 한다 (코드상 shoppingCart)

(3) 맨 처음 나온 품목(key)에 해당하는 value를 -1 한다. (빨간색 박스에 해당하는 품목) 

(4) 그리고 다음 품목(key)에 해당하는 value를 +1 한다. (파랑색 박스에 해당하는 품목)

(5) (2)번과 똑같이 기준에 map과 비교를 한다.

 

=> "10일 연속으로 일치"라는 문장을 이해하기전에 코드를 짰다가, 안되서 포기했던 로직이다. 

문제를 이해했으니 추후 다른 방식으로 풀어서 제출해보겠다.


✅풀이방법

int solution(vector<string> want, vector<int> number, vector<string> discount) {
    int answer = 0;
    int wholeCount = 10;

    map<string, int> shoppingCart;
    map<string, int> my;

    bool flag = false;

    for (int i = 0; i < want.size(); i++) 
    {
        shoppingCart.insert( {want[i], number[i]});
    }

    for (int i = 0; i <= discount.size() - 10; i++) 
    {
        map<string, int> innerMap;

        for (int j = i; j < i + 10 ; j++) 
        {
            innerMap[discount[j]]++;
        }

        bool flag = true;
        for (map<string, int>::iterator it = innerMap.begin(); it != innerMap.end(); it++)
        {
            string key = it->first;
            int value = it->second;

            // 갯수가 다르면 
            if (shoppingCart[key] != value)
            {
                flag = false;
                //break;
            }
        }

        if (flag)
            answer++;
    }

	
    return answer;
}

 


https://github.com/kimYouChae

 

kimYouChae - Overview

클라이언트 개발자 지망생입니다! . kimYouChae has 6 repositories available. Follow their code on GitHub.

github.com

 

https://school.programmers.co.kr/learn/courses/30/lessons/12985

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 


❗풀이방법

:  N이 범위가 넓어서 for문은 사용하면 안 되겠다고 생각했다.

 

: 대진표를 그려보면 이렇게 된다. 
1번과 2번은 두번째에 1이 되고, 

3번과 4번은 두번째에 2가 되고,

5번과 6번은 두번째에 3이 되고............

N-1과 N번은 두번째에 ( N-1 + 1 ) / 2 번째가 된다.

: 같은 '그룹'에 속해있는지 검사방법

-> 위의 사진에서 마지막에는 [1,2] 번이 대결하게 된다.

-> 한번더 순서를 정했을 때, 둘 다 1번이 된다. -> 같은 그룹에 속해있다. 

 

❗틀린 시도 

int aOrder = a;
int bOrder = b;

while (true) 
{
	if (abs(aOrder - bOrder))
	{
		break;
	}

	// a가 짝수/훌수이면?
	if (aOrder % 2 == 0)
		aOrder /= 2;
	else
		aOrder = aOrder / 2 + 1;

	// a가 짝수/훌수이면?
	if (bOrder % 2 == 0)
		bOrder /= 2;
	else
		bOrder = bOrder / 2 + 1;

	//cout << aOrder << " / " << bOrder << endl;
	answer++;
}

: while문을 break하는 조건문에서,

A순서와 B순서의 차이가 1이면 만난다,라고 생각했는데

만약 A가 2번, B가 3번 일 때는 같은 그룹이 아닌데도 조건에 만족하기 때문에 테스트케이스에서 틀렸었다.

[1,2][3,4]로 그룹이 이루어져 있는데, A-B가 1은 맞지만 같은 그룹이 아니다.

 


int solution(int n, int a, int b)
{
    int answer = 0;

    int aOrder = a;
    int bOrder = b;

    while (true) 

    {
        // 예외 : 처음에 2, 3이면 오류남, [1,2][2,3] 그룹이 이렇게 되어야함
        // 만약 둘이 붙어있는 그룹이면 [1,2] -> 한번 더 순서를 정했을 때 같은 순서가 된다.
        if (aOrder == bOrder)
        {
            break;
        }

        // 3,4일 때 (3+1)/2 = 2, (4+1)/2=3
        aOrder = (aOrder + 1) / 2;
        bOrder = (bOrder + 1) / 2;

        answer++;
    
    }

    return answer;
}

: [3,4]가 대전을 했을 때, 번호가 홀수일 때 짝수일 때로 조건을 나눴었다.

그런데 먼저 +1을 더하고 2로 나누게 되면 홀수와 짝수 둘 다 올바른 순서를 구할 수 있다.

 


https://github.com/kimYouChae

 

kimYouChae - Overview

클라이언트 개발자 지망생입니다! . kimYouChae has 6 repositories available. Follow their code on GitHub.

github.com

 

+ Recent posts