2024.07.02
깃허브! | 풀리퀘! |
---|---|
★ https://github.com/ChangJin-Lee/Project-Lena | ★ https://github.com/ChangJin-Lee/Project-Lena/pull/9 |
이번 포스팅에서는 조합 자물쇠 로직 구현 과정, 회전 정의 방법, 문과의 상호작용, 문제 상황 해결 방법 등을 다룹니다. 특히, 각 칸의 회전 처리 및 타임라인 사용법에 대해 자세히 설명합니다.
업데이트 된 상호작용 관련 상속 관계도 |
이번 작업에서는 조합 자물쇠의 로직을 구현했습니다. 조합 자물쇠는 각각의 숫자 칸이 회전할 수 있으며, 올바른 비밀번호를 입력하면 문이 열리도록 설계했습니다. 간단하게 애니메이션 블루프린트를 만들어 자물쇠가 열리는 애니메이션도 추가하였습니다.
만들고자 하는 자물쇠 |
각 칸의 회전 각도를 정의하는 데 여러 시도를 했습니다. 처음에는 switch case 문으로, 그 다음엔 if-else 문으로, 최종적으로는 36도로 나눈 몫을 사용하여 각도를 정의했습니다. 이렇게 하여 코드가 깔끔해졌습니다.
이렇게 하면 논리상 문제가 없지만 Unreal Engine의 Roll은 0~360까지의 범위가 아니라 -180~180까지의 범위를 가지기 때문에 3에서 4로 넘어갈 때 문제가 생깁니다.
// 각도 범위에 따라 문자열을 반환
if (RollValue >= 36 && RollValue < 72)
{
return FString("0");
}
else if (RollValue >= 72 && RollValue < 108)
{
return FString("1");
}
else if (RollValue >= 108 && RollValue < 144)
{
return FString("2");
}
else if (RollValue >= 144 && RollValue < 180)
{
return FString("3");
}
else if (RollValue >= 180 && RollValue < 216)
{
return FString("4");
}
else if (RollValue >= 216 && RollValue < 252)
{
return FString("5");
}
else if (RollValue >= 252 && RollValue < 288)
{
return FString("6");
}
else if (RollValue >= 288 && RollValue < 324)
{
return FString("7");
}
else if (RollValue >= 324 && RollValue < 360)
{
return FString("8");
}
else if (RollValue >= 0 && RollValue < 36)
{
return FString("9");
}
return "Invalid";
Enum Class 정의 | EnumClass 반환 |
다음 EnumClass로 넘어가기 |
그나마 제일 나은 방법이지만 더 좋은 방법이 떠오른다면 나중에 개선하려고 합니다.
float GetDialPosition(UStaticMeshComponent* WheelMeshDial)
{
float RollValue = WheelMeshDial->GetRelativeRotation().Roll;
RollValue = FMath::Fmod(RollValue + 180.0f, 360.0f) - 180.0f;
if (RollValue < 0)
{
RollValue += 360.0f;
}
int PositionIndex = FMath::RoundToInt(RollValue / 36.0f) % 10;
return static_cast<float>(PositionIndex);
}
문과의 상호작용도 구현했습니다. 자물쇠의 비밀번호가 올바르면 문이 열리도록 했습니다. TSubclassOf
로 에디터에서 받은 변수에 UGameplayStatics::GetAllActorsOfClass
로 월드에 있는 원하는 액터를 가져왔고, FindActors.Num()
으로 개수를 체크했습니다.
void ACombinationLockActor::CheckRightAnswer()
{
TArray<AActor*> FindActors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), DoorActorClass, FindActors);
GetCurrentDial();
if (FindActors.Num() >= 1)
{
DoorActor = Cast<ASlidingDoorActor>(FindActors[0]);
if (DoorActor)
{
if (GetCurrentDial() == DoorActor->GetPassWord())
{
UGameplayStatics::PlaySoundAtLocation(GetWorld(), RightAnswerSound, GetActorLocation());
DoorActor->RightAnswer(FVector(0, 130.0f, 0));
CheckAnim = true;
for (UStaticMeshComponent* InMesh : WheelMeshArray)
{
InMesh->SetSimulatePhysics(true);
}
}
}
}
}
회전이 끝나지 않았는데 한 번 더 돌리려고 하면 각도가 조금씩 더해지거나 빼지는 문제가 있었습니다. 이를 해결하기 위해 TimelineFinishedCallback
을 만들어 타임라인 시작 후 끝날 때까지는 입력이 안되도록 만들었습니다.
void ACombinationLockActor::ScrollCombinationLock(FRotator InTargetRotation)
{
if (bIsTimeLinePlaying || !WheelMeshArray[SelectedWheelIndex]) return;
InitialRotation = WheelMeshArray[SelectedWheelIndex]->GetRelativeRotation();
TargetRotation = InitialRotation + InTargetRotation;
if (CombinationLockTimeline)
{
bIsTimeLinePlaying = true;
CombinationLockTimeline->PlayFromStart();
}
}
void ACombinationLockActor::HandleCombinationLockProgress(float Value)
{
if (WheelMeshArray[SelectedWheelIndex])
{
FRotator NewRotation = FMath::Lerp(InitialRotation, TargetRotation, Value);
WheelMeshArray[SelectedWheelIndex]->SetRelativeRotation(NewRotation);
}
}
void ACombinationLockActor::OnTimeLineFinished()
{
bIsTimeLinePlaying = false;
}
float GetDialPosition(UStaticMeshComponent* WheelMeshDial)
{
// 현재 Roll 값을 가져옴
float RollValue = WheelMeshDial->GetRelativeRotation().Roll;
// Roll 값을 -180에서 180 범위로 정규화
RollValue = FMath::Fmod(RollValue + 180.0f, 360.0f) - 180.0f;
// 음수 값을 양수 값으로 변환
if (RollValue < 0)
{
RollValue += 360.0f;
}
// 각도 범위에 따라 숫자를 계산
int PositionIndex = FMath::RoundToInt(RollValue / 36.0f) % 10;
return static_cast<float>(PositionIndex);
}
GetActorOfClass
함수로 특정 블루프린트 액터를 가져오려 했으나, 원하는 인덱스의 액터를 가져올 수 없었습니다. 항상 첫 번째 액터만 가져오는 문제가 발생했습니다.GetActorOfClass
함수를 통해 간단하게 특정 클래스의 액터를 가져올 수 있으나 특정 인덱스의 액터를 가져오는 기능이 부족하기 때문에 GetAllActorsOfClass를 사용하기로 했습니다.
GetAllActorsOfClass로 모든 액터를 가져와서 인덱스로 몇 번째 액터를 조회할 것인지 만들었습니다.
ASlidingDoorActor* GetNthActorOfClass(TSubclassOf<AActor> ActorClass, int32 N)
{
TArray<AActor*> FoundActors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ActorClass, FoundActors);
if (FoundActors.IsValidIndex(N))
{
return Cast<ASlidingDoorActor>(FoundActors[N]);
}
return nullptr;
}
#include "MyAnimInstance.h" // UMyAnimInstance 클래스를 포함합니다
void ACombinationLockActor::BeginPlay()
{
Super::BeginPlay();
if (SkeletalMesh)
{
UAnimInstance* AnimInstance = SkeletalMesh->GetAnimInstance();
if (AnimInstance)
{
UMyAnimInstance* MyAnimInstance = Cast<UMyAnimInstance>(AnimInstance);
if (MyAnimInstance)
{
MyAnimInstance->bIsRunning = true;
}
else
{
UE_LOG(LogTemp, Warning, TEXT("AnimInstance is not of type UMyAnimInstance"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("SkeletalMesh does not have an AnimInstance"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("SkeletalMesh is null"));
}
}
자물쇠에서 현재 선택된 칸을 시각적으로 표시하기 위해 두 가지 방법을 생각해냈습니다.
레이캐스트 사용:
인덱스 기반 배열 관리:
- 인덱스를 만들어서 각 칸들을 관리하는 배열을 선택하게끔 했습니다.
- 특정 키를 눌러 칸을 지정하고 드래그할 수 있도록 구현했습니다.
- 매시의 매테리얼을 가져와서 BaseColor의 알파값을 조절하는 방법이 있고 메시의 크기를 조금 키우는 방법이 있습니다.
매시의 매테리얼을 가져와서 변경 |
메시의 크기를 조금 키우기 |
시각적으로 후자가 더 좋다고 판단하여 선택된 칸의 크기를 키우는 방법으로 구현했습니다.
void ACombinationLockActor::SelectWheelMesh(int32 Index)
{
if (SelectedWheelIndex != Index)
{
// 이전 선택된 매시의 크기를 원래대로 돌림
WheelMeshArray[SelectedWheelIndex]->SetRelativeScale3D(FVector::One() * DefaultScale);
// 새로운 매시를 선택하고 크기를 키움
SelectedWheelIndex = Index;
WheelMeshArray[SelectedWheelIndex]->SetRelativeScale3D(FVector::One() * SelectedScale);
}
}
이번 작업을 통해 조합 자물쇠 로직을 완성하고, 문과의 상호작용을 구현하며, 코드의 유지보수성을 높일 수 있었습니다. 앞으로도 지속적으로 개선해 나갈 계획입니다.
이와 같이 작업하면서 많은 것을 배웠고, 이를 통해 프로젝트의 완성도를 높일 수 있었습니다. 앞으로도 꾸준히 개선하여 더 나은 결과를 만들어 나가겠습니다.