유니티 기본 개꼬여서 정리

뭔가 한창 달리고 있다가,

쌩 프로그래밍 출발점 - [뭔가 있는 듯 없는 듯 비어있음] - 유니티 게임 개발자 이 상황인 것 같아서 글 하나에 정리하려고 한다. C#이라는 걸 제외하면 OOP나 3D 수학은 다른 엔진에 가서도 충분히 활용처가 있을 것이라고 생각한다.. 즉각즉각 답 할 수 있게 제대로 기억하기


3D 수학

Vector의 외적/내적 정의

  • 내적 : 한 벡터를 다른 벡터에 투영한 크기를 말한다. 핵심은 두 벡터가 얼마나 같은 방향을 보느냐?
  • 결과는 -1 ~ 1 사이에서 나올 수 있는데, 1에 가까우면 같은 방향, 0은 완전한 수직, -1로 갈수록 완전히 다른 방향이다. abs(A) * abs(B) * cos(theta)
  • 벡터를 가질 수 있는 두 객체 사이에서의 방향 관계를 정의 할 때 사용 할 수 있다.
  • 외적 : 3D 공간에서 쓰이는데, 두 벡터가 주어질 때 동시에 수직인 벡터를 찾는데 사용한다.
  • 왼손을 사용해서 엄지를 위로, 검지를 앞으로 가리키게 하고 이제 중지를 두 손가락에 모두 수직이 되는 방향으로 가리켜보자. abs(a) * abs(b) * sin(theta)
  • 평면에서 법선 벡터를 구하는데 사용 할 수 있는데, 법선 벡터는 이후 실제 렌더링에 포함할지 아닐지를 정하는데 기여한다.

 

Unity 기초 정의

Vertex와 Mesh에 대해 설명

  • Vertex : 3D 공간에서의 점 하나를 의미한다. Vertex는 위치 정보뿐만 아니라 법선 벡터, UV 좌표, 정점 색상 등 다양한 데이터를 포함 할 수 있다.
  • Mesh : Vertex를 최소 3개씩 연결하여 만든 삼각형들의 집합으로, 이 삼각형들이 모여 3D 공간의 겉면을 형성한다.

[Life Cycle] FixedUpdate, Update, LateUpdate의 각각 언제 사용, 어떤 용도?

  • FixedUpdate : 기본 값은 0.02초 단위로 불리며(수동으로 조정 가능), 물리와 연관된 연산에 용이한 라이프사이클
  • Update : 매 프레임마다 한 번은 불리며, 키 입력, 단순 시간 계산, 비 물리 기반 이동 등의 로직
  • LateUpdate : 모든 Update가 끝나고 난 뒤 매 프레임마다 한 번 호출, 카메라가 캐릭터 위치를 따라갈 때 카메라의 직접적 위치 이동에 사용

Coroutine 정의와 사용처

  • 코루틴은 C#의 IEnumerator 인터페이스를 활용한 협력형 멀티태스킹루틴으로, 코루틴 함수는 yield return 키워드를 만날 때까지 실행되다가 만나는 경우 함수가 일시 중지되고 제어권을 유니티 메인 루프로 반환한다. 그리고 yield 조건이 충족 되면, 다음 프레임의 Update 이후 멈췄던 지점부터 이어 실행한다.
  • “N초 후 이어서 실행”, “다음 프레임에서 실행”, “특정 조건 만족까지 대기” 등 시간의 흐름에 따라 순차적 코드를 실행해야 할 때 사용하며, Update 에 상태 플래그 변수를 두는 것보다 훨씬 간결해진다.

RectTransform에서 Anchor와 Pivot의 역할은?

  • Pivot : UI 요소 자체의 기준점이다. 정규화된 좌표를 사용하며, (0.5, 0.5) 는 정 중앙, (0, 0) 은 좌측 하단, 이런 식이다. 이 피봇을 기준으로 위치가 계산되며, 회전과 크기 조절의 중심점이 된다.
  • Anchor : 부모 RectTransform을 기준으로 자식 UI 요소가 어디 고정 될지를 결정하는 참조점이다. 이 앵커를 기준으로 피봇까지의 상대적 위치가 결정된다.
    • 앵커가 한 점에 보여 있을 때는 고정된 픽셀 오프셋을 유지하며 크기가 변하지 않는다.
    • 앵커가 벌어져 있을 때는 벌어진 비율만큼 부모의 크기에 따라 함께 변한다.

내 캐릭터를 기준으로 상대방이 앞, 뒤, 왼, 오 중 어느 방향에 위치하는지 판단하는 방법은?

  • 벡터 내적을 알면 바로 생각나는데, 정의 기억 안나서 못했음. 벡터 내적 관련해서는 앞의 3D 수학 파트에 적었다.

방향 벡터를 먼저 구한다 : Vec3 dirToTarget = (enemy.pos - player.pos).normalized;

