- 상황
- 에러는 아니지만, 최적화 관련한 문제
- random level streaming을 통해 5*5개의 레벨을 불러 맵을 생성하는 것에는 성공
- 하지만 맵을 생성하는 과정에서 너무 많은 시간 소모(대략 20~30초 가량 소모하는 듯.) (많으면 1분까지도 소모하는 것 같음)
- 실제로 측정해보니 1분 30초 정도 걸렸음
- ⇒ 생성 시간을 줄이는 최적화 필요
- 생각나는 해결 방안
- 생성 액터의 코드를 최적화하는 방법
- 현재는 for문 2개를 통해 중복 검사 후 생성
- 맵 생성하는 시간 동안, UI를 통해 스토리 설명 겸 시간 끄는 방법
- 스테이지 내부의 벽 액터와 문이 있는 벽 액터를 여러 개의 메시로 조합해서 꾸미기 보다 하나의 모델링 메시로 통일
=> 노션의 에러 상황 항목에서 발췌
처음에 구역별 존재했던 벽 메시는 따로 모델링 한 것이 아닌 StaticMesh를 여러개 붙여놓은 하나의 액터 형태였다. 하지만 따로 모델링을 새로 해서 하나의 Mesh로 변경시키니 레벨 생성 시간이 크게 줄어들었다. 30초대까지 줄어든 것을 확인했다.
이에 대한 이유로, 하나의 액터라도 5개의 메시가 부착된 액터 1000개와, 1개의 메시가 있는 액터 1000개를 메모리에 불러오고 난 뒤, GPU에 Draw Call하는데 걸리는 시간은 당연히 후자가 더 적게 걸리기 때문에 위와 같은 결과를 불러온 것이 아닌가 하고 생각했다.
- 상황
- 레벨 스트리밍으로 생성된 맵을 AI가 돌아다닐 때 팅김. SightDetect와 NoiseDetect에서 발생하는 에러로 판단됨
- 뭐가 문제를 일으키는지 확인 불가
- 포착한 상대 확인하기 위한 디버깅 로그 출력에서 문제 발생
- 그 전에는 NoiseDetect의 IsA 체크 과정에서 문제 발생
- 예상(그저 뇌피셜)
- sweepsinglebychannel의 잘못된 사용
- level streaming으로 생성된 복제된 레벨의 액터들이 중복된 이름을 가져서 같은 이름을 가진 액터를 포착 시 (ex. quad1의 normalwall1과 trip1의 normalwall1을 같이 포착) 팅기는 경우
- TraceChannel이랑 Collision Preset이랑 헷갈려서 잘못 설정
- level에 있는 actor들(벽, 문, 바닥 등)의 타입을 world static으로 설정되어있는 경우
=> 노션의 에러 상황 항목에서 발췌
AI들이 플레이어를 시각적, 청각적으로 감지하게 하기 위해 콜리전을 사용하였는데, 콜리전을 통한 감지를 하는 과정에서 팅기는 현상이 발생했다. 처음에 원인을 찾지 못했는데, 혹시나 하는 마음에 하나를 변경시켜보니 해결되었다. 벽들이 꺾이는 지점마다 데코레이션으로 기둥을 StaticMesh로 세워두었는데, 이것들이 튕기는 원인이였다. 블루프린트 액터 클래스를 해당 메시를 추가하여 생성시키고, 월드에 재배치시키니 AI가 돌아다니는 동안 팅기는 에러는 발생하지 않았다.
왜 에러를 발생시키는지 정확한 이유를 아직까진 파악하지 못했다.
이 점은 한 번 나중에 테스트를 해보아서 무엇이 문제였는지 확인해보아야할 것 같다.
- 상황
- 현재 이유는 모르겠으나, Level1의 로딩 중에는 FPS가 30 초중반대로 나왔지만 로딩이 끝나고 체감상 10분 정도 동안에 FPS가 한자리수로 급격히 떨어지는 상황이 발생함.
- Creature를 생성하고 이들이 활동하기 시작하는 단계에서 뭔가 시작되는 문제 같음.
- 그것이 아니라면 로딩되는 단계에서 너무 많은 오브젝트들이 호출되고 이들을 그리려는 과정에서 발생하는 문제인거 같기도 함.
=> 노션의 에러 상황 항목에서 발췌
무엇이 프레임 드랍을 유발시키는지 확인하기 위해 stat fps, stat unit 명령어를 사용했다.
위 명령어들로 확인해본 결과 Game Thread의 지연시간과 FPS 지연 시간이 거의 동일한 값을 가지는 것을 확인했고, GameThread에서의 병목 현상이 프레임 드랍의 원인이라는 사실을 알게 되었다.
병목 현상이 발생하는 원인을 찾기 위해 stat startfile 명령어를 사용해 프레임이 10으로 떨어질 때의 로그를 기록했다. 그리고 로그를 확인해본 결과 FTickFunctionTask로 인한 프레임 드랍임을 알게 되었다.
게임할 때마다 프레임 드랍이 생기면 재밌고 명작이라도 짜증이 나고, 사람들이 게임을 종료한다는 사실을 알고 있기 때문에, 최적화를 중요하게 생각해 C++ 클래스를 생성할 때마다 필요하지 않은 Tick은 전부 비활성화했는데도 왜 Tick 함수로 인한 프레임 드랍이 생기는지 이해할 수 없었다.
그러다가, C++ 파생이 아닌, 오로지 월드에 데코레이션을 하기 위해 만든 블루프린트 클래스들을 확인해본 결과, Start With Tick Enable의 체크를 해제하지 않아 Tick 함수가 호출되도록 해논 것을 발견했다.
-> 스테이지 1을 로딩되면, 벽, 바닥 등 모든 액터의 Tick 함수가 호출되고, 그로 인하여 GameThread에서 연산량이 많아서 프레임 드랍이 발생하는 것이었다.
따라서 Tick이 필요없는 블루프린트 클래스의 Tick을 모두 해제하였다.
그럼에도 불구하고 여전히 프레임이 20 초에서 10대로 떨어지는 현상 확인
=> 노션의 에러 상황 항목에서 발췌
이 문제를 해결하기 위해 다시 한 번 로그를 기록해 확인해보니, 크리처들의 AIController에 있는 Tick이 원인임을 알게 되었다.
AIController는 Behavior Tree를 이용해 모든 행동을 관리하고, 플레이어 감지를 위해 AI Perception을 사용하고 있지만 이 두 컴포넌트와 AIController의 Tick은 서로 영향을 주지 않는다는 사실을 알게 되어 AIController의 PrimaryActorTick의 bCanEverTick의 값을 false로 주어 Tick을 비활성화하였다.
--> 이렇게 하니, 프레임이 30대로 유지되었다.
처음에 시각과 청각으로 플레이어 감지를 위해, AI에 BT Service를 이용해서, 매 틱마다, Sight Detect 콜리전과 Noise Detect 콜리전을 만들게 시켰다. 하지만 벽 너머에 있는 플레이어가 내는 소리를 듣고 감지할 수 있는 Noise Detect와는 다르게 Sight Detect는 벽 너머에 있는 플레이어는 볼 수 없고 감지해서도 안 된다. 또한, 유리창이나 문에 붙어있는 유리를 통해서 플레이어를 보고 감지할 수 있어야 된다.
처음에 만들었던 Sight Detect로는 해당 조건들을 만족할 수 없기 때문에 나는 다른 방법을 찾을 수 밖에 없었다.
// BTService_SightDetect.cpp에서 플레이어를 감지하기 위한 콜리전 생성 코드
// 현재는 AI Perception Component를 사용함에 따라 사용하지 않는 코드
bool bResult = world->SweepSingleByChannel(
HitResult,
StartLocation,
EndLocation,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel6,
FCollisionShape::MakeBox(ForwardVector * 1600.f),
params
);
콜리전을 만들고, 충돌된 플레이어와 Ray Cast를 해서 AI와 플레이어 사이에 유리창이나, 유리가 달린 문 등을 제외한, 어떠한 오브젝트도 감지되지 않으면, 플레이어를 본 것으로 판정하는 방법을 생각해보았다.
그런데 이렇게 할 경우, 연산량이 많아지고 내가 생각했던 결과가 나오지 않을 것 같아서 다른 방법을 찾다가 AI Perception Component를 이용하기로 결정했다.
(AI Controller 클래스와 Creature Class는 C++로 작성하였고, 추가 설정은 블루프린트로 설정함)
AI Perception을 AI Controller에 부착하고, 감지할 감각은 Sight로 설정

위 컴포넌트를 이용해 벽 뒤의 플레이어를 감지하지 못하게 하였다.
아래는 디버깅했을 때의 이미지인데, 얇은 녹색 줄이 시야 범위이고, 굵은 녹색 줄이 시야 각도이다. 벽 뒤에 감지 범위 내에 플레이어가 있어도 AI는 감지하지 못하고 여전히 패트롤을 하고 있다.

플레이어를 감지하면, 감지된 플레이어를 쫓아갈 수 있도록 Trace TaskNode를 생성하고, 범위 내부의 플레이어를 계속 쫓아가도록 인식 범위를 BT Service로 만들어주었다.
