리타겟팅
총알 발사 로직


언리얼 에디터에서 Auto Create Retarget Chains 버튼을 누르면 자동으로 Retarget Chain을 생성해줌
그리고 옆에 Auto Create IK버튼을 누르면 Full Body IK도 자동으로 생성
다른 스켈레톤도 마찬가지로 해주기

하단 우측을 보면 Chain들이 있는데 이 Chain구조를 두 스켈레톤이 동일하도록 만들어주면 됨
Chain Name을 동일하게 가지도록 해주고, 각 뼈의 계층 구조를 확인하며 Chain에 각 뼈들을 넣어준다
해당 Chain에 속하는 뼈들은 Start Bone부터 End Bone까지가 하나의 Chain으로 포함된다
UEFN(애니메이션 스켈레톤)의 경우 Trooper(스켈레탈메시의 스켈레톤)와 다르게 팔-metacarpal-손가락 형식의 Chain을 가졌고, Trooper는 팔-손가락 형식의 Chain 구조를 가졌었음
따라서, UEFN의 metacarpal 체인을 제거하고 해당 뼈 구조를 팔 체인의 EndBone으로 설정해주어 팔 체인에 속하게 해줌

그리고 IK_ReTargeter를 이용해 방금 만든 두 개의 IK_Rig파일을 서로 연동해줌
우측 하단에서 볼 수 있듯이 Source와 Target의 체인이 서로 연결된 것을 확인할 수 있음
우측 상단의 Detail 패널에서는 위치 조정, 크기 조정 등 여러 옵션을 선택 가능
Asset Browser에서 애니메이션들을 확인해보며 잘 되었는지 확인 가능
Export버튼을 이용하여 리타겟팅이 적용된 애니메이션을 저장할 수 있다
대부분의 애니메이션은 잘 적용이 되었지만, 위의 영상에서도 나온 Land 애니메이션에 문제가 발생
리타겟터에서는 잘 작동하였지만, 막상 Export하여 저장 후 애니메이션 시퀀스를 보면 스켈레톤이 망가진 것을 확인하였음

새로 만든 애니메이션 시퀀스들로 애니메이션 블루프린트를 만들어 캐릭터에 적용하였음
테스트 결과, 캐릭터가 지상에서 걷다가 떨어지면, 바닥에 착지해도 착지 애니메이션이 발생하지 않고 계속 Falling 애니메이션이 나타나는 것을 발견
따라서 먼저 Falling State에서 Land State로 전환되는 조건인 Is Falling값이 잘 바뀌는지 확인하였음

