호경

[캡스톤디자인] VR게임 제작하기 - 무기 배치, 잡기 본문

캡스톤 졸업작품/언리얼 엔진 4

[캡스톤디자인] VR게임 제작하기 - 무기 배치, 잡기

rlaghrud1234 2023. 1. 17. 12:35

※본 글은 전자공학부 캡스톤 디자인 작품인 "VR 체험형 소프트웨어 제작"주제로 진행한느 내용을 정리하기 위한 글로, 개발 과정에 전반적인 흐름과 코드를 포스팅 하기 위해 작성 됐다. 혹여, VR 관련된 프로젝트를 하는 분들이 계시거나 UE4를 잘 아는 분이 계시면 해당 포스트에서 틀린 곳이나 수정해야 할 부분이 있으면 말씀해주시면 감사하겠습니다!

 

그리고 해당 프로젝트는 C++과 블루프린트의 혼용으로 진행할 예정입니다!


1. 프로젝트 세팅에서 집기 버튼 활성화

프로젝트 세탕 -> 입력 -> 액션 맵핑

이 과정을 통해 Grip 버튼을 활성화 시킨다.

 

액션 맵핑
캡스톤.mp4
3.65MB

 

이번에 내 프로젝트에서는 Oculus RIiftS를 사용할 예정이므로 Oculus Touch L, R 그립에 GrabLeft, GrabRight 액션을 맵핑을 해주었다.

 

2. 잡는 동작 만들기

1) AVRPawn 함수에 Overlap 되게 설정해주기

언리얼엔진에서는 집기 동작을 만들 때 Overlapping이라는 함수를 이용해서 만든다. 

우선 AVRPawn  함수에 Overlap 함수를 넣어줄 수 있는 Mesh에 Collision을 Set 해준다.

//Overlap이 돼야함
	LeftSword->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap);
	RightSword->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap);

 

2) 입력을 받기 위해 SetupPlayerInputComponent 함수에 입력 코드 넣어주기

이동은 BindAxis로 묶어주고, 버튼을 누르는 입력은 BindAction으로 입력 Mapping을 해준다.

IE_Pressed를 통해서 눌렀을 경우, GrabRight와 GrabLeft가 작동하게 만들어준다.

void AVRPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAction("GrabRight", IE_Pressed, this, &AVRPawn::GrabRightPressed);
	PlayerInputComponent->BindAction("GrabLeft", IE_Pressed, this, &AVRPawn::GrabLeftPressed);
}

3) 잡는 것에 대한 기능 구현하기 위해 헤더파일에 선언하고, 소스파일에 구현하기

a) VRPawn.h

//다음 레벨로 이동하기 위한 방법
	bool Right_has_sword;
	bool Left_has_sword;

	//왼손 오른손 칼잡기
	void GrabRightPressed();
	void GrabLeftPressed();

b) VRPawn.cpp

void AVRPawn::GrabRightPressed()
{
	//Collecti (Nodachi_white)
	FName RightWeaponSocket = TEXT("Sword Socket");
	TArray<AActor*> outoverlap;
	RightSword->GetOverlappingActors(outoverlap);

	//East_Sword
	FName RightEastSocket = TEXT("RightSword East");
	TArray<AActor*> outoverlap_East_Right;
	RightSword->GetOverlappingActors(outoverlap_East_Right);

	//Kitsune_Sword
	FName RightKitsuneSocket = TEXT("RightSword Kitsune");
	TArray<AActor*> outoverlap_Kitsune_Right;
	RightSword->GetOverlappingActors(outoverlap_Kitsune_Right);

	if (outoverlap.Num() > 0)
	{
		ACollecti* trycast = dynamic_cast<ACollecti*>(outoverlap[0]);
		Right_has_sword = true;

		if (trycast)
		{
			trycast->GetStaticMeshComponent()->SetSimulatePhysics(false);
			trycast->AttachToComponent(RightHand, FAttachmentTransformRules::SnapToTargetIncludingScale, RightWeaponSocket);
			
			if (Right_has_sword == true)
			{
				UE_LOG(LogTemp, Display, TEXT("Right Sword"));

				if (Left_has_sword == true && Right_has_sword == true)
				{
					UE_LOG(LogTemp, Display, TEXT("Both has sword"));
					this->OutputText->SetText("Wepon Selected!");
				}
			}
		}
	}

	if (outoverlap_Kitsune_Right.Num() > 0)
	{
		AKorean_Scabbard* trycast_Korean_Right = dynamic_cast<AKorean_Scabbard*>(outoverlap_Kitsune_Right[0]);
		Right_has_sword = true;

		if (trycast_Korean_Right)
		{
			trycast_Korean_Right->GetStaticMeshComponent()->SetSimulatePhysics(false);
			trycast_Korean_Right->AttachToComponent(RightHand, FAttachmentTransformRules::SnapToTargetIncludingScale, RightKitsuneSocket);

			if (Right_has_sword == true)
			{
				UE_LOG(LogTemp, Display, TEXT("Right Sword Kitsune"));

				if (Left_has_sword == true && Right_has_sword == true)
				{
					UE_LOG(LogTemp, Display, TEXT("Both has sword"));
					this->OutputText->SetText("Wepon Selected!");
				}
			}
		}
	}

	if (outoverlap_East_Right.Num() > 0)
	{
		AEast_Sword_1* trycast_East_Right = dynamic_cast<AEast_Sword_1*>(outoverlap_East_Right[0]);
		Right_has_sword = true;

		if (trycast_East_Right)
		{
			trycast_East_Right->GetStaticMeshComponent()->SetSimulatePhysics(false);
			trycast_East_Right->AttachToComponent(RightHand, FAttachmentTransformRules::SnapToTargetIncludingScale, RightEastSocket);

			if (Right_has_sword == true)
			{
				UE_LOG(LogTemp, Display, TEXT("Right Sword East"));

				if (Left_has_sword == true && Right_has_sword == true)
				{
					UE_LOG(LogTemp, Display, TEXT("Both has sword"));
					this->OutputText->SetText("Wepon Selected!");
				}
			}
		}
	}
}

