개발하던 언리얼 프로젝트를 Github 사용 시 1년에 8만원씩 써야 해서 git만 사용하다가 너무 불편하여 대안을 찾던 중 "차라리 AWS에 사설 Github 서버 올리면 그게 더 싸지 않을까?" 싶어서 찾아보다가 CodeCommit이라고 아얘 서비스가 나와서 환경을 설정해보았습니다.

일단 졸업하고 AWS를 처음 쓰는거라 환경 설정 하는데 시간이 꽤 걸린 것 같습니다.

윈도우 환경인 점이 한 몫 하기도 했구요.

그리고 AWS 사설 주소로 Push를 보내는게 지금 사용하는 GUI(Git Kraken)에서는 지원되지 않더라구요.

그래서 강제로 CUI 환경으로 복귀하게 되었습니다.

 

그래도 아직 자주 쓰지는 않았지만 장점도 몇 있었습니다.

일단 가격이 매우 쌌습니다. 여러 애셋을 같이 올려보았는데 한 8GB 올렸음에도 가격 정책에서 별로 잡히지가 않습니다.

그리고 개인 프로젝트라 커밋 수가 적은 것을 감안하면 가격 부담은 한달에 5달러는 커녕 1달러도 안넘을 것 같습니다.

 

한가지 아쉬운 점은 개인적으로 게임 프로젝트에서 리소스와 코드가 혼재되어 있어서 VCS 선정할 때 Git이 후순위로 밀리는 것에 대해 대안을 찾고자 Git과 Perforce를 혼용하는 방안을 고민 했는데 안타깝게도 이 방안을 구현하기는 힘들 것 같습니다.

다만 프로젝트가 어느정도 마무리가 되면 Perforce에 더 공부해서 카피 프로젝트를 EC2에 옮겨보고, 성공한다면 Github까지 복사를 한 뒤 다시 Github로 복귀해보고자 합니다.

 

여튼 개인 게임 프로젝트를 하면서 VCS에 고민이 있으신 분들은 하루 정도 투자하셔서 CodeCommit을 사용해보는 것을 추천드려 봅니다.

일반적으로 언리얼 엔진 입문을 하게 되면 Epic Games 플랫폼으로 프로젝트를 생성을 해서 개발을 시작하게 됩니다.

여기서 조금 더 발전하게 되면 VCS를 사용하게 되고, 더 나아가 엔진 코드를 따로 Clone을 해서 코드를 읽어보게 됩니다.

그러다 보면 문뜩 여타 기업들처럼 엔진 코드를 따로 빌드해서 프로젝트를 진행하고 싶은 욕심이 듭니다.

이는 엔진 코드를 수정하고자 하는 욕심에서 비롯되죠.

 

그런데 그 방법이 뭔가 명확하게 기재가 되어있지 않습니다.

특히 VCS로 관리를 하게 된다면 "그래서 이걸 어떻게 엔진에다가 관리를 하지?"라고 고민을 하게 됩니다.

오늘은 이러한 제 고민을 해결한 방식을 공유하고자 합니다.

 

미리 말씀드리자면, 제 나름대로의 방식이니 효율 등이 완벽하게 고려된 사항은 아님을 먼저 말씀드립니다.

https://github.com/RedChiken/UnrealPractice

 

프로젝터에 언리얼 엔진 코드를 넣자니 업데이트 때마다 커밋을 해줘야 하고, 그 양이 매우 방대합니다.

개인 프로젝트에서 레포지토리에 그 많은 코드를 통째로 넣는 것은 매우 비효율적이라 생각했습니다.

그리고 이미 UnrealEngine 레포지토리가 공개가 되어 있으니 이걸 최대한 활용을 하고 싶었습니다.

그러다가 제가 생각해낸 것은 submodule이었습니다.

 

https://git-scm.com/book/ko/v2/Git-%EB%8F%84%EA%B5%AC-%EC%84%9C%EB%B8%8C%EB%AA%A8%EB%93%88

 

Git - 서브모듈

