[TIL] 2025-06-20 크기가 변하고 눈금이 있는 HPBar 구현 과정과 문제 해결 과정, 기타 문제 해결
2025-06-20 TIL
최종 프로젝트
크기가 변하고 눈금이 있는 HPBar
플레이어 HP바를 구현하는데, 대략적인 기획은 다음과 같았습니다.
최대 체력에 따라 길이가 변하고, 특정 간격바다 눈금 이미지를 생성해주어야 하는 체력바 UI를 구현해야했습니다.
HP바의 눈금은 롤이나 이터널 리턴이라는 게임처럼 캐릭터의 체력바에 검은색 실선과 유사한 기능입니다.
아래의 이미지처럼 첫 기획에서는 HP바의 이미지가 3등분 되어있고, 체력이 채워져야하는 부분이 이미지마다 다르기 때문에 각 이미지 별로 한개의 HP에서 3등분된 이미지를 사용해 HP바를 구현해야했습니다.
이 경우 코드가 복잡해지고 유지보수가 좋지 않아질 것이라고 판단하여 팀의 아트 담당자에게 HP바 자체는 1개로 이루어지고, 테두리 양쪽 끝부분에 대해 별도의 이미지를 사용해 하나의 HP가 될 수 있도록 해주었습니다.
이렇게 결정한 이유는 다른 게임의 HP바를 살펴보았고, 엘든링이라는 게임에서 뜯어진 이미지 소스 파일을 살펴보았습니다.
해당 게임에서는 HP바와 양쪽 끝부분의 테두리가 별도로 이루어져있었고, 이 방법과 유사하게 구현해보기 위해 선택한 방법입니다.
구현한 방법은 다음과 같습니다.
HP바 체력을 나타내는 것은 프로그레스바를 선택해 체력이 반영되도록 해주었습니다.
HP바의 길이는 사이즈 박스로 프로그레스바를 래핑하여 길이가 변할 수 있도록 해주었습니다.
HP바에 눈금은 이미지를 동적으로 생성해 박스에 삽입하여 눈금이 최대 체력에 따라서 동적으로 생성되고 제거될 수 있게 해주었습니다.
생성된 눈금들은 호리즌탈 박스에 삽입하여 눈금들의 위치가 자동으로 설정되게 해주었습니다.
위의 과정을 거쳐 다음과 같은 체력바를 구현했습니다.
구현 중 발생한 문제
구현 중에 문제 또한 있었는데, HP바의 길이가 제대로 설정되지 않는다는 문제가 있었습니다.
해당 문제로 인해서 C++의 코드를 디버깅해보며 알게된 결과 다음과 같았습니다.
HP바 위젯의 가로 길이를 구하기 위해서 GetDesiredSize
함수를 사용했습니다.
그런데 해당 함수는 NativeConstruct
나 BeginPlay
중에서는 값을 제대로 넘겨줄 수 없다는 문제가 있어 HP바의 길이가 제대로 설정되지 않았던 것입니다.
이 문제를 해결하기 위해서 위젯의 길이를 가져오는 GetCachedGeometry().GetLocalSize()
함수를 알게 되었지만, 해당 함수는 렌더링 이후 화면에 보이는 실제 크기만을 반환하기 때문에 사용함에 맞지 않는 부분이 있었습니다.
그래서 다른 방법을 알아보았고, 레이아웃에 대한 계산을 즉시 수행하는 ForceLayoutPrepass
함수를 알게되어 이 문제를 해결할 수 있었습니다.
또 다른 문제로는 호리즌탈 박스의 특정 인덱스로 직접 이미지 위젯을 삽입해도 결과물은 인덱스와 다르게 무조건 호리즌탈 박스의 뒷 부분에 위젯이 렌더링 되는 문제가 있었습니다.
발생한 문제에 대한 이미지는 다음과 같습니다.
스페이서 위젯을 2개 사용하기 때문에 앞부분에 공백이 들어가게 되고, 그 스페이서 뒷 부분에 생성한 이미지 위젯이 그려진 상태입니다.
C++에서 디버깅하여 직접 살펴본 결과 해당 이미지 위젯은 호리즌탈 박스에서 인덱스가 정상적으로 설정되어 있다는 것을 파악했습니다.
이 과정에서 이미지를 생성하는 것과 호리즌탈 박스의 특정 인덱스에 삽입하는 로직에는 문제가 없다는 것을 파악했고, 다른 코드들을 하나씩 살펴보아도 별다른 문제는 파악할 수 없었습니다.
위젯이 어떻게 렌더링 되었는지 파악하기 위해서 Widget Reflector
을 사용해 렌더링 된 위젯을 살펴보았습니다.
그런데, 위젯의 인덱스와 상관없이 항상 생성된 순서로 호리즌탈 박스에 삽입되어 가장 우측에 하나씩 렌더링 된 것을 파악했습니다.
이 결과 동적으로 위젯을 삽입해도 인덱스와 무관하게 호리즌탈 박스에서 렌더링된다는 것을 알게되었고, 이 문제를 해결하기 위해 구글링 해보아 관련된 정보를 알아보았습니다.
우선 InsertChildAt
함수를 사용했기 때문에 해당 함수에 대한 언리얼 엔진의 공식 문서를 살펴보았습니다.
Unreal Engine C++ API Reference
해당 문서의 비고를 살펴보면 라이브 슬레이트 버전을 업데이트 하지 않는다라는 말을 볼 수 있었습니다.
그렇기 때문에 변경 사항을 적용하려면 전체 UI를 다시 빌드해야한다라고합니다.
UI를 다시 빌드한다라는 말 그대로 다시 빌드하기 위해서 RebuildWidget
함수를 호출해보았습니다.
하지만 해당 함수로는 이번 문제를 해결해내지 못했습니다.
그래서 다른 방법을 고민해보면서 관련된 정보를 살펴보았고, 다음과 같은 방법을 알게되었습니다.
호리즌탈 박스에 속한 위젯을 전부 캐싱해둔 다음 호리즌탈 박스에 속한 위젯을 초기화하여 비워주고, 다시 위젯을 원하는 순서로 삽입하는 방법을 알게되었습니다.
이 방법은 제가 발생했던 문제를 위에서 추측한 것처럼 생성되어 삽입된 순서대로 우측에 하나씩 스택되므로, 해결할 수 있는 방법이라고 판단했습니다.
하지만, 속한 모든 위젯을 초기화하여 비우는 것이 제가 구현한 위젯에서는 비효율적이라고 판단했습니다.
그 이유는 기존 호리즌탈 박스에서 가지고 있는 마지막 인덱스의 스페이서만 결과물의 가장 마지막 위치에 렌더링되기만 하면 됐기 때문입니다.
그렇기 때문에 다음과 같이 응용하여 문제를 해결해주었습니다.
호리즌탈 박스에서 기존에 가지고 있던 스페이서 위젯을 캐싱해두고, 동적으로 생성한 이미지 위젯을 먼저 삽입한 후 캐싱했던 스페이서 위젯을 나중에 삽입하여 문제를 해결해주었습니다.
이렇게 HP바를 구현하던 중 발생하던 문제를 모두 해결하여 기획에 맞게 UI를 구현해내었습니다.
기타 문제 해결
이외에 기획과 잘못 구현된 기능을 수정하고, 메시에 콜리전이 없어 충돌하지 않는 문제 등 자잘한 문제들을 해결해주었습니다.
위의 문제들과 별개로 기록해두는게 좋겠다라고 판단한 문제가 있습니다.
SimulatePhysics
가 ture
로 켜져있을 때, CollisionEnabled
가 NoCollision
이나 QueryOnly
로 설정된 상태거나 설정될 경우 경고가 발생하는 문제가 있었습니다.
이 문제에 대해서 알아본 결과 SimulatePhysics
는 컴포넌트에 대해서 물리 연산을 수행하도록 하는데, NoCollision
이나 QueryOnly
는 충돌 바디가 등록되지 않기 때문에 충돌할 바디 인스턴스를 찾지 못해 발생하는 문제였습니다.
해당 문제의 원인을 파악했기 때문에 문제가 되는 코드를 수정하는 것은 크게 어렵지 않았고, 빠르게 해결할 수 있었습니다.
이번 계기로 QueryOnly
와 NoCollision
, PhysicsOnly
에 대한 차이점을 이해하고, 기존보다 더 정확하게 콜리전 설정을 이해할 수 있는 좋은 계기가 되었습니다.
댓글남기기