Event Graph에서 Tick마다 Is Falling값을 확인해 본 결과, 착륙 시 false로 잘 바뀌었음. 즉, 착륙했다는 상태변환은 잘 되었음
Fall Loop에서 Land로 가는 Transition도 문제가 없으므로, 각 State에 문제가 있지 않을까 생각하여 확인해본 결과 To Land라는 State Alias 설정에 문제가 있었음
해당 Alias에 Fall Loop State가 체크되어 있지 않아, Transition 전환이 되지 않았던 것
설정해주어 문제를 해결하였음
날아가는 물체이므로 ProjectileMovementComponent를 이용하여 구현
적과 충돌 시 데미지 가할 것이므로 OnHit 이용
// Bullet.h
UCLASS()
class INTOTHEPARADOX_API ABullet : public AActor
{
// ... //
public:
ABullet();
virtual void FireInDirection(FVector Direction);
protected:
virtual void BeginPlay() override;
USceneComponent* SceneComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "SkeletalMesh")
UStaticMeshComponent* StaticMeshComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Collision")
UCapsuleComponent* CapsuleComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Movement")
UProjectileMovementComponent* ProjectileMovementComponent;
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, FVector NormalImpulse,
const FHitResult& Hit);
};
ABullet::ABullet() :
BulletSpeed(3000.f),
BulletDamage(20)
{
// ... //
CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("Capsule"));
CapsuleComponent->SetCollisionProfileName(TEXT("BlockAll"));
CapsuleComponent->SetupAttachment(SceneComponent);
CapsuleComponent->OnComponentHit.AddDynamic(this, &ABullet::OnHit);
ProjectileMovementComponent =
CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("PMovement"));
ProjectileMovementComponent->SetUpdatedComponent(SceneComponent);
ProjectileMovementComponent->InitialSpeed = BulletSpeed;
ProjectileMovementComponent->MaxSpeed = BulletSpeed;
ProjectileMovementComponent->bRotationFollowsVelocity = true;
ProjectileMovementComponent->ProjectileGravityScale = 0.f;
InitialLifeSpan = 3.0f;
}
Collision을 위해 Capsule Component 사용하였음.
모든 물체와 Hit 이벤트를 발생하므로 BlockAll을 사용
Projectile Movement Component
SetUpdatedComponent() : 어느 컴포넌트를 이동시킬지 설정하는 함수
bRotationFollowsVelocity : 발사체가 이동하는 방향(속도 벡터 방향)에 따라 발사체의 회전 방향을 자동으로 맞추는 기능
ProjectileGravityScale : 발사체가 중력의 영향을 받는 정도(0 ~ 1)
Actor클래스의 InitialLifeSpan을 통해 3초 뒤 소멸하도록 설정
void ABullet::FireInDirection(FVector Direction)
{
Direction.Normalize();
ProjectileMovementComponent->Velocity = Direction * BulletSpeed;
}
void ABullet::OnHit(UPrimitiveComponent* HitComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
FVector NormalImpulse,
const FHitResult& Hit)
{
if (AITPAICharacter* AI = Cast<AITPAICharacter>(OtherActor))
{
UGameplayStatics::ApplyDamage(AI,
BulletDamage,
nullptr,
this,
UDamageType::StaticClass());
}
Destroy();
}
FireInDirection() : 캐릭터에서 총알 spawn하고 호출하는 함수.
속도값을 초기화해주어 총알이 원하는 방향으로 날아가도록 함
OnHit() : 부딪힌 상대가 적일때만 데미지 발생시킴
공격 로직을 구현하기 위해, 마우스 좌클릭 시 총알이 발사되도록 구현하였음
Input 처리는 이전의 Enhanced Input System을 이용한 다른 Input Action과 동일하게 생성함
다만, 마우스 좌클릭 유지 시 Auto처럼 총알이 자동으로 연발되게 구현
// MyCharacter.cpp
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* EnhancedInput =
Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (EnhancedInput) {
if (AMyPlayerController* PlayerController =
Cast<AMyPlayerController>(GetController())) {
if (PlayerController->FireAction) {
EnhancedInput->BindAction(PlayerController->FireAction,
ETriggerEvent::Started, this,
&AMyCharacter::StartFire);
EnhancedInput->BindAction(PlayerController->FireAction,
ETriggerEvent::Completed,
this, &AMyCharacter::StopFire);
}
}
}
}
StartFire()를 호출할 것이므로, Started로 연결좌클릭시, FireRate(발사주기)마다 총알이 자동으로 나가야 함
좌클릭을 떼면, 총알이 나가지 않아야 함
하지만 좌클릭 해제했다가 바로 좌클릭했을 때, 아직 발사주기가 되지 않았다면 발사주기가 될때까지 기다렸다가 총알이 나가야 함
void AMyCharacter::StartFire(const FInputActionValue& Value)
{
bShouldFire = true;
if (!(GetWorldTimerManager().IsTimerActive(FireHandle)))
{
Fire();
GetWorld()->GetTimerManager().SetTimer(
FireHandle,
this,
&AMyCharacter::Fire,
ITPPlayerState->GetFireRate(),
true
);
}
}
void AMyCharacter::StopFire(const FInputActionValue& Value)
{
bShouldFire = false;
}
void AMyCharacter::Fire()
{
if (!bShouldFire)
{
GetWorldTimerManager().ClearTimer(FireHandle);
return;
}
if (!BulletClass)
return;
// 컨트롤러 회전 가져오기
FRotator SpawnRotation = GetControlRotation();
// 카메라 forward vector (바라보는 방향)
FVector CameraForwardVector = SpawnRotation.Vector();
// 총구 위치 설정 (캐릭터 위치 + 전방 오프셋)
FVector SpawnLocation =
GetActorLocation() + SpawnRotation.RotateVector(BulletOffset);
ABullet* Bullet = GetWorld()->SpawnActor<ABullet>(
BulletClass,
SpawnLocation,
SpawnRotation
);
// 발사체에 속도 설정 (포워드 벡터 방향)
if (Bullet)
{
Bullet->FireInDirection(CameraForwardVector);
}
}
좌클릭 시 주기적으로 발사 로직이 실행되며, 좌클릭 해제하여 bShouldFire가 거짓이라면 Fire함수에서 타이머를 초기화하여 멈춤
1번 조건은 반복실행되는 FireHandle타이머로 해결
2번 조건은 bShouldFire 변수를 통해, 총알을 발사할 것인지 아닌지를 정함
3번 조건은 이미 돌아가고 있는 타이머가 있으면 자동으로 Fire함수가 실행되므로 해결