gitmodules 파일에 있는 URL은 조건에 맞는 사람이면 누구든지 Clone 하고 Fetch 할 수 있도록 접근할 수 있어야 한다. 예를 들어 다른 사람이 Pull을 하는 URL과 라이브러리의 작업을 Push 하는 URL이 서로

git-scm.com

submodule을 이용하면 등록된 레포지토리를 통해 Github상에서 엔진 코드와 클라이언트 코드를 깔끔하게 분리할 수 있을거라 생각했습니다.

그리고 기회가 된다면 적극적으로 EngineCode를 수정하고 싶었습니다.

때문에 기존의 EpicGames/UnrealEngine 레포지토리를 Fork하여 RedChiken/UnrealEngine 레포지토리를 생성하였고, 이를 submodule로 등록을 했습니다.

 

이 뒤에는 튜토리얼에서 공개된 것과 비슷합니다.

프로젝트 레포지토리를 Clone을 받은 뒤, UnrealEngine 폴더에 들어가 엔진 코드를 빌드를 하고 실행을 합니다.

그럼 UE5 이후로 생성된 Unreal Engine 클라이언트를 실행한 것처럼 아래 창이 노출됩니다.

여기서 프로직트 위치를 Clone 받은 레포지토리로 지정하고, 그 안에 다시 언리얼 게임 프로젝트를 생성을 하면 됩니다.

 

이후 언리얼 게임 프로젝트를 열면 엔진 프로젝트와 비슷하게 매우 많은 항목들이 공개되어 있습니다.

대신 한가지 차이점이 있다면, 거기에 게임 프로젝트도 포함이 되어 있는거죠.

이 게임 프로젝트를 빌드를 한 뒤 개발을 시작하면 Unreal Engine 코드가 프로젝트에 포함된 게임 프로젝트를 Github에 관리하기 걸맞게 생성을 하게 됩니다.

 

이 과정에서 몇가지 고려를 해야 할 사항이 있습니다.

첫번째로, 엔진 빌드, 게임 프로젝트 초기 빌드 시 엔진 쪽 설정 파일 일부가 변경이 됩니다.

때문에 이들을 따로 커밋하거나, ignore 해야 하는데 위에 언급 했듯이 게임 프로젝트까지 모두 빌드를 한 뒤에 한꺼번에 처리하는 편이 용이합니다.

두번째로, 개발을 하다 보면 UE5는 오브젝트들을 바이너리 파일들로 관리를 하는 방식을 제공하는데 별 다른 설정 변경이 없다면 이 정책이 유지가 될겁니다.

이를 ignore하는 것도 좋지만, 저는 lfs를 사용하는 것을 추천해보고 싶습니다.

 

언리얼 엔진 개발을 하다 보면 기능 개발에 대한 설명은 조금이라도 남아 있지만 프로젝트 구성에 대한 설명은 매우 미흡합니다.

이 글이 다른 사람들에게 도움이 되었으면 합니다.

'메모장 > 개발 지식' 카테고리의 다른 글

AWS CodeCommit 사용 후기  (0) 2022.09.17
툴팁(Tooltip) 만들기  (0) 2022.05.05
C++ Lambda Capture 주의사항  (0) 2021.11.13
Print client side log in listen server  (0) 2020.06.25
FObjectInitializer constructor fatal error c1853  (0) 2020.04.20

UE4에서 제공하는 Tooltip 예시 이미지. 출처: Ryan Laley 유튜브

"언리얼에서 툴팁을 제공하는데 이걸 왜 따로 만들지?"라고 생각하실 수 있습니다.

언리얼 툴팁은 몇가지 특징이 있습니다.

 

1. 툴팁 UI가 적당히 잘 배치됩니다.

"적당히 잘"은 언뜻 들었을 때에는 썩 괜찮은 단어입니다.

하지만 시각적인 부분이 중요한 UI에서 "적당히 잘"은 최소한의 기능 제공만 의미하기도 합니다.

실제로 언리얼이 제공하는 툴팁은 보통 클릭한 커서를 중심으로 위젯이 호출됩니다.

이는 몇가지 누락된 기능이 있는데, 가장 아쉬운 점은 "화면 밖으로 나가는 것을 막지 못한다"는 점입니다.

