# 게임 클라이언트(Unity) 개발자 기술면접 대비
> UPnL 안단태  
> https://github.com/salt26/

## 큰 그림
### 일반적인 입사 절차
> 회사마다 다를 수 있으므로, 들어가려는 회사의 채용 사이트를 꼭 확인해 보시기 바랍니다.

1. 서류 심사 (이력서, 자기소개서, 포트폴리오 등)
2. 코딩 테스트 또는 과제 전형
3. **실무진 기술면접**
4. 경영진 인성면접
5. 처우 협의

### 기술면접 과정
* 보통 1시간 정도 소요

1. 자기소개 + 아이스 브레이킹
2. 제출한 서류의 내용 검증 + 경험 질문 + 과제에 대한 질문
3. 기술 질문
4. 회사에 대해 궁금한 점 (역질문)

## 개인적 감상
> 본 장의 내용은 어디까지나 제 경험에서 비롯한 "개인적 감상"이며, **주관적인 내용**이 다수 포함되어 있습니다.  
> 이는 모든 면접에 일반화되는 내용은 아닐 수 있습니다.  
> 저의 경우 대학원 졸업 후 전문연구요원(신입)으로 지원하였으므로, 경력자나 학부 졸업자(신입)를 뽑는 면접과 상이할 수 있습니다.  
> 참고만 하시기 바랍니다.

### 과제 또는 구현 테스트
> 알고리즘 문제를 푸는 코딩 테스트와는 다릅니다.  
> 회사에 따라 다르지만, Unity를 사용해 짧은 시간 동안 처음부터 게임을 개발할 것을 요구하는 전형에 대한 감상입니다.

* **내공이 없으면 통과할 수 없다.**
  * 문제 분석 및 이해, 코드 구조 설계, Unity C# 스크립팅, 알고리즘, 사용자 입력 처리, 시각화(애니메이션) 등을 종합적으로 요구한다.
  * 허수인 지원자를 가장 확실하게 거르는 전형이다. ~~알고보니 나도 허수였지만...~~
  * 편법으로 회사에서 내는 과제의 정보를 미리 입수하고 연습해 볼 수도 있겠지만, 이렇게 하면 합격에는 도움이 될지 몰라도 회사 가서 적응하기가 힘들 것이다.

* 단순 구현 문제처럼 보여도 알고리즘 문제 풀이 기술을 요하도록 설계되어 있다.
  * 다행인 점은, 프로그램 구현을 충분히 많이 해본 사람이라면 따로 알고리즘 공부를 해서 갈 필요는 없다.

* 필수 스펙을 시간 안에 전부 구현하는 사람도 매우 드물 것 같은데 선택 스펙도 굉장히 많이 달려있다.
  * 이런 점이 *'누군가는 이걸 다 해내는데, 당신은 과연 다 할 수 있을까요?'* 하는 무언의 압박을 준다.

* 라이브 구현 테스트의 경우 시간이 매우 부족하므로 문제를 보고 첫 번째로 떠올린 설계로 끝까지 가야 한다.
  * 설계하는 데에 시간을 쏟다가는 제 시간 안에 구현을 다 못 한다.
  * 첫 설계가 괜찮은 설계이려면 정말 수도 없이 많은 프로젝트를 바닥부터 짜 봤어야 한다.
  * 인터넷 검색이 가능하더라도 검색할 시간이 아깝다. 손에 익은 구현 기술만으로 자급자족할 수 있어야 한다.

* **짧은 시간 안에 게임 하나를 바닥부터 개발해서 혼자 완성하는 연습을 많이 해봐야 한다.**
  
### 기술면접
* 기술면접은 지원자의 내공을 측정하는 면접이 아니다.
  * 단기적인 단순 암기로 넘길 수 있는 면접이다.
  * 즉, 지원자가 우리 회사에 들어오기 위해서 **'최근에' 공부를 하는 열의를 보였는가**를 평가하는 자리이다.
  * 학교에서 가르쳐 준 적 없는 내용이 대부분이므로, "개발 많이 해 봤는데 나 정도면 충분히 붙겠지~" 하고 아무 준비 없이 가면 아무 대답도 하지 못할 것이다.
    * 뒤에 나올 내용들을 공부하고 가면 된다.

* 기술면접에서도 인성면접에서 할 법한 질문이 많이 나온다.
  * 자신이 쓴 서류의 내용에 대해 완벽히 이해하고 설명할 수 있어야 한다.
  * **회사의 인재상에 대해 숙지하고 가야 한다.**
  * 평소에 "왜?"에 대한 질문을 많이 던져봐야 한다.
    * *다른 개발 직군 많은데 왜 게임 업계로 왔는지?*
    * *왜 기획자 말고 클라이언트 프로그래머로 지원했는지?*
    * *왜 우리 회사여야 하는지?*
    * *팀원과 갈등을 겪었던 경험?*
    * *기획자가 정말 말도 안 되는 것을 꼭 구현해야 한다고 주장하고 이를 굽히지 않을 때, 프로그래머로서 어떻게 대처할 것인가?*

* 편하게 진행한다고 하지만 지원자에게 암묵적인 압박을 많이 넣는다.
  * 지원자 스스로 굉장히 자랑스럽게 여기고 잘 했다고 생각하는 것에 대해 *"이런 부분이 아쉬운데 더 잘 할 수는 없었을까요?"* 라고 묻는다.

* 뽑는 입장이 한 번이라도 되어본 적이 있다면, 회사에서 필요한 사람이 바로 자신임을 어필하는 것이 중요하다는 것을 알 수 있다.
  * 모든 것은 **직무 적합성**으로 통한다.
    * 내가 이 직무에 있어서 뛰어난 사람이라는 것을 마음껏 자랑하자.
  * 내가 하고 싶은 이야기보다 면접관이 듣고 싶어하는 이야기를 하는 것이 좋다.
    * 예: 피아노를 화려하게 정말 잘 치는데 지휘자를 보지 않는 반주자는 팀에서 원하는 반주자가 아닐 것이다.
    * 예: "전 나중에 창업을 하고 싶습니다!"라고 말한다면 면접관은 *'금방 나갈 사람이구나'* 하고 생각한다.
  * 회사를 칭송하는 아부를 많이 할 필요는 없다.
    * 이것이 지원자를 뽑아야 할 이유가 되지는 않는다.
  * 거짓말은 하면 안 된다.
    * 예: 활동적인 일을 좋아하지 않는데 "활동적인 일은 저에게 맡겨 주십시오!"라고 말하고 면접을 통과했다면 회사 가서도 원하지 않는 활동적인 일만 계속 맡게 될 것이다.

* '나 같은 인재를 안 뽑으면 회사가 손해'라는 마음가짐으로 면접에 임하면 두려울 것이 없다.
  * 자신감 있는 모습을 보여줄 수 있고, 떨어져도 상처를 덜 받는다.
  * 그렇다고 준비를 하나도 안 하고 가면 안 된다.
  
* 면접은 회사가 지원자를 평가하는 자리이기도 하지만, 지원자가 회사를 평가하는 자리이기도 하다.
  * 특히 실무진 면접의 면접관들은 입사하고 나면 동료가 될 사람들이다.
  * 면접 경험이 불쾌한 회사는 근무 경험도 불쾌할 가능성이 높다.

* 내가 이 회사와 잘 맞을지 끊임없이 고민해야 한다.
  * 내가 포기할 수 있는 것과 포기할 수 없는 것이 무엇인지 알아야 한다.
    * *직무의 전환배치가 일어나도 괜찮은가? (예: 클라이언트 -> DevOps)*
    * *지방에 있는 스튜디오로 근무지가 옮겨져도 괜찮은가?*
    * *회식 등 사내 친목 모임이 자주 열려도(또는 전혀 열리지 않아도) 괜찮은가?*
    * *월급을 올려주는 대신 정말 하기 싫은 일을 시키면 묵묵히 할 수 있는가?*
  * **회사에 대해 궁금한 점을 물어보라고 하면, 내가 포기할 수 없는 것을 회사가 보장해 주는지 물어보면 좋다.**
  * 개인적인 생각으로, 취업 과정은 있는 그대로의 내가 가장 빛날 수 있는 회사를 찾는 과정이다.
    * 면접관들이 내 능력을 온전히 알아봐 주려 하지 않는다면, 가서도 좋은 대우를 받기 어렵다.
  * **내 능력이 충분한데도 면접에서 떨어졌다면, 회사에서 당장 필요한 사람이 아니거나 서로 fit이 안 맞아서 그럴 수 있다.**
    * Fit이 안 맞았다면 붙어서 갔어도 크게 고생하다가 언젠가 퇴사할 것이다. 차라리 안 붙은 것을 다행으로 생각하는 편이 마음 편하다.

* 회사 상황에 따라 지원자마다 다른 질문을 던지기도 한다.
  * 지원자를 걸러야 하는 상황이면 일부러 직무와 관련이 적은 세세한 내용을 질문하기도 한다.
    * 예: 한 채용 공고에서 필요한 인원을 모두 선발했는데 아직 전형을 진행하고 있는 지원자가 있을 경우
    * 이 경우 저렇게 질문을 받고 떨어지면 지원자가 실력이 없는 것이 아닌데도 공부를 덜 해서 떨어진 것이라고 스스로 믿게 될 수 있다.
    * 보통은 지원하는 타이밍이 안 좋았던 것이다.
  * 지원자를 뽑고 싶은 상황이면 어느 면접에서나 나오는 질문들을 물어보는 편이다.

