[C++] STL4 - 연속 구간 합, 유니크 값, 총합 구하기, 빠른 이진 탐색, 원소 변환 & [UE5] Unreal - 강참조, 약참조, GameMode 및 GameState 관련, UI, 바인딩 등

김영웅·2025년 2월 12일

연속 구간 합

partial_sum을 이용해 연속 구간의 합을 간단하게 구할 수 있다.

vector<int> v = {3, 5, 8, 10};
vector<int> psum(4);

partial_sum(v.begin(), v.end(), psum.begin());

cout << psum[0] << endl; // 3
cout << psum[1] << endl; // 8 (3 + 5)
cout << psum[2] << endl; // 16 (3 + 5 + 8)
cout << psum[3] << endl; // 26 (3 + 5 + 8 + 10)

이런 식으로 사용해 간단하게 구할 수 있다.

int sum = psum[3] - psum[1]; // 구간 [8, 10]의 합

이런 식으로 활용도 할 수 있다.


유니크 값

unique는 연속된 중복 원소를 제거하고 끝 부분 iterator를 반환하는 함수이다.
이 함수를 쓰기 위해선 우선 정렬이 필수이다.
이 함수를 사용하면 앞쪽에 중복되지 않은 원소로 나열해주고 뒤쪽은 그냥 의미없는 값들이다.
그래서 반환된 iterator부터 erase()해주면 완벽하게 정렬한 값을 얻을 수 있다.

vector<int> v = {9, 9, 8, 7, 7, 7, 8, 6};
sort(v.begin(), v.end()); // 정렬하면 {6, 7, 7, 7, 8, 8, 9, 9} 이렇게 됨

auto newEnd = unique(v.begin(), v.end());
v.erase(newEnd, v.end()); //중복되는 구간 전부 삭제

for (int x : v)
	cout << x << " "; //6 7 8 9 출력

총합 구하기

accumulate는 총합을 구하는데 사용한다.
초기값도 설정 가능하다.

int sum = accumulate(vec.begin(), vec.end(), 0);

이런식으로 쓰면 vec안에 있는 값을 총 합쳐서 반환해준다.

4번째 매개변수로 법칙을 정해서 전해줄 수도 있다.
예를들어 총곱을 알고싶다면 multiplies<int>을 쓰면 된다.

int product = accumulate(vec.begin(), vec.end(), 1, multiplies<int>);

이렇게 하면 전체 곱을 구할 수 있다.


빠른 이진 탐색

이진 탐색으로 위치값을 찾는다.
이진 탐색으로 찾기 때문에 반드시 오름차순으로 정렬되어 있어야만 사용할 수 있다는 단점이 있지만, 속도는 O(log n)으로 빠른 편이다.
lower_bound와 upper_bound로 할 수 있다.

이 둘은 약간의 차이점이 있는데
lower_bound는 찾는 값 이상인 값의 위치를 반환하고
upper_bound는 초과한 값의 위치를 반환한다.

 vector<int> v = {1,2,3,3,3,4,4,4,5,6,7};
 auto a = lower_bound(v.begin(), v.end(), 4);	//5를 반환
 auto b = upper_bound(v.begin(), v.end(), 4);	//8을 반환

원소 변환

transform을 이용해 구간 내의 원소들을 내가 원하는대로 변환할 수 있다.
주의할 점은 반복문이나 for_each 함수 처럼 순서대로 접근하는 것이 보장되지 않는다

사용법은 2가지다

  • 단항 연산(Unary Operation) : 각 원소를 하나씩 변환 (ex: 제곱, 2배, ...)
  • 이항 연산(Binary Operation) : 두 구간의 원소를 짝지어서 변환 (ex: 같은 인덱스끼리 더하기, 곱하기, ...)

아래처럼 사용해 소문자/대문자 치환 같은거 할 때 쓰면 좋다.

transform(str.begin(), str.end(), str.begin(), toupper);

위는 단항 연산이며, 매개변수의 의미는 순서대로
범위 시작, 범위 끝, 저장할 위치 시작, 변환 방식(함수 등) 이다.

이항 연산은 여기서 2번째 범위 시작 위치를 추가해서 할 수 있다.

vector<int> vec1 = {1, 2, 3, 4, 5};
vector<int> vec2 = {6, 7, 8, 9, 10};
vector<int> saveVec(5);

transform(vec1.begin(), vec1.end(), vec2.begin(), saveVec.begin(), [](int a, int b)
	{
		return a + b;
	});

매개변수의 의미는 순서대로
첫번째 범위 시작, 첫번째 범위 끝, 두번째 범위 시작, 저장할 위치 시작, 변환 방식(함수 등) 이다.
위의 예시는 각 int를 더해서 saveVec에 넣게된다.


강 참조

  • A 오브젝트가 B 오브젝트를 참조하여 A를 로드하면 B가 로드되게 만드는 방식

  • 일반적으로 많이 사용하는 방식이며, 메모리에 로드된 상태인 것에 접근한다.

  • 당장 사용되지 않아도 메모리에 로드해버리기 때문에 메모리 소모가 심할 수 있다.

  • 대표적으로 UPROPERTY로 공개 처리한 변수가 있다. 에디터에서 직접 조절하며 이미 값이 설정되어 있다.

  • ConstructorHelpers를 사용한 오브젝트 검사도 강 참조다. 보통 생성자 단계에서 ConstructorHelpers::FObjectFinder를 쓰게 되는데 이 때, 경로상의 오브젝트가 없다면 nullptr을 반환하고 크래시가 날 수 있다.

