[C#과 유니티, 실전 게임으로 제대로 시작하기]섹션5. 스터디
게임 중 생성되는 데이터는 메모리에 저장되어있어야 한다. 하지만 게임이 종료되면 유실되기 때문에 유지되어야하는 데이터는 파일 형태로 디스크에 저장해야한다.
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는 0과 1로 이루어진 데이터 형식으로 사람보다 컴퓨터에 가까운 타입이다.
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,..등 을 활용하여 저장된 문구의 타입을 알려줘야 제대로 반환할 수 있다.
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를 사용하려면 system 네임스페이스를 사용해야한다.
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);
}
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이 출력된다.
}