구현 내용
- 런타임 도중 움직임에 따른 Crosshair 변경
- Aim의 LineTrace - HitResult.GetActor가 플레이어이고 인터페이스를 갖고있다면 Crosshair 색상 변경
BlasterCharacter가 Interface를 상속받는 클래스로 수정 - Automatic Fire 구현
Crosshair HUD관련 코드
TraceUnderCrosshairs함수
뷰포트의 크기를 얻어와 화면 중앙의 위치를 얻어낸 뒤 3D공간의 월드 좌표와 방향을 얻은 뒤 해당 방향으로 LineTrace를 쏴 충돌된 Actor가 있고 해당 액터가 인터페이스를 갖고있다면 Crosshair의 색상을 빨간색으로 표시
플레이어 액터만 인터페이스를 상속받고 있기때문에 아래와 같은 코드 구성이 가능한것
void UCombatComponent::TraceUnderCrosshairs(FHitResult& TraceHitResult)
{
FVector2D ViewportSize;
if (GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->GetViewportSize(ViewportSize);
}
FVector2D CrosshairLocation(ViewportSize.X / 2.f, ViewportSize.Y / 2.f);
FVector CrosshairWorldPosition;
FVector CrosshairWorldDirection;
// 화면공간의 2D좌표 CrosshairLocation을 3D 월드 공간의 점 위치와 방향으로 변환
bool bScreenToWorld = UGameplayStatics::DeprojectScreenToWorld(
UGameplayStatics::GetPlayerController(this, 0),
CrosshairLocation,
CrosshairWorldPosition,
CrosshairWorldDirection
);
if (bScreenToWorld)
{
FVector Start = CrosshairWorldPosition;
if (Character)
{
float DistanceToCharacter = (Character->GetActorLocation() - Start).Size();
Start += CrosshairWorldDirection * (DistanceToCharacter + 100.f);
//DrawDebugSphere(GetWorld(), Start, 16.f, 12, FColor::Red, false);
}
FVector End = Start + CrosshairWorldDirection * TRACE_LENGTH;
GetWorld()->LineTraceSingleByChannel(TraceHitResult, Start, End,
ECollisionChannel::ECC_Visibility);
// Character가 Interface를 상속받은 상태기 때문에
// TraceHit된 객체가 Character라면 Crosshair를 빨간색으로 변경
if (TraceHitResult.GetActor() && TraceHitResult.GetActor()->Implements<UInteractWithCrosshairsInterface>())
{
HUDPackage.CrosshairsColor = FLinearColor::Red;
}
else
{
// Trace가 벽에 충돌했다면 White로 변경
HUDPackage.CrosshairsColor = FLinearColor::White;
}
// 트레이스 끝이 충돌되지 않았을때는 강제로 끝 충돌위치를 End로 지정
if (!TraceHitResult.bBlockingHit)
{
TraceHitResult.ImpactPoint = End;
}
}
}
SetHUDCrosshairs함수
장착하고있는 무기별 Crosshair의 텍스처, Crosshair의 움직임 정도를 달리하도록 구성 후 HUD에 전달 저장하는 함수
void UCombatComponent::SetHUDCrosshairs(float DeltaTime)
{
if (Character == nullptr || Character->Controller == nullptr)
return;
// 컨트롤러 지정
Controller = Controller == nullptr ? Cast<ABlasterPlayerController>(Character->Controller) : Controller;
// HUD 세팅
if (Controller)
{
HUD = HUD == nullptr ? Cast<ABlasterHUD>(Controller->GetHUD()) : HUD;
if (HUD)
{
// 장착된 무기가 있다면 HUD지정하고 장착된 무기에 따른 크로스 헤어 지정
if (EquippedWeapon)
{
HUDPackage.CrosshairCenter = EquippedWeapon->CrosshairsCenter;
HUDPackage.CrosshairLeft = EquippedWeapon->CrosshairsLeft;
HUDPackage.CrosshairRight = EquippedWeapon->CrosshairsRight;
HUDPackage.CrosshairTop = EquippedWeapon->CrosshairsTop;
HUDPackage.CrosshairBottom = EquippedWeapon->CrosshairsBottom;
}
else
{
// 장착된 무기가 없다면 HUD의 크로스헤어 텍스처 nullptr
HUDPackage.CrosshairCenter = nullptr;
HUDPackage.CrosshairLeft = nullptr;
HUDPackage.CrosshairRight = nullptr;
HUDPackage.CrosshairTop = nullptr;
HUDPackage.CrosshairBottom = nullptr;
}
// 크로스헤어 십자선 확장 계산
// 플레이어 최대속도
FVector2D WalkSpeedRange(0.f, Character->GetCharacterMovement()->MaxWalkSpeed);
// 플레이어 맵핑 범위
FVector2D VelocityMultiplierRange(0.f, 1.f);
// 플레이어 현재 속도 : z는 0
FVector Velocity = Character->GetVelocity();
Velocity.Z = 0.f;
// 플레이어 현재의 범위 맵핑된 값을 구함
// 0 ~ 1의 값이 나오게됨
CrosshairVelocityFactor = FMath::GetMappedRangeValueClamped(WalkSpeedRange, VelocityMultiplierRange, Velocity.Size());
if (Character->GetCharacterMovement()->IsFalling())
{
CrosshairInAirFactor = FMath::FInterpTo(CrosshairInAirFactor, 2.25f, DeltaTime, 2.25f);
}
else
{
CrosshairInAirFactor = FMath::FInterpTo(CrosshairInAirFactor, 0.f, DeltaTime, 30.f);
}
if (bAiming)
{
CrosshairAimFactor = FMath::FInterpTo(CrosshairAimFactor, 0.58f, DeltaTime, 30.f);
}
else
{
CrosshairAimFactor = FMath::FInterpTo(CrosshairAimFactor, 0.f, DeltaTime, 30.f);
}
// 총을 발사할때 CrosshairShootingFactor를 0.2f로 지정해놓으면 알아서 여기서 0으로 보간되며 줄어듬
CrosshairShootingFactor = FMath::FInterpTo(CrosshairShootingFactor, 0.f, DeltaTime, 30.f);
HUDPackage.CrosshairSpread = 0.5f + CrosshairVelocityFactor +
CrosshairInAirFactor -
CrosshairAimFactor +
CrosshairShootingFactor;
HUD->SetHUDPackage(HUDPackage);
}
}
}
UCombatComponent::TickComponent()함수
최종적으로 위에서 구현한 내용들은 매 프레임마다 변경되는 부분이기 때문에 Tick에서 관리해준다.
다만 위 함수들은 로컬 플레이어만 해당되는 부분이기 떄문에 IsLocallyControlled()가 트루일때만 적용
로컬 컨트롤러란 각PC에서 직접 플레이중인 컨트롤러라는 의미이다.
void UCombatComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (Character && Character->IsLocallyControlled())
{
FHitResult HitResult;
TraceUnderCrosshairs(HitResult);
HitTarget = HitResult.ImpactPoint;
SetHUDCrosshairs(DeltaTime);
InterpFOV(DeltaTime);
}
}
DrawHUD
최종적인 데이터를 CrawCrosshair에 넘겨주는 역할
DrawCrosshair
최종적인 데이터를 갖고 그리는 함수
void ABlasterHUD::DrawHUD()
{
Super::DrawHUD();
FVector2D ViewportSize;
if (GEngine)
{
GEngine->GameViewport->GetViewportSize(ViewportSize);
// 정 중앙 위치
const FVector2D ViewportCenter(ViewportSize.X / 2.f, ViewportSize.Y / 2.f);
float SpreadScaled = CrosshairSpreadMax * HUDPackage.CrosshairSpread;
if (HUDPackage.CrosshairCenter)
{
// 센터는 중앙 고정이다 Spread에 영향 x
FVector2D Spread(0.f, 0.f);
DrawCrosshair(HUDPackage.CrosshairCenter, ViewportCenter, Spread, HUDPackage.CrosshairsColor);
}
if (HUDPackage.CrosshairLeft)
{
// 왼쪽은 위, 아래에 영향 받지 않는다.
FVector2D Spread(-SpreadScaled, 0.f);
DrawCrosshair(HUDPackage.CrosshairLeft, ViewportCenter, Spread, HUDPackage.CrosshairsColor);
}
if (HUDPackage.CrosshairRight)
{
// 오른쪽은 위, 아래에 영향 받지 않는다.
FVector2D Spread(SpreadScaled, 0.f);
DrawCrosshair(HUDPackage.CrosshairRight, ViewportCenter, Spread, HUDPackage.CrosshairsColor);
}
if (HUDPackage.CrosshairTop)
{
// 위는 좌, 우에 영향 받지 않는다.
// UV좌표기때문에 위아래 바꿔야됨 위가 -
FVector2D Spread(0.f, -SpreadScaled);
DrawCrosshair(HUDPackage.CrosshairTop, ViewportCenter, Spread, HUDPackage.CrosshairsColor);
}
if (HUDPackage.CrosshairBottom)
{
// 아래쪽은 좌, 우에 영향 받지 않는다.
FVector2D Spread(0.f, SpreadScaled);
DrawCrosshair(HUDPackage.CrosshairBottom, ViewportCenter, Spread, HUDPackage.CrosshairsColor);
}
}
}
void ABlasterHUD::DrawCrosshair(UTexture2D* Texture, FVector2D ViewportCenter, FVector2D Spread, FLinearColor CrosshairColor)
{
const float TextureWidth = Texture->GetSizeX();
const float TextureHeight = Texture->GetSizeY();
// 텍스쳐를 화면의 정 중앙에 그리기 위해서 ViewportCent에 텍스처 크기의 1/2만큼 -해준다
const FVector2D TextureDrawPoint(
ViewportCenter.X - (TextureWidth / 2.f) + Spread.X,
ViewportCenter.Y - (TextureHeight / 2.f) + Spread.Y
);
DrawTexture(Texture, TextureDrawPoint.X, TextureDrawPoint.Y,
TextureWidth, TextureHeight, 0.f, 0.f, 1.f, 1.f, CrosshairColor);
}
Automatic Fire관련 코드
기존 반자동라이플의 경우 발사하면 총알 한 번 발사하고 끝이였는데 타이머를 이용하여 발사가 끝난 직후 타이머를 실행시켜 해당 무기가 자동화기 무기라면 타이머의 딜레이 이후 다시 발사하도록 구현
void UCombatComponent::FireButtonPressed(bool bPressed)
{
bFireButtonPressed = bPressed;
if (bFireButtonPressed)
{
Fire();
}
}
void UCombatComponent::Fire()
{
if (bCanFire)
{
bCanFire = false;
ServerFire(HitTarget);
if (EquippedWeapon)
{
CrosshairShootingFactor = 0.75f;
}
// 총을 발사한 뒤 FireTimer를 실행한다.
// 그래야 총을 쏜 뒤의 딜레이 만큼 실행
StartFireTimer();
}
}
void UCombatComponent::StartFireTimer()
{
if (EquippedWeapon == nullptr || Character == nullptr)
return;
Character->GetWorldTimerManager().SetTimer(
FireTimer,
this,
&UCombatComponent::FireTimerFinished,
EquippedWeapon->FireDelay
);
}
void UCombatComponent::FireTimerFinished()
{
if (EquippedWeapon == nullptr || Character == nullptr)
return;
bCanFire = true;
// 자동화기 무기여야지만 가능
if (bFireButtonPressed && EquippedWeapon->bAutomatic)
{
Fire();
}
}
영상
'언리얼5' 카테고리의 다른 글
[UE5 Multiplayer Shooting-13] Health 및 PlayerState 추가Score, Defeasts 관련 (2) | 2023.08.14 |
---|---|
[UE5 Multiplayer Shooting-12] UE5 Framework 구조 (6) | 2023.08.11 |
[UE5 Multiplayer Shooting-10] 반자동 라이플 발사, Crosshair 일부 (2) | 2023.08.08 |
[UE5 Multiplayer Shooting-9] FABRIK IK를 활용한 무기 장착, AimOffset, FootStepSound 구현 (0) | 2023.08.06 |
[UE5 Multiplayer Shooting-8] 무기 HUD 표시 및 장착 | RPC (11) | 2023.08.02 |
댓글