엄폐 시에 이동을 담당하는 CoverTrace 함수는 굉장히 길고 복잡하다.
따라서 이에 대한 설명을 포스팅 안에서보단, 코드에 주석을 최대한 많이 다는 것으로 대체하고자 한다.
코드는 다음과 같다. (PC 환경에서 보는 것을 추천)
void AKannaCharacter::CoverTrace()
{
FHitResult HitResult;
FCollisionQueryParams CollisionParameters;
CollisionParameters.AddIgnoredActor(this);
FVector ActorLocation = GetActorLocation();
UCharacterMovementComponent* Movement = GetCharacterMovement();
//현재 PlaneConstraintNormal은 플레이어의 이동 제한 평면의 법선벡터이다.
//이는 벽면에서 바깥쪽으로 가는 방향이다.
//원하는 벡터는 캐릭터가 벽을 향하는 방향의 벡터이므로, -1을 곱해준다.
FVector WallDirection = Movement->GetPlaneConstraintNormal() * (-1.f);
//Right Vector는 벽면을 바라보고 섰을 때 오른쪽 방향을 나타낸다.
//먼저 Rotator를 만들고, 거기서 벡터를 뽑아낸다.
//변수명을 RightRotator로 했는데, RightVector를 뽑아내기 위한 것이어서 그렇지
//실제 방향은 WallDirection이다.
//MakeRotFromX는 X축에만 기반하여 Rotator를 만든다는 의미이다.
FRotator RightRotator = UKismetMathLibrary::MakeRotFromX(WallDirection);
FVector RightVector = UKismetMathLibrary::GetRightVector(RightRotator);
//캐릭터의 살짝 우측에서 벽 방향으로 라인트레이싱을 수행할 것이다.
FVector RightStart = ActorLocation + RightVector * 45.f;
FVector RightEnd = RightStart + WallDirection * 200.f;
//라인트레이싱이 맞았다면 RightHit이 true
RightHit = GetWorld()->LineTraceSingleByChannel
(HitResult,
RightStart,
RightEnd,
ECollisionChannel::ECC_GameTraceChannel1,
CollisionParameters);
//이번엔 같은 작업을 왼쪽에서 할 것이다.
//WallDirection의 반대 방향 Rotator를 만들고, 거기서 RightVector를 뽑아내면
//그건 캐릭터 기준 왼쪽이된다.
FRotator LeftRotator = UKismetMathLibrary::MakeRotFromX(WallDirection * (-1.f));
FVector LeftVector = UKismetMathLibrary::GetRightVector(LeftRotator);
FVector LeftStart = ActorLocation + LeftVector * 45.f;
FVector LeftEnd = LeftStart + WallDirection * 200.f;
//Set Left Hit
LeftHit = GetWorld()->LineTraceSingleByChannel(
HitResult,
LeftStart,
LeftEnd,
ECollisionChannel::ECC_GameTraceChannel1,
CollisionParameters);
//MoveActionBinding은 이동 인풋 값을 가져다쓰기 위해 만든 것이다.
//이에 대한 설명도 후술할 것이다.
FVector2D MoveVector = MoveActionBinding->GetValue().Get<FVector2D>();
//PlayerRightVector는 카메라로 보는 시점 기준 오른쪽 방향 벡터이다.
FVector PlayerRightVector = UKismetMathLibrary::GetRightVector(UKismetMathLibrary::MakeRotator(0.f, 0.f, GetControlRotation().Yaw));
//왼쪽, 오른쪽 둘다 엄폐 공간이 남아있다면, 어느쪽이든 갈 수 있다.
if (LeftHit && RightHit)
{
if (MoveVector.X != 0.f) //좌우 이동이 0이 아니라면
{
//앞에 엄폐물이 있는지 라인트레이싱으로 한번 더 확인하고,
//법평면 제한을 한번 더 걸어준 뒤, AddMovementInput을 통해 이동시킨다.
// 제한을 다시 거는 이유는, 실린더 모양 엄폐물에서도
//좌우이동이 자연스럽게 되도록 하기 위해서이다.
if (GetWorld()->LineTraceSingleByChannel(
HitResult,
ActorLocation,
ActorLocation + WallDirection * 200.f,
ECollisionChannel::ECC_GameTraceChannel1,
CollisionParameters)
)
{
Movement->SetPlaneConstraintNormal(HitResult.Normal);
AddMovementInput(PlayerRightVector, MoveVector.X);
}
}
}
else // 엄폐공간이 좌우 모두 자유롭게 남아있지 않은 경우
{
float MovementScale; //필요에 따라 0이 될 수도 있다.
//오른쪽 공간있고, 오른쪽 이동 인풋이 들어왔다면 스케일을 그대로 해준다.
if (RightHit && MoveVector.X > 0)
{
MovementScale = MoveVector.X;
}
//왼쪽도 마찬가지
else if (LeftHit && MoveVector.X < 0)
{
MovementScale = MoveVector.X;
}
// 남은 공간의 위치와 이동 방향이 어긋날 때는
//MovementScale를 0으로 하여 이동하지 못하게한다.
else
{
MovementScale = 0;
}
// 계산된 MovementScale에 따라 캐릭터를 이동시킨다.
AddMovementInput(PlayerRightVector, MovementScale);
}
}
중간에 MoveActionBinding에 관한 내용이 있었다.
Move 인풋액션의 콜백함수가 아닌 CoverTrace함수가 Move 인풋 값을 사용할 수 있도록 사용한 방법이다.
이는 BindActionValue 함수를 사용해야 한다.
// Called to bind functionality to input
void AKannaCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AKannaCharacter::Move);
MoveActionBinding = &(EnhancedInputComponent->BindActionValue(MoveAction)); //이 부분
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AKannaCharacter::Look);
EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Triggered, this, &AKannaCharacter::Aim);
EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Completed, this, &AKannaCharacter::ReleaseAim);
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Triggered, this, &AKannaCharacter::Interact);
EnhancedInputComponent->BindAction(SwitchWeaponAction, ETriggerEvent::Triggered, this, &AKannaCharacter::SwitchWeapon);
EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &AKannaCharacter::Attack);
EnhancedInputComponent->BindAction(RollAction, ETriggerEvent::Triggered, this, &AKannaCharacter::Roll);
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &AKannaCharacter::Fire);
EnhancedInputComponent->BindAction(ReloadAction, ETriggerEvent::Triggered, this, &AKannaCharacter::Reload);
EnhancedInputComponent->BindAction(CoverAction, ETriggerEvent::Triggered, this, &AKannaCharacter::TakeCover);
}
}
이렇게 해 둠으로써 다른 함수에서도 MoveActionBinding을 통해 Move 인풋 값에 접근할 수 있는 것이다.
마지막으로, 엄폐 이동 중 손을 떼면 다시 정면을 바라보도록 하였다. 이는 Tick 함수에서 처리한다.
void AKannaCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 엄폐중이고, 이동 중이지 않을 시 항상 캐릭터는 엄폐물 방향을 향함
if (IsInCover && (GetVelocity().Length() == 0))
{
FVector Direction = GetCharacterMovement()->GetPlaneConstraintNormal() * (-1.f);
AddMovementInput(Direction);
}
}
다음은 현재까지 진행사항의 테스트 영상이다.
https://www.youtube.com/watch?v=_l2Yf2ReraE&ab_channel=Ciel45
높은 벽면에서의 엄폐는 어떻게 해야할 지 고민을 좀 해봐야 할 것 같다.
'언리얼 엔진 5 > 개발 일지' 카테고리의 다른 글
[UE5] 블루아카이브 TPS게임 개발일지 (35) - 엄폐 시스템 구현 5 (0) | 2024.01.10 |
---|---|
[UE5] 블루아카이브 TPS게임 개발일지 (34) - 엄폐 시스템 구현 4 (0) | 2024.01.10 |
[UE5] 블루아카이브 TPS게임 개발일지 (32) - 엄폐 시스템 구현 2 (0) | 2024.01.07 |
[UE5] 블루아카이브 TPS게임 개발일지 (31) - 엄폐 시스템 구현 1 (0) | 2024.01.07 |
[UE5] 블루아카이브 TPS게임 개발일지 (30) - 리팩토링 (블루프린트 -> C++) (2) | 2024.01.06 |