1. 객체란 무엇인가요? 클래스와 어떤 연관이 있나요?
객체란 속성과 행동을 함께 가지는 독립적인 단위입니다. 속성은 객체가 가지는 상태로 데이터를 뜻하며, 행동은 객체가 할 수 있는 동작으로 함수를 뜻합니다. 예를 들어 사람은 객체이며 이 객체는 이름, 키 같은 속성을 가지고 있고 걷기, 점프하기 같은 행동을 가지고 있습니다.
이때 클래스는 객체를 생성하기 위한 틀입니다. 클래스에 속성과 행동에 대한 정보를 정의하고 인스턴스화 시킨 것이 객체입니다.
2. 생성자에 대해 간단하게 설명해주세요.
간단하게 설명하면 객체를 생성할 때 호출되는 함수입니다. 객체가 만들어질 때 초기값을 설정해주는 역할을 수행합니다.
3. 접근제한자란 무엇이며, 각각 어떤 차이가 있는지 비교해서 설명해주세요.
접근제한자란 단어 그대로 클래스나 멤버의 접근 범위를 제한하는 키워드 입니다. 저희가 사용하는 C#에는 private, protected, public, internal이 존재합니다. private은 같은 클래스 내에서만 접근이 가능하고 protected는 해당 클래스와 상속받은 자식 클래스까지 접근이 가능하며 public은 어디서든 접근 가능합니다. 그리고 자주 사용하진 않지만 internal은 같은 프로젝트 내에서 접근 가능합니다.
4. static 한정자에 대해 설명해주세요.
static 한정자는 클래스로부터 객체를 생성하지 않고 직접 호출할 수 있도록 합니다. 프로그램의 시작 시점에 메모리에 로드되어 클래스의 이름을 통해 접근할 수 있다는 특징을 가지고 있습니다. 이를 활용해 Mathf, Time 같은 유틸리티 클래스, 싱글톤 패턴에서 단일 객체 유지 등을 할 수 있습니다.
5. SOLID 원칙에 대해 설명해주세요.
SOLID원칙은 객체 지향 프로그램에서 지켜야할 5가지 원칙을 뜻합니다. 클래스는 단 하나의 목적을 가져야 하며, 클래스를 변경하는 이유는 단 하나의 이유여야 한다는 단일 책임 원칙, 클래스는 확장에는 열려 있고, 변경에는 닫혀 있어야 한다는 개방 폐쇄 원칙, 상위 타입의 객체를 하위 타입으로 바꾸어도 프로그램은 일관 되게 동작해야 한다는 리스코프 치환 원칙, 클라이언트는 이용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리해야 한다는 인터페이스 분리 원칙 마지막으로 클라이언트는 추상화에 의존해야 하며, 구체화에 의존해선 안된다는 의존 역전 법칙이 있습니다.
6. 객체지향 프로그래밍의 속성 중 하나인 다형성과 이를 활용한 설계의 장점에 대해 설명해주세요.
다형성이란 같은 종류의 클래스가 하나의 메시지에 대해 서로 다른 행동을 하는 특성을 뜻합니다. 다형성은 오버라이딩과 오버로딩 형태로 제공됩니다.
7. override와 overload에 대해 설명해주세요.
간단하게 설명하면 override는 부모 클래스에서 정의된 가상 메서드를 자식 클래스에서 재정의하는 것이고, overload는 같은 이름의 메서드를 매개변수를 다르게 하여 여러 개 정의하는 것입니다.
8. 확장 메서드에 대해 설명하고 어떻게 활용했는지 알려주세요.
확장 메서드란 기존 클래스나 구조체의 코드를 변경하지 않고도 마치 그 클래스의 메서드처럼 호출할 수 있게 해주는 static 메서드입니다. 흔히 기존의 라이브러리를 확장하는 유틸 함수를 만들 때 사용합니다. 제가 사용했던 확장 메서드는 ResetTransform이 존재합니다. 원래 transform을 초기화 하려면 position, rotation, scale을 따로 초기화해줘야 하는데, ResetTransform이라는 확장 메서드를 만들어서 GameObject.transform.ResetTransform으로 바로 초기화할 수 있도록 만들었습니다. 확장메서드를 만들 때 주의할 점은 첫 번째 매개변수로 this를 사용해야하며 static 클래스안에 static 메서드로 만들어야 됩니다.
9. 콜백이란 무엇인가요? 콜백을 사용해본 경험이 있을까요?
콜백이란 다른 메서드에 인자로 전달되어, 특정 시점에 나중에 호출되는 함수를 뜻합니다. Unity에서 지원하는 버튼 클릭 이벤트도 흔하게 사용하는 콜백이라고 할 수 있습니다.
10. 델리게이트(delegate; 대리자)란 무엇인가요?
델리게이트란 메서드의 참조 주소를 저장하는 타입입니다. 간단하게 설명하면 함수를 변수처럼 취급할 수 있도록 해주는 것 입니다.
11. C#의 event란 무엇인가요?
델리게이트를 기반으로 만들어진 시스템으로, 외부에서 메서드를 등록하거나 제거 하도록 허용하지만 직접 호출은 불가능하게 만드는 것 입니다. 델리게이트는 외부에서 등록된 모든 이벤트를 강제로 제거할 수 있고, 직접 호출도 가능한 데 이러한 보안 문제를 보완한 것이 이벤트입니다.
12. Unity에서 사용하는 델리게이트 혹은 이벤트에는 어떤 것이 있나요?
Unity에서 가장 많이 사용하는 델리게이트 혹은 이벤트에는 Action, Func 그리고 OnClick, OnCollisionEnter 등의 내장 이벤트 메서드가 있습니다.
여기서 Action이란 반환값이 없는 델리게이트이고, Func는 반환값이 있는 델리게이트 입니다.
13. 참조 형식과 값 형식에 대해 설명해주세요.
먼저 참조 형식은 힙 메모리에 저장되며 실제데이터가 아니라 주소를 가지는 형식입니다. 다음으로 값 형식은 스택 메모리에 저장되며 값 자체를 복사해서 가지는 형식입니다.
14. 메모리에서 스택과 힙의 차이점에 대해 설명해주세요.
일단 스택과 힙은 프로그램이 실행될 때 데이터를 저장하는 메모리 공간의 구조와 방식이 다른 두 영역입니다. 먼저 스택 영역은 메서드 호출 시 생성되는 임시 메모리 영역으로 값 형식을 저장할 때 사용합니다. 컴파일러가 자동으로 메모리를 관리하는 것이 특징이며 선입후출 구조인 스택 자료구조를 사용해서 속도가 빠른 것이 장점입니다.
다음으로 힙 영역은 장기적으로 객체를 저장하는 동적 메모리 영역으로 참조 형식의 객체를 저장할 때 사용합니다. 메모리 관리를 직접해야하는 단점이 있지만 C#에선 이를 가비지컬렉터가 관리하여 단점을 보완하고 있습니다. 포인터 기반의 비정형 구조이기 때문에 스택과 비교했을 때 속도가 느립니다.
15. 위 두 질문의 답안을 기반으로 struct와 class의 차이점에 대해 설명해주세요.
먼저 struct는 값 형식입니다. 그래서 스택 영역에 저장되며 가비지 컬렉터의 영향을 받지 않습니다. 데이터의 크기가 작고, 변하지 않으며 복사가 필요할 때 사용합니다.
다음으로 class는 참조 형식이며, 이에 따라 힙 영역에 저장됩니다. 힙 영역에 저장되므로 struct와 달리 가비지 컬렉터의 영향을 받습니다. 비교적 복잡하고 상태 공유가 필요한 경우에 class를 사용합니다. 그리고 상속이 가능하고 기본 생성자를 자유롭게 정의할 수 있다는 특징을 가지고 있습니다.
16. 얕은 복사와 깊은 복사의 차이점은 무엇인가요?
얕은 복사와 깊은 복사는 참조형 데이터를 다룰 때 매우 중요한 이론입니다. 얕은 복사는 참조만 복사하는 것이며, 깊은 복사는 내용까지 전부 복사하는 것입니다. 간단하게 설명하면 얕은 복사는 서로 다른 객체가 같은 주소를 가리키도록 하는 것이고, 깊은 복사는 객체의 내부를 완전히 복사해서 완전히 새로운 인스턴스로 생성하는 것입니다.
17. 박싱과 언박싱이 일어나는 과정을 메모리 관점에서 설명해주세요.
박싱은 값 형식을 참조 형식으로 변환하는 과정이고, 반대로 언박싱은 박싱된 값을 다시 값 형식으로 돌려놓는 과정입니다.
박싱의 과정을 보면 스택에 저장되어 있는 값 형식 데이터를 참조 형식으로 다루기 위해 힙에 새로운 객체를 만들어 복사합니다. 이때 메모리 비용과 시간 비용이 발생합니다. 왜냐하면 스택에서 힙으로 값을 복사하기 위해 새로운 색체를 만들어야 하기 때문입니다.
다음으로 언박싱의 과정을 보면 힙에 박싱된 객체에서 값 형식 데이터를 다시 꺼내야 하기 때문에 타입 캐스팅이 필요합니다. 이 때 형식이 맞지 않으면 예외가 발생할 수 있으므로 이부분에서 추가적인 성능 비용이 생깁니다. 그리고 스택에 값을 저장하기위해 값 복사가 일어나므로 시간도 소요됩니다.
따라서 박싱과 언박싱을 최소화하기 위해 struct와 class를 적절히 선택해야하며, 제네릭 컬렉션을 활용해야합니다.
18. 클래스를 다른 클래스로 상속하기 위한 방법은 무엇인가요?
클래스 이름 옆에 콜론을 사용하여 부모 클래스를 지정하면 됩니다.
19. 클래스 상속에서 다이아몬드 문제(diamond problem)가 발생하는 이유와 이를 해결하는 방법에 대해 설명해주세요
다이아몬드 문제는 다중 상속을 허용할 때 발생하는 문제입니다. 자식 클래스가 여러 부모 클래스를 상속 받을 때, 부모 클래스들이 동일한 메서드를 정희하고 있을 경우, 자식 클래스의 입장에서 해당 메서드를 호출할 때 어떤 부모 클래스의 메서드를 호출해야할 지 모호해지는 문제입니다.
C#에서는 애초에 단일 상속만을 허용하기 때문에 다이아몬드 문제가 발생하지 않습니다. 그리고 단일 상속체제를 보완하기 위해 인터페이스로 다중 상속의 효과를 낼 수 있도록 설계되어 있습니다.
20. 인터페이스란 무엇인가요?
인터페이스란 클래스나 구조체가 반드시 구현해야 하는 멤버들을 정의하는 틀입니다. 간단하게 설명하면 해당 인터페이스를 상속받는 클래스는 이러한 기능들을 반드시 정의해야 한다는 규칙을 정해주는 역할입니다.
21. 인터페이스와 추상클래스의 차이는 무엇인가요?
인터페이스와 추상클래스의 가장 큰 차이점은 추상클래스는 추상메서드가 아닌 다른 멤버는 일반적인 클래스와 같이 구현할 수 있다는 점입니다. 인터페이스는 모든 메서드가 public이어야 하고, 구현을 포함할 수 없습니다. 이러한 특징은 추상메서드와 같습니다. 따라서, 추상클래스는 인터페이스의 특징과 일반 클래스의 특징이 합쳐져 있는 클래스입니다.
22.가비지 컬렉터랑 무엇인가요
힙영역에 동적으로 할당된 객체의 메모리를 추적하여 필요하지 않은 개체를 검출하여 자동으로 해제해주는 메모리 관리 시스템입니다.
23. 가비지 컬렉터의 장점과 단점에 대해 설명해주세요.
가비지 컬렉터의 장점은 자동으로 메모리를 관리해줘서 메모리 누수를 방지하고 안전성을 향상시킵니다. 그리고 개발할 때 메모리 관리에 신경 쓰지 않을 수 있어서 개발 생산성을 크게 향상 시킵니다. 단점은 성능적인 부분입니다. 가비지 컬렉터가 동작하기 위해서는 메모리가 필요하기 때문에 빈번하게 사용하게되면 부하가 생길 수 있습니다.
24. 가비지 컬렉터의 세대 개념에 대해 설명해주세요.
힙영역에 할당된 메모리들은 0세대, 1세대, 2세대로 구분됩니다. 가장 처음 할당된 메모리들은 0세대이며, 가비지 컬렉션이 한번 이루어질 때마다 한세대씩 증가합니다. 그리고 0세대 가비지 컬렉션이 일어났을 때 아직도 메모리가 부족하면 1세대 가비지 컬렉션이 일어나는 방식입니다. 이러한 방식으로 동작하는 이유는 최근 생성된 객체는 금방 사용하지 않을 가능성이 높고 오래된 객체일수록 계속 사용할 가능성이 높다는 이유 때문입니다.
25. 박싱, 언박싱을 사용할 때 주의해야 할 점은 무엇일까요?
박싱과 언박싱은 CPU처리능력을 소모하기에 과도하게 사용되면 성능 저하의 원인이 될 수 있으며, 언박싱 시 타입 캐스팅을 잘못하면 오류가 발생할 수 있습니다. 그리고 박싱된 값은 힙에 저장되므로, 과도하게 사용하면 가비지 컬렉터에 부담을 줄 수도 있습니다.
26. 오브젝트 풀을 사용하면 메모리 관리에 도움이 되는 이유가 무엇일까요?
Unity에서 Instantiate와 Destroy는 매우 부하가 큰 작업이기 때문에 게임이 시작될 때 사용할 오브젝트들을 한번에 Instantiate 하고 SetActive를 통해 활성화 비활성화로 재사용하게 된다면 가비지 컬렉터의 부담을 줄일 수 있어서 메모리 관리에 도움이 됩니다.
27. 제네릭이란 무엇인가요?
제네릭이란 데이터 형식에 의존하지 않는 코드를 작성할 수 있도록 해주는 문법입니다. 제네릭을 활용하면 재사용 가능하고 타입 안정성 있는 코드를 작성할 수 있습니다. 예를 들어 Unity에서 가장 많이 사용하는 제네릭 함수인 GetComponent는 꺽쇠 안에 들어가는 것이 컴포넌트 이기만 하면 오류 없이 해당 타입의 컴포넌트를 가져옵니다.
28. 람다식(Lambda Expression)이 무엇인지 설명해주세요.
람다식은 익명 함수를 간결하게 표현하는 문법으로 코드를 줄이고 가독성을 높이기 위해 사용합니다. 일반적으로 소괄호안에 매개변수를 넣고 오른쪽 화살표를 작성한 다음 중괄호 안에 실행할 코드를 넣어 사용합니다.
29.LINQ란 무엇인가요?
LINQ는 C#에서 데이터를 조회, 필터링, 정렬, 변환 등을 간결하고 읽기 쉽게 처리할 수 있는 문법입니다. 쉽게 말하면 SQL처럼 쿼리문을 사용할 수 있게 해주는 기능입니다.
30. 리플렉션(Reflection)이 뭔지, 사용을 해봤다면 어떤 이유에서 사용했는지 설명해주세요.
런타임에서 클래스, 메서드 등의 정보를 코드 실행 중에 들여다보고 조작할 수 있게 해주는 기능입니다. 즉, 컴파일된 객체의 내부 구조를 코드로 분석하고 제어할 수 있는 도구 입니다. 디버그를 위해 커스텀 에디터를 만들 때 사용할 수 있다고 알고 있는데 아쉽게도 사용해 본 경험은 없습니다.
31. Unity 생명주기(Unity Life Cycle)에 대해서 설명해주세요.
간단하게 설명하면 게임 오브젝트 및 컴포넌트가 씬에서 생성되고 제거되는 동안 호출되는 일련의 콜백 메서드들의 순서를 뜻합니다. 생명주기를 통해 오브젝트의 초기화, 업데이트, 종료를 적절한 시기에 처리할 수 있습니다.
32. MonoBehaviour 클래스의 주요 메서드와 그 기능에 대해 설명해주세요.
MonoBehaviour 클래스의 주요 메서드는 생명주기와 관련된 다양한 콜백 함수들이라고 할 수 있습니다.
그 중 Awake와 Start의 가장 큰 차이점은 호출 시점입니다. Awake는 스크립트 인스턴스가 초기화될 때 가장 먼저 호출되고, Start는 첫 번째 프레임이 시작될 때 호출됩니다. 따라서 Awake는 다른 오브젝트에 의존하지 않는 기본적인 초기화 작업에 사용하고, Start는 의존성이 필요한 컴포넌트를 초기화할 때 사용합니다.
33. Update, FixedUpdate, LateUpdate의 차이점에 대해 설명해주세요.
각 업데이트 함수들을 호출 시점이 다릅니다. Update는 매 프레임 호출되고, FixedUpdate는 고정된 시간 간격으로 호출됩니다. 마지막으로 LateUpdate는 모든 Update가 호출된 후에 호출됩니다.
34. Time.deltaTime이란 무엇이며, 사용하는 이유에 대해 설명해주세요.
TIme.deltaTime은 현재 프레임이 시작된 시점과 이전 프레임이 시작된 시점 사이의 시간을 초 단위로 반환합니다. 사용하는 가장 큰 이유는 프레임 레이트에 관계없이 일정한 속도로 동작하게 하여 일관성 잇는 게임 경험을 제공하기 위함입니다.
예를들어 1프레임당 1만큼 이동하면 30FPS 환경에서는 1초에 30만큼 이동하고 60FPS 환경에서는 1초에 60만큼 이동할 것입니다. 이를 방지하게 위해 Time.deltaTime을 사용하여 FPS상관없이 1초에 30만큼 이동하도록 만드는 것입니다.
35. 코루틴의 동작원리와 사용해본 예시를 함께 설명해주세요.
코루틴은 하나의 메서드에서 호출을 중단하고 이어서 실행할 수 있도록 만들어진 비동기적 작업 흐름입니다. 코루틴은 매 프레임마다 실행을 잠시 멈추고, 나중에 다시 실행할 수 있는 지점들을 설정합니다. 이를 통해 시간 지연 작업이나 프레임당 연산을 효율적으로 분배할 수 있습니다.
지금 생각나는 사용 경험은 대화내용 출력입니다. 게임에서 대화내용을 출력할 때 한번에 모든 내용이 나오는 것이 아니라 1글자씩 나오도록 구현하기 위해 코루틴의 waitforseconds를 사용해서 구현했던 경험이 있습니다.
36. Invoke와 코루틴의 차이에 대해 설명해주세요.
코루틴과 Invoke는 Unity에서 시간을 지연시켜서 작업을 처리할 때 사용됩니다. 하지만 Invoke는 메서드 호출 이후 실행을 중지하거나, 그 사이에 조건을 변경하는 등의 제어가 불가능하므로 단순하게 함수를 지연실행할 때 사용합니다. 반대로 코루틴은 복잡하게 시간 흐름을 처리할 수 있고, 조건 제어도 가능합니다. 예를 들어 hp가 0이되면 죽고, 2초뒤에 아이템을 드랍하도록 하는 것이 가능합니다.
37. 코루틴과 멀티쓰레딩은 어떤 차이가 있는지 설명해주세요
코루틴과 멀티쓰레딩의 가장 큰 차이는 코루틴은 메인 쓰레드 하나만 사용하고 멀티쓰레딩은 별도의 쓰레드를 사용한다는 점입니다. 따라서 코루틴은 병렬적으로 실행되는 것이 아니라 함수의 실행을 프레임 단위로 쪼개어 순차적으로 실행하는 것이고, 멀티쓰레딩은 병렬적으로 동시에 실행되는 것입니다.
38. 유니티 최적화 기법은 어떤 것들이 있나요?
Unity에서 지원하는 LOD, Occlusion Culling, Light Baking, 스프라이트 아틀라스 등을 사용해봤으며, 코드적으로 반응형 프로퍼티나 Unitask, 코루틴을 사용하여 Update최소화, Object Pooling, Getcomponent 최소화 등을 해봤습니다.
플랫폼에 따라 텍스처 압축 설정을 다르게 지정하는 것이 최적화에 도움이 된다고 들어 봤지만, 아쉽게도 최적화를 위해 적용해본 텍스쳐 포맷은 없습니다.
39. 드로우콜에 대해서 설명하고, 최적화하는 방식에 대해 알고 있는 것이 있으면 설명하세요.
드로우콜은 CPU에서 GPU에 오브젝트를 렌더링하라고 명령하는 호출입니다. 제가 자주 사용하는 드로우콜 최적화 방식은 스프라이트 아틀라스와 Canvas 최적화가 있습니다. Canvas는 하나의 UI가 변경되면 캔버스 자체를 다시 그리기 때문에 드로우콜이 한번에 늘어나는데 비슷한 타이밍에 변하는 UI끼리 Canvas를 묶어서 관리하는 기법을 사용하고 있습니다.
40. Find 함수 사용을 자제해야 하는 이유에 대해 설명해주세요.
Find 함수는 씬 내 모든 오브젝트를 순회하며 문자열 비교로 탐색하기 때문에 매우 비효율적인 탐색 방식을 채택하고 있습니다. 따라서 인스펙터 창에서 연결하는 캐싱 방법으로 대체하는 것이 훨씬 좋은 방법입니다.
41. Update에서 GetComponent와 그 계열의 캐싱을 지양해야하는 이유를 설명해주세요.
Unity는 GameObject에 붙어 있는 모든 컴포넌트를 리스트 형태로 관리하는데, GetComponent는 이 리스트를 순회하며 일치하는 컴포넌트를 찾는 로직입니다. 때문에 Find와 같이 매우 비효율적인 탐색 방식을 채택하고 있어 매 프레임 호출되는 Update에서 호출하는 것은 성능에 문제가 생길 수 있습니다.
42. CSV/JSON 등 데이터 저장 포맷에 대해 설명하고, 활용에 적절한 상황을 설명해주세요.
CSV와 JSON은 둘다 텍스트 기반 포맷으로 CSV는 콤마(,)로 데이터를 구분하고 JSON은 키와 밸류의 쌍으로 데이터를 구분합니다. 따라서 CSV는 간단한 데이터 테이블을 작성하는 데 유리하고, JSON은 비교적 복잡한 데이터 세이브, 네트워크 전송 등에 유리합니다.
43. 특정 데이터를 JSON으로 활용하기 위해 해야하는 작업은 무엇인가요?
데이터를 JSON으로 활용하기 위해 반드시 직렬화 라는 과정을 거쳐야 합니다. 직렬화란 객체를 저장하거나 전송할 수 있는 형식으로 바꾸는 과정을 뜻하는데 Unity에선 클래스를 JSON 형식으로 바꾸는 과정을 뜻합니다.
44. Unity에서 필드를 직렬화하려면 어떻게 해야하는지 설명해주세요.
Unity에서 필드를 직렬화하려면 public으로 선언하여 Unity가 자동으로 직렬화하도록 하거나 [SerializeField] 어트리뷰트를 추가하면 됩니다.
45. Unity에서 멀티스레딩을 구현하기 위한 방법에 대해 설명해주세요.
Unity에서는 using System.Threading을 사용하여 멀티스레딩을 구현할 수 있습니다. 이를 통해 여러 스레드를 생성하여 작업을 병렬로 처리할 수 있지만, Unity API는 지원하지 않으므로 Unity API 호출은 메인 스레드에서 진행해야 합니다.
46. CPU와 GPU의 작동 방법은 어떤 차이가 있는지 설명해주세요.
CPU는 프로그램의 명령어를 처리하고 연산을 수행하는 역할을 수행합니다. 주로 계산, 흐름 제어, 데이터 처리를 담당합니다. CPU는 고성능 코어를 4~16개 정도 갖고 있으며 각 코어는 복잡한 연산을 빠르게 처리할 수 있습니다. 다음으로 GPU는 그래픽 처리에 특화된 장치로, 병렬 처리를 통해 대량의 데이터를 동시에 처리하는 데 특화되어 있습니다. 수백~수천개의 작은 코어를 갖고 있어 대규모 데이터 연산을 빠르게 처리할 수 있습니다.
47. 월드 스페이스(World Space)와 로컬 스페이스(Local Space)의 차이에 대해 설명해주세요.
월드 스페이스는 게임의 전역 좌표계입니다. 모든 객체의 transform은 월드 스페이스에서 정의됩니다. 로컬 스페이스는 어떤 한 객체를 기준으로 하는 좌표계입니다. 즉, 부모 객체의 좌표를 기준으로 transform이 정의됩니다.
48. 벡터의 내적과 외적을 어느 상황에 사용할 수 있는지 설명해주세요.
내적은 두 벡터가 이루는 각도의 코사인 값을 반환하기 때문에, 각도를 계산할 수 있습니다. 이 방식으로 게임에서 시야각 내에 물체가 존재하는 지 판단할 수 있습니다. 외적은 두 벡터에 수직인 벡터를 반환합니다. 이를 활용하여 주로 평면의 법선 벡터를 찾는 데 사용하며 법선의 벡터는 지금 평면에 서 있는 지 경사로에 서 있는 지 판단하는 데 사용합니다.
49. 쿼터니언을 사용하는 이유에 대해 설명해주세요.
쿼터니언을 사용하는 가장 큰 이유는 오일러 각의 문제인 짐벌락을 피하기 위함입니다. 짐벌락이란 3D 환경에서 회전할 때 발생하는 문제로 세개의 축을 순차적으로 계산하는 오일러 각에서 특정한 각도에서 두 회전축이 겹쳐 하나의 축만 남게 되는 상태입니다.
50. 네트워크 프로토콜 (IP, TCP, UDP)에 대해 설명해주세요.
IP는 네트워크 계층 프로토콜입니다. IP는 데이터를 패킷 단위로 나누어 목적지까지 전송하며, 각 패킷은 출발지 주소와 목적지 주소를 포함합니다. IP는 신뢰성을 제공하지 않으며, 전송된 데이터가 제대로 전달되었는 지, 순서가 맞는지, 손실이 없는 지 등을 확인하지 않습니다.
TCP는 신뢰할 수 있는 데이터 전송 계층 프로토콜입니다. TCP는 연결 지향적이며 패킷 순서와 데이터의 정확성을 보장합니다. 데이터를 전송하기 전에 송신측과 수신측의 연결상태를 체크하고 신뢰가 보장된 연결을 바탕으로 데이터를 주고 받습니다.
UDP는 비연결성 전송 계층 프로토콜로, 데이터 전송 시 오버헤드가 적고 빠르지만 신뢰성을 제공하지 않습니다. 패킷의 순서, 패킷 손실을 보장하지 않아 신뢰성이 중요한 데이터 전송에는 적합하지 않지만, 속도가 중요한 애플리케이션엔 적합합니다.
51. 렌더링 파이프라인에 대해 설명해주세요.
렌더링 파이프라인이란 장면을 최종적으로 화면에 렌더링하기까지의 일련의 처리과정을 뜻합니다.
52. 3D공간에 있는 오브젝트들이 화면에 표현되는 픽셀로 표시되기까지의 과정을 설명해보세요.
렌더링 파이프라인은 순서대로 Input Assembler, Vertex Shader, Rasterization, Pixel Shader, Output Merge의 과정을 거칩니다. Input Assembler에서는 CPU에서 렌더링을 수행할 도형의 정점 정보들을 정점 버퍼로 변환하여 GPU에 전달합니다. GPU는 받은 정점 버퍼를 조립하여 삼각형과 같은 프리미티브로 조립해서 Vertex Shader에 전달합니다. Vertex Shader에서는 프리미티브를 토대로 공간 좌표계를 변환합니다. Local Space, World Space, View Space, Clip Space 순서로 변환되며 변환된 데이터를 넘겨줍니다. 다음으로 Rasterization 단계에서는 정점 정보가 결정된 도형을 2D상에 표현하기 위해 프래그먼트로 변환합니다. 이후에 Pixel Shader에서는 렌더링 될 각각의 픽셀들의 색을 계산하고 마지막 단계인 Output Merge에서 투명도, 깊이 등을 계산하여 색을 정하고 최종적으로 화면에 그려질 픽셀을 정합니다.
53. 셰이더를 활용해본 경험이 있을까요? 어떻게 활용했는지 설명해주세요.
먼저 셰이더란 GPU가 화면을 출력할 때 픽셀, 버텍스 등을 어떻게 그릴 지 색이나 빛 같은 것을 결정하는 것입니다. Unity에선 머티리얼의 핵심이 됩니다. 제가 직접 제작을 해서 활용한 적은 없지만 3D오브젝트의 아웃라인을 그려주는 셰이더를 Asset으로 다운받아서 사용한 경험이 있습니다.
54. LinkedList의 특성을 설명해주세요.
LinkedList란 노드들이 참조를 통해 연결된 자료구조입니다. 각 노드는 데이터와 다음 노드의 참조값을 갖고 있는 것이 특징입니다. 따라서 중간에 값을 삽입하거나 삭제할 때 유리하고, 크기가 자주 바뀔 때 유리합니다. 하지만 인덱스 기반 접근을 많이 사용해야 될 때 원하는 위치로 가려면 처음부터 순회해야한다는 단점이 있고, 또 데이터와 다음 노드의 참조값을 갖고 있기 때문에 메모리 사용량이 비교적 큽니다.
그래서 예전에 몬스터를 LinkedList에서 관리해본 경험이 있습니다. 몬스터는 생성된 순서와 상관없이 언제든지 죽을 수 있기 때문에 죽어서 List에서 빼야할 때 삭제할 때 유리한 LinkedList를 사용했습니다.
55. Stack의 특성을 설명해주세요.
Stack이란 후입선출구조로 이루어진 자료구조입니다. 가장 나중에 들어간 데이터가 가장 먼저 나온다는 특징을 갖고 있습니다. 그래서 작업을 순차적으로 되돌릴 때 유용합니다. 하지만 데이터의 순회가 필요하거나 원하는 위치에 접근해야할 때 사용할 수 없다는 단점이 있습니다.
이전에 Stack을 이용해서 뒤로가기를 구현한 경험이 있습니다. 모바일 앱에서 다음 Scene으로 넘어갔다가 뒤로가기 버튼을 누르면 이전 Scene으로 돌아가는 기능을 구현할 때 Stack을 활용했습니다.
56. Queue의 특성을 설명해주세요.
Queue란 선입선출구조로 이루어진 자료구조입니다. 가장 먼저 들어간 데이터가 가장 먼저 나온다는 특징을 갖고 있습니다. 따라서 작업을 순서대로 처리할 때 유용합니다. 하지만 스택과 같이 원하는 위치에 접근할 수 없습니다. 게다가 데이터의 순서를 무시해도 되는 경우라면 List가 더 효율적입니다.
간단하게 Unity에서 여러 몬스터를 소환하도록 예약해두고 시간이 지나면 예약한 몬스터를 순서대로 소환하는 로직을 만들 때 사용해본 경험이 있습니다.
57. Tree의 순회(Traversal) 방법에 대해 설명해주세요.
트리의 순회 방법은 대표적으로 3가지가 존재합니다. 첫 번째로 전위 순회는 현재 노드, 왼쪽 자식, 오른쪽 자식 순으로 순회하는 방법입니다. 주로 트리의 구조를 복사할 때 사용합니다. 두 번째는 중위 순회는 왼쪽 자식, 현재 노드, 오른쪽 자식 순으로 순회하는 방법입니다. 값이 오름차순으로 정렬되어 있을 때 순서대로 방문할 때 사용합니다. 마지막으로 후위 순회는 왼쪽 자식, 오른쪽 자식, 현재 노드 순으로 순회하는 방법입니다. 트리 삭제 시 자식 노드를 먼저 삭제할 때 사용합니다.
58. DFS와 BFS에 대해 설명해주세요.
먼저 DFS, 깊이우선 탐색은 한 방향으로 노드를 방문하다가 더 이상 방문할 노드가 없을 때 돌아와서 다른 노드를 탐색하는 방식입니다. 주로 경로를 찾을 때 사용합니다. 다음으로 BFS, 너비우선 탐색은 시작 노드에서부터 가까운 노드부터 차례대로 방문하며 탐색하는 방식입니다. 주로 최단 경로를 찾을 때 사용합니다.
59. 행동 트리(Behaviour Tree)에 대해 설명해주세요.
트리 구조로 이루어진 의사결정 시스템으로, 각 노드가 행동이나 조건 또는 제어 흐름의 역할을 하기때문에 복잡한 AI 행동 패턴을 만들 때 주로 사용합니다. 사용해 본적은 없지만 모듈화가 잘 되어 있어 재사용 및 확장이 용이하고 복잡한 AI 행동 패턴을 간결하고 명확하게 설계 가능하다는 장점이 있다고 알고 있습니다.
60. 길찾기 알고리즘에 대해 알고 있는 것이 있나요?
61. 각 길찾기 알고리즘의 차이점은 무엇인가요?
62. A* 알고리즘에 대해 설명해주세요.
63. 해당 알고리즘들을 프로젝트에 적용해본 경험이 있나요?
대표적인 길찾기 알고리즘은 DFS, BFS, A* 가 있습니다.
DFS, 깊이우선 탐색은 스택을 사용해서 메모리 사용량이 적지만, 최단 경로를 보장하지 않습니다. 그래서 현재 노드에서 지정한 특정 노드를 갈 수 있는 지 없는 지 판단하는 간단한 프로그램을 만들 때 사용해본 경험이 있습니다.
BFS, 너비우선 탐색은 메모리 사용량이 비교적 크지만 최단경로를 보장하는 알고리즘입니다. 직접 프로젝트에 적용해본 적은 없지만 타워 디펜스 같은 장르의 게임에서 가장 가까운 적을 공격해야 될 때 사용할 수 있다고 생각합니다.
마지막으로 A 알고리즘은 시작 노드에서 특정 노드 n까지 이동하는 실제 경로 비용과 n에서 목표 지점까지의 추정 비용을 더해서 시작점부터 목표 지점까지의 비용을 계산하는 알고리즘입니다. A 알고리즘도 직접 구현해서 사용해본 적은 없지만 Unity에서 지원하는 AI Navigation이 A* 알고리즘으로 구현되어 있다고 알고 있으며 AI Navigation을 사용해본 경험은 있습니다.
64. 게임 개발 학습 과정에서 자신이 가장 재미있었거나 자신 있게 설명할 수 있는 부분이 있다면 설명해주세요.
65. 포트폴리오에서 본인이 담당한 부분이 무엇인가요?
66. 만약 본인이 개발한 것을 다시 개발한다면, 어떻게 개선할 것인지 설명해주세요.
67. 본인이 활용하는 디버그 방법을 설명해보세요.
68. 협업하면서 가장 어려웠던 점이 무엇인가요?
69. 구현하면서 기술적으로 어려웠던 부분을 어떻게 해결하였나요?
70. Git Repository를 팀원들과 공동으로 작업하면서 발생했던 문제점이 있다면 무엇이었는지, 어떻게 해결하였는지 이야기해주세요.