### 회사와의 Fit
* 작성자의 주관적인 생각으로, 크게 두 종류의 인재상이 있다.
  * 어떤 인재를 원하는지는 회사마다, 그리고 직무마다 다르다.
* 나와 맞지 않는 유형의 회사에 지원하면 면접에서 떨어질 가능성도 높고, 붙더라도 다니기 힘들다.
  * 유명하고 돈 많이 주는 곳이 가장 다니기 좋은 곳은 아닐 수 있다.
  * 당장 합격이 가장 중요하다면, 나를 속여서 회사에 맞추는 것이 나중에 감당 가능한 일인지 생각해 보는 것이 좋다.

1. **대기업형 인재**
   * 한 가지 일에 특화된 스페셜리스트를 원한다.
   * 튀려고 하지 않고 말 잘 듣는 사람을 원한다.
   * 뽑을 때 직무와 직접적으로 관련 있는 능력에만 관심이 있다.
   * 거대한 조직에서 오래 서비스해 온 게임을 유지보수하게 된다.
   * 상사가 시키는 일을 하게 될 가능성이 높으며 직무가 잘 바뀌지 않는다.
   * 의견 개진이 어렵고 조직이 잘 변화하지 않는다.
   * 안정을 추구하여 개인의 성장 속도가 회사의 성장 속도보다 느린 경우에 잘 맞다.
   * 학부 졸업 후 취업하는 경우에 비교적 잘 맞다. (대기업 다니다가 대학원 가는 경우도 종종 있다.)
   * 돈과 커리어를 쌓기 좋다.

2. **스타트업형 인재**
   * 이것저것 다 잘 하는 올라운더를 원한다.
   * 대기업에 못 간 사람이 아니라 안 간 사람을 원한다.
   * 이타적이고 타 직군과도 소통을 잘 하는 사람을 원한다.
   * 뽑을 때 직무와 직접적으로 관련 없는 능력들도 중요하게 본다.
   * 작은 조직에서 빠르게 매번 새로운 게임을 만들게 된다.
   * 일을 주도적으로 찾고 스스로 배워서 해야 할 가능성이 높으며 여러 직무를 혼자 맡기도 한다.
   * 의견을 비교적 쉽게 개진할 수 있지만 그 일을 본인이 맡게 되는 경우가 많다.
   * 도전을 추구하여 개인의 성장 속도가 회사의 성장 속도보다 빠른 경우에 잘 맞다.
   * 대학원 졸업 후 취업하는 경우에 비교적 잘 맞다.
   * 본인이 하고 싶은 일을 하게 될 가능성이 높다.

## 기술면접 대비 목차
> **볼드체**는 나올 확률이 높은 질문입니다.  
> Unreal을 사용하는 직무의 경우 C++과 Unreal에 대한 이해가 필요합니다. 이는 여기서 다루지 않습니다.

* Unity & C# 스크립팅
  * Unity에서 `Update()`와 `FixedUpdate()`와 `LateUpdate()`의 차이에 대해 설명해 보세요.
  * 값 형식과 참조 형식이 메모리에 어떻게 저장되는지 설명해 보세요.
  * C#에서 boxing과 unboxing이 무엇인가요?
  * Unity에서 serialization이 무엇인가요? 어떻게 serialize하나요?
  * C#에서 `const`와 `readonly`의 차이를 설명해 보세요.
  * C#에서 `struct`와 `class` 인스턴스가 어떻게 다른가요?
  * C#에서 `string`을 `+`로 연결할 때 생기는 문제점에 대해 설명해 보세요.
  * **C#과 Unity의 garbage collector가 서로 다른데, 어떤 차이가 있는지 설명해 보세요.**
  * C#의 `delegate`와 `event`는 언제 사용하나요?
  * **Unity의 fake null에 대해 설명해 주세요.**
  * C#에서 정적 함수의 인자에 `this`를 넣으면 어떻게 되는지 설명해 보세요.
  * C#의 `List`와 `Dictionary`는 내부적으로 어떻게 구현되어 있나요?
  * C#의 얕은 복사와 깊은 복사를 할 때 각각 메모리에서 어떤 일이 일어나는지 설명해 보세요.
  * C#의 LINQ에 대해 설명해 보세요.
  * C#의 Reflection에 대해 설명해 보세요.
  * Unity 프로파일러를 사용해 본 경험에 대해 이야기해 주세요.
  * **Unity에서 최적화를 해본 경험에 대해 이야기해 주세요.**

* Unity 그래픽스
  * Unity에서 씬의 오브젝트를 디바이스 화면에 렌더링하기까지 거치는 과정을 설명해 보세요.
  * 배치 렌더링의 장점과 배치 렌더링을 활용하는 방법을 설명해 보세요.
  * **드로우 콜을 최적화하려면 어떻게 해야 하나요?**
  * **스프라이트 아틀라스를 사용해 본 경험에 대해 이야기해 주세요.**
  * Unity에서 보통 어떤 텍스처를 사용하시나요?
  * 2D 렌더링와 3D 렌더링의 차이에 대해 설명해 보세요.

* 객체지향 프로그래밍
  * 객체지향 프로그래밍의 네 가지 속성에 대해 설명해 보세요.
  * 객체지향 프로그래밍의 5원칙에 대해 설명해 보세요.
  * C#에서 다형성을 어떻게 활용하는지 설명해 보세요.
  * C#에서 자식 클래스가 virtual로 선언된 부모 클래스의 메서드를 override할 때, 이것이 메모리에서 어떻게 관리되는지 설명해 보세요.

* 디자인 패턴
  * **디자인 패턴을 적용해 본 경험에 대해 이야기해 주세요.**
  * Singleton 패턴에 대해 설명해 보세요.
  * Null object 패턴에 대해 설명해 보세요.
  * Strategy 패턴에 대해 설명해 보세요.
  * Proxy 패턴에 대해 설명해 보세요.
  * Facade 패턴에 대해 설명해 보세요.
  * State 패턴에 대해 설명해 보세요.
  * Adapter 패턴에 대해 설명해 보세요.
  * Observer 패턴에 대해 설명해 보세요.

* 운영체제
  * 리틀 엔디언과 빅 엔디언의 차이를 설명해 보세요.
  * 프로세스와 스레드의 차이를 설명해 보세요.
  * 메모리 단편화의 종류와 해결 방법에 대해 설명해 보세요.
  * Page fault에 대해 설명해 보세요.
  * Mutex와 semaphore에 대해 설명해 보세요.
  * 데드락이 일어나는 조건에 대해 설명해 보세요.
  * CPU 스케줄러 알고리즘을 아는 대로 설명해 보세요.

* 데이터베이스
  * Primary key가 가져야 하는 특성을 설명해 보세요.
  * 데이터베이스 정규화에 대해 설명해 보세요.

---

## 시작하기 전에
* https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview
  * 본 자료 내용의 대부분은 위 링크에서 가져왔습니다.
  * 여기에는 없는, C++ 및 Unreal에 대한 내용도 위 링크에 있습니다.
* 나올 가능성이 높은 질문들은 **중요!** 표시를 달아두었습니다.
* **본 자료 내용에는 오류가 있을 수 있습니다.**
  * 모든 내용을 그대로 외우기보다는, 다른 자료도 찾아보고 내용을 검증하면서 공부하시면 학습에 도움이 될 것입니다.

## Unity & C# 스크립팅

### Unity Lifecycle
> https://docs.unity3d.com/kr/current/Manual/ExecutionOrder.html

* Awake -> OnEnable -> Start -> FixedUpdate -> Update -> LateUpdate -> OnApplicationPause
* Awake
  * Enable 여부와 상관없이 호출된다.
  * 항상 가장 먼저 호출된다. 인스턴스 생성 또는 스크립트 로드 시 한 번 불린다.
  * Awake끼리는 호출 순서가 무작위이다.
  * 참조를 형성할 때 쓰인다.

```C#
public static GameManager instance;
void Awake()
{
    instance = this;
}
```

* OnEnable
  * Start보다 일찍 불린다.
  * 스크립트가 재활성화될 때마다 불린다.
  * 오브젝트 풀링에 주로 사용한다.
* Start
  * 해당 스크립트 컴포넌트가 Enable되어야 불린다.
  * 한 번 불린다.
* Update와 LateUpdate는 호출 횟수가 같고 프레임마다 한 번씩 호출되며, 프레임 드랍의 영향을 받아 호출을 건너뛰는 경우가 있다. LateUpdate는 Update보다 나중에 불린다.
  * Update에서는 주로 사용자 입력 처리를 한다.
* FixedUpdate는 Update보다 일찍 불리며, 프레임 드랍이 생기더라도 물리 엔진의 고정된 주기에 따라 호출하지 못한 만큼 추가로 함수를 호출하여, 호출 횟수가 경과한 시간에 비례함을 보장한다.
  * FixedUpdate에서는 주로 물리 연산 처리를 한다.

