이번 시간에는 마우스를 사용해 포탑을 회전시켜보자.
포탑 조준하는 방향을 구하기 위해서는 벡터 계산이 필요하다. 포탑의 위치를 TurretLocation이라고 하고 트레이스의 위치를 TraceHitLocation이라고 둔다.
우리는 TurretLocation에서 TurretHitLocation까지의 벡터에 관심을 두고 있다.
이것을 ToTarget이라고 두며, 사실 이 벡터 계산은 꽤 간단하다.
ToTarget= TraceHitLocation-TurretLocation으로 끝점에서 시작점을 빼준다. ToTarget 방향 벡터를 구한 다음에는 이 값을 회전값으로 변환해서 탱크 포탑의 회전 값을 설정할려고 한다.
FVector 타입에는 기본적으로 주어지는 Rotation이라는 함수가 존재한다(FVector::Rotation)
ToTarget 벡터를 구해서 Rotation을 호출하면, 그 방향에 해당하는 FRotator가 반환된다.(ToTarget.Rotation())
여기서 염두해야 할 것이 있는데 트레이스 히트 결과는 플로어에 위치하는 경우가 많고 탱크 포탑에서 트레이스 히트 결과까지의 벡터는 지면과 평행하지 않는다. 즉, 탱크 포탑 회전 값을 방향 벡터로 구한 로테이터로 설정하면 지면을 향해 포탑이 기울어질 수 있다. 바닥을 보는게 아닌 정면을 보고 싶으며, 다시 말해 탱크 포탑 회전값 중에서 Yaw 컴포넌트만 바꾸고 싶다. 이렇게 하면 올바른 방향을 가리키면서도 지면과 평행해진다.
이것은 탱크와 적이 될 터렛도 기능을 가지고 있어야하므로 BasePawn 클래스에 작성하도록한다
먼저 포탑 컴포넌트에서 마우스 커서에 위치한 트레이스 히트 결과까지의 ToTarget 벡터가 필요하다.
FVector ToTarget = LookAtTarget - TurretMesh->GetComponentLocation();
LookAtTarget에서 TurretMesh 위치를 빼면 TurretMesh에서 LookAtTarget까지의 벡터가 나온다.
이후 FVector Rotation 함수를 이용해 ToTarget에서 FRotator을 구할 수 있다.
void ABasePawn::RotateTurret(FVector LookAtTarget)
{
FVector ToTarget = LookAtTarget - TurretMesh->GetComponentLocation();
FRotator LookAtRotation = FRotator(0.f, ToTarget.Rotation().Yaw, 0.f);
}
이렇게 작성 해주고 이제 회전 값을 구해야 하는데 SetWorldRotation이라는 함수를 사용해 주어진 컴포넌트의 회전값을 설정할 수 있다.
void ABasePawn::RotateTurret(FVector LookAtTarget)
{
FVector ToTarget = LookAtTarget - TurretMesh->GetComponentLocation();
FRotator LookAtRotation = FRotator(0.f, ToTarget.Rotation().Yaw, 0.f);
TurretMesh->SetWorldRotation(LookAtRotation);
}
이제 탱크 클래스에서 해당 함수를 사용해본다
void ATank::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (PlayerControllerRef)//유효한지 확인
{
FHitResult HitResult;
PlayerControllerRef->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, false, HitResult);
RotateTurret(HitResult.ImpactPoint);
HitResult.ImpactPoint; //히트 이벤트가 발생한 스페이스 위치를 가져올 수 있다. 라인 트레이스에 대한 정보를 가져오기 위해 HitResult 사용함
}
}
RotateTurret() 함수 안에 HitResult.Impact(트레이스 맞은데)를 인자로 주면 된다
포탑이 마우스를 따라가고 실행 해도 wasd 누르는 동안에 마우스를 보며 회전하는 것을 볼 수 있다.
몇가지 문제가 존재하는데,
탱크 중심 가까이 마우스 커서를 움직여서 통과할 때 첫 번째 문제가 나타난다. 어떤 지점부터는 아주 빠르게 덜컥 거리면서 회전하는 것을 영상에서도 볼 수 있다.
두 번째는 클릭해서 움직이다가 가장자리로 갈 경우 마우스 커서를 위로 움직이면 다른 방향으로 포탑이 덜컥 거리는게 보인다 .
맵 밖 등 히트 결과를 만들지 못하는 곳에는 임팩트 지점이 정해지지 않아 라인 트레이스에 부딪힐 만한 무언가가 필요하다.
여기서 블로킹 볼륨을 추가해준다. 기본적으로 보이진 않는다. 디테일 창을 확인해보면 콜리젼프리셋이 InvisibleWall로 설정되어 있으며 거의 모든 것이 블록으로 설정되어 있다. 비저빌리티 채널만 유일하게 블록 설정이 되지 않았으며 라인 트레이스는 이 비저빌리티 채널을 추적하는데 월드에서 비저빌리티 채널이 가리는 모든 것이 라인 트레이스에 부딪히게 된다. 하지만 블로킹 볼륨은 그렇지 않다. Custom으로 변경해 비저빌리티 채널에 대한 콜리전 반응을 블록으로 바꿀 수 있다.
볼륨은 보이지 않지만 비저빌리티 채널을 막도록 설정하였다. 그러면 이렇게 비저빌리티 채널을 추적하는 라인 트레이스와 부딪히게 된다.
모든 벽에 콜리전 볼륨 추가해주면 끝! 하지만 탱크 근처에서 마우스를 움직이면 계속 산발적인 동작이 나타난다 이는 회전 값을 직접 설정하는 대신 보간 함수를 사용하면 된다.
이 함수는 기본적으로 입력한 타깃을 향해 매끄럽게 회전한다.
BasePawn.cpp의 RotateTurret을 수정하여 포탑의 SetWorldRotation을 LookAtRotation 대신 보간 함수에서 반환되는 값과 같도록 설정하면 된다. FMATH 함수의 로테이터의 보간을 처리하기 위한 함수인 RInterpTo를 사용한다. 첫 번째 인풋 매개 변수는 FRotator 구조체 참조값 Current이다. 즉 바꾸려는 현재 값이다. 포탑의 회전값을 바꾸려고 하니 TurretMesh에서 가져올 수 있다. 그리고 사용할 함수는 GetComponentRotation이다. 다음 인풋은 Target 이라는 FRotator인데 부드럽게 움직일 수 있게 InterTo로 바꾸려는 포탑 메시 회전 값을 말한다. 앞에서 만든 LookAtRotation이 여기에 들어간다. 세 번째로는 중요한 델타 타임을 넣고, InterSpeed 값을 넣어줘야 하는데 이 값이 클수록 타깃을 향해 더욱 빠르게 보간된다.
void ABasePawn::RotateTurret(FVector LookAtTarget)
{
FVector ToTarget = LookAtTarget - TurretMesh->GetComponentLocation();
FRotator LookAtRotation = FRotator(0.f, ToTarget.Rotation().Yaw, 0.f);
TurretMesh->SetWorldRotation(
FMath::RInterpTo(
TurretMesh->GetComponentRotation(),
LookAtRotation,
UGameplayStatics::GetWorldDeltaSeconds(this),
5.f
)
);
}
마우스를 중간으로 계속 옮겨봤지만 뭔가 갑자기 회전이 되진 않는다!~
TIL: 뭔가를 발사하고 포탑을 사용하는 게임은 많이 봤는데 이렇게 복잡하고 세세한 부분까지 다 체크해야지 게임을 만드는데 더 나은 사람이 될 수 있다는 것을 깨닫게 되었다. 이걸 하면서 여러가지 함수를 사용해 보는것이 매우 의미있는 활동이라고 생각한다