[UE5] Project_StackBlock #4 - 게임 릴리즈 및 최종 과정 정리

ChangJin·2024년 8월 2일
0

Unreal Engine5

목록 보기
91/102
post-thumbnail

2024.08.02

이번 글에서는 프로젝트 StackBlock의 개발 과정, 문제 해결 방법, 최적화 과정 및 최종 릴리즈에 대한 모든 것을 정리합니다. 각 단계에서 겪었던 어려움과 그에 대한 해결 방법을 상세히 설명합니다.

UE5 Project_StackBlock 게임 개발 일지

  1. UE5 Project_StackBlock #1 - 기획 초안

    • 프로젝트 초기 기획과 목표 설정에 대한 내용입니다.
  2. UE5 언리얼 엔진 협업을 위한 Perforce 설정 및 문제 해결 가이드 - Github 대신 Perforce를 써야 하는 이유

    • 협업을 위한 Perforce 설정 방법과 문제 해결 가이드입니다. Github 대신 Perforce를 선택한 이유를 설명합니다.
  3. UE5 Project_StackBlock #2 - DynamicMeshComponent 활용 및 메쉬 불리언 연산 문제 해결

    • DynamicMeshComponent를 활용하고 메쉬 불리언 연산 문제를 해결한 과정입니다.
  4. UE5 Project_StackBlock #3 - DynamicMeshComponent의 겹친 영역을 완벽히 제거하는 방법!

    • DynamicMeshComponent의 겹친 영역을 제거하는 최적화 방법을 다룹니다.

깃허브 레포지토리 및 릴리즈

다음의 릴리즈 버전에서 게임을 해볼 수 있습니다.

Project_StackBlockProject_StackBlock v1.0
깃허브 레포지토리릴리즈

게임 초기 화면
게임 플레이 화면

1. 게임 개요

StackBlock은 플레이어가 다양한 블록을 쌓아 올리며 높은 점수를 목표로 하는 퍼즐 게임입니다. UE5의 기능을 활용하여 설계했습니다. 이번 프로젝트를 통해 얻은 많은 경험을 공유하고자 합니다.

2. 개발 과정

2.1 초기 기획 및 설계

게임의 초기 기획 단계에서, 우리는 다음과 같은 핵심 요소들을 결정했습니다:

  • 게임의 기본 메커니즘: 블록 쌓기
  • 블록 잘리기
  • 블록 스폰하기
  • 블록 이동하기

2.2 블록의 동적 생성 및 관리

블록의 동적 생성과 관리는 게임의 핵심 요소 중 하나였습니다. 다음과 같은 과정을 거쳐서 만들었습니다.

  • 블루프린트 사용: 초기에는 블루프린트를 사용하여 블록의 생성 및 관리 로직을 구현했습니다.
    AABB(Axis-Aligned Bounding Box) 사용: 블록과 스테이지와의 겹친 부분을 검출하기 위해서 사용했습니다.

AABB의 자세한 내용은 여기를 참고하면 좋습니다.
[UE5] Project_StackBlock #3 - DynamicMeshComponent의 겹친 영역을 완벽히제거하는 방법!

다음처럼 OnConstruction에서 HitBox의 크기에 맞게끔 다이나믹 메시가 생성이되고 GetOverlappedArea에서 겹친 영역의 크기와 좌표를 가지는 다이나믹 메시를 생성해서 반환하게끔 만들었습니다.


#include "DynamicBlockActor.h"
#include "AI/NavigationModifier.h"
#include "GeometryScript/MeshPrimitiveFunctions.h"

ADynamicBlockActor::ADynamicBlockActor()
{
	BoxCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxCollision"));
	BoxCollision->SetupAttachment(DynamicMeshComponent);
}

// BoxCollision의 Scale에 따라서 매시의 크기가 바뀜
void ADynamicBlockActor::OnConstruction(const FTransform& Transform)
{
	Super::OnConstruction(Transform);

	UDynamicMesh* DynamicMesh =  DynamicMeshComponent->GetDynamicMesh();
	if(DynamicMesh)
	{
		DynamicMesh->Reset();

		FTransform T;
		FGeometryScriptPrimitiveOptions Options;
		FVector Dimension = BoxCollision->GetScaledBoxExtent();
		float DimensionX = Dimension.X * 2;
		float DimensionY = Dimension.Y * 2;
		float DimensionZ = Dimension.Z * 2;
		float TransformLocationZ = Dimension.Z * -1;
		T.SetLocation(FVector(0,0,TransformLocationZ));
		UGeometryScriptLibrary_MeshPrimitiveFunctions::AppendBox(DynamicMesh, Options, T, DimensionX, DimensionY, DimensionZ);
		DynamicMeshComponent->EnableComplexAsSimpleCollision();
	}
}

// 겹쳐진 영역의 좌표, 크기를 구하는 함수
void ADynamicBlockActor::SetOverlapExtentAndLocation(UBoxComponent* Box1, UBoxComponent* Box2)
{
    FVector Box1Location = Box1->GetComponentLocation();
    FVector Box1Extent = Box1->GetScaledBoxExtent();

    FVector Box2Location = Box2->GetComponentLocation();
    FVector Box2Extent = Box2->GetScaledBoxExtent();

    FVector Max1 = Box1Location + Box1Extent;
    FVector Min1 = Box1Location - Box1Extent;

    FVector Max2 = Box2Location + Box2Extent;
    FVector Min2 = Box2Location - Box2Extent;
	
	FVector OverlapMax = FVector(FMath::Min(Max1.X, Max2.X), FMath::Min(Max1.Y, Max2.Y), FMath::Min(Max1.Z, Max2.Z));
    FVector OverlapMin = FVector(FMath::Max(Min1.X, Min2.X), FMath::Max(Min1.Y, Min2.Y), FMath::Max(Min1.Z, Min2.Z));
	
	OverlapLocationVector = (OverlapMin + OverlapMax) / 2.0f;
	OverlapExtentVector = OverlapMax - OverlapMin;

	UE_LOG(LogTemp, Warning, TEXT("OverlapLocationVector :  %s"), *OverlapLocationVector.ToString());
	UE_LOG(LogTemp, Warning, TEXT("OverlapExtentVector :  %s"), *OverlapExtentVector.ToString());
}