### Stack / Heap Memory
> 메모리 그림을 검색해서 같이 보면서 공부하시면 좋습니다.

* 높은 주소 쪽에 Stack이 있다.
  * 쌓일수록 아래로(낮은 주소 쪽으로) 내려온다.
  * 컴파일러가 관리하며, 실행할 수 없다.
  * 값 타입을 저장한다.
* Stack보다 아래에 Heap이 있다.
  * 쌓일수록 위로(높은 주소 쪽으로) 올라간다.
  * 프로그래머가 관리하며, 실행할 수 없다.
  * 동적 할당한 객체(참조 타입)를 저장한다.
  * Unity에서의 관리되는 메모리 시스템
    * https://docs.unity3d.com/kr/current/Manual/performance-managed-memory.html
* 그 아래에 Static data가 있다. 쓸 수 있고 실행할 수 없다.
* 그 아래에 Literals가 있다. 이는 읽기 전용이며 실행할 수 없다.
* 그 아래에 Instructions가 있다. 이는 읽기 전용이며 실행할 수 있다.

### C# Boxing & Unboxing
* 값 타입
  * C#의 구조체와 열거(enum) 타입이 여기에 속한다.
  * `System.ValueType`으로부터 상속된다.
  * 스레드 스택에 할당된다.
* 참조 타입
  * C#의 클래스가 여기에 속한다.
  * `System.Object`로부터 상속된다.
  * 힙에 할당되며 GC(garbage collector)가 관리한다.
    * 이 힙 메모리 주소를 가리키는 주소 값은 스택에 저장된다.
* Boxing: 값 타입을 참조 타입으로 변환
* Unboxing: 참조 타입을 값 타입으로 변환
* Boxing과 Unboxing은 비싸다.
  * 힙에 garbage를 많이 남기고, GC가 일하게 된다.

### Unity Serialization / Deserialization
* 직렬화
  * (동적 할당을 통해 저장된) 객체를 바이트 단위로 변환하여 데이터화하는 것
  * 직렬화를 하면 디스크에 저장하거나 네트워크를 통해 전송하기 용이하며 프로그램의 실행이 멈추어도 데이터를 보존할 수 있다.
* 역직렬화
* Unity에서 어떤 클래스를 직렬화하려면 어떻게 해야 하는가?
  * 추상 클래스나 일반 클래스가 아니어야 하고
  * 클래스 앞에 `[Serializable]` 데코레이터를 붙이고
  * 해당 클래스의 모든 필드가 직렬화 가능해야 하는데
  * `int`, `bool`, `string` 등의 primitive 타입은 모두 직렬화 가능하고
  * 열거형(enum)으로 정의된 타입도 직렬화 가능하고
  * `Vector3`, `Color` 등의 일부 Unity 내장 타입도 직렬화 가능하고
  * 구조체는 구조체 앞에 `[Serializable]` 데코레이터를 붙이면 직렬화 가능하다.
  * 추가로 `UnityEngine.Object`에서 파생된 오브젝트를 가리키는 레퍼런스도 직렬화 가능하다.
  * 다만 `static`, `const`, `readonly`는 직렬화되지 않으며
  * `private` 필드도 `[SerializeField]`가 붙어있지 않다면 직렬화되지 않는다.

### C# `const`와 `readonly`의 차이
* `const`
  * 컴파일 타임에 변수가 값으로 대체된다.
  * 스택에 저장된다.
  * 선언할 때에만 값을 설정할 수 있다.
  * 값이 바뀌면 다시 빌드해야 한다.
  * 내장형 타입과만 사용할 수 있다.
* `readonly`
  * 런타임 상수이다.
  * 힙에 저장된다.
  * 선언할 때나 생성자에서만 값을 설정할 수 있다.
  * 코드에 대한 참조를 유지하므로 값이 바뀌더라도 전체를 다시 빌드하지 않아도 된다.
  * 어떤 타입과도 사용할 수 있다. (사용자 정의 클래스 포함)
* 일반적으로 `const`보다 `readonly`를 쓰는 것이 좋다.
  * `const`가 조금 빠르기는 하며, 다음의 경우에는 `const`를 사용해도 된다.
    * `switch`/`case`문 레이블
    * `enum` 정의
    * 프로퍼티의 매개변수

### C# `struct`와 `class` 인스턴스의 차이
* `struct`
  * 값 타입이다.
  * **스택에 저장된다.**
  * 메서드를 가질 수 없다.
* `class` 인스턴스
  * 참조 타입이다.
  * **힙에 저장된다.**
  * 필드와 메서드를 가질 수 있다.

### C# `string`
* Immutable이다.
  * 문자열 값을 수정하면 새로운 값을 만들고 참조를 거기로 잇는다.
  * 안 쓰는 문자열 값은 GC가 처리한다.
* 왜 이렇게 구현되어 있는가?
  * 멀티스레딩 환경에서 동기화를 모두 신경쓰는 것보다 readonly로 하는 것이 편하기 때문이다.
* 유의할 점이 있는가?
  * string을 자주 바꾸면 GC가 많이 일하게 된다.
  * **이럴 때에는 `StringBuilder`를 사용하는 것이 낫다.**
* `StringBuilder`
  * 기본적으로 16글자를 담을 수 있는 버퍼를 잡는다.
  * 이 버퍼 안에서는 수정이 이루어져도 GC가 처리하지 않는다.
  * 기존 버퍼가 꽉 찼는데 append하는 경우 뒤에 새 버퍼를 만들고 링크하여 연결한다.
* 문자열 보간
  * 문자열 앞에 `$`를 붙여주면 사용할 수 있다.
  * 예: `text = $"(x, y) = ({pos.x}, {pos.y})"`

### C#과 Unity의 Garbage Collector
> **중요!**

* *C#과 Unity의 GC가 어떻게 다른지 설명할 수 있는가?*
* *`GC.Collect()` 함수를 명시적으로 호출해본 적이 있는가?*

#### GC를 쓸 때의 장점
* 사용자가 메모리를 관리할 필요가 없다. 편하다.
* 메모리 누수가 일어나지 않는다.
* 관리되는 힙에 효율적으로 저장한다. 메모리 압축을 한다.
* 한 객체가 다른 객체가 가진 메모리에 접근하는 일을 막아 메모리 안전성을 높인다.
* 상호 참조 해결법
  * 두 객체가 서로를 상호 참조하고 있어도, root로부터 시작하는 외부 개체와 연결되어 있지 않다면 mark되지 않는다.

#### .NET의 GC
> https://learn.microsoft.com/ko-kr/dotnet/standard/garbage-collection/fundamentals

* **세대 구분이 있다.**
  * 0세대, 1세대, 2세대
  * 새로 생긴 객체들은 0세대에 넣는다.
  * 0세대에서 가장 자주 GC가 돌아간다.
  * GC로부터 한 번 살아남은 객체들은 세대가 1씩 오른다.
  * 0세대에서 돌려서 메모리를 확보할 수 없는 경우 1세대도 돌린다. 마찬가지로 1세대에서도 메모리를 확보할 수 없는 경우 2세대까지 돌린다. 즉, 2세대에서 GC가 돌아갔다면 1세대와 0세대에서도 GC가 돌아간 것이다.
  * 3세대도 있다. 대형 개체를 저장하는 힙이다. 여기서는 주소 이동이 거의 일어나지 않는다. 복사하면 오래 걸리기 때문이다. 이 3세대는 논리적으로는 2세대로 취급한다.
* 관리되는 힙 영역이 있다.
* Mark and Sweep 알고리즘으로 GC를 돌린다.
  * `static` 변수, 스레드 스택의 로컬 변수, CPU 레지스터 등을 root로 잡는다.
  * 여기서부터 참조 가능한 모든 변수들을 탐색하면서 mark한다.
  * mark 페이즈가 끝나면 관리되는 힙 영역에 할당된 모든 참조 타입 변수를 탐색하면서 mark되지 않은 것들을 sweep한다.
  * sweep할 때 힙 압축을 수행한다. 만약 garbage가 있으면 그 위(그보다 높은 주소)에 저장된, mark된 메모리를 garbage가 있던 공간에 옮길 준비를 한다. 변경될 주소 포인터를 계산하고, 메모리를 복사하여 옮기는 작업을 수행한다.
* GC가 돌아가는 중에는 모든 다른 스레드가 suspended 상태가 된다.

#### Unity의 GC