내적을 계산한다 : 이 방향 벡터와 내 캐릭터의 “앞”, 그리고 이 방향 벡터와 내 캐릭터의 “우”를 내적한다.

float dotForward = Vector3.Dot(player.transform.forward, dirToTarget);

float dotRight = Vector3.Dot(player.transform.right, dirToTarget);

이제 결과를 알 수 있다 :

dotForward > 0 : 상대방이 내 앞에 있다 (1에 가까울 수록 정면)

dotForward < 0 : 상대방이 내 뒤에 있다 (-1에 가까울 수록 정후면)

dotRight > 0 : 상대방이 내 우측에 있다

dotRight < 0 : 상대방이 내 좌측에 있다

더 나아가면 dotForwarddotRight 의 절댓값을 비교하여 앞/뒤 주인지 왼/오 중인지 더 지배적인 판단을 판단 할 수도 있다.

Fake Null이란?

  • Destroy 함수의 배경을 알아야한다. Destroy 하는 즉시 GameObject에 해당하는 원본 객체는 즉시 없어진다. 하지만 이건 C++ 엔진 레벨의 이야기이고, C#의 래퍼 객체로써는 아직 살아있고 GC로 인해 제거되어야 한다.
  • 유니티는 엔진 개발에 있어 == 로 null 여부를 확인하는 것 만큼은 GC 대기중이라고 하더라도 엔진 레벨의 객체의 실제 상태를 확인 할 수 있게 했다.
  • 하지만 C#의 일정 버전 이상의 Null 병합/조건 연산자 ?? , ?. 는 유니티가 오버로딩한 == 를 쓰는게 아니라서 C# 래퍼 객체의 null 여부를 확인한다. 그럼 이제 래퍼는 살아있으니 바로 게임 꺼지는 것이다..!!
  • 정리 : Destroy로 인한 실제 객체가 없음에도 == 가 아닌 다른 수단을 통해 null 접근 시도시 마저 GC까지 되지 못한 C# 레벨의 래퍼 함수가 있는 것처럼 반환되어 예외가 일어나는 경우를 말한다.

 

C# 특성 및, 객체지향 (유니티 감안)

C#에서의 클래스는?

클래스는 객체를 만들기위한 틀이다. 객체는 이 틀을 바탕으로 메모리에 실제 생성된 실체이다.

생성자가 반대되는 개념인 파괴자는 C++과 달리 C#에서는 종료자라고 부른다. GC의 메모리 수거에서 호출 되기 떄문에 호출 순서나 시점을 보장 할 없다. 명시적 해제는 IDisposable 인터페이스 또는 Dispose() 메서드를 통해 구현한다.

const와 readonly의 차이

값이 언제 확정되는가에 대한 차이가 있다.

const: 컴파일 시점에 값이 확정되어야 하며, 선언과 동시에 초기화해야한다. 숫자, 문자열, bool 등 기본 형식만 가능하며, 구조체나 클래스 인스턴스는 const 가 될 수 없다. (클래스 문자열은 된다)

컴파일 시 값이 리터럴로 대체되며, static 성격을 가진다.

readonly : 런타임 시점에 값이 확정된다. 선언 시 또는 생성자 내부에서 단 한 번만 값을 할당 할 수 있다. 모든 타입에 사용 할 수 있고, 인스턴스 멤버이며 물론 static readonly 로 정적 멤버로도 만들 수 있다.

클래스와 구조체의 차이

  • Class는 참조 형식으로, 실제 데이터는 힙에 저장된다. 변수는 힙에 있는 데이터의 주소만 저장된다. 다른 변수에 대입하면 ‘주소’가 얕은 복사로 이루어지며, 상속이 가능하다는 점이 있다.
  • Struct는 값 형식으로, 실제 데이터는 스택에 저장된다. (클래스의 멤버일 경우엔 클래스의 룰로 이루어진다) 변수 자체가 데이터 덩어리로, 다른 변수에 대입하면 데이터 전체가 통째 깊은 복사가 이루어진다. 상속은 불가능하다. (인터페이스 구현은 가능하다)

유니티에서 이 차이를 논할 때 제일 중요한 것은 struct 가 스택을 주된 저장위치로 사용하기 때문에 GC의 수집 대상이 아니기 때문이다. (스택에서 힙으로 복사되는 Boxing을 제외하고)

abstract와 virtual의 차이

공통점은 상속에 사용하는 키워드이다.

  • virtual은 기본 구현을 제공하는데, 재정의 override 는 선택사항이다.
  • abstract 는 구현 없이 틀만 제공하며, 재정의 override 가 강제된다. 근데 꼭 또 그런것만은 아니다 :

논외로, abstract 는 Interface 랑 비교되는데, 두 상황에서 고민하게 되는 중 abstract class 의 경우 virtual 기능을 포함 할 수 있다는 부분이 있다.

상속은 무엇인가

기존 클래스의 멤버를 새로운 클래스가 물려받는 기능으로, 코드의 재사용과 다형성의 기반을 마련하는데 의의가 있다.