Deeper Learning

[UE5] 애니메이션 개선, FABRIK, 격발 기능 추가 (프로젝트ASR - 개발일지1) 본문

Game Development/Devlog

[UE5] 애니메이션 개선, FABRIK, 격발 기능 추가 (프로젝트ASR - 개발일지1)

Dlaiml 2024. 8. 3. 10:35

기획 변경

 

[UE5] Rifle 픽업, 착용 기능 구현, Replication 문제 해결 (TPS - 개발일지3)

ProjectVT가 크라우드 펀딩을 앞두게 되어 개발할 것들이 크게 늘어 TPS 개발에 시간을 이전보다 많이 쓰지 못하였다. 저번 포스팅 이후 진척도를 공유하려 한다.   [UE5] 애니메이션 추가, Character

dlaiml.tistory.com

 

저번 포스팅 이후 많은 게임을 찾아보고 기획에 변경이 생겼다. 멀티플레이 기반의 TPS에서 싱글 플레이 액션 게임으로 프로젝트의 코어 컨셉을 변경하였다. 현실적인 개발 환경과 예산 제약이 가장 큰 이유이다. 현재 개발 장비로 테스트 또한 쉽지 않고 멀티플레이 슈팅게임의 가장 큰 재미요소인 유저를 초기에 적은 마케팅 비용으로 모으는 것은 현실적으로 쉽지 않기 때문이다.

 

기획이 변경됨에 따라 기존 구현 스펙에 있던 다양한 무기군, 원할한 멀티플레이를 위한 서버 관련 코드 작성, 유저 간 소통을 위한 보이스, 핑, 채팅 시스템이 제거 되었고 대신 여러 컨셉의 캐릭터의 기초, 전투 애니메이션 제작을 우선하려 한다.

 

개발중이었던 메인 캐릭터 클래스는 대신 하나의 무기군(총)을 사용하는 캐릭터로 변경하였다.

 

다른 개발일지와 구분하기 위해 프로젝트명을 임의로 ASR로 정하였다.

 

  • 애니메이션 추가
  • Locomotion 개선
  • 조준, 사격 애니메이션 및 기능 추가

 

애니메이션 추가

 

이번에도 애니메이션에 가장 시간을 많이 사용하였다. 로코모션 개선, 조준/사격 애니메이션 추가, 격발 기능 구현 및 투사체(총알) 발사 기능을 추가하였다.



 

 

 

퍼스트 디센던트, 배틀그라운드, 명조, 몬스터 헌터 월드에서 원거리 무기 장착, 해제, 조준 상태에서의 카메라 시점, 캐릭터 Orientation을 조사해보았다.

이들을 참고하여 물론 옵션에 따라 다르지만 기본 설정은 우선 조준 시에는 조준점 중심으로 캐릭터 Rotation 고정, 무기를 착용하지 않고 있을 때에는 Velocity에 따라 캐릭터의 Yaw값이 조정되어 항상 앞을 향하도록 하기로 결정하였다. 

 

 

 

Locomotion 개선

무기를 장착하지 않고 있는 상태에서 Blend Space로 실행하던 Idle ~ Jog 애니메이션을 제거하고 앞 방향으로 걷고 달리는 애니메이션만 사용하기로 결정하였다.

 

GetCharacterMovement()->bOrientRotationToMovement = true;

 

회전 속도가 너무 느려 방향전환이 부자연스러웠는데 Character Movement Component의 Yaw Rotation Rate 값을 조절하여 이를 해결하였다.

 

총기를 장착한 상태에서의 이동 애니메이션은 모두 Blend Space를 사용하였다 (걷기&뛰기 / 웅크린 상태에서 걷기 / 조준하며 걷기 / 웅크리고 조준하며 걷기)

 

 

State Machine은 가장 간단하면서도 자연스러웠던 Animation Starter Pack의 구조를 차용하였다. 

 

