프로세스는 각자 독립된 메모리공간을 가진 실행중인 프로그램
스레드는 하나의 프로세스 안에서 실행되는 작업 흐름, 같은 프로세스의 스레드끼리는 전역/정적, 힙을 공유한다.
각 스레드는 스택과 레지스터를 따로 가진다.
같은 프로세스 내 스레드 전환에는 주로 레지스터, 프로그램 카운터, 스택 포인터같은 스레드 상태를 교체하면 되지만, 다른 프로세스의 스레드로 전환하면 주소 공간도 달라지므로 페이지 테이블 기준도 변경해야 한다. 이 과정에서 TLB캐시에도 영향을 줄 수 있어 일반적으로 비용이 더 크다.
CPU가 GPU에게 특정 메시를 특정 머티리얼/셰이더/렌더 상태로 그리라고 제출하는 명령
많이 호출하면 성능 저하가 오는 이유는 Draw Call마다 CPu가 렌더 상태를 설정하고 명령을 제출하는 비용이 반복되므로 GPU보다 CPu에서 병목이 걸릴 수 있다.
GPU Instancing : 같은 메시와 같은 머티리얼을 사용하는 여러 객체를 한번의 Draw Call로 그림, 위치, 회전, 크기 같은 값만 인스턴스 데이터로 따로 넘김
Batching : 여러 렌더링 작업을 묶어서 Draw Call의 수를 줄이는 방법
gpu인스턴싱은 배칭의 한 방식이라고 보면 됨
dynamic_cast는 RTTI(런타임 타입 검사)를 하기 때문에 다형적 계층에서 안전한 다운캐스팅이 필요할 때 적합하다.
static_cast는 런타임 타입 검사를 안하기 때문에 실제 객체 타입이 맞지 않으넫도 캐스팅이 성립할 수 있고, 이후 잘못 사용하면 정의하지 않은 동작으로 이어질 수 있음
dynamic_cast로 다운캐스팅하려면 일반적으로 부모 클래스가 다형적이어야 함, 하나이상의 virtual함수가 있어야 하고 보통 virtual 소멸자를 두는 경우가 많다.
데드락은 여러 스레드가 서로 상대방이 가진 락이 해제되기를 기다리면서 아무도 진행하지 못하는 상태
대표적으로 A가 Lock1을 잡고 Lock2를 기다리고, B가 Lock2를 잡고 Lock1을 기다리는 경우입니다
예방하려면 프로그램 전체에서 락 획득 순서를 일관되게 정하거나, 여러 락을 안전하게 획득하는 std::scoped_lock 등을 사용할 수 있다.
락을 획득하는 순서를 전역적으로 통일한다고 표현하는 게 정확하다 예를 들어 모든 코드에서 반드시 Mutex1 → Mutex2 순서로만 락을 잡게 하면 서로 반대 방향으로 기다리는 상황을 막을 수 있다.
스택 : 함수 호출과 함께 스택 프레임 생성, 지역변수/매개변수 등이 저장되고 함수 종료 시 자동 정리
힙 : 런타임에 동적으로 할당하는 메모리, 수명을 개발자가 직접 관리하거나 RAII 객체가 관리
전역/정적 영역 : 전역 변수, static 변수등이 위치하고 일반적으로 프로그램 수명과 함께 유지
코드 영역(Text) : 컴파일된 함수의 기계어 코드
데이터 영역 : 전역, static변수, 초기값이 있는 전역/정적 변수 -> Data, 0또는 초기화되지 않은 전역/정적 변수 -> BSS
힙 : new/malloc 등으로 동적 할당
스택 : 함수 호출 프레임, 지역 변수, 매개변수 등
UObject는 언리얼 오브젝트 시스템의 기본 클래스, 데이터 객체나 에셋, 여러 시스템 객체에 사용된다. 기본적으로 월드에 배치되거나 Transform을 가지는 객체가 아니다.
AActor는 UObject를 상속하며 월드에 Spawn되거나 배치될 수 있는 게임 오브젝트이다. RootComponent를 통해 위치,회전,크기를 가지며 BeginPlay, Tick, 네트워크 복제관련 기능을 사용할 수 있다.
UActorComponent는 Actor에 기능을 추가하는 기본 컴포넌트, 자체적인 위치/회전/크기, 즉 Transform이 없다.
USceneComponent는 UActorComponent를 상속하고 Transform을 가질 수 있다. 또 다른 SceneComponent에 붙어서 부모/자식 계층구조를 만들 수 있다. 부모의 Transform변화도 따라갈 수 있다.
그래서 Actor의 RootComponent도 ScencComponent계열이어야 한다. Actor의 월드 위치/회전/크기 기준점 역할을 해야 하므로
GameMode는 게임의 규칙을 담는 곳이다. 멀티플레이에서는 서버에서만 존재한다. ex) 사망 규칙, 룰 등등
GameState는 게임에서 모든 플레이어들이 알아야하는 상태를 저장하는 곳이다. 멀티플레이에서는 서버는 권한을 가지고 값을 관리하고, 필요한 상태가 클라이언트들에게 복제된다. ex) 점수판, 시간등
Reliable RPC는 반드시 전달되어야 하는 RPC로, 유실되면 재전송함. UnReliable RPC는 유실되어도 재전송하지 않음. 중요한 이벤트에는 Reliable을 쓰고 자주 발생하며 일부 유실되어도 상관없는 RPC에는 UnReliable을 사용한다. 모든 RPC를 Reliable하게 사용하면 재전송과 대기로 인해 네트워크 지연과 부하가 증가할 수 있다.
TObjectPtr : 이미 메모리에 로드된 UObject를 강하게 참조하는 포인터, GC가 이 참조를 인식해서 강한참조가 살아있는한 대상이 GC로 수집되지 않게 한다.
TWeakObjectPtr : 이미 존재하는 UObject를 소유하지 않고 관측만함. 대상이 GC로 사라질 수 있으므로 사용전에 IsValid()같은 검사가 필요
TSoftObjectPtr : 객에셋 경로 기반의 소프트 레퍼런스를 가진다. 아직 메모리에 없어도 가리킬 수 있고, 필요할때 동기/비동기 로드할 수 있다.
TSubclassOf는 클래스 정보를 저장하는 타입
TSubclassof<AProjectile> ProjectileClass;
이렇게 해놓으면 AProjectile을 상속받은 모든 클래스들이 들어갈 수 있음
UClass*로 클래스 정보를 가리킬 수 있지만 어떤 계열의 클래스인지 타입 수준에서 제한하지 않음
UPROPERTY(EditAnywhere)
TSubclassOf<AProjectile> ProjectileClass;
TSubclassof로 해두면 에디터에서도 AProjectile계열 클래스만 선택할 수 있어서 안전하다.
Cast()는 UObject계열 객체를 원하는 타입으로 안전하게 변환할때 사용한다.
캐스팅에 실패하면 nullptr을 반환함, Cast()를 쓰는 이유는 언리얼의 리플렉션/타입 시스템을 이용해서 UObject계열 타입을 검사하기 때문
IsValid는 UObject가 nullptr이 아닌지, 이미 파괴/삭제 대상으로 처리된 상태는 아닌지 -> 이 두가지를 확인한다. 지금 안전하게 사용할 수 있는 UObject인지를 판단하는데 사용
nullptr비교는 포인터 주소가 비어있는지만 확인하는데 UObject는 포인터 값이 남아 있어도 이미 파괴되었거나 유효하지않은 상태 (Destory는 호출되었지만 GC가 아직 수거하지 않은상태) 일수 있다. IsValid는 이런 상태까지 확인을 하기 때문에 IsValid를 사용해야 한다.
CreateDefaultSubobject는 클래스가 기본적으로 항상 가져야 하는 서브오브젝트를 생성자에서 만듬
NewObject는 런타임 중 필요할 때 UObject를 동적으로 생성할때 사용한다. 월드에 존재하는 Actor를 생성할때는 SpawnActor사용
두 컴포넌트가 Overlap 반응을 하도록 설정되어있고, 실제로 겹치기 시작하면 호출, 물리적으로 서로 막지는 않음
서로 Block충돌이 발생했을때 호출, 실제 물리 충돌이나 이동 중 막힘과 관련된 이벤트
충돌 시스템에 참여하지 않음
트레이스, 오버랩, 물리 충돌 전부 안 함
트레이스나 오버랩 같은 쿼리 검사에는 참여
물리 시뮬레이션 충돌은 하지 않음
예: 공격 판정 영역, 아이템 감지
물리 시뮬레이션에는 참여
트레이스/오버랩 같은 쿼리 검사에는 참여하지 않음
쿼리와 물리 둘 다 참여
Object Channel은 오브젝트 자신이 어떤 종류인지 나타내는 분류값
나는 Pawn이다 같은 식으로 객체에 붙는 이름표
Trace Channel은 라인 트레이스나 스윕 같은 쿼리가 어떤 기준으로 검사할지 정하는 채널
예를들어 총알 라인트레이스를 Visibility채널로 쐈다면, 각 오브젝트는 그 Visibility Trace Channel에 대해 Block / Overlap / Ignore 중 어떻게 반응할지를 정해둘 수 있다.
UDataAsset은 하나의 논리적인 설정 묶음을 에셋 하나로 관리할 때 좋음, 무기 설정, 캐릭터설정처럼 데이터 종류가 하나의 객체 단위로 묶이는 경우
DataTable의 경우 행으로 구성된 표형태 데이터, 스텟처럼 같은 구조의 데이터를 대량으로 관리할때 좋음, 기획자가 CSV/에디터에서 수치 수정하기 편함
GAS의 중심 역할을 하는 컴포넌트 GA 부여/활성화, GE 적용/제거, GameplayTag관리, AttributeSet 연결 및 속성 변화 처리, 네트워크 복제와 예측 처리 등
Owner Actor는 ASC의 소유 주체, 능력, AttributeSet같은 정보를 지속적으로 보관하는 쪽, 멀티플레이에서는 PlayerState에 두는 경우가 많음
Avatar Actor는 현재 월드에서 실제로 움직이고 능력을 사용하는 Pawn/Character, 리스폰하면 Avatar는 새 Character로 바뀔 수 있음
플레이어가 죽고 다시 살아났을때 능력이나 스텟을 유지하고 싶다면 PlayerState가 Owner Actor고 캐릭터가 Avatar Actor가 된다 (우리 프로젝트)
Subsystem은 GameInstance나 World같은 특정 생명주기에 맞춰 엔진이 자동으로 생성하고 관리하는 기능 객체, 여러 곳에서 사용하는 기능을 한 클래스에 몰아넣지 않고 분리해서 결합도를 낮추고 유지보수성을 높이는데 사용한다. (세이브/로드, 에셋 관리, 매치메이킹)
C++은 성능이 중요한 반복 로직, 복잡한 시스템, 기반 기능, 수학 계산등
블루프린트는 UI연결, 애니메이션 이벤트, 이펙트/사운드 등
블루프린트는 언리얼 VM을 한번 거치기때문에 C++보다 비교적 느려서 이런 차이가 생긴다.
UENUM은 서로 구분되는 상태나 종류를 이름있는 값으로 정의할때 사용한다. 내부적으로는 정수값으로 표현되지만 블루프린트나 에디터에서 드롭다운에서는 정수값이 아닌 설정값으로 나오게 된다.
USTRUCT는 구조체고 관련된 여러 데이터를 하나의 타입으로 묶을때 사용한다. 아이템 정보나 상점 데이터같은 여러값을 하나로 묶을때 사용한다.
CDO는 클래스별로 하나 존재하는 기본값 템플릿 객체. 클래스가 준비될 때 생성자가 실행되면서 CDO의 기본값과 기본 서브오브젝트가 구성되고, 블루프린트에서 수정한 기본값도 해당 클래스의 CDO에 저장된다. 이후 새로운 인스턴스를 생성할때 CDO의 값을 기준으로 초기화한다.
UWorld는 게임 세계 전체를 관리하는 객체이고, Level은 그 World를 구성하는 Actor나 맵 데이터의 묶음이다. Persistent Level은 기준이 되는 항상 로드된 Level이고, Sub Level은 필요에 따라 추가로 로드하거나 언로드할 수 있는 레벨이다.
Unreal Build Tool이 읽는 모듈 빌드 설정 파일
어떤 언리얼 모듈에 의존하는지(UMG, Core, Engine), 외부 라이브러리나 헤더 경로, 컴파일 옵션, 전처리기 정의등
UBT : C++ 모듈과 타깃을 어떻게 컴파일할지 관리, Build.cs와 Target.cs를 읽는다.
UHT : UCLASS, UPROPERTY, UFUNCTION, USTRUCT같은 리플렉션 매크로 분석, .generated.h, 생성 코드등을 만들어줌, GENERATED_BODY도 매크로중 하나, UHT가 이런 매크로들을 읽고 정보를 얻어서 .generated.h라는 자동 생성 코드를 만듬
UAT : Unreal Automation Tool, 빌드/Cook/Package/배포같은 자동화 작업 담당, 패키징시 많이 관여함
Event Graph : 애니메이션 판단에 필요한 값을 갱신하는 곳(블루프린트 노드)
Anim Graph : Event Graph에서 갱신한 값을 이용해서 실제 최종 애니메이션 포즈를 만드는곳 (Idle <-> Run 상태 전환, BS, State Machine등)
Montage : 특정 행동이 발생했을때 끼워넣는 애니메이션 (공격, 피격, 재장전 등)
State Machine : 계속 유지되고 전환되는 기본 상태
RootMotion은 애니메이션의 RootBone 이동/회전 값을 실제 캐릭터 이동에 반영
In-Place는 애니메이션은 제자리에서 실행, 실제 이동은 CharacterMovementComponent나 코드에서 담당
둘다 메모리연속성, 동적할당의 공통점이 있고 TArray는 UPROPERTY와 함께 사용하면 리플렉션, 직렬화, 복제같은 언리얼 시스템과 연동가능
실행중에 클래스, 프로퍼티, 함수등의 타입 정보와 메타데이터를 조회할 수 있게하는 시스템. UHT가 매크로를 읽어서 자동생성코드만들고 GENERATED_BODY를 통해 그 코드가 클래스에 연결됨. 이를 기반으로 블루프린트 노출, 직렬화, 복제, 에디터 편집등의 기능이 동작한다.
Outer란 UObject가 어떤 객체에 소속되어 있는지를 나타내는 관계
UInventoryItem* Item = NewObject<UInventoryItem>(PlayerCharacter);
여기서는 PlayerCharacter가 Item의 Outer다. Item은 PlayerCharacter라는 컨텍스트/소속 안에 있는 객체다.
객체의 소속 관계 표현, 객체 이름 경로 구성
여기서 NewObject의 <>는 어떤 타입의 객체를 새로 만들것인가를 정의하고 ()는 Outer를 누구로 할것인가를 정의한다.
중요한건 Outer는 강한 참조가 아니다. 다시말해 PlayerCharacter가 살아있는 동안에 무조건 Item이 살아있음을 보장할수는 없다. 보통 계속사용할거면 캐릭터에 강한참조로 붙임
생성자에서 기본값과 컴포넌트를 구성하고, OnConstrcution에서 배치/스폰 후 구성을 조정
게임 시작시 Beginplay가 호출되고, Tick이 활성화되어있으면 Tick을돌고, 제거되거나 레벨이 끝날때 Endplay가 호출되고 이후 UObject 시스템에 의해 최종적으로 정리된다.
포워드 렌더링은 물체를 그리면서 바로 조명 계산을 하느냐이고
디퍼드 렌더링은 화면에 뭐가 보이는지 먼저 정리하고 조명을 나중에 한꺼번에 계산한다.
디퍼드는 동적 조명이 많은 경우에 물체마다 조명 계산을 반복하지 않고 화면에 보이는 최종적인 픽셀을 기준으로 조명처리가 가능해서 조명이 많은경우 유리하다. 단점은 G-Buffer를 여러장 사용해서 메모리/대역폭을 많이 쓰고 투명 물체 처리가 까다롭다.
버텍스 셰이더 : 어디에 그릴지, 각 정점이 화면의 어디에 위치해야 하는지 계산, 그 다음 정점들이 삼각형을 만들고 화면의 픽셀들로 채워지면
픽셀 셰이더 : 무슨 색으로 그릴지, 각 픽셀들이 무슨 색으로 보일지 계산
객체나 메모리는 이미 해제되었는데 그 주소를 가리키는 포인터가 아직 남아있는 상태
스마트 포인터를 사용하거나, RAII로 객체 수명을 명확하게 관리, raw pointer를 직접 해제했다면 nullptr로 초기화
워커 스레드들이 플레이어의 공유 상태를 직접 수정하지 않도록 하는게 핵심
몬스터마다 스레드를 하나씩 만드는 게 아니라 고정 크기 스레드 풀을 두고 몬스터들을 작업 단위로 나눠 병렬 처리, 워커 스레드는 플레이어 상태를 직접 수정하지 않고 공격 결과만 로컬 버퍼에 모은 뒤, 작업이 끝나면 게임 스레드에서 결과를 취합해 플레이어 상태에 반영하는 방식으로 설계할 수 있다.
스레드풀은 미리 정해진 수의 스레드를 생성해놓고, 작업이 들어오면 작업 큐에서 꺼내 처리하는 구조, 매 작업마다 스레드를 생성/삭제하는 비용을 줄일 수 있고, 스레드 수를 제한해서 불필요한 컨텍스트 스위칭을 줄일 수 있다.
Game Thread: 게임 로직, Actor Tick, 대부분의 UObject 처리
Render Thread: 렌더링 명령 준비 및 렌더링 관련 처리
RHI Thread: Render Hardware Interface 쪽 작업, 즉 실제 그래픽 API 명령 처리에 가까운 역할