* [Boehm–Demers–Weiser garbage collector 알고리즘](https://www.hboehm.info/gc/gcdescr.html)을 사용한다.
  * Mark and Sweep의 변형이다.
* **.NET의 GC와 무엇이 다른가?**
  * 세대 구분이 없다.
  * 메모리 압축이 없다.
  * 별도의 GC 스레드 없이, 메모리 할당 스레드에서 돌아간다.
  * *아무튼 별로 안 좋다.*
* 왜 다른가?
  * 싱글 스레드 환경에서 사용하기 위해
* 유의할 점
  * 메모리 최적화가 없기 때문에 19버전 이상에서 사용하는 [점진적 GC](https://docs.unity3d.com/kr/current/Manual/performance-incremental-garbage-collection.html)를 사용하거나 오브젝트 풀링 등의 최적화 기법을 사용할 필요가 있다.
  
#### Unity에서 Garbage 생성을 줄이는 법
> https://docs.unity3d.com/kr/current/Manual/performance-garbage-collection-best-practices.html

* 임시 할당
  * 매 프레임마다 새로 힙에 할당하는 동적 메모리가 있다면 이를 줄여야 한다. (예: `Update()`)
* 재사용 가능 오브젝트 풀 (object pool 패턴)
  * 게임오브젝트를 `Destroy()`하지 않고 `SetActive()`하여 재사용한다.
* 반복되는 문자열 연결
  * 문자열을 `+=`으로 연결하지 않고 `System.Text.StringBuilder` 등을 사용한다.
  * 특히 매 프레임마다 UI의 Text를 `+`로 연결해 업데이트하는 일을 피하기 위해 다음의 방법을 사용할 수 있다.
    * 매 프레임 조건을 확인하여 해당 조건이 만족될 때에만 텍스트를 업데이트한다.
    * 고정된 부분과 변하는 부분을 서로 다른 UI Text 오브젝트로 둔다.
* 배열 값 반환 메서드
  * 매번 새로 배열을 만들어 반환하지 말고 기존 배열을 인자로 받아 수정하도록 한다.
* 컬렉션과 배열 재사용
  * 리스트나 딕셔너리를 `Clear()`하여 재사용한다.
* 클로저 및 익명 메서드
  * C#의 메서드 참조는 레퍼런스 타입이므로 힙에 할당된다. 즉, 익명 메서드이든 미리 정의된 메서드이든 메서드 참조를 인자로 전달하면 임시 할당(garbage)이 발생한다.
  * 익명 메서드를 클로저로 전환하면 메모리 양이 상당히 증가한다.
    * 클로저: 익명 메서드 밖에서 정의된 변수를 익명 메서드 안에서 사용하는 경우
    * 클로저를 만들면 정확한 값을 전달하기 위해 외부 범위 변수를 유지할 수 있는 익명 클래스를 만들고 이것을 인스턴스화하여 힙에 할당한다.
    * 가급적 클로저보다는 익명 메서드를 쓰는 것이 좋다.
* 빈 배열 재사용 (null object 패턴)
  * 배열 값 기반의 메서드가 빈 세트를 반환해야 할 때 `null` 값 대신 빈 배열로 미리 할당된 정적 인스턴스를 반환하면 더 효율적이다.
* 박싱
  * 값 타입을 `Equals(Object obj)`의 인자로 넘길 때 흔히 발생
  * C#에서는 세대 기반 GC 덕분에 큰 문제가 없지만 Unity에서는 문제가 될 수 있다.

```C#
int x = 1;
object y = new object();
y.Equals(x);  // 값 타입인 x를 참조 타입인 Object 타입으로 boxing하여 전달 -> garbage 생성
```

* 배열 기반 Unity API
  * 반복문에서는 배열을 반환하는 프로퍼티에 자주 접근하지 않는 것이 좋다.
  * 예: `Input.touches`

```C#
// Bad C# script example: Input.touches returns an array every time it’s accessed
for ( int i = 0; i < Input.touches.Length; i++ )
{
   Touch touch = Input.touches[i];

    // …
}
```

```C#
// Better C# script example: Input.touches is only accessed once here
Touch[] touches = Input.touches;

for ( int i = 0; i < touches.Length; i++ )
{

   Touch touch = touches[i];

   // …
}
```

```C#
// BEST C# script example: Input.touchCount and Input.GetTouch don’t allocate at all.
int touchCount = Input.touchCount;  // access outside the loop

for ( int i = 0; i < touchCount; i++ )
{
   Touch touch = Input.GetTouch(i);

   // …
}
```

### `delegate` & `event`
* `delegate`는 함수 포인터이다.
  * 함수를 타입처럼 취급하고 함수에 대한 참조를 갖는다.
  * 인자 수, 인자 타입, 반환 타입을 통해 정의된다.
    * 같은 함수 시그니처끼리는 모두 호환된다.
  * 호출할 함수 목록을 담을 수 있다.
  * 이것이 가리키는 함수들을 순서대로 모두 호출할 수 있다.
* `event`는 선언한 클래스에서만 호출할 수 있는 `delegate`이다.
  * 다른 클래스에서는 함수를 등록할 수만 있다.
* `Action`: 인자 타입이 T이고 반환 타입이 void인 함수 대리자 템플릿
* `Func<T, TResult>`: 인자 타입이 T이고 반환 타입이 TResult인 함수 대리자 템플릿
* `Predicate`: 인자 타입이 T이고 반환 타입이 bool인 함수 대리자 템플릿
* 호출할 함수가 `null`인 문제
  * 대리자의 함수 목록이 비어있음을 확인하지 않고 호출하면 `NullReferenceException`이 발생한다.
  * `if`문으로 `null` 체크를 하는 것은 좋지 않다.
    * 멀티스레딩 환경에서 `null` 체크 통과 후 다른 스레드에서 등록 취소를 하면 대리자가 `null`인 경우가 생길 수 있다.
  * `?.`(null conditional operator)를 사용하는 것이 스레드로부터 안전하다.
    * 이 연산자는 atomic하기 때문에 멀티스레딩 환경에서도 `null` 체크와 호출을 동시에 해준다.
    * 문제가 있다면, Unity에서는 `?.`을 잘 쓰기 어렵다.

### C#과 Unity의 `null`
> https://stackoverflow.com/questions/62678228/why-does-c-sharp-null-conditional-operator-not-work-with-unity-serializable-vari  
> **중요!**

* *Unity의 Fake null에 대해 설명해 보세요.*

* Unity에서는 null 검사를 위해 `==`과 `!=`을 커스텀으로 구현했다.
  * Unity 엔진의 백엔드는 C++로 짜여 있고, Unity C#의 문법들은 이 C++ 엔진과 소통하기 위한 개발자 API이다.
  * C#의 `System.Object`가 null이 아니더라도(Unity 객체의 메타데이터 잔존) Unity에서 `Destroy()`한 `UnityEngine.Object`에 대해서는 `obj == null`이 `true`가 나온다.
    * Unity 내부의 C++에서의 객체가 `null`이면 C#의 `UnityEngine.Object`도 `null`이지만, GC가 돌기 전까지는 C#의 `System.Object`가 `null`이 아니다.
    * 이를 **Fake null**이라고 한다.
  * 그러나 **`?.`(null conditional operator)의 경우 `UnityEngine.Object`가 아니라 `System.Object`의 null 여부를 검사하기 때문에 `UnityEngine.Object`에 대해서는 사용할 수 없다.**
    * `?.`는 오버라이드할 수 없다.
  * Unity에서 `Destroy()`한 오브젝트에 접근하는 경우 `NullReferenceException`이 아니라 `MissingReferenceException`이 뜨는데, 이는 Unity에서 뭔가 처리하는 로직(fake null)이 있음을 의미한다.
* Unity의 null 비교는 느리다.
  * `UnityEngine.Object`가 살아있는지 검사하는 로직이 무겁기 때문이다.
  * 이것이 살아있다는 것이 확실하면(`Destroy()`된 객체가 아님을 보장할 수 있다면) 다음 세 가지 방법으로 null 비교 속도를 빠르게 할 수 있다.
    1. `System.Object`로 캐스팅하는 방법
    2. `System.Object.ReferenceEquals()`로 비교하는 방법
    3. 패턴 매칭 `is null`을 이용하는 방법
* **`== null` vs. `is null`**
  * `== null`은 타입 별로 오버라이드된 `==` 연산자를 호출하여 계산한다. 따라서 `UnityEngine.Object`을 비교할 때에는 느리다.
  * `is null`은 바로 `ceq` 인스트럭션 연산을 적용하기 때문에 빠르다. 다만 `==`이 오버라이드된 경우 `is null`을 사용하면 의도하지 않은 동작이 나타날 수 있다.
* 속도 비교
  * `is null`과 `ReferenceEquals(null)`은 속도가 비슷하게 가장 빠르다.
  * (가장 빠름) `is null` < `Equals(a, null)` < `a.Equals(null)` < `a == null` (가장 느림)
* `== null`은 예외적인 상황을 내부에서 체크하므로 가장 안전하다.

> https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-

* `?[]`(요소 액세스 null 조건부 연산자)
  * 예: `a?[x]`
    * `a`가 `null`이면 `null`을 반환한다.
    * `a`가 `null`이 아니면 `a[x]`의 값을 반환한다.
    * `x`가 `a`의 인덱스 범위 밖에 있는 경우 `IndexOutOfRangeException`을 띄운다.
  * `==`이 오버라이드된 경우에 `?[]`을 사용하면 의도하지 않은 동작이 나타날 수 있다.

> https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/null-coalescing-operator

* `??`(null coalescing operator)
  * 예: `return a ?? 3;`
    * `a`가 `null`이 아니면 `a`를 반환하고, `null`이면 3을 반환
  * 왼쪽 연산항이 `null`이 아니면 오른쪽 연산항을 평가하지 않는다.
  * `==`이 오버라이드된 경우에 `??`를 사용하면 의도하지 않은 동작이 나타날 수 있다.
  * `??`는 오버라이드할 수 없다.
* `??=`(null coalescing assignment operator)
  * 예: `a ??= 3;`
    * `a`가 `null`일 때에만 `a`에 3을 대입
  * `==`이 오버라이드된 경우에 `??=`을 사용하면 의도하지 않은 동작이 나타날 수 있다.
  * `??=`는 오버라이드할 수 없다.

### this
* 클래스의 현재 인스턴스를 가리킨다.
  * 로컬 변수와 필드를 구분할 때 필드에 대해 `this.필드`를 사용하여 구분할 수 있다.
* 생성자에서 `: this()`를 사용할 수 있다.
  * 생성자를 여러 개 만드는 경우, 중복되는 코드를 `: this()`로 정리할 수 있다.
  * `this(int a)`와 같이 인자도 입력할 수 있다. 그러면 그 생성자를 가리키게 된다.
  * 예: 아래 코드를 그 아래의 코드로 바꿀 수 있다. 기능은 같다.

```C#
class MyClass
{
    int a;
    int b;

    public MyClass()
    {
        a = 10;
    }

    public Myclass(int b)
    {
        a = 10;
        this.b = b;
    }
}
```

```C#
class MyClass
{
    int a;
    int b;

    public MyClass()
    {
        a = 10;
    }

    public Myclass(int b) : this()
    {
        this.b = b;
    }
}
```

* 정적 함수에서 인자에 `this`를 쓸 수 있다.
  * 예: `public static void Shuffle<T>(this IList<T> list)`
  * 이렇게 하면 확장 메서드를 만들 수 있다.
  * 사용 예 (둘 다 된다.)
    * `Shuffle(list);`
    * `list.Shuffle();`
  * 그러나 기존 클래스의 함수와 동일한 시그니처로 확장 메서드를 정의하면 호출되지 않는다. 컴파일 타임에 인스턴스 함수가 우선적으로 호출되고, 이것이 없으면 확장 메서드를 호출하기 때문이다.

### List & Dictionary
* `List<>`: 배열(ArrayList)
  * 용량(capacity)을 초과하여 삽입하는 경우 용량을 늘린 새 배열을 할당하고 기존 배열의 값을 복사한다. 따라서 시간 복잡도가 O(n)이다. 이를 피하려면 미리 사용할 만큼의 용량을 할당할 필요가 있다.
  * `TrimExcess()`를 쓰거나 `Capacity`를 직접 변경하여 낭비되는 공간을 줄이는 경우에도 새 배열을 할당한다. 따라서 시간 복잡도가 O(n)이다.
  * `Remove()`는 배열의 용량을 변경하지 않는다.
* `Dictionary< , >`: 해시 테이블
* `HashSet<>`: 해시 테이블
* `SortedSet<>`: 레드-블랙 트리
* `SortedList< , >`: 배열
* `SortedDictionary< , >`: 레드-블랙 트리

> https://stackoverflow.com/questions/3070644/ordered-list-of-keyvaluepairs

* `SortedList<TKey, TValue>`와 `SortedDictionary<TKey, TValue>`의 차이
  * 둘 다 검색은 O(log n)
  * 삽입, 삭제에서 차이가 있다.
    * `SortedList< , >`는 삽입, 삭제가 O(n)
    * `SortedDictionary< , >`는 삽입, 삭제가 O(log n)
  * 메모리는 `SortedList< , >`가 더 적게 차지한다.
  * 정렬된 데이터로부터 자료구조가 생성된 경우에는 `SortedList< , >`가 더 빠르다.
  * `Keys`나 `Values` list를 반환해야 할 때 `SortedList`는 이 list를 그냥 반환하면 되고, `SortedDictionary`는 list를 생성해서 반환해야 한다.
  * `SortedDictionary`는 generic이 아니다.
* `List<KeyValuePair< , >>`의 용도
  * 이것은 `SortedDictionary< , >`와 다르게, **삽입 순서를 보존한다.**
  * generic이기도 하다.
* `Dictionary`와 `SortedDictionary` 중 누가 더 나은가?
  * 삽입과 삭제는 `Dictionary`가 더 빠르다.
  * 검색에 있어서 `SortedDictionary`가 아주 조금 더 빠르다.
  * 보통은 `Dictionary`를 쓰는 것이 낫다.

* Hash Table
  * 운이 좋으면 O(1)만에 삽입, 삭제, 검색이 가능하다.
  * 운이 나쁘면(해시 함수가 계속 충돌하면) 최악의 경우 O(n)이 걸린다.
  * 충돌 시 linear probing(+1, +2, +3, ...), quadratic probing(+1, +4, +9, ...), double hashing(두 개의 해시 함수 사용)을 통해 내부를 채워 나간다.

* Red-black Tree
  * self-balancing binary search tree
  * 삽입, 삭제, 검색 모두 O(log n)이다.

### C#에서의 얕은 복사 vs. 깊은 복사
* 얕은 복사: 같은 힙 메모리 주소를 가리키도록 주소를 복사
* 깊은 복사: 힙에 복사할 객체가 가진 메모리만큼을 새로 할당하여 복사하고 새 메모리 주소를 반환
* C++과는 조금 다르다.
  * C++에서의 얕은 복사: 멤버의 값만 복사
  * C++에서의 깊은 복사: 멤버의 값 복사 + 포인터가 참조하는 대상까지 복사

### C# LINQ
> https://learn.microsoft.com/ko-kr/dotnet/csharp/linq/

* Language-Integrated Query

```C#
string sentence = "the quick brown fox jumps over the lazy dog";
// Split the string into individual words to create a collection.
string[] words = sentence.Split(' ');

// Using query expression syntax.
var query = from word in words
            group word.ToUpper() by word.Length into gr
            orderby gr.Key
            select new { Length = gr.Key, Words = gr };

// Using method-based query syntax.
var query2 = words.
    GroupBy(w => w.Length, w => w.ToUpper()).
    Select(g => new { Length = g.Key, Words = g }).
    OrderBy(o => o.Length);

foreach (var obj in query)
{
    Console.WriteLine("Words of length {0}:", obj.Length);
    foreach (string word in obj.Words)
        Console.WriteLine(word);
}

// This code example produces the following output:
//
// Words of length 3:
// THE
// FOX
// THE
// DOG
// Words of length 4:
// OVER
// LAZY
// Words of length 5:
// QUICK
// BROWN
// JUMPS
```

### C# Reflection
> https://learn.microsoft.com/ko-kr/dotnet/fundamentals/reflection/reflection

* 로드된 어셈블리 내에 정의된 타입에 대한 정보를 런타임에 가져올 수 있다.
* 어셈블리에는 모듈이 포함되고, 모듈에는 타입이 포함되고, 타입에는 멤버가 포함된다.
* 리플렉션은 어셈블리, 모듈 및 타입을 캡슐화하는 개체를 제공한다.
  * 동적으로 타입 인스턴스를 만들거나, 타입을 기존 개체에 바인딩하거나, 기존 개체에서 타입을 가져올 수 있다.
  * 해당 타입의 메서드를 호출하거나 필드 및 프로퍼티에 접근할 수 있다.

### Unity 프로파일러
> https://docs.unity3d.com/kr/2021.3/Manual/Profiler.html
> https://learn.unity.com/tutorial/diagnosing-performance-problems-2019-3?language=en&courseId=5c87de35edbc2a091bdae346#648abf6eedbc2a6ad72aff24  

* *과거에 진행했던 프로젝트에서 프로파일러를 써본 경험을 말씀해 주세요.*

* CPU, GPU, Garbage Collection Profiling 등이 가능하다.
  * 대부분은 Rendering 관련 이슈일 것이다. 이 경우 CPU가 병목인지 GPU가 병목인지 찾아야 한다.
    * `Gfx.WaitForPresent` 함수에서 병목이 생긴다면 이것은 CPU가 GPU 처리를 기다리고 있다는 뜻이다. 이때는 GPU가 병목이다.
    * CPU가 병목이라면 [여기](https://docs.unity3d.com/kr/2021.3/Manual/graphics-performance-profiling.html)를 살펴본다.
  * `GC.Collect()` 함수가 호출된 시점에서 병목이 생긴다면 [여기](https://docs.unity3d.com/kr/current/Manual/performance-garbage-collection-best-practices.html)를 살펴본다.
  * Physics가 병목인 경우 [여기](https://docs.unity3d.com/kr/current/Manual/iphone-Optimizing-Physics.html)를 살펴본다.
  * 사용자 스크립트가 병목인 경우 [여기](https://docs.unity3d.com/kr/2021.3/Manual/BestPracticeUnderstandingPerformanceInUnity.html)를 살펴본다.
* 성능 문제를 찾아 해결하고 싶다면 [여기](https://learn.unity.com/tutorial/fixing-performance-problems-2019-3?uv=2022.2&courseId=5c87de35edbc2a091bdae346#)를 읽어보자.
* 안드로이드 빌드를 만들고 모바일 하드웨어에서 프로파일링을 돌리는 방법
  * https://docs.unity3d.com/kr/current/Manual/profiler-profiling-applications.html

### Unity 성능 최적화
> **중요!**  

* *과거에 진행했던 프로젝트에서 최적화를 해본 경험을 말씀해 주세요.*

* **아래 세 문서를 모두 읽어보시기를 강력히 추천합니다!**
  * https://docs.unity3d.com/kr/2021.3/Manual/BestPracticeUnderstandingPerformanceInUnity.html
  * https://docs.unity3d.com/kr/current/Manual/performance-garbage-collection-best-practices.html
  * https://learn.unity.com/tutorial/fixing-performance-problems-2019-3?uv=2022.2&courseId=5c87de35edbc2a091bdae346#

## Unity 그래픽스

### 그래픽 렌더링 파이프라인
> https://docs.unity3d.com/kr/2021.3/Manual/render-pipelines-overview.html

* 종류
  * 빌트인 렌더 파이프라인: 예전 파이프라인. 커스터마이징 제한적
  * 스크립터블 렌더 파이프라인 (SRP)
    * 유니버설 렌더 파이프라인 (URP)
    * 고해상도 렌더 파이프라인 (HDRP)
    * 커스텀 렌더 파이프라인

* 개발 초기에 파이프라인을 잘 선택하여 결정하는 것이 중요하다.

* 과정
  1. 게임오브젝트의 트랜스폼을 월드 좌표계로 변환
  2. 카메라 시야 밖의 물체들을 렌더링 대상에서 제외
  3. 월드 좌표계를 카메라의 viewport 상 좌표로 변환
  4. 매터리얼의 셰이더 코드 실행하여 렌더링
  5. 렌더링한 결과를 '렌더 텍스처' 또는 '백 버퍼'에 저장
  6. '렌더 텍스처' 또는 '백 버퍼'에 있는 내용을 디바이스 화면에 출력

### 배치 렌더링

* 여러 개체를 한 번의 그래픽스 API 호출(드로우 콜)로 결합하여 한번에 렌더링하는 기법
* 장점
  * 렌더링 함수 호출 횟수 감소: 동일한 매터리얼과 설정을 갖는 개체들을 그룹화하여 한 번의 API 호출로 그려내므로 오버헤드를 줄인다.
  * 그래픽 카드 친화적: 많은 수의 개별적인 요청보다 한 번의 큰 요청을 병렬적으로 처리하는 데 특화된 그래픽 카드의 성능을 최대한으로 활용한다.
  * FPS 향상
  * 메모리 사용량 감소: 호출 횟수가 줄어들기 때문에 남는 메모리를 다른 곳에 더 활용할 수 있게 된다.
* 배치 렌더링 활용 방법
  * 매터리얼 공유
  * 레이어 정렬: Sorting Layers와 Order in Layer를 통해 개체의 그룹화를 관리할 수 있다.
    * https://docs.unity3d.com/kr/2021.3/Manual/class-SortingGroup.html
  * GPU 인스턴스화: 동일한 메시와 매터리얼을 사용하는 여러 개체를 하나의 그래픽 API 호출로 처리할 수 있다.

### 드로우 콜 최적화
> https://docs.unity3d.com/kr/2021.3/Manual/optimizing-draw-calls.html  
> **중요!**

* 드로우 콜: 그래픽스 API가 화면에 그릴 내용과 그릴 방법을 알려주는 것
* 드로우 콜을 호출하기 전에 준비 단계가 있는데, 이때 GPU의 렌더 상태 변경(다른 매터리얼로 전환 등)에 리소스가 많이 든다.
* 렌더 상태 변경 수 줄이는 법
  * 드로우 콜 전체 수를 줄인다.
  * 동일한 렌더 상태를 사용하여 다수의 드로우 콜을 수행하는 경우 이들을 묶어 처리한다.
* 드로우 콜과 렌더 상태 변경 최적화의 이점
  * 전력량과 발열량 감소
  * 유지보수성 향상: 더 많은 게임 오브젝트 추가 가능
* 최적화 방법
  * GPU 인스턴싱: 동일한 메시 사본 여러 개를 동시에 렌더링
  * 드로우 콜 배칭(batching): 메시를 결합하여 드로우 콜 횟수 감소
    * 정적 배칭: 정적 게임 오브젝트의 메시를 미리 결합
    * 동적 배칭: 동일한 설정(동일한 수와 타입의 속성을 저장하는 버텍스)을 공유하는 메시 버텍스를 그룹화하여 한 번의 드로우 콜로 렌더링
  * 메시 수동 결합: 여러 메시를 단일 메시로 수동 결합
  * SRP 배처(batcher): 스크립터블 렌더 파이프라인 사용 시 활용 가능

### 스프라이트 아틀라스
> https://docs.unity3d.com/kr/2021.3/Manual/class-SpriteAtlas.html  
> **중요!**

* 여러 개의 텍스처를 단일 텍스처로 결합하여 **한 번의 드로우 콜로 처리**하는 기법
* 큰 성능 소모 없이 패킹된 텍스처에 동시에 접근할 수 있다.
* 사용법
  * Asset > Create > Sprite Atlas 메뉴를 통해 `*.spriteatlas` 파일을 생성한다.
  * `Objects for Packing`에 `Texture2D`, 스프라이트 에셋, 스프라이트가 담긴 폴더를 넣으면 해당 스프라이트가 동일한 설정을 가지고 하나의 아틀라스로 묶이게 된다.
* 아틀라스 성능 최적화
  * 스프라이트가 씬에서 활성화될 때 Unity가 해당 스프라이트가 속한 스프라이트 아틀라스 전체를 로드한다. 이 크기가 너무 크고 씬에서 이 아틀라스의 텍스처를 거의 사용하지 않는 경우 오버헤드가 크다.
  * **같은 씬에서 활성화하는 대부분의 스프라이트가 동일한 아틀라스에 속해 있도록 하는 것이 좋다.**
  * 아틀라스 팩 미리보기 기능을 통해 빈 공간이 과도하게 있는지 확인하고 줄이면 좋다. Max Texture Size를 줄이면 스프라이트 텍스처 크기를 줄이지는 않고 이 크기에 맞게 아틀라스의 빈 공간을 최대한 잘라낸다.

### 텍스처
> https://docs.unity3d.com/kr/2021.3/Manual/Textures.html

* 3D 오브젝트의 메시 표면에 걸쳐 적용되는 비트맵 이미지
* 텍스처는 매터리얼을 사용해 오브젝트에 적용할 수 있고, 매터리얼은 셰이더를 사용해 메시 표면의 텍스처를 렌더링한다.
* 텍스처는 2의 제곱수 크기로 만들어야 한다.
  * 예: 32x32, 64x64, 128x128, 256x256
  * 정사각형이 아니어도 된다.
* 텍스처는 2D 스프라이트, 메시, 파티클 시스템, GUI, 지형 높이 맵(Terrain Heightmap)에 사용된다.
* 텍스처 타입
  * Default
  * 노멀 맵: 컬러 채널을 실시간 노멀 매핑에 적합한 포맷으로 변환할 때 사용
  * 에디터 GUI 및 레거시 GUI: HUD 또는 GUI 컨트롤에서 사용
  * 스프라이트(2D 및 UI)
  * 커서
  * 쿠키: 빌트인 렌더 파이프라인에서 씬의 광원 쿠키로 사용
  * 라이트맵: 특정 포맷으로 인코딩이 가능해지고 텍스처 데이터에 대해 포스트 프로세싱 단계 수행 가능
  * 단일 채널: 하나의 채널만 필요한 경우
* 텍스처 임포트 시 고려 사항
  * 노멀 맵
    * 노멀 맵 셰이더에서 로우 폴리곤 모델에 디테일이 더 많이 포함된 것처럼 보이게 하는 데 사용
  * 알파 맵
    * 알파(투명도) 정보만 포함하는 텍스처
  * 디테일 맵
    * 지형(terrain)에서 주 텍스처가 가까워질 때 작은 디테일을 페이드 인 하여 텍스처가 흐릿하게 보이는 현상 방지
  * 큐브 맵 (반사)
    * 텍스처를 반사 맵(반사 프로브 또는 큐브맵 스카이박스)으로 사용하려면 Texture Shape를 Cube로 설정
    * https://docs.unity3d.com/kr/2021.3/Manual/class-Cubemap.html
  * 이방성 필터링
    * Aniso 레벨을 높이면 지표각에서 보이는(기울인) 텍스처 품질 향상
    * 바닥과 천장 텍스처에 사용하면 좋다.

### 밉맵(Mip Map)
> https://docs.unity3d.com/kr/2021.3/Manual/texture-mipmaps-introduction.html

* 원본 텍스처에서 2의 거듭제곱만큼 가로와 세로 크기를 축소한 낮은 해상도의 텍스처 버전
* 3D 씬에서 오브젝트를 렌더링할 때, 더 높은 mip 레벨(고해상도)은 카메라에 가까운 오브젝트에 사용되고, 더 낮은 mip 레벨(저해상도)은 더 먼 오브젝트에 사용된다.
  * 매번 새로 샘플링(크기 조절)하지 않고 미리 캐시해 놓는 것
  * 렌더링 작업 속도를 늘리고 렌더링 아티팩트를 줄일 수 있다.
* 밉맵을 사용하면 전체 텍스처 용량을 33% 늘린다.
  * 항상 같은 크기로만 렌더링되는 UI 텍스처 등은 밉맵을 사용하지 않는 것이 유리하다.

## 객체지향 프로그래밍

### 네 가지 속성
* 추상화
  * 하나의 객체가 하나의 역할을 맡도록
  * 역할과 구현의 분리
  * 인터페이스 / 추상 클래스
* 상속
  * 클래스 간 위계질서 만들기
  * 코드의 공통된 부분을 재사용 가능하게
* 캡슐화
  * public, protected, private
  * Property (getter, setter)
* 다형성
  * 상위 클래스 타입으로 하위 클래스 조작 가능
  * 함수 오버로딩, 함수 오버라이딩
  * 코드의 반복 줄임

### 5원칙 (SOLID 원칙)
1. 단일 책임 원칙 (Single Responsibility Principle)
   * 하나의 객체는 하나의 역할(책임)만을 가져야 한다.
   * 추상화와 관련
2. 개방-폐쇄 원칙 (Open-closed Principle)
   * 수정에 닫혀 있고 확장에 열려 있어야 한다.
   * 캡슐화, 상속, 다형성과 관련
3. 리스코프 치환 원칙 (Liskov Substitution Principle)
   * 자식 클래스는 부모 클래스로 대체할 수 있어야 한다.
   * 상속, 다형성과 관련
4. 인터페이스 분리 원칙 (Interface Segregation Principle)
   * 필요하지 않은 기능을 의존하도록 강요하지 않아야 한다.
   * 인터페이스의 모든 메서드를 구현하도록 강요하지 않아야 한다. (강요라고 느끼지 않게 필수적인 것만 인터페이스에 둔다.)
   * 추상화와 관련
5. 의존성 역전 원칙 (Dependency Inversion Principle)
   * 추상적인 것에 구체적인 것이 의존해야 한다. 구체적인 것에 추상적인 것이 의존하면 안 된다.
   * 부모 클래스에 자식 클래스가 의존해야 한다. 반대가 되면 안 된다.
   * 추상화, 상속과 관련
* 소프트웨어의 유지보수성, 재사용성, 확장성을 높이기 위해 이 원칙들을 지키면 좋다.

### C# 다형성
* 자식 클래스가 부모 클래스의 메서드를 `override`하거나 `new` 키워드로 숨길 수 있다.
* `A`가 `virtual` 클래스이고 `B`가 `A`를 상속하는 자식 클래스이며 둘이 같은 이름의 메서드를 구현하고 있을 때, 다음의 경우에 동작이 다르다.
  * `A a = new A();`
    * `A`의 메서드가 호출된다.
  * `A a = new B();`
    * `B`의 메서드를 `override`로 정의한 경우, `B`의 메서드가 호출된다.
    * `B`의 메서드를 `new`로 정의한 경우, `A`의 메서드가 호출된다.
  * `B b = new B();`
    * `B`의 메서드가 호출된다.
  * `A a = new B(); B b = (B) a;`
    * `B`의 메서드를 `new`로 정의한 경우, `B`의 메서드가 호출된다.
    * 아무튼 `B`의 메서드가 호출된다.
  
### C# VTable
> https://ko.wikipedia.org/wiki/%EA%B0%80%EC%83%81_%EB%A9%94%EC%86%8C%EB%93%9C_%ED%85%8C%EC%9D%B4%EB%B8%94  
> https://www.csharpstudy.com/DevNote/Article/28

* *virtual class와 이를 상속한 클래스가 있고 자식 클래스에서 부모 클래스의 메서드를 오버라이드했을 때, 이 둘이 메모리에서 어떤 자료구조로 관리되는가?*

* Virtual table(VTable)은 가상 메서드(virtual 또는 abstract)를 갖는 클래스를 상속하여 해당 메서드를 override할 때 생긴다.
  * 메서드 포인터를 저장하는 배열이다.
  * Heap 상 객체의 Type Handle이 가리키는 곳의 Method Table 메타데이터 안에 들어있다.
* 클래스의 객체가 생성될 때, 컴파일러가 이 VTable에 대한 포인터(vpointer)를 객체의 숨은 멤버로 추가한다.
* C#의 모든 클래스는 `System.Object`의 자식이고 4개의 가상 메서드(`ToString()`, `Equals()`, `GetHashCode()`, `Finalize()`)를 자신의 VTable 안에 가진다.
  * 여기에 추가로, 자신의 부모 클래스가 가진 가상 메서드를 자신의 VTable 안에 가진다.
  * 가장 부모의 것부터 자식 클래스 자신의 메서드까지 계층 순서대로 메서드 포인터 슬롯을 갖게 된다.
* 메서드 `override` 시 자식 클래스의 VTable에는 해당 메서드가 부모의 것 대신의 자신의 것으로 들어간다. 부모의 메서드는 자식의 VTable에 남아있지 않다.
* 메서드를 `new`로 숨길 시 자식 클래스의 VTable에는 해당 메서드가 부모의 것과 자신의 것 모두 들어있다.

## 디자인 패턴
> **중요!**  
> 디자인 패턴 각각을 완벽하게 외우기보다, 아래 질문에 대해 생각해보면 좋습니다.

* *디자인 패턴을 적용해본 적이 있는가?*
* *디자인 패턴을 따로 익혀야 한다고 생각하는가?*

### Singleton 패턴
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DesignPattern/SingletonPattern.md

* *멀티스레드 환경에서 싱글톤을 써본 적이 있는가? 어떤 점을 고려해야 하는가?*

### Null Object 패턴
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DesignPattern/NullObjectPattern.md

* 함수의 반환값으로 null 대신 null과 같은 역할을 하는 dummy 오브젝트를 생성하여 반환한다.
* Dummy 오브젝트를 받으면 아무 연산도 수행하지 않도록 구현한다.
* null 체크를 안 해도 된다.

### Strategy 패턴
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DesignPattern/StrategyPattern.md

* 추상화된 인터페이스를 두어서 구현이 바뀌거나 교환되더라도 호출에서 코드를 수정하지 않아도 되게 하는 방법
* 예: `Interact()` 하나로 `Portal.Interact()`, `Alter.Interact()`, `Monster.Interact()` 등을 수행할 수 있게 하는 방법

### Proxy 패턴
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DesignPattern/ProxyPattern.md

* 포장을 통해 접근하고, 그 내부는 다를 수 있는 패턴.

### Facade 패턴
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DesignPattern/FacadePattern.md

* 중간에 인터페이스(facade)를 하나 두어, 실제 worker(클래스)들이 하는 일을 적당히 숨기면서도 간단한 인터페이스로 worker들에게 일을 줄 수 있는 패턴

### State 패턴
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DesignPattern/StatePattern.md

* FSM(finite state machine)을 만들 때 주로 사용
* 여러 상태를 인터페이스로 묶어서 각 상태 별로 해당 상태에서 할 수 있는 일(함수)을 정의해주는 패턴
* 모든 state 패턴은 strategy 패턴이지만 역은 성립하지 않는다.
  * State 패턴에서는 상태 파생 클래스들이 일할 때 context에 대한 참조를 갖는다. 그러나 strategy 패턴에서는 이러는 경우가 없다.

### Adapter 패턴
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DesignPattern/AdapterPattern.md

* 호환되지 않는 클래스의 인터페이스를 클라이언트(호출자)와 호환되도록 중간에 인터페이스를 두어 함수 시그니처 등을 변환해주는 패턴

### Observer 패턴
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DesignPattern/ObserverPattern.md

* 상태 변화를 감지하는 이벤트 함수 등을 구독하는 옵저버들을 만들고, 상태 변화가 생길 때 자신을 구독하고 있는 옵저버들에게 신호를 보내 추가적인 업데이트를 할 수 있도록 한다.
* 콜백 함수를 만들 때 유용하다.
* 장점
  * 결합이 느슨해진다.
* 단점
  * 복잡해지고 느려진다.
  * 멀티스레딩 환경에서 실행과 구독 취소가 동시다발적으로 발생하면 버그가 쉽게 생긴다.

## 운영체제
> 게임 클라이언트가 아닌 개발 직군에서는 많이 묻지만, 게임 클라이언트에서는 잘 묻지 않는 것 같기도 합니다.
> 학부 졸업생이 신입으로 입사하는 경우에는 물어볼 확률이 높습니다.

### 리틀 엔디언 vs. 빅 엔디언
* 빅 엔디언
  * 16진수 int `1A2B3C4D`를 주소가 낮은 위치부터 높은 위치 순서대로 `1A`, `2B`, `3C`, `4D` 순으로 메모리에 저장한다.
  * 사람이 읽기 쉽다.
  * 컴퓨터가 읽기 어렵다.
* 리틀 엔디언
  * 16진수 int `1A2B3C4D`를 주소가 낮은 위치부터 순서대로 `4D`, `3C`, `2B`, `1A` 순으로 저장한다.
  * 사람이 읽기 어렵다.
  * 사칙연산에 유리하다.
  * x86 시스템은 리틀 엔디언을 사용한다.
* ARM 같은 곳에서는 둘 다 사용하기도 한다.

### 프로세스 vs. 스레드
* 프로세스: OS의 작업 단위
  * 스택, 힙, 코드, 데이터를 모두 복사해 가진다.
  * 다른 프로세스와 독립적으로 돌아간다. (공유 메모리를 사용하지 않는다면)
  * context switching 등이 무겁다.
* 스레드: 프로세스 내에서의 실행 단위
  * 스택만 복사하고 나머지 자원은 같은 프로세스 내에서 여러 스레드가 모두 공유한다.
  * 가볍게 만들고 없앨 수 있다.
* 멀티프로세스 vs. 멀티스레드
  * 프로세스보다 스레드가 가볍기 때문에 멀티스레드를 더 자주 사용하게 된다.
  * 멀티프로세스든 멀티스레드든 동기화 이슈는 중요하다.

### Memory Fragmentation
* 외부 단편화
  * 남은 메모리 총합은 충분한데 각각이 다 쪼개져 있어 하나의 큰 메모리 공간을 할당할 수 없는 경우
* 내부 단편화
  * 많이 할당해놓고 쓰지 않는 경우
  * 페이징할 때 발생하기도 한다.
* 외부 단편화 해결책
  * 페이징
    * 디스크 등의 보조 기억 장치를 활용해 메모리 일부를 일정한 페이지 단위로 쪼개 거기에 옮겨 놓고, 다시 불러오고 하는 방법
    * 어느 주소에 있는지 기억해야 하므로 페이지 테이블을 관리해야 한다. 페이지 테이블은 가상 페이지 주소와 물리 메모리(디스크에 있는 경우 메모리의 프레임에 먼저 로드함) 상의 프레임 주소를 연결하는 정보를 가지고 있다.
    * 디스크까지 내려가는 데 너무 시간이 오래 걸리므로 TLB 등의 버퍼를 활용한다. TLB는 페이지 테이블에 대한 캐시이다.
    * https://ko.wikipedia.org/wiki/%ED%8E%98%EC%9D%B4%EC%A7%95
  * 압축 (조각 모음)
    * 오래 걸린다.
  * 통합
    * 근처에 있는 메모리를 하나로 연결한다.
* 내부 단편화 해결책
  * 세그멘테이션
    * 변동 크기로 잘라 관리한다.
    * "필요한" 만큼만 할당한다.
    * 외부 단편화가 생길 수 있으므로 그냥 이럴 바에는 페이징을 한다.
* 두 단편화를 모두 해결하는 방법
  * 메모리 풀 사용
    * 매 번 새로운 메모리를 할당하는 것이 아니라, 풀(pool)에서 메모리를 빌려주고 다시 반환받으면 재사용할 수 있도록 하는 방법이다.
    * 필요한 만큼만 할당하므로 내부 단편화가 일어나지 않는다.
    * 메모리를 할당 해제한 후에 재사용할 수 있으므로 외부 단편화가 일어나지 않는다.

### 가상 메모리
* 메모리 가상화를 하는 이유
  * 사용자에게는 메모리가 무한한 것처럼 보여준다.
  * 실제로는 보조 기억 장치(디스크 등)를 활용하여 부족한 메모리 공간을 관리한다.
* MMU(Memory Management Unit)을 통해 관리
* Page fault
  * 원하는 페이지가 메모리에 없고 디스크에 있을 때 발생
* 페이지 교체 정책
  * 메모리에 남길 페이지와 디스크로 보낼 페이지를 결정한다.
  * LRU (Least Recently Used)
* TLB (Translation Lookaside Buffer)
  * 자주 쓰이는 페이지의 물리 주소를 기억한다.

### Mutex & Semaphore
* Mutex
  * 하나의 스레드가 mutex(lock) 객체를 갖는다.
  * 다른 스레드나 프로세스는 누군가 이 mutex를 가지고 있는 동안 접근할 수 없다.
  * 사용이 끝나면 mutex를 놓는다.
  * Binary semaphore라고도 한다.
* Counting Semaphore
  * 둘 이상의, 정해진 수의 스레드가 동시에 접근할 수 있다.
  * 카운터를 두고 있으며, 이것이 0이 되면(정해진 수의 스레드가 사용 중이면) 더 들어갈 수 없다.
    * 사용할 때 카운터를 1 내린다.
    * 사용이 끝나면 카운터를 1 올린다.
  * try를 뜻하는 P(*사용할래요!*)와 increment를 뜻하는 V(*사용 끝났어요!*)를 사용한다.
    * P - critical section - V 순으로 사용해야 한다.
  * 잘못 사용하면 데드락이 되거나 상호 배제에 실패할 수 있다.
* 사용 권한을 얻을 때까지 무한 루프를 돌면서 기다리거나, 이보다 효율적으로는 스레드를 sleep했다가 다른 스레드가 사용 권한을 놓을 때 sleep한 스레드를 깨우는 방식으로 구현할 수 있다.

### Deadlock (교착 상태)
* A를 잡은 스레드가 B를 갖고 싶어하고, B를 잡은 스레드가 A를 갖고 싶어하는데, A와 B 모두 상호 배제가 필요한 자원이고, 서로가 자신이 가진 것을 놓을 생각이 없다면 데드락이 발생한다. 이때 누구라도 A와 B를 모두 잡는 경우는 평생 생기지 않는다.
* 다음 네 가지 조건을 **모두 만족해야** 데드락이 발생한다.
  1. 어떤 자원에 대해 상호 배제가 필요하다.
  2. 한 자원을 잡으면서 다른 자원을 기다리는 상황이 있다.
  3. 선취 불가능하다(non-preemptive). 다른 프로세스를 종료시킬 수 없다.
  4. 자원을 얻고자 하는 프로세스를 방향이 있는 그래프로 나타낼 때 사이클이 존재한다.
    * 예: 어떤 곳에서는 A -> B 순으로 mutex를 잡고, 다른 곳에서는 B -> A 순으로 mutex를 잡는다면 사이클이 발생하여 데드락에 걸릴 수 있다.
* 위의 조건 중 하나라도 해결하면 데드락이 풀린다.
  * 조건 1.은 없앨 수 없다. 상호 배제를 안 해도 된다면 mutex를 쓸 이유가 없다.
  * 대부분은 조건 4.의 사이클을 제거하여 해결한다.
  * 조건 3.에 대해 선취 가능하게 만들어 해결하는 방법도 있다.
* 데드락이 발생하면 해당 프로세스들을 차례로 강제 종료하여 해결해야 한다.

### CPU 스케줄러 알고리즘
* FCFS: First Come First Serve
  * 먼저 온 것부터 먼저 처리
  * Non-preemptive하다.
  * 긴 프로세스가 오면 짧은 프로세스를 오랫동안 실행할 수 없다.
* SJF: Shortest Job First
  * 가장 짧은 일을 우선적으로 처리한다.
  * Preemptive하게 할 수도, 아니게 할 수도 있다.
  * Preemptive한 경우 긴 프로세스는 계속 처리되지 못한다. (Starvation)
  * 얼마나 걸릴지를 예측해야 하는데, 이전에 실행했던 프로세스의 예상 수행 시간과 실제 수행 시간을 바탕으로 예측한다.
* Priority
  * 프로세스마다 나름의 우선순위를 둔다.
  * 우선순위가 높은 프로세스가 오면 하던 걸 멈추고 그걸 먼저 한다.
    * Preemptive하다.
  * 역시 starvation 문제가 있고, 이를 해결하기 위해 오랫동안 실행이 안 되면 aging을 도입해 우선순위를 조금씩 높여준다.
* RR: Round Robin
  * 일정 시간 단위를 정하고 이보다 넘어가면 무조건 다른 프로세스로 바꿔 실행한다.
  * 시간 단위가 `q`이고 `N`개의 프로세스가 있으면 한 프로세스가 `(N-1) * q` 이상 기다리는 경우는 없다.
  * 우선순위를 부여하지 않는다.
* SRTF: Shortest Remaining Time First
  * 가장 짧게 남은 프로세스를 먼저 처리한다.
  * Preemptive하다.
  * 새 프로세스가 들어올 때마다 스케줄을 다시 계산한다.
  * 역시 starvation 문제가 있다.
  * CPU burst time을 측정하기 어렵다.

## 데이터베이스

### Key
* Key: attributes의 집합.
* Candidate key: 유일성과 최소성을 만족하는, primary key가 될 수 있는 모든 key의 집합.
* Primary key: candidate key 중 하나로, 모든 레코드를 구분할 수 있으며 NULL일 수 없다.
* Alternate key (Unique key): candidate key 중 primary key가 아닌 것들
* Superkey: 유일성은 만족하지만 최소성을 만족하지 못하는 attributes의 집합
* Foreign key: 다른 릴레이션(표)의 레코드를 참조하기 위해 그 레코드의 primary key를 내 릴레이션에 두는 것

### 정규형
> https://github.com/Romanticism-GameDeveloper/GameDeveloper-Client-Interview/blob/main/DB/%EC%A0%95%EA%B7%9C%ED%98%95.md
> DB 설계에 도움이 되는 내용입니다.