// 겹친 영역 만큼 DynamicMesh를 만들고 Return하는 함수
UDynamicMesh* ADynamicBlockActor::GetOverlappedArea(UBoxComponent* TargetBoxComponent)
{
	UDynamicMesh* TargetMesh = nullptr;
	SetOverlapExtentAndLocation(BoxCollision, TargetBoxComponent);

	UDynamicMesh* DynamicMesh = DynamicMeshComponent->GetDynamicMesh();
	FTransform Transform = DynamicMeshComponent->GetComponentTransform();
	FVector BoxTransform = Transform.InverseTransformPosition(OverlapLocationVector);

	if(DynamicMesh)
	{
		UDynamicMesh* ComputeMesh = AllocateComputeMesh();
		if(ComputeMesh)
		{
			FTransform T;
			FGeometryScriptPrimitiveOptions Options;
			FVector LocalOverlapLocation = FVector(BoxTransform.X, BoxTransform.Y, BoxTransform.Z - OverlapExtentVector.Z /2 );
			T.SetLocation(LocalOverlapLocation);
			
			TargetMesh = UGeometryScriptLibrary_MeshPrimitiveFunctions::AppendBox(
				ComputeMesh,
				Options,
				T,
				OverlapExtentVector.X,
				OverlapExtentVector.Y,
				OverlapExtentVector.Z
				);
		}
	}

	return TargetMesh;
}
스폰 로직

블록을 자르는 기능은 다이나믹 매쉬를 사용했습니다. 처음에는 Apply Mesh Boolean의 Substract로 보이지 않는 벽과 부딪치면 해당 면을 자르게끔 했습니다. 그러다보니 바닥면을 둘러쌓고 있는 보이지 않는 벽을 모두 배치해야 했습니다.

그런데 생각을 해보니 차라리 바닥면을 포함하는 부분만 살려두고 나머지를 자르면 되는걸 깨닫고는 Apply Mesh Boolean을 Intersection으로 바꾸고 바닥면에 수직인 보이지 않는 벽 1개를 만들었습니다.

보이지 않는 벽 배치
SubstractIntersection

2.3 충돌 감지 및 반응

충돌 감지는 블록이 적절히 쌓이고 무너지지 않도록 하는 데 중요한 역할을 합니다. 다음과 같은 절차로 구현했습니다:

  • 액터 태그 사용: 충돌을 감지할때 감지한 액터의 태그가 무엇인지 검출하도록 만들었습니다. 그리고 바닥인 경우, 블록인 경우를 나누어서 로직을 완성했습니다.
  • 스포너: 스포너에서는 현재 블록의 위치와 마지막 블록의 위치를 비교하여 정상적으로 쌓을 수 있는 블록인지 아니면 실패한 블록인지를 판단합니다.
  • 이벤트 디스패처 사용: 블록과 스포너를 연결할 때 사용했습니다.
  • 충돌 후 반응: 충돌 감지 후 블록이 더 이상 움직이지 않도록 처리했습니다.
스포너에서 쌓을 수 있는 블록인지 관리블록에서 액터 태그로 충돌 검출

3. 문제 해결 과정

3.1 블루프린트와 C++ 간의 호환성 문제

초기에는 블루프린트로 많은 로직을 구현했으나, 성능 이슈로 인해 C++로 전환했습니다. 이 과정에서 블루프린트와 C++ 간의 호환성 문제를 겪었고, 이를 해결하기 위해 다음과 같은 방법을 사용했습니다:

  • 정확한 데이터 타입 매칭: 블루프린트와 C++ 간의 데이터 타입을 정확히 매칭하여 호환성을 유지했습니다.
  • 블루프린트 노드 재작성: 필요한 경우, 블루프린트 노드를 C++ 함수로 재작성하여 사용했습니다.
UFUNCTION(BlueprintCallable)
void UpdateScore(int32 NewScore);

3.2 충돌 감지 최적화

충돌 감지 로직이 복잡해지면서 성능 저하가 발생했습니다. 이를 해결하기 위해 다음과 같은 최적화 기법을 적용했습니다:

  • AABB 계산 최적화: AABB 계산 로직을 단순화하고, 필요한 경우에만 계산하도록 수정했습니다.
  • 충돌 후 처리 간소화: 충돌 후 처리 로직을 간소화하여, 불필요한 연산을 줄였습니다.

4. 결론

이번 프로젝트를 통해 블록 쌓기 게임의 개발 과정, 다이나믹 메시의 사용법, AABB, 이벤트 디스패처등을 학습할 수 있었습니다. 블루프린트와 C++의 효율적인 사용과 충돌 감지 로직의 최적화가 큰 도움이 되었습니다.

참고 자료


이 글이 여러분의 프로젝트에 도움이 되길 바랍니다. 추가적인 질문이나 수정이 필요하다면 언제든지 알려주세요!

profile
Unreal Engine 클라이언트 개발자

0개의 댓글