C#과 유니티5-2

망고·2024년 2월 20일

C#과 유니티

목록 보기
10/10
post-thumbnail

[C#과 유니티, 실전 게임으로 제대로 시작하기]섹션5. 스터디

파일 기본(FileStream)

게임 중 생성되는 데이터는 메모리에 저장되어있어야 한다. 하지만 게임이 종료되면 유실되기 때문에 유지되어야하는 데이터는 파일 형태로 디스크에 저장해야한다.

using System.IO; //IO는 InputOutPut을 의미한다.
void Start(){

//파일 쓰기
	FileStream fs = new FileStream(C:\Users\박예진\OneDrive\바탕 화면\유니티\example.txt,FileMode.Create); //네임스페이스 using System.IO에 있는 객체로 using System.IO 네임스페이스를 작성해야한다.
    StreamWriter sw = new StreamWriter(fs);
    sw.Write("안녕하세요"); //Writer()를 활용하여 텍스트파일에 저장할 문구를 작성한다.
    sw.Close(); //작업이 끝났으면 Close()를 활용하여 닫아준다.
    
//파일 읽기
	fs = new FileStream(C:\Users\박예진\OneDrive\바탕 화면\유니티\example.txt,FileMode.Open);
    StreamReader sr = new StreamReader(sr);
    //Debug.Log(sr.ReadLine());->ReadLine()은 텍스트파일에 저장된 문구를 한 줄 읽어온다.
    string line = sr.ReadLine();
    Debug.Log(line); // "안녕하세요"가 출력된다.
    sr.Close(); //작업이 끝났으면 Close()활용하여 닫아준다.
    
}

FileStream([파일경로],[파일모드])생성할 파일의 형태(txt, dat,..)와 함께 파일 경로를 첫 번째 인수에 작성하고, 두 번째 인수에 파일모드를 작성한다.
System.IO 네임스페이스에서 제공하는 파일모드는 다음과 같다.

Binary

Binary는 0과 1로 이루어진 데이터 형식으로 사람보다 컴퓨터에 가까운 타입이다.

  • Binary 형태로 파일 쓰고 읽기
void Start(){

//파일 쓰기
	FileStream fs = new FileStream(C:\Users\박예진\OneDrive\바탕 화면\유니티\example.txt,FileMode.Create);
    BinaryWriter bw = BinaryWriter(fs);
    bw.Write("안녕하세요");
    bw.Writer(1234);
    bw.Close();
    
//파일 읽기
	FileStream fs = new FileStream(C:\Users\박예진\OneDrive\바탕 화면\유니티\example.txt,FileMode.Open);
    BinaryReader br = new BinaryReader(fs);
    string str = br.ReadString(); //첫 번째 작성한 문구는 문자열이기에 ReadString()을 활용한다.
    int num = br.ReadInt32(); //두 번째 작성한 문구는 int형이기에 32바이트인 ReadInt32()를 활용한다.
    Debug.Log(str); //"안녕하세요"가 출력된다.
    Debug.Log(num); //1234가 출력된다.
    br.Close();
}

-> binary로 작성한 텍스트 파일을 열려면 경고가 뜬다.

인코딩 : 텍스트등의 형태를 컴퓨터가 이해하는 2진수로 반환하는 과정을 인코딩이라 한다. 인코딩 과정에서 보편적으로 쓰이는 방식은 UTF-8이다.

보통 텍스트 편집기등의 프로그램은 UTF-8로 인코딩하고. 데이터를 읽어올 때도 UTF-8로 인코딩이 되어 텍스트로 바꿔 파일을 열 수 있다.

앞서 경고가 발생한 이유도 인코딩 과정을 겪지 않고 Binary객체로 넣었기 때문이다. ReadString, ReadInt32,..등 을 활용하여 저장된 문구의 타입을 알려줘야 제대로 반환할 수 있다.

객체를 파일로 저장(Serialization)

void Start(){
	User user = new User();
    user.level = 1;
    user.name = "chulsoo";
}

class User{
	public int level;
    public string name;
}

-> 게임이 매번 실행될 때 마다 새로운 User를 만들고 level은 1, name은 chulsoo로 지정된다.

using System.IO;
using System.Runtime.Serialization.Formatter.Binary;

void Start(){

	User user = new User();
    user.level = 1;
    user.name = "chulsoo";
    
	FileStream fs = new FileStream(C:\Users\박예진\OneDrive\바탕 화면\유니티\date.dat,FileMode.Create); //binary로 저장될 것이기 때문에 txt로 굳이 할 필요없다.
    BinaryFormatter bf = new BinaryFormatter(); //User 객체를 파일로 저장한다.
    br.Serialize(fs,user); //Serialize(직렬화) : 객체를 파일로 저장하기 좋은 형태로 가공하는 과정이다. Serialize([저장할 파일],[저장할 객체])
    br.Close();
    
    FileStream fs = new FileStream(C:\Users\박예진\OneDrive\바탕 화면\유니티\date.dat,FileMode.Open);
    User user = (User)bf.Deserialize(fs); //Deserializs() : 역직열화도 객체를 불러온다. Deserialize는 object타입으로 반환하기 때문에 (User)로 형변환한다.
    Debug.Log(user.level); //1이 출력된다.
    Debug.Log(user.name); //chulsoo가 출력된다.
}

[System.Serializable] //User 클래스가 직렬화한(serializable) 클래스임을 명시해준다. 명시한 뒤 Serialize()를 사용할 수 있다.
class User{
	public int level;
    public string name;
}

->User의 내용을 한번만 파일에 저장하면 따로 User 객체를 이용하여 부를필요가 없다. 파일을 사용하여 user의 level과 name을 부를 수 있다.

예외처리

예외(Exception) : 오류

  • 런타임 에러 : 디버깅 중 발생하는 에러이다. -> 프로그램이 비정상적으로 종료된다.
  • 컴파일 에러 : 디버깅 전 컴파일 단계에서 발생하는 에러이다.

런타임에러를 처리하는 방법
try, catch, finally를 활용한다.

void Start(){
	int[] arr{1, 2, 3, 4, 5,};
    int sum = 0;
    
    try { //try함수 안에 있는 코드를 실행한다.
    	for(int i=0; i<10; i++)
        {
        	Debug.Log(arr[i]);
            sum+=arr[i];
        }
    }
    catch(system.IndexOutOfRangeException exception){ //try에서 실행한 코드에서 발생한 예외를 처리한다.
    	Debug.Log(exception.Message); //발생한 오류의 메시지를 출력한다.
    } //catch문은 중첩하여 사용할 수 있다.
    finally { //예외 처리가 완료된 후에 실행할 코드를 작성한다.
    	Debug.Log(sum); //예외처리가 발생해도 값을 출력한다. 5까지 반복문이 실행되니 15를 출력한다.
    }
}

델리게이트

델리게이트(delegate)는 메서드를 담는 타입이다.

델리게이크의 기본 구조

delegate 반환_타입 델리게이트_이름(매개변수);

델리게이트에 담을 메서드의 반환타입과 매개변수를 작성한다.

delegate void ExampleDelegate(); //반환타입은 void이고, 매개변수가 없는 메서드를 담는 ExamleDelegate를 선언하였다.

void Hello(){
	Debug.Log("Hello");
}
void Bye(){
	Debug.Log("Bye");
}

void Start(){
	// ExampleDelegate ed = new ExampleDelegate(Hello); //델리게이트의 변수를 선언하고 Hello 메서드를 담는다.
    ExampleDelegate ed= Hello; //위 코드를 축약할 수 있다.
    ed += Bye; //멀티캐스트(+=, -=)을 활용하여 메서드를 추가하고 제거할 수 있다. 
    ed();
}

델리게이트는 위 코드보단 콜백의 개념과 같이 쓰이는 경우가 많다.

void Start(){
	Mom mom = new Mom();
    Son son = new Son();
	
    mon.GetSonToStudy(son); //mom의 객체가 GetSonToStudy 메서드를 호출한다. mom객체가 son의 객체에 있는 Study()를 호출하기때문에 호출자가 된다.
}
    
Class Mon{
	public void GetSonToStudy(Son son){
    	son.Study(this); //아들 객체에 있는 Study 메서드를 호출한다.
    }
    
    public void FinishStudy(){
    	Debug.Log("Study done");
    }
}

Class Son{ //호출되기때문에 피호출자가 된다.
	public void Study(Mom mom){
    	Debug.Log("Studying...");
        mom.FinishStudy(); //mom 객체의 FinishStudy 메서드를 호출한다. 피호출자가 호출자의 메서드를 다시 부르는데 이걸 콜백이라고 한다.
    }
}

다시말해 콜백에는 호출자(caller)와 피호출자(callee)가 있는데, 피호출자가 호출자의 메서드를 다시 호출하는 것을 콜백이라 한다.

델리게이트

Class Son{
	public void Study(Mom mom){
    	Debug.Log("Studying...");
        mom.FinishStudy(); 
    }

이 부분에서 FinishStudy메서드를 호출하기 위해서 엄마 객체를 전달해줬는데 delegate를 활용하여 효율적인 코드를 작성할 수 있다.

void Start(){
	Mom mom = new Mom();
    Son son = new Son();
    
    Mom.GetSonToStudy(son);
}

delegate void StudyDelegate();

Class Mom{
	StudyDelegate sd;
    
	public void GetSonToStudy(Son son){
    	sd += FinishStudy; //sd 메서드에 FinishStudy를 담는다.
        son.Study(sd); //Study메서드에 sd 델리게이트를 전달한다.
    }
    
    public void FinishStudy(){
    	Debug.Log("Study done");
    }
}
    
Class Son{
	public void Study(StudyDelegate sd){ //전달받은 델리게이트를 이용하여 함수를 호출한다.
    	Debug.Log("Studying...");
        sd(); //sd 델리게이트에 저장된 FinishStudy함수를 실행한다.
    }
}

이벤트

이벤트(event)는 델리게이트의 한정자이다.

void Start(){
	Mom mom = new Mom();
    Son son = new Son();
    
    Mom.GetSonToStudy(son);
}

delegate void StudyDelegate();

Class Mom{
	//StudyDelegate sd;
    public static StudyDelegate sd; //static을 활용하여 외부클래스에서 실행시키도록 할 수 있다.
    // public static event StudyDelegate sd; //event로 선언하였을 때 외부클래스에서 델리게이트를 실행할 수 없다.
    
	public void GetSonToStudy(Son son){
    	sd += FinishStudy; 
        son.Study(sd);
    }
    
    public void FinishStudy(){
    	Debug.Log("Study done");
    }
}
    
Class Son{
	//public void Study(StudyDelegate sd){ 
    publid void Study(){ 
    	Debug.Log("Studying...");
        //sd();
        Mom.sd(); //sd를 static으로 선언하였기 때문에 클래스 이름을 통해 바로 호출시킬 수 있다.
        		//event로 선언되었다면 Mom.sd();는 오류가 뜬다.
    }
}

event의 일정한 패턴

void Start(){
	Publisher pub = new Publisher();
    Subscriber sub = new Subscriber();
    
    pub.RunEvent();
}
    
delegate void myEventHandler();

Class Publisher{
	public static event myEventHandler myEvent;
    
    public void RunEvent(){
    	if (myEvent != null)
        {
        	myEvent();
        }
    }
}

Class Subscriber{
	public Susbscriber(){
    	Publisher.myEvent += DoSomething();
    }
    
	public void DoSomething(){
    	Debug.Log("do something");
    }
}

익명메서드

익명메서드는 무명메서드라고도 불리는데 이름이 없는 메서드이다.

익명메서드 구조

delegate(매개변수){
...
};


delegate int myDelegate(int a, int b);

void Start(){
	myDelegate del = delegate(int a, int b){
    	return a+b;
    };
    
    Debug.Log(del(1,2)); // 3이 출력된다.
}

람다식

메서드를 하나의 식으로 표현한 것

람다식 기본 구조

(매개변수) => {
...;
};

delegate int myDelegate(int a, int b);

void Start(){
	myDelegate del = (int a, int b) => {
    	return a+b;
    };
    
    Debug.Log(del(2,4));
}

람다식 생략 구조

(매개변수) => ...;

매개변수의 타입, 중괄호, return을 생략하여 만들 수 있다.

delegate int myDelegate(int a, int b);

void Start(){
	myDelegate del = (a,b) => a+b; //내용이 return문 한 줄 만 있을 때 사용 가능하다.
    
    Debug.Log(del(2,4));
}

Action/Func

  • Action : 반환타입이 없는 델리게이트 타입이다.
  • Func : 반환타입이 있는 델리게이트 타입이다.

Action과 Func를 사용하려면 system 네임스페이스를 사용해야한다.

Action

delegate void exDelegate(string message);

void Start(){
	exDelegate del = PrintMessage;
    
    del("Hi");
}

void PrintMessage(string message){
	Debug.Log(message);
}

Action을 활용하여 코드를 단순화할 수 있다.

void Start(){
	Action<string> act = PrintMessage; //delegate 타입을 선언하지 않고 Action을 사용하여 함수를 담는다. PrintMessage함수는 매개변수가 있기 때문에 제네릭을 활용하여 매개변수 타입을 담는다.
    
    act();
}

void PrintMessage(string message){
	Debug.Log(message);
}

Func

void Start(){
	Func<int, int, string> act = Mult; //<매개변수타입, 매개변수타입, 반환타입>
    
    Debug.Log(act(1,3)); //3이 출력된다.
}

string Mult(int a, int b){ //Mult는 반환타입 string를 가진다.
	return (a * b).ToString();
}

=>Func와 람다식을 활용하여 코드를 축약할 수 있다.

void Start(){
	Func<int, int, string> func = (a,b) => (a*b).ToString();

	Debug.Log(func(2,3)); //6이 출력된다.
}

0개의 댓글