800*600 화면에서 700위치에 150*50 크기의 툴팁을 생성하면 1/3은 화면 밖에 나가게 되는 것이죠.

 

2. 원하는 위치에 배치할 수 없다.

예를 들어 인벤토리에서 아이템 융합 UI를 생성한다고 합시다.

이 UI는 현재 융합 대기중인 아이템 뿐만 아니라 남은 아이템 목록도 한 눈에 들어와야 하기 때문에 인벤토리와 겹치면 안됩니다.

이럴 때 이 UI를 툴팁으로 만들어 버린다면 원하는 스펙을 맞출 수 없겠죠.

 

위에서 얘기한 것을 바탕으로, 새로 만들 툴팁은 최소 2가지 기능을 갖추어야 합니다.

1) 내가 원하는 위치에 정확히 배치를 해야 한다.

2) 화면 밖으로 나가지 않아야 한다.

 

1. UI의 위치 적용

1. 툴팁 위젯을 대상 위젯과 중심을 일치시키는 작업

언리얼상에서 디스플레이의 원점은 좌상단입니다.(사실 보통 그래픽스는 대체로 그렇습니다.)

그리고 UI의 기본 중심 또한 좌상단으로 잡혀 있죠.

하지만 툴팁은 기준 UI의 무게중심(개념상 일치하니 이렇게 지칭하겠습니다.)을 기준으로 그 외각에 붙어 있어야 하죠.

그렇기 때문에 1차적으로 각 UI의 중심지를 좌상단에서 무게중심으로 이동시키고,

툴팁 UI의 중심과 기준 UI의 중심을 좌표상 같은 곳에 일치하도록 하는 작업을 선행해야 합니다.

 

이는 여러분들이 고등학교(근래는 대학교)에서 배운 기하와 벡터만으로 충분합니다.

기준 UI(이하 A)의 좌상단 좌표가 (a1, a2)이고, 툴팁 UI(이하 B)의 좌상단이 (b1, b2)입니다.

여기서 A의 중심 좌표가 A`(a`1, a`2)이고, B의 것은 B`(b`1, b`2)입니다.

이 때 B와 A의 중심 좌표를 일치시키기 위해서는 디스플레이서부터 A`까지의 벡터와 디스플레이서부터 B`까지의 벡터를 감산 해야 합니다.

그렇게 되면 AB(a`1 - b`1, a`2 - b`2)라는 벡터가 나오게 됩니다.

2. 툴팁을 중심 UI와 겹치지 않게 하는 작업

A와 B가 겹쳐지면 이제 B를 A의 테두리 바깥에 붙도록 위치를 옮겨야 합니다.

이는 1보다는 훨씬 간단한 작업입니다.

B를 옮길 방향에 맞게 A와 B의 해당 크기 값을 연산해주면 됩니다.

여기서는 B를 A의 상단에 붙일겁니다.

이 때 A의 크기는 Ax * Ay이고, B의 크기는 Bx * By입니다.

그럼 B의 좌표(A`1, A`2)에 A와 B의 y크기의 절반만큼을 감산을 합니다.

감산을 하는 이유는 원점이 좌상단이기 때문에 윗 방향은 -Y축이기 때문이구요.

그럼 B의 중심 좌표가 이제 (A`1, A`2 - (Ay + By) / 2)가 됩니다.

 

3. 화면 밖으로 나간 툴팁을 다시 안으로 밀어줘야 한다.

일반적으로는 2번 과정까지만 하면 큰 문제가 없습니다.

하지만 우리는 한가지 더 고려를 해줘야 합니다.

B가 화면 밖으로 삐져나가는 경우에 다시 안쪽으로 밀어줘야 합니다.

 

이 또한 간단한 산수작업입니다.

B의 상단서부터 A의 중심까지의 Y값은 By + Ay / 2입니다.

그리고 실제 A의 중심의 Y좌표값은 A'y입니다.

