무기 발사 기능
InhancedInput을 사용하기 위해 InputAction을 생성해주고 IMC에 등록해 줍니다.
이후에 코드를 고쳐줍니다.
ShooterCharacter.h
protected:
...
void FireWeapon(const FInputActionValue &Value);
private:
...
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction *FireAction;
ShooterCharacter.cpp
void AShooterCharacter::FireWeapon(const FInputActionValue &Value)
{
UE_LOG(LogTemp, Warning, TEXT("Fire Weapon"));
bool isFire = Value.Get<bool>();
}
void AShooterCharacter::SetupPlayerInputComponent(UInputComponent *PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
check(PlayerInputComponent);
if (UEnhancedInputComponent *EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
...
// 단발로 설정하기 위해서 ETriggerEvent::Started 사용
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &AShooterCharacter::FireWeapon);
}
}
컴파일 후 IA_Fire를 BP_ShooterCharacter의 Fire Action에 등록합니다.
Shooting Sound Effect
Assets 폴더와 Sounds, Gunshots 폴더를 만들고 총성 Sound Wave를 import합니다.
이후에 Sound Cue를 만들어줍니다. (AR_Shot)
이후 모든 Sound Wave를 선택하고 드래그를 통해 AR_shot에 넣어줍니다.
Random 노드를 만들어서 Sound Wave를 연결시켜 줍니다.
생성한 SoundCue를 등록하고 실행하기 위한 로직을 ShooterCharacter에 작성해 줍니다.
ShooterCharacter.h
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Combat, meta = (AllowPrivateAccess = "true"))
class USoundCue *FireSound;
ShooterCharacter.cpp
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"
void AShooterCharacter::FireWeapon(const FInputActionValue &Value)
{
// UE_LOG(LogTemp, Warning, TEXT("Fire Weapon"));
bool isFire = Value.Get<bool>();
if (FireSound)
{
UGameplayStatics::PlaySound2D(this, FireSound);
}
}
이후 BP_ShooterCharacter에서 Fire Sound에 AR_Shot을 설정해 줍니다.
Shooting Particle
발사 파티클을 만들 소켓을 만들기 위해서 Belica의 스켈레톤 메시를 열어줍니다.
Weapon을 우클릭하여 BarrelSocket을 만들어주고 위치를 총구의 앞으로 설정해줍니다.
※ Particle 사용시 Rotation도 고려해야 하기 때문에 X축(빨간색)이 전방을 향하도록 설정해야 합니다.
ShooterCharacter를 수정해 줍니다.
ShooterCharacter.h
// Flash spawned at BarrelSocket
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Combat, meta = (AllowPrivateAccess = "true"))
class UParticleSystem *MuzzleFlash;
ShooterCharacter.cpp
#include "Engine/SkeletalMeshSocket.h"
...
void AShooterCharacter::FireWeapon(const FInputActionValue &Value)
{
...
const USkeletalMeshSocket *BarrelSocket = GetMesh()->GetSocketByName("BarrelSocket");
if (BarrelSocket)
{
const FTransform SocketTransform = BarrelSocket->GetSocketTransform(GetMesh());
if (MuzzleFlash)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleFlash, SocketTransform);
}
}
}
- GetSocketByName() : 정적 메시 구성 요소의 명명된 소켓을 반환합니다.
- FTransfrom : 사용되는 구조체로, 3D 공간에서 오브젝트의 위치(Position), 회전(Rotation), 그리고 크기(Scale)를 표현합니다. 이 구조체는 게임 개발에서 오브젝트의 3D 변환을 관리하는 데 널리 사용됩니다.
- GetMesh() : ACharacter 클래스의 인스턴스가 가지고 있는 스켈레탈 메시 컴포넌트(USkeletalMeshComponent)를 반환하는 멤버 함수입니다.
- USkeletalMeshComponent는 USkinnedMeshComponent를 상속 받습니다.
- SkinnedMeshComponent는 스켈레탈 메시나 스태틱 메시 등의 3D 모델을 렌더링하기 위한 기본 클래스입니다.
- USkinnedMeshComponent::GetSocketByName : 소켓을 이름으로 찾아 반환하는 기능을 합니다.
- BarrelSocket->GetSocketTransform(GetMesh()) :
- GetSocketTransform() : 소켓이 속한 메시의 컨텍스트를 기반으로 소켓의 월드 변환을 계산하는 데 필요하며, 이는 소켓의 정확한 위치와 방향을 얻기 위해 중요합니다.
- UGameplayStatics::SpawnEmitterAtLocation() : 소켓의 위치와 방향에서 시각적 효과(총구 화염)를 생성합니다.
이후 P_BelicaMuzzle을 찾아 복사 후 Assets>FX 폴더를 새로만들고 붙여넣은 다음 이름을 수정해 줍니다.
기존의 파티클은 3번 생성되는데 이것은 Spawn > Burst > Burst List 가 3개의 Array elements로 이루어져 있는 것들을 1개로 만들어서 수정이 가능합니다.
이후에 수정한 Single_burst를 BP_Character에 지정해줍니다.
Shooting Animation
Animation Montage를 만들어 줍니다. ( HitFireMontage )
Animation Montage
여러 애니메이션 시퀀스를 하나의 흐름으로 결합하여 복잡한 애니메이션 로직을 구현할 수 있게 해주는 기능입니다.
애니메이션 몽타주로 여러 애니메이션 시퀀스 를 하나의 애셋으로 합칠 수 있으며, 이를 섹션 으로 나누어 그 일부 또는 조합 재생이 가능합니다. 몽타주 안에서 이벤트 를 발동시켜 다양한 로컬 또는 리플리케이트 작업을 할 수 있는데, 예를 들면 사운드 큐나 파티클 이펙트 재생, 탄환 수와 같은 플레이어 값 변경, 심지어 네트워크 게임에서 루트 모션 리플리케이션도, 애니메이션에 루트 모션이 켜진 경우 가능합니다.
Primary_Fire_Fast를 찾아서 복사 해줍니다. (_Trimmed를 붙여서 구분되게!)
이후 Primary_Fire_Fast _Trimmed의 9~19 프레임을 제거해줍니다.
그 다음 HitFireMontage에서 Primary_Fire_Fast_Trimmed를 올려줍니다.
좌측의 Asset Details에서 Blend Option > Blend In > Blend Time = 0 으로 조정해줍니다.
새로운 Montage 그룹을 만들어 줍니다.(우클릭, StartFire)
애니메이션 몽타주 그룹(Animation Montage Group)
애니메이션 몽타주 내에서 여러 애니메이션 시퀀스를 관리하고 조직하는 데 사용되는 기능입니다. 애니메이션 몽타주 그룹은 특정 애니메이션 슬롯에 연결되어, 복수의 애니메이션 시퀀스를 그룹화하고 제어하는 데 도움을 줍니다.
좌하단의 Anim Slot에서 WeaponFire이라는 슬롯을 만들어 줍니다.
애니메이션 슬롯(Animation Slot)
애니메이션 블루프린트 내에서 복수의 애니메이션 시퀀스를 관리하고, 여러 애니메이션 레이어를 조합하는 데 사용됩니다. 이 슬롯을 통해 개발자는 캐릭터의 다양한 신체 부분에 서로 다른 애니메이션을 적용할 수 있습니다.
애니메이션 슬롯의 주요 사용 사례
다중 애니메이션 레이어링: 애니메이션 슬롯은 캐릭터의 상반신과 하반신에 서로 다른 애니메이션을 동시에 적용하는 데 유용합니다. 예를 들어, 캐릭터가 걷는 동안 상반신으로 무언가를 조작하는 별도의 애니메이션을 재생할 수 있습니다.
애니메이션 몽타주와의 연동 : 애니메이션 몽타주를 사용할 때, 특정 슬롯에 애니메이션을 할당하여, 그 슬롯에만 애니메이션 효과가 적용되도록 할 수 있습니다. 이는 애니메이션 몽타주를 더 세밀하게 제어하는 데 도움을 줍니다.
애니메이션 블렌딩 : 서로 다른 애니메이션 슬롯에 할당된 애니메이션들 사이에서 블렌딩을 수행하여, 부드러운 애니메이션 전환을 만들 수 있습니다.
그 다음 기본 그룹을 WeaponFire로 바꿔줍니다.
WeaponFire가 사용되고 있는지 확인해야 하는데, 이는 좌상단의 Blueprint 버튼을 눌러서 확인할 수 있습니다.
New Save Cached poses를 통해서 Cached 노드를 만들고 이름을 Cached Ground Locomotion으로 변경해줍니다.
Use Cached pose ~ 를 통해서 저장된 Cache를 사용할 수 있습니다.
여기에서 추가로 Slot DefaultSlot 노드를 만들어줍니다.
그 다음 Details 패널에서 WeaponFire로 변경해 줍니다. (Montage_Play() 시 ABP가 overriding 됩니다.)
몽타주를 등록하고 실행시키는 것을 CPP로 구현하였다.
ShooterCharacter.h
// Montage for firing the weapon
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Combat, meta = (AllowPrivateAccess = "true"))
class UAnimMontage *HipFireMontage;
ShooterCharacter.cpp
void AShooterCharacter::FireWeapon(const FInputActionValue &Value)
{
...
UAnimInstance *AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && HipFireMontage)
{
AnimInstance->Montage_Play(HipFireMontage);
AnimInstance->Montage_JumpToSection(FName("StartFire"));
}
}
- GetAnimInstance() : Mesh의 AnimInstance를 반환합니다.
- UAnimInstance : 스켈레탈 메시(Skeletal Mesh)에 적용되는 애니메이션의 상태와 동작을 관리하는 클래스입니다.
- 이 클래스는 애니메이션 블루프린트(Animation Blueprint)의 인스턴스로 사용되며, 애니메이션 로직과 변수, 애니메이션 이벤트 등을 처리합니다.
- AnimInstance->Montage_Play(HipFireMontage) : 애니메이션 인스턴스를 통해 HipFireMontage라는 애니메이션 몽타주를 재생합니다.
- AnimInstance->Montage_JumpToSection(FName("StartFire")) : 함수를 사용하여 몽타주 내의 "StartFire"라는 이름의 특정 섹션으로 바로 점프합니다.
늘 그렇듯, 이후 BP_ShooterCharacter에서 HipFireMontage 설정을 해줍니다.
Blending Shooter Animation
우선 아래와 같이 ABP_Shootercharacter를 만들어 줍니다. (주석 단축키는 드래그 + c 입니다.)
- Layered blend per bone : 신체 부위에 서로 다른 애니메이션을 적용할 수 있게 해줍니다. 캐릭터의 특정 뼈(bone)를 기준으로 애니메이션을 레이어링(층을 이루어 결합)할 수 있습니다.
Layered blend per bone을 사용하여 상체는 총을 쏘는 Cached Weapon Fire, 하체는 Ground Locomotion으로 하기 위해서 Belica의 Skeleton Mesh에 들어가서 상체에 해당하는 본을 찾는다. (spine_01)
ABP로 돌아와서 Layered blend per bone의 Details 패널에서 Layer Setup > Index > Branch Filters의 추가 버튼을 눌러주고, Index[0] > Bone Name을 spine_01으로 설정해 준다.
그 다음 주석을 달고 완성해 줍니다.
+실수로 Blend between Ground Locomotion and Weapon Fire 주석을 단 부분의
Use cached pose 'Cached Weapon Fire'과 Use cached pose 'Cached Ground Locomotion'의 위치가 바뀌었으니 아래와 같이 수정해 주세요
LineTracing for Bullet Hits
ShooterCharacter.cpp 에서 LineTrace를 통해서 Bullet Hits를 구현합니다.
마우스 트리거시 BarrelSocket이 존재할때 총구에서 500m까지 LineTrace로 검사하고 FHitResult에 저장합니다.
ShooterCharacter.cpp
void AShooterCharacter::FireWeapon(const FInputActionValue &Value)
{
...
const USkeletalMeshSocket *BarrelSocket = GetMesh()->GetSocketByName("BarrelSocket");
if (BarrelSocket)
{
...
// 새로 작성한 부분
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};
GetWorld()->LineTraceSingleByChannel(FireHit, Start, End, ECollisionChannel::ECC_Visibility);
if (FireHit.bBlockingHit)
{
DrawDebugLine(GetWorld(), Start, End, FColor::Red, false, 2.f);
DrawDebugPoint(GetWorld(), FireHit.Location, 5.f, FColor::Red, false, 2.f);
}
}
...
}
- FHitResult 초기화:
- FHitResult FireHit : 라인 트레이스의 결과를 저장할 FHitResult 구조체를 초기화합니다. 이 구조체는 트레이스가 교차한 오브젝트에 대한 정보를 담습니다.
- 시작점과 끝점 계산:
- const FVector Start{SocketTransform.GetLocation()} : 라인 트레이스의 시작점을 소켓의 위치로 설정합니다.
- const FQuat Rotation{SocketTransform.GetRotation()} : 소켓의 회전을 가져옵니다.
- const FVector RotationAxis{Rotation.GetAxisX()} : 회전 축의 X 방향을 계산합니다.
- const FVector End{Start + RotationAxis * 50'000.f} : 라인 트레이스의 끝점을 계산합니다. 여기서 50,000 단위만큼 소켓의 X 방향으로 뻗어나가는 선을 그립니다.
- 라인 트레이스 실행:
- GetWorld()->LineTraceSingleByChannel(FireHit, Start, End, ECollisionChannel::ECC_Visibility) : 라인 트레이스를 실행하여, 시작점에서 끝점까지 시각적으로 보이는 첫 번째 오브젝트를 탐지합니다.
- 트레이스 결과 처리:
- if (FireHit.bBlockingHit) : 라인 트레이스가 어떤 오브젝트와 충돌했는지 확인합니다.
- DrawDebugLine : 시작점에서 끝점까지 빨간색 선을 그려서 트레이스 경로를 시각적으로 표시합니다.
- DrawDebugPoint : 트레이스가 교차한 위치에 빨간색 점을 그려서 충돌 지점을 표시합니다.
'Unreal 공부 > Unreal Engine 4 C++ The Ultimate Shooter' 카테고리의 다른 글
Animation - 5 (1) | 2023.12.31 |
---|---|
Animation - 4 (1) | 2023.12.31 |
Animation - 3 (1) | 2023.12.30 |
Animation (0) | 2023.12.27 |
[UE] C++에서 SpringArm과 Camera 구현하기 (1) | 2023.12.22 |