Trace from Gun Barrel
아래와 같이 Cube를 놓고 총을 쏴보면, 연기(BeamParticles)는 총구에서 뒤의 벽까지 생성됨을 알 수 있다. ( impactParticles도 마찬가지다)
이는 저번 시간에 LineTraceSingleByChannel에 쓰이는 시작 FVector를 CrosshairWorldPosition로 초기화 해주었기 때문이다. 이번 시간에는 이를 수정해 보겠다.
기존의 로직은 바꾸지 않고, 한번더 LineTrace를 통해서 총구부터 착탄 지점사이에 Hit이 있으면 BeamEndPoint를 수정해 주겠다.
ShooterCharacter.cpp
void AShooterCharacter::FireWeapon(const FInputActionValue &Value)
{
// ...
// 총을 발사하는 로직 수행
// ...
// 두 번째 트레이스 수행, 이번에는 총 구로부터
FHitResult WeaponTraceHit;
const FVector WeaponTraceStart{SocketTransform.GetLocation()};
const FVector WeaponTraceEnd{BeamEndPoint};
GetWorld()->LineTraceSingleByChannel(
WeaponTraceHit,
WeaponTraceStart,
WeaponTraceEnd,
ECollisionChannel::ECC_Visibility);
if (WeaponTraceHit.bBlockingHit) // 총과 BeamEndPoint 사이에 물체가 있는지 확인
{
BeamEndPoint = WeaponTraceHit.Location;
}
// ...
}
추가로 impactParticles를 생성하는 구문을 아래로 내리고 WeaponTraceHit.Location을 BeamEndPoint로 수정한다.
if (WeaponTraceHit.bBlockingHit) // object between barrel and BeamEndPoint?
{...}
// Spawn impact particles after updating BeamEndPoint
if (impactParticles)
{
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
impactParticles,
BeamEndPoint); // WeaponTraceHit.Location -> BeamEndPoint
}
컴파일 후, 총구와 Crosshair 사이에 Visibility가 있으면 그곳에서 Particle이 발생하는 모습을 볼 수 있다.
Refacter Beam End Code
GetBeamEndLocation이라는 함수를 통해 리팩토링 합니다.
AShooterCharacter.h
protected:
bool GetBeamEndLocation(const FVector& MuzzleSocketLocation, FVector& OutBeamLocation);
AShooterCharacter.cpp
void AShooterCharacter::FireWeapon(const FInputActionValue &Value)
{
// UE_LOG(LogTemp, Warning, TEXT("Fire Weapon"));
bool isFire = Value.Get<bool>();
if (FireSound)
{
UGameplayStatics::PlaySound2D(this, FireSound);
}
const USkeletalMeshSocket *BarrelSocket = GetMesh()->GetSocketByName("BarrelSocket");
if (BarrelSocket)
{
const FTransform SocketTransform = BarrelSocket->GetSocketTransform(GetMesh());
if (MuzzleFlash)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleFlash, SocketTransform);
}
FVector BeamEnd;
bool bBeamEnd = GetBeamEndLocation(SocketTransform.GetLocation(), BeamEnd);
if (bBeamEnd)
{
// Spawn impact particles after updating BeamEndPoint
if (impactParticles)
{
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
impactParticles,
BeamEnd);
}
if (BeamParticles)
{
UParticleSystemComponent *Beam = UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
BeamParticles,
SocketTransform);
if (Beam)
{
Beam->SetVectorParameter(FName("Target"), BeamEnd);
}
}
}
}
/*
FHitResult FireHit;
const FVector Start{SocketTransform.GetLocation()};
const FQuat Rotation{SocketTransform.GetRotation()};
const FVector RotationAxis{Rotation.GetAxisX()};
const FVector End{Start + RotationAxis * 50'000.f};
FVector BeamEndPoint{End};
GetWorld()->LineTraceSingleByChannel(FireHit, Start, End, ECollisionChannel::ECC_Visibility);
if (FireHit.bBlockingHit)
{
// DrawDebugLine(GetWorld(), Start, End, FColor::Red, false, 2.f);
// DrawDebugPoint(GetWorld(), FireHit.Location, 2.f, FColor::Red, false, 2.f);
BeamEndPoint = FireHit.Location;
if (impactParticles)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), impactParticles, FireHit.Location);
}
}
if (BeamParticles)
{
UParticleSystemComponent *Beam = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BeamParticles, SocketTransform);
if (Beam)
{
Beam->SetVectorParameter(FName("Target"), BeamEndPoint);
}
}
*/
UAnimInstance *AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && HipFireMontage)
{
UE_LOG(LogTemp, Warning, TEXT("Fire Weapon"));
AnimInstance->Montage_Play(HipFireMontage);
AnimInstance->Montage_JumpToSection(FName("StartFire"));
}
}
bool AShooterCharacter::GetBeamEndLocation(
const FVector &MuzzleSocketLocation,
FVector &OutBeamLocation)
{
// Get Current size of the viewport
FVector2D ViewportSize;
if (GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->GetViewportSize(ViewportSize);
}
// Get Screen space location of crosshirs
FVector2D CrosshairLocation(ViewportSize.X / 2.f, ViewportSize.Y / 2.f);
FVector CrosshairWorldPosition;
FVector CrosshairWorldDirection;
// Get World position and direction of crosshairs
bool bScreenToWorld = UGameplayStatics::DeprojectScreenToWorld(UGameplayStatics::GetPlayerController(this, 0),
CrosshairLocation,
CrosshairWorldPosition,
CrosshairWorldDirection);
if (bScreenToWorld) // was deprojection successful
{
FHitResult ScreenTraceHit;
const FVector Start{CrosshairWorldPosition};
const FVector End{CrosshairWorldPosition + CrosshairWorldDirection * 50'000.f};
// Set beam end point to line trace end point
// FVector BeamEndPoint{End};
OutBeamLocation = End; // 바뀐 부분!
// Trace outward from crosshairs wolrd location
GetWorld()->LineTraceSingleByChannel(ScreenTraceHit, Start, End, ECollisionChannel::ECC_Visibility);
if (ScreenTraceHit.bBlockingHit) // we there a trace hit?
{
// Beam end point is now trace hit Location
OutBeamLocation = ScreenTraceHit.Location; // 바뀐 부분!
// Perform a second trace, this time from the gun barrel
FHitResult WeaponTraceHit;
const FVector WeaponTraceStart{MuzzleSocketLocation};
const FVector WeaponTraceEnd{OutBeamLocation};
GetWorld()->LineTraceSingleByChannel(
WeaponTraceHit,
WeaponTraceStart,
WeaponTraceEnd,
ECollisionChannel::ECC_Visibility);
if (WeaponTraceHit.bBlockingHit) // object between barrel and BeamEndPoint?
{
OutBeamLocation = WeaponTraceHit.Location;
}
}
return true;
}
return false;
}
Movement Offset Yaw
스트레이핑을 구현하기 위해서 이동방향과, 바라보고 있는 방향을 확인합니다.
ShooterAnimInstance.h
// Offset yaw used for strafing
UPROPERTY(EditAnywhere, BluepintReadWrite, Category = Movement, meta = (AllowPrivateAccess = "true"))
float MovementOffsetYaw;
ShooterAnimInstance.cpp
FRotator AimRotation = ShooterCharacter->GetBaseAimRotation();
FString RotationMessage = FString::Printf(TEXT("Base Aim Rotation: %f"), AimRotation.Yaw);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(1, 0.f, FColor::White, RotationMessage);
}
- FRotator AimRotation = ShooterCharacter->GetBaseAimRotation() : 폰의 에임 Rotation을 반환한다.
- FString::Printf() : 포맷 문자열을 사용하여 문자열을 생성하는 데 사용됩니다.
- GEngine : 언리얼 엔진에서 제공되는 전역 엔진 객체(Global Engine Object)입니다.
- AddOnScreenDebugMessage : 콘솔 출력, 로그 메시지, 디버깅 정보를 표시할 수 있습니다.
ShooterAnimInstance.cpp에 추가로 MovementRotation도 만들어준다.
#include "Kismet/KismetMathLibrary.h"
FRotator MovementRotation = UKismetMathLibrary::MakeRotFromX(ShooterCharacter->GetVelocity());
FString MovementRotationMessage = FString::Printf(TEXT("Movement Rotation: %f"), MovementRotation.Yaw);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(1, 0.f, FColor::White, RotationMessage);
GEngine->AddOnScreenDebugMessage(2, 0.f, FColor::White, MovementRotationMessage);
}
- UKismetMathLibrary : 언리얼 엔진에서 제공되는 수학 관련 함수들을 모아놓은 유틸리티 라이브러리입니다.
- MakeRotFromX : 어떤 벡터를 X축으로 하는 회전값을 생성합니다.
컴파일 후 실행해주면, 좌상단에 디버그 값을 확인 할 수 있는데, Movement Rotation은 이동 방향으로 Base Aim Rotation은 Controler의 Yaw값으로 설정됨을 알 수 있다.
우리는 MovementOffsetYaw를 구하기 위해서 AimRotation과 MovementRotation을 구하였다.(얼마나 돌려줘야 하는지 알기 위해서) 따라서 이후 MovementOffsetYaw를 계산한다.
ShooterAnimInstance.cpp 최종 추가 코드
FRotator AimRotation = ShooterCharacter->GetBaseAimRotation();
FRotator MovementRotation = UKismetMathLibrary::MakeRotFromX(ShooterCharacter->GetVelocity());
MovementOffsetYaw = UKismetMathLibrary::NormalizedDeltaRotator(MovementRotation, AimRotation).Yaw;
/*
FString RotationMessage = FString::Printf(TEXT("Base Aim Rotation: %f"), AimRotation.Yaw);
FString MovementRotationMessage = FString::Printf(TEXT("Movement Rotation: %f"), MovementRotation.Yaw);
FString OffsetMessage = FString::Printf(TEXT("Movement Offset Yaw: %f"), MovementOffsetYaw);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(1, 0.f, FColor::White, RotationMessage);
GEngine->AddOnScreenDebugMessage(2, 0.f, FColor::White, MovementRotationMessage);
GEngine->AddOnScreenDebugMessage(3, 0.f, FColor::Red, OffsetMessage);
}
*/
- UKismetMathLibrary::NormalizedDeltaRotator : 두 개의 회전값 FRotator 사이의 차이를 계산하여 정규화된 형태의 FRotator로 반환합니다. 이 함수는 회전 차이를 범위 [-180, 180]도 내로 정규화하여, 회전값이 이 범위를 벗어나지 않게 합니다.
- .Yaw : Rotator에서 Yaw(float)의 값을 저장한다.
Strafing Blendspace
좌우로 이동을 구헌하기 위해서 Blend Space를 만들어 줍니다. (RunningBlendSpace)
이후 Asset Details > Axis Settings 을 아래와 같이 수정해줍니다.
이후 좌하단의 Asset Browser에서 검색을 통해 원하는 에니메이션을 위치에 설정해 줍니다.
저장해주고 ABP_ShooterCharacter에 가서 Run State를 RunningBlendSpace로 바꿔주고 OffsetYaw 변수에 저번에 cpp에서 선언해주었던 MovementOffsetYaw를 getter로 설정해 줍니다.
'Unreal 공부 > Unreal Engine 4 C++ The Ultimate Shooter' 카테고리의 다른 글
Aiming and Crosshairs - 1 (0) | 2023.12.31 |
---|---|
Animation - 5 (1) | 2023.12.31 |
Animation - 3 (1) | 2023.12.30 |
Animation - 2 (1) | 2023.12.28 |
Animation (0) | 2023.12.27 |