이들의 차인 By + Ay / 2 - A`y 는 B가 화면 밖으로 나간만큼의 Y 수치가 되는 것이죠.

이만큼 Y좌표 값에 가산을 해주면 B가 화면에 맞게 조절이 됩니다.

 

4. 툴팁 완성

 

자세히 보면 B가 A의 위에 살짝 걸쳐진 것을 볼 수 있습니다.

하지만 어쩔 수 없습니다. 여기서는 B의 크기를 고정해놓기 때문에 A와의 겹침보다 화면 안에 들어가는게 더 우선순위가 높거든요.

물론 UI를 줄일 수 있으나 이런 UI들은 특성상 상당히 많은 정보를 제공하기 때문에 쉬이 크기 조절을 할 수 없을겁니다.

장담컨에 디 방식이 보편적인 타협책이라고 봅니다.

 

이런 과정을 거치기 위해서는 우리는 5개의 값이 필요합니다.

1) 화면 전체 크기

2) 기준 UI의 중심 좌표

3) 기준 UI의 크기

4) 툴팁 UI의 중심 좌표

5) 툴팁 UI의 크기

 

이를 3가지로 줄일 수 있는 것이 있는데, 바로 FGeometry입니다.

https://docs.unrealengine.com/4.26/en-US/API/Runtime/SlateCore/Layout/FGeometry/

 

FGeometry

Represents the position, size, and absolute position of a Widget in Slate.

docs.unrealengine.com

 

FGeomtery는 의미 그대로 Geometry. 크기와 좌표 정보를 가지고 있습니다.

이걸 이용하면 위 5가지 정보를 아래 3가지로 압축할 수 있습니다.

1) 화면 전체 크기

2) 기준 UI의 Geometry

3) 툴팁 UI의 Geometry

 

여기서 한가지 편의사항을 알려드리자면, 게임 개발 하면서 화면 전체를 덮는 UI가 적어도 1개는 발생을 하게 됩니다.

그 UI의 Geometry를 구한다면, 화면 전체의 크기를 구할 수 있게 되죠.

그럼 최종적으로 이렇게 됩니다.

1) 화면 전체를 덮는 UI의 Geometry

2) 기준 UI의 Geometry

3) 툴팁 UI의 Geometry

 

Geometry를 이용한 위젯의 위치 변경은 대략 이렇게 표현이 가능할 것 같습니다.

const FVector2D GetMovementVector(const FGeometry& DisplayGeometry, const FGeometry& IconGeometry, const FGeometry& TooltipGeometry, const EAlignVertical:::Type& VertAlign, cont EAlignHorizental::Type& HorAlign)
{
	const FVector2D& DisplaySize = DisplayGeometry.GetAbsoluteSize();

	const FVector2D& IconCenterPosition = IconGeometry.GetAbsolutePositionAtCoordinates(FVector2D(0.5f, 0.5f));
    const FVeoctor2D& IconCenterVector = DisplayGeometry.AbsoluteToLocal(IconCenterPosition);
    const FVector2D& IconSize = IconGeometry.GetAbsoluteSize();
    
	const FVector2D& TooltipCenterPosition = TooltipGeometry.GetAbsolutePositionAtCoordinates(FVector2D(0.5f, 0.5f));
    const FVeoctor2D& TooltipCenterVector = DisplayGeometry.AbsoluteToLocal(TooltipCenterPosition);
    const FVector2D& TooltipSize = IconGeometry.GetAbsoluteSize();
    
    FVector2D MoveVector = IconCenterVector = TooltipCenterVector;
    const FVector2D& TotalOutlineVector = IconSize + TooltipSize;
    Switch(VertAlign)
    {
    	case EAlignVertical::Type::Left
        	MoveVector.x -= TotalOutlineVector.x;
        	break;
        case EAlignVertical::Type::Right
        	MoveVector.x += TotalOutlineVector.x;
            break;
    }
    MoveVector.x = FMath::Clamp(MoveVector.x, 0, DisplaySize.x);
    
    swietch(HorAlign)
    {
    	case EAlignHorizental::Type::Up
        	MoveVector.y -= TotalOutlineVector.y;
            break;
        case EAlignHorizental::Type::Down
        	MoveVector.y += TotalOutlineVector.y;
            break;            
    }
    MoveVector.y = FMath::Clamp(MoveVector.y, 0, DisplaySize.x);
    
    return MoveVector;
}

 

수식은 위에서 다 설명 하였고, 몇가지 함수 설명을 첨하면 될 것 같습니다.

 

GetAbsolutePositionAtCoordinates는 NormalizedVector를 통해 중심값에서부터 절대 좌표값을 구하는 수식입니다.

즉, UI의 좌상단을 기준으로 무게중심의 절대 좌표를 구하는 함수입니다.

AbsoluteToLocal은 절대 좌표를 해당 Geometry의 Position을 기준으로 지역 좌표로 변환하는 함수입니다.

위 코드에서는 DisplayGeometry의 Position, 즉 화면의 좌상단을 기준으로 하는 상대 좌표로 변환을 하게 됩니다.

이를 통해 TooltipCenterVector와 IconCenterVector는 Display상에서의 값들로 변환이 되게 됩니다.

 

화면 밖으로 나가는 것은 DisplaySize를 구해놓고 Clamp로 변환을 해주었습니다.

Vector2D Clamp가 따로 있었다면 좋았겠지만 아쉽게도 없더군요.

 

자 이렇게 해서 간단한 수식을 작업해 보았습니다.

이걸로 마무리...를 하면 여러분들은 제대로 된 결과를 보지 못하게 될겁니다.

그리고 절 욕하겠죠.

위 코드는 한가지 맹점이 있습니다. 무엇일까요?

 

바로 Geometry 부분입니다.

정확히는 저 Geometry가 런타임 상에서 Invalid한 값이 들어올 수 있다는 점입니다.

보통 Geometry 값을 받아올 때에는 GetCachedGeometry라는 함수를 사용합니다.

https://docs.unrealengine.com/4.27/en-US/BlueprintAPI/Widget/GetCachedGeometry/

 

Get Cached Geometry

Get Cached Geometry

docs.unrealengine.com

그런데 이 GetCachedGeometry는 구조 상 몇가지 제약이 있습니다.

1) 화면상에 노출되어야 한다.

2) 최상위 레이어에 위치해야 한다.

 

이는 GetCachedGeometry가 호출 될 시 그 Tick에서의 Geometry 정보를 담아와서 반환하기 때문입니다.

사실 UI의 크기가 고정적이라면 아무런 문제가 되지 않습니다.

UI 내부에 USizeBox를 두고 모든 항목을 그 아래로 내린 뒤, OverrideSize를 지정해서 사이즈를 고정해버리면 해당 SizeBox의 OverrideSize로 값을 연산할 수 있을테니까요.

하지만 UI가 내부 데이터에 따라 크기가 변동이 된다면 엄청난 오차가 발생을 하게 될겁니다.

그리고 이런 눈꼴시러운 오차를 없애기 위해 노력을 하다 보면 저 GetCachedGeometry에서 막히게 될겁니다.

 

저 제약이 썩 거지같은 점은 "무조건 한번은 화면 레이어에 올라가야 한다"는 점입니다.

이는 디스플레이에 한번은 찍혀야 하는 의미이고, 이는 곧 잔상을 의미합니다.

이제 우리는 3번째 기능을 고민해야 합니다.

3) 화면상에 잔상이 남지 않고 어느 사이즈더라도 동작하는 Geometry 값을 구한다.

 

문제를 해결하기 위해서는 제약을 잘 살펴봐야 합니다.

1)을 다시 볼까요? 

"노출 되어야 한다"라고 되어 있지 "표시되어야 한다"라고 되어 있지 않습니다.

이것이 의미하는 바는, 어떤 형태로든 화면상에 나오기만 하면 Valid한 Geometry를 구할 수 있다는 점입니다.

그것이 "투명"하더라도 말이죠.

 

네 3번째 기능에 대한 해답은 바로 Opacity에서 alpha값을 0으로 내려버리는 것이었습니다.

이렇게 하면 레이어 상으로는 최상위에 툴팁 UI가 올라가 있지만, 투명하기 때문에 유저들이 육안으로 볼 수는 없죠.

이 때 Geometry를 구해서 위치를 옮겨주고, alpha값을 복구하면 잔상 없는 커스텀 툹팁 기능이 완성이 됩니다.

 

UTooltipWidger : public UWidget
{
	virtual void NativeConstruct() override
    {
    	SetOpacity(0.f, 0.f, 0.f, 0.f);
    }

	virtual void Open(bool bCancelAnim) override
    {
    	Super::Open(bCancelAnim);
    	auto World = GetWorld();
        if(IsValid(World) == false)
        {
        	return;
        }
        
        World->GetTimerManager().AddTimer(PositionHandle, this, &ThisClass::UpdatePosition, World->GetTickDeltaTime());
    }
    
    void UpdatePosition()
    {
    	const FGeometry& DisplayGeometry = GetDisplayGeometry();	// 어떻게 구해 왔다고 가정
        const FGeometry& IconGeometry = GetIconGeometry();			// 어떻게 구해 왔다고 가정
        const FGeometry& TooltipGeometry = GetCachedGeoemtry();
        
        SetPosition(GetMovementVector(DisplayGeometry, IconGeometry, TooltipGeometry, EVerticalAlign::Center, EHorizentalAlign::Up));
    	SetOpacity(0.f, 0.f, 0.f, 1.f);
	}
}

 

물론 이 방식은 약간의 딜레이를 요구합니다.

하지만 그 수치가 Tick 함수가 한번 호출 될 정도의 간극이라는 점을 감안하면 손해라고는 있을 수 없는 로직이라 볼 수도 있습니다.

 

저처럼 개발자(혹은 타 업무자)가 좀 더 디테일하게 커스텀이 가능한 툴팁을 필요로 하는 경우가 얼마나 있을지 잘 모르겠습니다.

특히 이런 블로그 글을 보고 들어온건 개인개발이라는 건데 개인 개발에서 이런 정도의 스펙은 잘 구현 안하잖아요?

하지만 어떻게 해야 할지 조금 막막한 기능이었으나 의외로 쉬운 로직이라는 점을 명시하면서.

이 글이 필요로 하시는 분들에게 도움이 되었으면 좋겠습니다.

근래에 동료로부터 들은 내용이라 사실 확인을 하다가 확인한 사항입니다.

처음에는 별 생각 없었는데 좀 더 생각해보니 제가 C++를 공부했을 때
Lambda의 사용 방식이나 Capture의 기능 동작은 설명이 되어 있는데 
내부적으로 어떻게 동작하는지, 혹인 이런 주의 사항은 보지 못했던 것 같더군요.

 

요는 Lambda의 Capture를 사용하면 어떻게 값을 받아오느냐 입니다.

이를 제대로 이해하기 위해서는 Lambda가 Functor와 비교해 어떻게 구성되었느냐를 알아야 합니다.

 

결론부터 얘기하면 Capture가 없는 Lambda나

Call-by-Value로 Capture를 쓰는 Lambda는 일반 Functor와 거의 동일하게 구성됩니다.

 

하지만 Call-by-Reference로 Capture를 쓰면 구조 차이가 발생합니다.

첫번째로, Reference 된 Parameter가 Pointer로 참조가 됩니다.

여기서 이 글의 가장 중요한 전달사항이 나옵니다.

Lambda의 Capture는 Call-by-Reference더라도 Pointer이기 때문에 Pointer Dangling이 발생할 수 있습니다.

이는 Lambda가 비동기적으로 생성되는 경우,

즉 네트워크나 쓰레드와 사용하는 경우에 Invalid Argument를 발생할 수 있습니다.

 

두번째로, Functor는 Constructor가 외부에 별도로 존재를 하는 반면, 

Lambda의 Constructor는 그 내부에 존재를 하고있습니다.
즉, Constructor에서 발생하는 복사 작업이 요구되지 않아 일반 Functor보다 조금 더 가볍습니다. 아주 조금요.

 

이에 대해 자세한 설명은 원문을 본 블로그를 첨부하겠습니다.

어셈블리까지 분석을 해서 비교를 해주고 있기에 직접 포스트를 읽으시면 더 명확히 이해가 갈겁니다.

 

원본 포스트 출처 - https://web.mst.edu/~nmjxv3/articles/lambdas.html

 

C++ Lambdas Under The Hood

C++ Lambdas Under The Hood Introduction C++11 introduced lambdas, which provide a syntactically lightweight way to define functions on-the-fly. They can also capture (or close over) variables from the surrounding scope, either by value or by reference. In

web.mst.edu

Multiplay 기능을 지원 할 때 단순히 UE_LOG를 사용하면

테스트 중 접속한 Client 갯수만큼 Log가 출력되어 정확히 판단하기 힘들 때가 있다.

 

이에 대해 여러가지 답안을 모색 했으나, 비루한 영어 실력과 그에 비해 광활한 답안으로 인해 명확한 답을 찾지 못했다.

그리고 마침내, 한가지 제대로 동작하는 케이스가 있어 이를 따로 작성한다.

 

https://forums.unrealengine.com/development-discussion/c-gameplay-programming/49577-how-to-know-on-client-side-if-an-actor-is-own-by-the-current-network-connection

 

How to know on client side if an actor is "own" by the current network connection - Unreal Engine Forums

For gameplay programmers writing C++ code.

forums.unrealengine.com

혹 나와 같은 문제를 겪은 사람이 이 글을 보고 보다 빠르게 문제를 해결하기를 바란다.

이틀 전 토요일, 정오 즈음에 이를 사용하는 Constructor를 작성하였으나 fatal error c1853이 발생.

 

에러 코드 절반은 crushed 되어 있어서 문제를 알 수 없는 상황.

 

하루 종일 검색과 시도를 하였으나 해결하지 못하였다.

 

오늘도 1시간 정도 삽질을 하다가 log 중 intermediate 경로의 파일이 있어서

혹시나 하는 마음에 중간 생성 파일을 모두 지우고 VS project 재생성 뒤 rebuild를 하였다.

 

그 결과 문제 해결...

 

기존의 아무 인자도 없는 Constructor의 build 정보가 코드를 엉키게 하는 것 같다.

 

어쩌면 Unreal을 쓰는 사람들에게는 손 발을 사용하는것 만큼 익숙한 것일수도 있지만 내게는 아직 이런 과정을 거치는 것이 어색하다.

 

나와 같은 문제를 겪는 사람들은 고통을 받기 전에 Rebuild를 한번 해보자.

'메모장 > 개발 지식' 카테고리의 다른 글

C++ Lambda Capture 주의사항  (0) 2021.11.13
Print client side log in listen server  (0) 2020.06.25
UE4 2개 이상의 Layered blend per bone  (1) 2019.12.20
창문 달린 벽 만드는 법  (0) 2019.08.31
샷건 탄환 구현  (0) 2019.07.29

서류 몇개 더 떨어지고 정말 간만에 개발을 하다가 막혔던 부분 중 공유하면 좋을 것 같아서 적어봅니다.

 

Animation에는 Layered blend per bone이라는 노드가 있습니다.

 

기존의 포즈에 지정한 포즈를 특정 bone을 기준으로 일부만 적용 할 수 있는 노드입니다.

 

움직이면서 상호작용, 점프 공격 등을 이로 구현을 하였는데 구현하면서 문제가 있었습니다.

 

구현해야 할 Layered pose는 2개인데 노드에 포즈를 추가하면 제대로 작동을 하지 않았습니다.

 

한참을 뒤적이다가 다음 페이지를 찾고 시도한 결과 문제를 해결할 수 있었습니다.

 

https://answers.unrealengine.com/questions/233385/view.html

 

Layered blend per bone and multiple slots - UE4 AnswerHub

Hello My character has two attack types. One is a melee attack, the other is a type of whirlwind spin attack. I've set them up with different slots in the animation montages. The slot for the melee attack affects the character from spine and up, so you sti

answers.unrealengine.com

이와 같이 Montage를 두 Layered blend per bone 노드 사이에 배치를 하면 무사히 두 Layered Pose가 적용이 됩니다.

 

한국어로 적어놓으면 누구라도 찾아서 도움이 되겠죠...

'메모장 > 개발 지식' 카테고리의 다른 글

Print client side log in listen server  (0) 2020.06.25
FObjectInitializer constructor fatal error c1853  (0) 2020.04.20
창문 달린 벽 만드는 법  (0) 2019.08.31
샷건 탄환 구현  (0) 2019.07.29
기능 구현 - Zoom  (0) 2019.07.08

테스트용 맵을 만들면서 건물을 만드려는데 벽에 달린 창문을 만드는 방법이 고민이 되더군요.

 

처음에는 창문 기준으로 상하좌우 벽 나눠서 작은 벽을 쌓을까 싶었습니다.

 

하지만 문뜩 "큰 벽에 구멍 뚫는게 엔진에서 지원이 안될까?"라는 생각이 들더군요.

 

그래서 커뮤니티에 질문을 올렸는데 답이 바로 달리더군요.

 

"지오메트리 쓰면 구멍 뚫을 수 있습니다."

 

찾아보니까 지오메트리에서 addictive와 subtractive가 있는데, 전자는 범위 안을 채워주고, 후자는 비워주더군요.

 

그래서 큰 벽을 addictive로 만들고, 창문 크기 만큼 subtractive 지오메트리를 겹쳐놓으면 창문이 완성이 됩니다.

 

그리고 그 부분은 투명한게 아니라 아예 통과도 됩니다.

 

뭐라도 만드시는 분들 레벨 디자인 한번쯤 거치실텐데 도움이 되셨으면 좋겠습니다.

한 2주 동안 아무것도 못했는데 그 이유가 샷건 탄환을 구현하는데 투자했기 때문이다.

7월 중순에 시작했던 탄환 구현을 오늘에서야 완성을 하며, 방식을 공유하고자 한다.

샷건 탄환 구현에 있어서 가장 중요한 부분은 3가지이다.

 

1. 탄환이 규칙적인 모양으로 발사되지 않도록 한다.

2. 탄환이 방사형으로 퍼지면서 발사되도록 한다.

3. 엉뚱한 방향으로 탄환이 튀지 않도록 한다.

 

3번부터 설명을 하자면, 우선 탄환의 Collision을 수정하여 탄환들끼릭은 서로 부딛치지 않고 무시하고 통과되도록 하였다.

게임 내 탄환을 도탄하는 기능도 없고, 샷건은 탄환 수가 많아 크게 이질감을 느끼지 않는다.

그리고 탄환이 출발하는 Location을 수정해주었다. 

구체적으로, 내가 구현한 샷건은 20발의 탄환이 발사가 된다.

이 20발의 탄환을 5 * 4 형태로 발사가 되도록 하였다. 

탄환들끼리의 거리가 가까우면 이상한 방향으로 발사되기 때문에, 각 탄환의 index 별 위치를 조절해가며 퍼지지 않는 위치를 지정하였다.

 

여기까지 한다면 직사각형 모양으로 균등하게 발사 되는 샷건이 된다. 

그렇다면 어떻게 1번 조건을 만족 시키는가?

탄환의 위치에 랜덤 변수를 추가하였다.

각 탄환은 해당 index를 5로 나누어 그 몫을 column index로, 나머지를 row index로 가진다.

그리고 이 column index와 row index의 각각의 중심값으로부터의 차이를 구한 뒤 일정 값과 플레이어 화면 중심에서의 우측 벡터, 혹은 위 벡터를 곱하여 탄환의 위치를 지정한다.

이때, 이 일정 값을 특정 범위 안에서의 랜덤한 값을 주어 좀 더 혼잡한 위치에서부터 탄환이 출발하도록 하였다.

 

마지막으로, 각 탄환이 퍼져야 할 벡터들을 더한 뒤 Find Look at Rotation으로 해당 벡터로 변환해주는 Rotation을 구해서 Transform에 적용하면 샷건 탄환의 발사가 완성이 된다.

 

글로만 보면 이해가 잘 안가니 블루프린트 스크린샷을 첨부하겠다.

 

 

UCameraComponent에서 FieldOfView 라는 변수가 있습니다.

이를 줄이면 줌을 땡기게 되고, 늘리면 줌을 놓는게 됩니다.

+ Recent posts