약 참조

  • 오브젝트 경로같은 문자열 형태의 간접 메커니즘을 통해 오브젝트 A 가 오브젝트 B 를 참조하게 만드는 방식

  • 비동기식으로 로드하는걸 말하는 건가 싶긴도 한데. 그것 보단 느린 초기화를 말하는 것 같다. 아마 보통은 비동기식 로드랑 같이 섞여 쓰이지 않을까 싶다.

더 자세한 정보는

이 사람이 쓴 글과

언리얼의 공식 문서를 보자

TSubclassOf : 하드 레퍼런스(강 참조)
TSoftClassPtr : 소프트 레퍼런스(약 참조)
TSoftObjectPtr : 소프트 레버런스(약 참조)


PlayerState

  • 플레이어마다의 고유한 정보를 저장한다.
  • 하지만 솔로 게임에선 어차피 플레이어가 한명이라 별로 의미가 없다.

GameState

  • 게임의 전역 정보들을 가지고 있다.
  • 스폰 이벤트, 아이템 갯수 등 현재 맵이나 게임에서 필요한 전역 정보 등을 여기서 관리한다.
  • 보통 여기서 게임이 시작될 때 필요한 여러가지 초기화 작업 등을 해주게 된다.

GameMode

  • 싱글 게임에서는 게임의 흐름을 구현하는 경우가 많긴 한데 원래는 서버만 접근할 수 있는 게임의 규칙, 로직 등이 들어간다.
  • GameMode를 클라이언트가 접근하려면 접근을 못하는 상황이 벌어져 문제가 발생한다.
  • 보통 클라이언트도 알아야할 정보는 GameState에 넣고 GameMode엔 게임 규칙 자체만 넣는 경우가 많다.

PlayerState와 GameState는 동기화를 할 때 굉장히 중요한 클래스이다.

GameMode를 상속 받아 쓰는데 GameStateBase쓰면 문제가 생길 수 있다.
이 둘은 강하게 연결 되있는데 GameStateBase는 간소화 되있기 때문이다.
걍 GameState를 상속받아 만들도록 하자.

GameInstance

  • 게임 전체에서 지속적으로 유지를 되어야하는 데이터를 저장한다.
  • 점수 등의 이 게임이 실행되는 동안 유지되어야 할 데이터 등을 저장한다.
  • GameInstance는 게임이 실행되는 동안 계속 존재하며, 레벨이 변경되거나 게임이 재시작 되어도 소멸하지 않는다.

이분이 이 4개의 사용처와 분리를 잘 정리해놨으니 한번 보도록 하자.


UI

C++에서 쓰고 싶다면 .Build.cs에 "UMG"를 추가해야한다.

UI는 PlayerController에 붙여줘야함
CreateWidget<UUserWidget>(this, 클래스 타입);으로 위젯을 생성할 수 있다.

메뉴 UI 같은거 띄울 땐 메뉴 UI 전용 맵을 따로 만드는게 좋다라고 함.
Title 레벨을 만들라는 소리인 듯

UI 요소가 이벤트를 받으려면 변수 여부(Is Variable)를 체크해야한다.

WidgetComponent에서 WidgetSpace로 스크린 모드과 월드 모드를 설정할 수 있다.
스크린 모드는 가리는 오브젝트가 있어도 계속 화면에 보이고, 월드 모드는 액터마냥 3D 공간에 존재해 가릴 수 있는 UI 모드이다.

WidgetComponent에서 GetUserWidgetObject()로 UUserWidget*을 받아올 수 있다.


Unreal Interface를 상속받아 제작했을 때 U인터페이스이름, I인터페이스이름 이런식으로 생성되는데 둘 다 필요하니 건드리지 말자.
cpp 파일도 있는게 최대한 문제가 터질 가능성을 줄일 수 있으니 가만히 냅두자
실제 인터페이스로 쓰는건 I인터페이스이름에 순수 가상 함수를 선언해 사용하면 된다.

.AddDynamic(Object, FuncName) 매크로로 델리게이트에 함수를 체인할 수 있다.
물론 매개변수가 같아야하며 BeginPlay에 등록해야 문제가 안생긴다.

이렇게 연결하는 함수는 반드시 언리얼의 리플렉션 시스템에 등록이 되어야해서 UFUNCTION()을 붙어줘야 하는데 부모 최상단에서 한번만 등록 시키면 이미 등록됬으므로 자식에서 매크로를 추가하지 않아도 됨

Colllision 컴포넌트엔 GetOverlappingActors()라는 함수가 있다.
이걸로 Overlap된 엑터들 가져올 수 있다.

Spawner 같은거 만들 때 SpawnVolume라고 이름을 많이 짓는다고 함

GetAllRows()를 쓸 때, 첫번째 인자로 TCHAR*를 받는데 디버깅 용도로 어디서 쓰는지 string으로 남기는거임

UGamePlayStatics::ApplyDamage() : 데미지를 입히는 함수, static 이다.
AActor::TakeDamage() : 데미지를 받을 때 호출, override해서 사용하면 된다.

UGameplayStatics::GetAllActorsOfClass로 월드 안에 있는 원하는 클래스 타입의 액터를 가져올 수 있다.

IsA()로 해당 오브젝트의 클래스 타입을 검사할 수 있다.

profile
게임 프로그래머

0개의 댓글