언리얼5

[UE5 Multiplayer Shooting-11] Automatic Fire, CorsshiarHUD, Zoom 완성

TIN9 2023. 8. 11.
반응형

구현 내용

  • 런타임 도중 움직임에 따른 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();
	}
}

 

영상

https://youtu.be/25Y83eHn5D0

 

반응형

댓글