void AVRPawn::GrabLeftPressed()
{
	//Nodachi_Black Sword
	FName LeftWeaponSocket = TEXT("LeftSword Socket");
	TArray<AActor*> outoverlap_Left;
	LeftSword->GetOverlappingActors(outoverlap_Left);

	//Kitsune_Sword
	FName LeftKitsuneSocket = TEXT("LeftSword Kitsune");
	TArray<AActor*> outoverlap_Kitsune_Left;
	LeftSword->GetOverlappingActors(outoverlap_Kitsune_Left);

	//East_Sword
	FName LeftEastSocket = TEXT("LeftSword East");
	TArray<AActor*> outoverlap_East_Left;
	LeftSword->GetOverlappingActors(outoverlap_East_Left);

	if (outoverlap_Left.Num() > 0)
	{
		ANodachi_Black* trycast_Left = dynamic_cast<ANodachi_Black*>(outoverlap_Left[0]);
		Left_has_sword = true;

		if (trycast_Left)
		{
			trycast_Left->GetStaticMeshComponent()->SetSimulatePhysics(false);
			trycast_Left->AttachToComponent(LeftHand, FAttachmentTransformRules::SnapToTargetIncludingScale, LeftWeaponSocket);

			if (Left_has_sword == true)
			{
				UE_LOG(LogTemp, Display, TEXT("Left Sword Nodachi"));

				if (Left_has_sword == true && Right_has_sword == true)
				{
					UE_LOG(LogTemp, Display, TEXT("Both has sword"));
					this->OutputText->SetText("Wepon Selected!");
				}
			}
		}
	}

	if (outoverlap_Kitsune_Left.Num() > 0)
	{
		AKorean_Sword1* trycast_Korean_Left = dynamic_cast<AKorean_Sword1*>(outoverlap_Kitsune_Left[0]);
		Left_has_sword = true;

		if (trycast_Korean_Left)
		{
			trycast_Korean_Left->GetStaticMeshComponent()->SetSimulatePhysics(false);
			trycast_Korean_Left->AttachToComponent(LeftHand, FAttachmentTransformRules::SnapToTargetIncludingScale, LeftKitsuneSocket);

			if (Left_has_sword == true)
			{
				UE_LOG(LogTemp, Display, TEXT("Left Sword Kitsune"));

				if (Left_has_sword == true && Right_has_sword == true)
				{
					UE_LOG(LogTemp, Display, TEXT("Both has sword"));
					this->OutputText->SetText("Wepon Selected!");
				}
			}
		}
	}

	if (outoverlap_East_Left.Num() > 0)
	{
		AEast_Sword_1* trycast_East_Left = dynamic_cast<AEast_Sword_1*>(outoverlap_East_Left[0]);
		Left_has_sword = true;

		if (trycast_East_Left)
		{
			trycast_East_Left->GetStaticMeshComponent()->SetSimulatePhysics(false);
			trycast_East_Left->AttachToComponent(LeftHand, FAttachmentTransformRules::SnapToTargetIncludingScale, LeftEastSocket);

			if (Left_has_sword == true)
			{
				UE_LOG(LogTemp, Display, TEXT("Left Sword East"));

				if (Left_has_sword == true && Right_has_sword == true)
				{
					UE_LOG(LogTemp, Display, TEXT("Both has sword"));
					this->OutputText->SetText("Wepon Selected!");
				}
			}
		}
	}
}

원하는 칼을 고를 수 있게 코드를 짠 것이다. 스켈레톤에 소켓을 구성하고 거기에 칼을 넣는 방식으로 코드를 구현했다.

 

4) 칼에 Collsion 입히기

#include "Korean_Sword1.h"
#include "Components/BoxComponent.h"

AKorean_Sword1::AKorean_Sword1()
{
	//Find object and Get Overloap to Object
	static ConstructorHelpers::FObjectFinder<UStaticMesh> loadedObj(TEXT("/Game/Kitsune/Mesh/SM_Kitsune_Katana_Sword1.SM_Kitsune_Katana_Sword1"));

	this->GetStaticMeshComponent()->SetStaticMesh(loadedObj.Object);

	this->GetStaticMeshComponent()->SetWorldScale3D(FVector(1.0f, 1.0f, 1.0f));

	static ConstructorHelpers::FObjectFinder<UMaterial> loadedMat(TEXT("Material'/Game/Kitsune/Materials/M_Kitsune_Katana.M_Kitsune_Katana'"));

	this->GetStaticMeshComponent()->SetMaterial(0, loadedMat.Object);
	this->GetStaticMeshComponent()->SetSimulatePhysics(true);
	this->SetMobility(EComponentMobility::Movable);

	this->GetStaticMeshComponent()->SetGenerateOverlapEvents(true);

	//Making Collsion Box
	SwordHitBox = CreateDefaultSubobject<UBoxComponent>(TEXT("Collsion Box"));
}

이렇게 해야지 칼을 잡을 수 있다.

3. VRPawn blueprint, VRGameMode 설정

1) 게임 모드 설정을 위해 VRGameMode BP를 만들고 Player Start 기본 Pawn을 VRPawn으로 한다.

2) 이후, 레벨에 액터들을 배치해주고 실행을 확인한다.

 

4. 실행 결과

 

5. 다음 개발 계획

1) 검에 Collision Box 붙이기

2) 날아오는 구체를 튕길 수 있는 검 만들기

Comments