Character ํด๋์ค๋ฅผ ํ์ฅํด์ ํ๋ ์ด์ด์ ์ ๋ ฅ์ ๋ฐ์ํ๋ ์บ๋ฆญํฐ๋ฅผ ๋ง๋ค์ด ๋ณผ ๊ฒ์ด๋ค.
1. Character ํด๋์ค
Character ํด๋์ค๋ฅผ ์์๋ฐ๋ C++ ํด๋์ค๋ฅผ ์์ฑํ๋ค. Character ํด๋์ค๋ Pawn ํด๋์ค์ ์์์ผ๋ก ์ด์กฑ ๋ณดํ๊ณผ ๊ฐ์ ์๋ฐํ์ ์ฌ์ฉ๋๋ค.
์ด์ ์ด ์บ๋ฆญํฐ ํด๋์ค์ 3์ธ์นญ ์นด๋ฉ๋ผ๋ฅผ ๋ถ์ผ ๊ฒ์ด๋ค.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
USpringArmComponent* CameraBoom;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
UCameraComponent* FollowCamera;
ASurvivorCharacter::ASurvivorCharacter()
{
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 150.0f;
CameraBoom->bUsePawnControlRotation = true;
CameraBoom->bDoCollisionTest = false;
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
}
- CreateDefaultSubobject: ํด๋์ค๋ฅผ ์ธ์คํด์คํ์ํค๋ ํจ์์ด๋ค. ์์ฑ์ ๋ด๋ถ์์ ์ฌ์ฉ๋๋ฉฐ ํ๋ผ๋ฏธํฐ๋ก ์ด๋ฆ์ ๋๊ฒจ์ค๋ค.
๊ทธ ํ ์์ฑํ Character ํด๋์ค๋ฅผ ๋ฒ ์ด์ค๋ก ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ฅผ ์์ฑํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ฅผ ์ค์ ์์ Default Pawn Class๋ก ์ ์ฉํ๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ ๋ฒจ ์์ ์ ์์ฑํ ์บ๋ฆญํฐ๋ก ํ๋ ์ด๊ฐ ๊ฐ๋ฅํ๊ฒ ๋๋ค.
2. FBX ์ํฌํธ
์บ๋ฆญํฐ๋ก ์ฌ์ฉ๋ ๋ฉ์๋ฅผ ์ํฌํธ ํ ๊ฒ์ด๋ค. ์ฝํ ์ธ ๋ธ๋ผ์ฐ์ ๋ด ์ํฌํธ ๋ฒํผ์ ํตํด FBX๋ฅผ ์ํฌํธ ํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ ์ํฌํธ ์ต์ ์ฐฝ์ด ๋จ๊ฒ ๋๋ค.
- Mesh-Auto Generate Collision: ์ถฉ๋๊ฐ์ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค.
์ํฌํธ๋ฅผ ํ๊ฒ ๋๋ฉด ๋ฉ์์ ํจ๊ป ์ฌ๋ฌ ํ์ผ๋ค์ ํจ๊ป ๊ฐ์ ธ์จ ๊ฒ์ ํ์ธํ ์ ์๋ค. ํน์ ํ์ผ์ ๊ฐ์ ธ์ฌ์ง ๋ง์ง๋ ์ต์ ์ ํตํด ์ ํ ์ ์๋ค.
- ์ค์ผ๋ ํค: ์ค์ผ๋ ํ ๋ฉ์์์ ์ฌ์ฉํ๋ ๋ผ ๊ตฌ์กฐ์ด๋ค.
- ์ค์ผ๋ ํ ๋ฉ์
- ํผ์ง์ค ์ ์ : ์ ๋๋ฉ์ด์ ๋์ ์ฝ๋ฆฌ์ ๋ฐ์ดํฐ์ด๋ค.
- ์ ๋๋ฉ์ด์ ์ํ์ค: ์ ๋๋ฉ์ด์ ๋ฐ์ดํฐ์ด๋ค.
- ๋จธํฐ๋ฆฌ์ผ
์ด๋ ๊ฒ ๊ฐ์ ธ์จ ์ ์ ๋ค์ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค ๋ณด๋ฉด ์ ์ ๊ฐ ํผ๋์ด ์์ ์ ์๊ธฐ ๋๋ฌธ์ ๊ฐ๋ณ ์ ์ ์ ๋ํ ๊ณตํต์ ๋ช ๋ช ๊ท์น์ ์ ํ๋ ๊ฒ์ด ์ข๋ค.
์ด์ ์บ๋ฆญํฐ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค์์ ์ค์ผ๋ ํ ๋ฉ์ ์ ์ ์ ์ํฌํธ ํ ์ค์ผ๋ ํ ๋ฉ์๋ก ๋ณ๊ฒฝํ๋ค.
๋ฉ์๋ฅผ ๋ณ๊ฒฝํ ํ ๋ ๋ฒจ ๋ด ์ฌ์ด์ฆ์ ๋ง์ง ์๋ค๋ฉด, ๋ฉ์์ ํธ๋์คํผ ๋ณ๊ฒฝ์ด ํ์ํ๋ค.
3. ๋จธํฐ๋ฆฌ์ผ
์ด์ ์บ๋ฆญํฐ์๊ฒ ์์ ์ ํ๋ณผ ๊ฒ์ด๋ค. ๋จผ์ ๊ด๋ จ Texture ํ์ผ์ ์ํฌํธ ํ ํ FBX ์ํฌํธ ์ ํจ๊ป ์์ฑ๋ ๋จธํฐ๋ฆฌ์ผ์ ๋๋ธ ํด๋ฆญํ๋ค. ์ด์ Texture Sample ๋ ธ๋๋ฅผ ํตํด ๊ฐ ๊ฐ์ ๋ง๋ ํ ์ค์ณ๋ฅผ ์ฐ๊ฒฐํ๋ค. ๋ฒ ์ด์ค ์ปฌ๋ฌ๋ง ์์ด๋ ์์ ๋ง๋ค์ด์ง์ง๋ง, ๋ค๋ฅธ ํน์ ๊ฐ์ ํตํด ๋์ฑ๋ ๋ํ ์ผํ๊ฒ ๋ณด์ผ ์ ์๋ค.
- ๋ฒ ์ด์ค ์ปฌ๋ฌ: ๋จธํฐ๋ฆฌ์ผ์ ์ ๋ฐ์ ์ธ ์์์ด๋ค.
- ์คํํ๋ฌ: ๋น๊ธ์ ํ๋ฉด์ ํ์ฌ ์คํํ๋ฌ ๊ฐ์ผ๋ก ํ์ด๋ผ์ดํธ๋ฅผ ๋ํ๋ธ๋ค.
- ๋ ธ๋ฉ: ํ๋ฉด์ ๋ฌผ๋ฆฌ์ ๋ํ ์ผ์ ๋ํ๋ ๊ฐ์ด๋ค.
- ์ฐ๋น์ธํธ ์คํด๋ฃจ์
์ด์ ์ ์ฅํ๋ฉด ๋จธํฐ๋ฆฌ์ผ์ด ์ ์ ์ฉ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
4. ์ ๋ ฅ
์ ๋ ฅ์ ํฅ์๋ ์ ๋ ฅ ์์คํ ์ ์ฌ์ฉํ ๊ฒ์ด๋ค. ์ด ์์คํ ์ ๋ณต์กํ ์ ๋ ฅ ์ฒ๋ฆฌ์ ์ค์๊ฐ ์ ๋ ฅ ๋ฆฌ๋งคํ์ ์ง์ํ๋ค. ์์ธํ ์ค์ ์ ์ด ๊ธ์ ์ฐธ๊ณ ํ๋ฉด ๋๋ค. ํด๋น ๊ธ์ ๋ธ๋ฃจํ๋ฆฐํธ๋ฅผ ํ์ฉํ์์ง๋ง, ์ฌ๊ธฐ์ C++๋ก ๊ตฌํํด ๋ณผ ๊ฒ์ด๋ค.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputMappingContext* DefaultMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* LookAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* WalkAction;
// ์
๋ ฅ ๋งคํ ์ปจํ
์คํธ๋ฅผ ๋ก์ปฌ ํ๋ ์ด์ด์ ํฅ์๋ ์
๋ ฅ ๋ก์ปฌ ํ๋ ์ด์ด ์๋ธ์์คํ
์ ์ถ๊ฐํด์ผ ํ๋ค.
void ASurvivorCharacter::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
}
// ๊ฐ ์
๋ ฅ ์ก์
์ ํด๋นํ๋ ์ ์
์ด๋ฆ์ ๊ฐ์ง ์ด๋ฒคํธ๋ฅผ ์์ฑํด์ผ ํ๋ค.
void ASurvivorCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
// Set up action bindings
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent)) {
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ASurvivorCharacter::Look);
EnhancedInputComponent->BindAction(WalkAction, ETriggerEvent::Triggered, this, &ASurvivorCharacter::Walk);
}
else
{
UE_LOG(LogTemplateCharacter, Error, TEXT("'%s' Failed to find an Enhanced Input component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this));
}
}
void ASurvivorCharacter::Look(const FInputActionValue& Value)
{
FVector2D LookAxisVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
}
void ASurvivorCharacter::Walk(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
}
5. ์ ๋๋ฉ์ด์
1) FSM(Finite State Machine)
์ธ๋ฆฌ์ผ์ ์ ๋๋ฉ์ด์ ์ ์ ์ฉํ๊ธฐ ์ํด์๋ ๋จผ์ FSM์ ์์์ผ ํ๋ค. ์ ํ ์ํ ๊ธฐ๊ณ๋ผ๊ณ ๋ ๋ถ๋ฅด๋ FSM์ ์ํ(state)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์์ ์ ์ดํ๊ธฐ ์ํ ๋์์ธ ํจํด์ด๋ค. ์ธ๋ฆฌ์ผ์ ์ ๋๋ฉ์ด์ ์ ์ด FSM์ ๋ฐ๋ฅด๊ณ ์๋ค.
๋ง์ฝ ์ ํ, ์ด๋จ ์ ํ, ์๋๋ฆฌ๊ธฐ ๋ฑ ์ฌ๋ฌ ์ ๋๋ฉ์ด์ ์ ๊ด๋ฆฌํด์ผ ํ ๋, ์ฝ๋๋ก ์์ฑํ๋ค๋ฉด ํ๋์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๋๋ง๋ค ๊ณ์ํด์ if ๋ฌธ์ด ๋์ด๋ ๊ฒ์ด๋ค.
if (input == PRESS_B)
{
if (!bIsJumping)
{
bIsJumping = true;
Jump();
}
}
else if (input == PRESS_DOWN)
{
if (!isJumping_)
{
Duck();
}
}
else if (input == RELEASE_DOWN)
{
Stand();
}
FSM์ ์ฌ์ฉํ๋ค๋ฉด ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค. ์ ํ์ ๊ฐ์ ํ๋์ ๋์์ ์คํ ์ดํธ๋ก ์ ํ๊ณ ์ด ์คํ ์ดํธ๋ค ์ค ์กฐ๊ฑด์ ๋ง๋ ํ๋๋ง ๋์ํ๋๋ก ํ๋ ๊ฒ์ด๋ค. ์คํํ ์คํ ์ดํธ๋ฅผ ๋ณ๊ฒฝํ๊ฒ ๋ง๋๋ ์กฐ๊ฑด์ ํธ๋์ง์ ์ด๋ผ ๋ถ๋ฅธ๋ค.
2) AnimInstance ํด๋์ค
์บ๋ฆญํฐ ์ ๋๋ฉ์ด์ ์ ์ํ AnimInstance ํด๋์ค๋ฅผ ์์๋ฐ๋ C++ ํด๋์ค๋ฅผ ์์ฑํ๋ค. AnimInstance ํด๋์ค๋ ์ค์ผ๋ ํ ๋ฉ์๋ฅผ ์์ ํ๋ ํฐ์ ์ ๋ณด๋ฅผ ๋ฐ์ ์ ๋ ๊ทธ๋ํ๊ฐ ์ฐธ์กฐํ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ค.
์์ฑํ AnimInstance ํด๋์ค์์ ์บ๋ฆญํฐ Movement์ ๊ฐ์ ๋ฐ๋ผ LandState๊ฐ ๋ณ๊ฒฝ๋๋๋ก ๊ตฌํํ๋ค. 1์ผ ๋ ์ด๋ ์ํ์ด๋ค.
UPROPERTY(BlueprintReadWrite)
ASurvivorCharacter* Character;
UPROPERTY(BlueprintReadWrite)
int LandState;
void USurvivorCharacterAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
if (IsValid(Character) == false)
{
auto Pawn = TryGetPawnOwner();
Character = Cast<ASurvivorCharacter>(Pawn);
}
if (IsValid(Character))
{
LandState = 0;
if (Character->IsWalking())
{
LandState = 1;
}
}
}
bool ASurvivorCharacter::IsWalking() const
{
return GetCharacterMovement()->Velocity.Length() > 0;
}
์ด์ ์์ฑํ AnimInstance ํด๋์ค๋ฅผ ๋ฒ ์ด์ค๋ก ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ฅผ ์์ฑํ๋ค. ์ด๋ ์บ๋ฆญํฐ์ ๋ง๋ ์ค์ผ๋ ํค์ ์ ํํด์ผ ํ๋ค.
์ด์ ์ ๋ ๊ทธ๋ํ์์ LandState์ ๋ง๊ฒ ์ ๋๋ฉ์ด์ ์ด ๋์๋๋๋ก ํด์ผ ํ๋ค. ์ ๋ ๊ทธ๋ํ(Anim Graph)๋ ์ ๋ ์ธ์คํด์ค์ ๋ณ์์ ๋ฐ๋ผ ๋ณํํ๋ ์ ๋๋ฉ์ด์ ์์คํ ์ ์ค๊ณํ๋ ๊ณต๊ฐ์ด๋ค. ์ ๋ ๊ทธ๋ํ์์ ์คํ ์ดํธ ๋จธ์ ์ ์ถ๊ฐํ ํ Final Animation Pose ๋ ธ๋์ Result ์ ๋ ฅ ์คํ ํ์ ์ฐ๊ฒฐํ๋ค.
์์ฑํ ์คํ ์ดํธ ๋จธ์ ์ ๋๋ธํด๋ฆญํ์ฌ ์ฐ ํ ์ ๋๋ฉ์ด์ ์ ์คํํ๊ธฐ ์ํ ์คํ ์ดํธ๋ฅผ ์ถ๊ฐํ๋ค. ๊ฐ๊ฐ์ ์คํ ์ดํธ๋ Idle, Walk ์ ๋๋ฉ์ด์ ์ด ์คํ๋๋๋ก ์ฐ๊ฒฐํด์ผ ํ๋ค.
๋ชจ๋ ์คํ ์ดํธ๋ฅผ ์ถ๊ฐํ๋ค๋ฉด ์คํ ์ดํธ ์ฌ์ด์ ํธ๋์ง์ ๋ ์ถ๊ฐํ์ฌ LandState๋ฅผ ํตํด ๋ฐ๋๋๋ก ์ฐ๊ฒฐํ๋ค.
์ค์ ์ด ๋ค ์๋ฃ๋์์ผ๋ ์บ๋ฆญํฐ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค์ AnimInstance ๋ธ๋ฃจํ๋ฆฐํธ๋ฅผ ์ฐ๊ฒฐํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ์ ๋๋ฉ์ด์ ์ด ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ฐธ๊ณ
https://wergia.tistory.com/127#footnote_127_2