리타겟팅 Source 캐릭터가 사용한 총기와 현재 import하여 사용하고 있는 총기가 다르다 보니까 총기의 Rotation, Location을 약간씩 수정하여 만든 9방향의 애니메이션 시퀀스로 AimOffsets을 만들었는데 Blend 하면서 손이 총에서 떨어지는 문제가 있었다.

 

 

이전에도 겪었던 문제라서 hand_l의 bone이 총기의 원하는 부분에 딱 맞도록 세팅한 Socket으로의 Transform 수치를 구하고 이를 FABRIK의 Effector Transform으로 사용하였다.

 

// Get Hand Position Matching Transform for FABRIK
if (bIsWeaponEquipped && EquippedWeapon != nullptr &&
    EquippedWeapon->GetWeaponMesh() != nullptr && WarComposerCharacter->GetMesh() != nullptr)
{
    LeftHandMatchingTransform = EquippedWeapon->GetWeaponMesh()->GetSocketTransform(FName("LeftHandSocket"), ERelativeTransformSpace::RTS_World);
    FVector OutVector;
    FRotator OutRotator;
    WarComposerCharacter->GetMesh()->TransformToBoneSpace(FName("fhand_r"), LeftHandMatchingTransform.GetLocation(),
        FRotator::ZeroRotator, OutVector, OutRotator);
    LeftHandMatchingTransform.SetLocation(OutVector);
    LeftHandMatchingTransform.SetRotation(FQuat(OutRotator));
}

 

 

Solver의 경우 Root Bone을 팔로 제한하다보니 우측 하단이나 우측 상단을 조준하는 경우 왼팔을 최대로 뻗어도 총기에 손이 닿지 않아 대신 어깨를 Root Bone으로 설정하였더니 자연스러운 애니메이션이 연출되었다.

 

 

 

AnimGraph을 보면 무기 장착 상태에서는 AimOffset + FABRIK이 적용된 상반신의 포즈와 걷기, 뛰기 포즈인 하반신 포즈를 spine_01을 Branch Filter로 하고 Layered blend per bone을 사용하여 포즈를 블렌딩 한 것을 볼 수 있다.

 

 

 

아래 Lyra의 애니메이션 시스템을 참고하였다.

 

 

이후 발이 닿는 타이밍에 Anim Notify를 추가하고 걷기, 뛰기에 따라 볼륨을 조절하는 방식으로 발소리 Sound Cue를 재생하도록 하였다.

아래처럼  Wireframe + 시점교체를 하면 훨씬 쉽게 발이 지면에 닿는 타이밍을 알 수 있다.

 

 

조준, 사격 애니메이션 및 기능 추가

 

 

애니메이션 스타터팩에 있는 Fire 애니메이션 시퀀스들을 배치한 Animation Montage를 만들었다. 조준을 하고 있는지 여부에 따라 다른 애니메이션이 재생될 수 있도록 하였다.

 

void AWarComposerCharacter::PlayFireAnimMontage(bool bAiming)
{
	FName SectionName = bAiming ? FName("RifleIronsights") : FName("RifleHip");
	if (BattleComponent != nullptr && BattleComponent->EquippedWeapon != nullptr)
	{
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
		if (AnimInstance != nullptr && FireAnimMontage != nullptr)
		{
			AnimInstance->Montage_Play(FireAnimMontage);
			AnimInstance->Montage_JumpToSection(SectionName);
		}
	}
	return;
}

 

총기와 상반신이 살짝 흔들리는 간단한 애니메이션이기 때문에 AimOffset을 사격 애니메이션에 Blend 하였다.

 

사격의 경우 총기 클래스에 Spawn할 투사체 클래스를 지정하고 이를 Spawn 하는 방식으로 구현하였다. 투사체 클래스인 Projectile은 Projectile Movement Component를 가지고 있으며 Rotation을 설정하고 속도를 가하는 방식인 물리 기반의 탄환이다. 

 

// ProjectileGun.cpp
//...
const USkeletalMeshSocket* ProjectileSpawnSocket = GetWeaponMesh()->GetSocketByName(FName("MuzzleFlash"));
APawn* FireInstigator = Cast<APawn>(GetOwner());
if (ProjectileSpawnSocket != nullptr && FireInstigator != nullptr)
{
    FTransform SocketTransform = ProjectileSpawnSocket->GetSocketTransform(GetWeaponMesh());
    FVector ToDestVector = Destination - SocketTransform.GetLocation();
    FRotator DestRotation = ToDestVector.Rotation();
    if (ProjectileClass != nullptr)
    {
        FActorSpawnParameters ActorSpawnParameters;
        ActorSpawnParameters.Owner = GetOwner();
        ActorSpawnParameters.Instigator = FireInstigator;
        UWorld* World = GetWorld();
        if (World != nullptr)
        {
            World->SpawnActor<AProjectile>(
                ProjectileClass, SocketTransform.GetLocation(),
                DestRotation, ActorSpawnParameters
            );
        }
    }
}
//...

 

Initial Speed와 Max Speed는 실제 총기들의 총구에서의 속도인 750 m/s로 설정하였다.

 

총구에서 Viewport의 중앙을 향하는 벡터를 구하고 해당 방향으로 총기의 유효사거리 만큼 LineTrace를 수행하는 방식을 사용하였다. 

 

void UWarComposerBattleComponent::TraceCrosshairs(FHitResult& HitResult)
{
	FVector2D ViewportSize;
	FVector2D CrosshairScreenPosition;
	FVector CrosshairWorldPosition;
	FVector CrosshairWorldDirection;

	if (GEngine != nullptr && GEngine->GameViewport)
	{
		GEngine->GameViewport->GetViewportSize(ViewportSize);
	}
	CrosshairScreenPosition.X = ViewportSize.X / 2.f;
	CrosshairScreenPosition.Y = ViewportSize.Y / 2.f;

	bool bSuccess = UGameplayStatics::DeprojectScreenToWorld(
		UGameplayStatics::GetPlayerController(this, 0),
		CrosshairScreenPosition, CrosshairWorldPosition, CrosshairWorldDirection
	);

	if (bSuccess)
	{
		FVector TraceStartPosition = CrosshairWorldPosition;
		FVector TraceEndPosition = TraceStartPosition + CrosshairWorldDirection * 50000.f;
		GetWorld()->LineTraceSingleByChannel(
			HitResult, TraceStartPosition, TraceEndPosition, ECollisionChannel::ECC_Visibility);
	}
}

 

 

Projectile은 생성과 동시에 궤적 Particle을 Spawn하고 BoxCollider의 OnComponentHit에 바인딩한 함수에서는 충돌 액터를 판단하고 로직을 처리한 뒤, Destory 된다. 이때 Destroyed를 Override 하여 총알이 적중하였을 때 나는 소리, 파티클을 Spawn 하였다.

 

Reference

[0] https://dev.epicgames.com/documentation/ko-kr/unreal-engine/animation-in-lyra-sample-game-in-unreal-engine

[1] https://dlaiml.tistory.com/entry/UE5-%EB%AC%BC%EC%B2%B4-%EC%98%AE%EA%B8%B0%EA%B8%B0-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-Grabber

 

[UE5] 물체 옮기기 기능 구현 (Grabber)

어렸을 때 즐겨하던 게임인 샌드박스 장르의 게임인 게리모드에는 물체를 잡고 돌릴 수 있는 physics gun이라는 장비가 있었다. physics gun의 기능 중에서 물체를 클릭하여 잡고 옮기는 기능 언리얼

dlaiml.tistory.com

 

[2] https://dev.epicgames.com/documentation/en-us/unreal-engine/using-a-single-line-trace-raycast-by-channel?application_version=4.27

 

[3] Udemy Unreal Engine C++ Multiplayer Shooter

Comments