본문 바로가기

스테이트 머신과 애니메이션 Blueprint 연동

C++의 이동·공중·공격 상태를 Animation Blueprint 변수와 스테이트 머신 전이 조건에 연결합니다.

컴포넌트 시스템 이해와 활용까지 마치면 캐릭터는 입력에 따라 움직이고 카메라도 따라오지만, 몸은 여전히 같은 자세로 미끄러지듯 이동합니다. 걷거나 뛸 때, 점프해서 공중에 있을 때 알맞은 애니메이션을 재생하려면 "지금 캐릭터가 어떤 상태인가"를 판단하고, 상태가 바뀔 때 애니메이션도 자연스럽게 바꿔야 합니다. 이 판단을 조건문으로 흩어 놓으면 상태가 늘어날수록 어떤 조건에서 어떤 애니메이션이 나오는지 알기 어려워집니다.

이 글에서는 이 문제를 스테이트 머신(state machine)으로 정리하고, 언리얼의 애니메이션 블루프린트(Animation Blueprint)에서 구현합니다. C++에서 평면 속도와 낙하 상태를 계산해 애니메이션 블루프린트로 넘기고, 그 값으로 Idle, Walk_Run, Jump 상태 사이의 전이를 만듭니다. 공격 플래그는 이후 구현을 위해 자리만 만들어 두며, 이 글의 코드에는 공격 상태를 갱신하는 로직이 없습니다.


스테이트 머신으로 동작 나누기

스테이트 머신은 대상이 가질 수 있는 유한한 수의 상태(state)와, 한 상태에서 다른 상태로 넘어가는 전이(transition)로 이루어진 모델입니다. 어떤 조건이 만족되면 현재 상태에 정의된 규칙에 따라 다른 상태로 전이합니다. 게임 캐릭터의 애니메이션에서는 상태와 전환 조건을 따로 관리하는 방식으로 쓰입니다.

예를 들어 캐릭터는 아무것도 하지 않고 서 있는 Idle(대기), 걷는 Walk(걷기), 뛰는 Run(달리기), 점프 중인 Jump(점프), 공격 중인 Attack(공격) 같은 상태를 가질 수 있습니다. 각 상태는 고유한 애니메이션을 재생하고, 이동 입력이 생기거나 점프 버튼을 누르거나 공격 버튼을 누르는 것 같은 조건이 만족되면 다른 상태로 넘어갑니다. 이렇게 상태와 전이를 그림처럼 정리해 두면, 복잡한 애니메이션 흐름도 어느 상태에서 어느 조건으로 넘어가는지 한눈에 볼 수 있습니다.


애니메이션 블루프린트의 두 그래프

언리얼 엔진은 스테이트 머신을 포함한 애니메이션 로직을 시각적으로 만들 수 있도록 애니메이션 블루프린트를 제공합니다. 애니메이션 블루프린트는 특정 스켈레톤에 맞춰 만들어지며, 스켈레탈 메시 컴포넌트의 Anim Class로 지정되어 그 메시를 가진 캐릭터의 애니메이션을 제어합니다.

애니메이션 블루프린트에는 두 가지 그래프가 있습니다. 애님 그래프(Anim Graph)는 스켈레탈 메시의 최종 포즈를 계산하는 그래프로, 캐릭터의 주요 동작 상태를 정의하는 스테이트 머신 노드가 주로 여기에 놓입니다. 여러 애니메이션 시퀀스를 섞거나 몸의 일부에만 애니메이션을 적용하는 일도 이 그래프에서 합니다.

이벤트 그래프(Event Graph)는 애니메이션 블루프린트가 동작하는 동안 일어나는 Event Blueprint Update Animation 같은 이벤트를 처리합니다. 여기서 캐릭터의 속도, 점프 상태, 공격 상태처럼 게임플레이와 관련된 값을 읽어 애니메이션 블루프린트의 변수에 저장하고, 애님 그래프의 스테이트 머신이 그 변수를 씁니다.


C++에서 애니메이션에 쓸 값 계산하기

캐릭터의 이동 속도나 점프 상태 같은 게임플레이 데이터는 C++에서 계산하고, 애니메이션 블루프린트가 그 값을 읽어 알맞은 애니메이션을 재생하게 합니다. 값을 넘기는 방법은 UPROPERTY로 C++ 변수를 블루프린트에 공개하고, 애니메이션 블루프린트의 이벤트 그래프에서 그 변수를 읽는 것입니다.

애니메이션용 변수 선언하기

AMyCharacter에 애니메이션에 필요한 변수를 UPROPERTY로 선언합니다. 아래의 생략 부분은 앞 두 글의 입력과 카메라 코드를 그대로 둘 자리입니다. 애니메이션 멤버와 함수 선언을 기존 헤더에 더하고, 이미 있는 Tick 하나에 UpdateAnimationVariables 호출을 더합니다. 생성자, BeginPlay, Tick을 두 번씩 정의하지 않도록 주의합니다.

MyCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
// ... (기존 헤더들) ...
#include "MyCharacter.generated.h"

UCLASS()
class MYFIRSTCPPPROJECT_API AMyCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    AMyCharacter();

protected:
    virtual void BeginPlay() override;

public:
    // ... (기존 Tick, SetupPlayerInputComponent 등) ...

protected:
    // ... (기존 Input 관련 UPROPERTY 변수들) ...

    // 애니메이션 블루프린트에 노출할 변수들
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
    float CurrentSpeed; // 현재 이동 속도

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
    bool bIsInAir; // 공중에 떠 있는지 여부

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
    bool bIsAttacking; // 공격 중인지 여부 (예시)

    // 애니메이션 블루프린트에 필요한 데이터를 업데이트하는 함수 (Tick에서 호출)
    void UpdateAnimationVariables();
};

세 변수에는 VisibleAnywhere와 BlueprintReadOnly를 붙였습니다. 애니메이션 블루프린트는 이 값을 읽기만 하고, 값을 정하는 일은 C++ 캐릭터가 맡기 때문입니다.

매 프레임 값 갱신하기

MyCharacter.cpp의 Tick에서 이 변수들을 갱신하는 함수를 부릅니다. 생성자와 BeginPlay는 앞 글의 내용을 그대로 두므로, 새로 볼 곳은 Tick과 그 아래의 UpdateAnimationVariables 두 함수입니다. 갱신 코드를 따로 함수로 뺀 것은, 나중에 Tick에 다른 일이 늘어나도 애니메이션에 넘길 값의 계산을 한곳에서 찾을 수 있게 하려는 것입니다. 또 Tick이 불리려면 앞 글의 생성자처럼 PrimaryActorTick.bCanEverTick이 true여야 합니다.

MyCharacter.cpp
#include "MyCharacter.h"
// ... (기존 헤더들) ...
#include "GameFramework/CharacterMovementComponent.h" // GetCharacterMovement()를 위해 포함

AMyCharacter::AMyCharacter()
{
    // ... (기존 생성자 로직) ...
}

void AMyCharacter::BeginPlay()
{
    Super::BeginPlay();
    // ... (기존 BeginPlay 로직) ...
}

void AMyCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    // 매 프레임 애니메이션 관련 변수 업데이트
    UpdateAnimationVariables();
}

void AMyCharacter::UpdateAnimationVariables()
{
    // 현재 이동 속도 계산 (XY 평면 속도)
    CurrentSpeed = GetCharacterMovement()->Velocity.Size2D();

    // 공중에 떠 있는지 여부
    bIsInAir = GetCharacterMovement()->IsFalling();

    // 공격 상태는 추후 공격 시스템 구현 시 설정 (예시)
    // bIsAttacking = ...;
}

CurrentSpeed는 이동 컴포넌트의 속도 벡터 Velocity에서 Size2D()로 XY 평면의 크기만 구합니다. 점프하거나 떨어질 때의 수직 속도를 빼야 걷기와 달리기를 구분하는 속도로 쓸 수 있기 때문입니다. bIsInAir는 이동 컴포넌트의 IsFalling() 결과로, 캐릭터가 낙하 중이면 참이 됩니다.


애니메이션 블루프린트 준비하기

먼저 캐릭터의 스켈레탈 메시 에셋(예: SK_Mannequin이나 외부에서 가져온 모델)과, Idle, Walk, Run, JumpStart, JumpLoop, JumpEnd 같은 애니메이션 시퀀스 에셋이 필요합니다. 콘텐츠 브라우저에서 쓸 스켈레탈 메시를 마우스 오른쪽 버튼으로 누르고 Create 메뉴의 Anim Blueprint를 고른 뒤, 이름을 ABP_MyCharacter로 정합니다.

이벤트 그래프에서 C++ 값 읽기

ABP_MyCharacter를 더블클릭해 애니메이션 블루프린트 에디터를 열고 이벤트 그래프로 이동합니다. Event Blueprint Update Animation 노드를 찾거나 만듭니다. 이 이벤트는 애니메이션을 갱신할 때 불리는데, 화면에 보이는지 여부나 최적화, 틱 설정에 따라 갱신이 생략될 수 있으므로 모든 게임 프레임과 항상 하나씩 짝을 이룬다고 가정하지 않습니다.

다음으로 이 애니메이션을 쓰는 캐릭터를 가져옵니다. Try Get Pawn Owner의 반환값이 유효한지 확인한 뒤 Cast To MyCharacter로 AMyCharacter로 캐스팅합니다. 에디터의 미리보기 창이나 다른 소유자에서는 반환값이 null이거나 캐스팅이 실패할 수 있으므로, 성공한 경로에서만 변수를 읽습니다.

캐스팅 결과에서 끌어 Current Speed, Is In Air, Is Attacking 값을 읽는 노드를 만듭니다. C++에서 BlueprintReadOnly로 선언했으므로 읽기 노드가 자동으로 제공되며, bool 변수 이름 앞의 b는 에디터에 표시될 때 빠집니다. 읽은 값은 애니메이션 블루프린트에 만든 같은 의미의 변수에 Set 노드로 저장하고, 애님 그래프의 스테이트 머신은 이 변수들을 씁니다.

애님 그래프에 스테이트 머신 만들기

애님 그래프로 이동해 출력 포즈(Output Pose) 노드 옆에 State Machine 노드를 추가하고 연결한 뒤, 그 노드를 더블클릭해 안으로 들어갑니다. Entry 노드에서 끌어 Idle 상태를 추가하고, Idle에서 끌어 Walk_Run 상태를, Walk_Run에서 끌어 Jump 상태를 추가합니다. 점프를 시작, 반복, 착지 상태로 나눌 수도 있고, 필요하면 Attack 같은 상태도 더합니다.

각 상태 노드를 더블클릭해 들어가 재생할 애니메이션을 연결합니다. Idle에는 Idle 애니메이션 시퀀스를 끌어다 놓고 출력 포즈에 연결합니다. Walk_Run에는 Walk와 Run을 속도에 따라 섞는 Blend Space 1D를 쓰고, 블렌드 스페이스의 X축에 CurrentSpeed 변수를 연결합니다. 블렌드 스페이스를 아직 만들지 않았다면 블렌드 스페이스 기본 사용법에서 속도 축 하나로 Idle, Walk, Run을 섞는 에셋을 만드는 과정을 먼저 따라 합니다. Jump에는 우선 반복 재생할 JumpLoop 하나를 출력 포즈에 연결합니다. 시작과 착지 클립까지 쓰고 싶다면 JumpStart, JumpLoop, JumpEnd를 각각 별도의 상태로 만들고, 재생이 끝났는지와 착지했는지를 조건으로 전이시킵니다. 한 상태 안에 시퀀스 세 개를 늘어놓는다고 차례로 재생되지는 않습니다.

이제 상태 사이의 전이를 만듭니다. 전이는 한 방향으로만 동작하므로, 두 상태를 오가려면 방향마다 전이선을 따로 만들어야 합니다. Idle과 Walk_Run 사이에 양방향으로 전이선을 만들고, Idle과 Walk_Run 각각에서 Jump로 가는 전이선과 Jump에서 Idle과 Walk_Run으로 돌아오는 전이선을 만듭니다. 여러 전이에 공통 규칙이 필요하면 콘딧(Conduit)이나 스테이트 에일리어스(State Alias)를 쓸 수 있지만, 이 실습에서는 전이선을 하나씩 직접 연결합니다.

전이 규칙은 단순하게 정합니다. bIsInAir가 참이면 Jump로 가고, 거짓이면서 평면 속도가 10cm/s 미만이면 Idle로, 10cm/s 이상이면 Walk_Run으로 갑니다. 각 출발 상태에서 목적 상태로 가는 전이선마다 그 목적 상태의 조건을 검사합니다. 여기서 "지상"은 이 실습에서 bIsInAir가 거짓인 경우를 줄여 부른 말이며, 수영이나 비행 상태까지 나누는 전체 이동 모델은 아닙니다.

낙하 여부와 평면 속도로 목적 상태를 고릅니다

Idle/Walk_Run/Jump가 IsFalling과 평면 속도 10 경계로 전환한다. (300,400,1200)의 Size2D 500과 낙하, 착지 시 (6,8,0)의 10 경계로 실제 선택 상태를 보여 줄 수 있다.

UNREAL C++ · CH3-4

낙하 여부와 평면 속도로 목적 상태를 고릅니다

단순 실습의 Jump는 IsFalling 결과로 선택합니다. 지상에서는 CurrentSpeed의 10 경계를 기준으로 Idle과 Walk_Run 전이를 나눕니다.

세 그림은 같은 상태 머신의 전이선을 쌍별로 나눈 것입니다. 같은 상태 이름은 같은 상태를 가리킵니다. 지상은 이 실습의 !bIsInAir이며 수영·비행은 별도입니다.

Idle와Walk_Run의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Idle→Walk_Run:지상,Speed ≥10;Walk_Run→Idle:지상,Speed <10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.EntryIdle대기 포즈Walk_Run보행·달리기 포즈지상 · Speed ≥10지상 · Speed <10같은 상태 머신의 Idle ↔ Walk_Run 전이
Idle와Walk_Run의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Idle→Walk_Run:지상,Speed ≥10;Walk_Run→Idle:지상,Speed <10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Idle대기 포즈Walk_Run보행·달리기 포즈지상Speed ≥10지상Speed <10각 방향의 조건을 검사
Idle와Jump의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Idle→Jump:공중,bIsInAir;Jump→Idle:지상,Speed <10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Idle대기 포즈Jump공중 포즈공중 · bIsInAir지상 · Speed <10같은 상태 머신의 Idle ↔ Jump 전이
Idle와Jump의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Idle→Jump:공중,bIsInAir;Jump→Idle:지상,Speed <10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Idle대기 포즈Jump공중 포즈공중bIsInAir지상Speed <10각 방향의 조건을 검사
Walk_Run와Jump의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Walk_Run→Jump:공중,bIsInAir;Jump→Walk_Run:지상,Speed ≥10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Walk_Run보행·달리기 포즈Jump공중 포즈공중 · bIsInAir지상 · Speed ≥10같은 상태 머신의 Walk_Run ↔ Jump 전이
Walk_Run와Jump의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Walk_Run→Jump:공중,bIsInAir;Jump→Walk_Run:지상,Speed ≥10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Walk_Run보행·달리기 포즈Jump공중 포즈공중bIsInAir지상Speed ≥10각 방향의 조건을 검사
단순 지상·공중 상태의 전이 조건
목적 상태들어갈 조건원문 값의 의미
JumpbIsInAir가 trueIsFalling 결과; 모든 수영·비행 상태를 뜻하지 않음
IdlebIsInAir가 false이고 CurrentSpeed < 10XY 평면 속도; 기본 길이 단위에서는 cm/s
Walk_RunbIsInAir가 false이고 CurrentSpeed >= 10이 값으로 Blend Space의 보행·달리기 포즈 혼합
Jump
들어갈 조건: bIsInAir가 true
원문 값의 의미: IsFalling 결과; 모든 수영·비행 상태를 뜻하지 않음
Idle
들어갈 조건: bIsInAir가 false이고 CurrentSpeed < 10
원문 값의 의미: XY 평면 속도; 기본 길이 단위에서는 cm/s
Walk_Run
들어갈 조건: bIsInAir가 false이고 CurrentSpeed >= 10
원문 값의 의미: 이 값으로 Blend Space의 보행·달리기 포즈 혼합

전이선은 각 목적 상태에 연결합니다

Idle ↔ Walk_Run, Idle ↔ Jump, Walk_Run ↔ Jump의 연결을 직접 만듭니다. 표의 목적 상태 조건을 각 전이 규칙에서 검사합니다.

Entry는 Idle에 연결합니다. 이 조건표를 작성하는 것만으로 Anim Graph의 전이선이 생성되지는 않습니다.

10은 실습용 임계값입니다. Walk_Run의 포즈 혼합과 실제 이동 속도 설정은 별개입니다. Attack 플래그는 현재 코드에서 갱신되지 않으며 공격 전이도 아직 구현하지 않았습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목Idle 및 Walk_Run의 방향별 전이
관계·조건 설명같은 세 상태를 전이 쌍별로 나눈 부분 그래프다. 각 단방향 전이마다 서로 다른 조건이 있다. Idle→Walk_Run: 지상, Speed ≥10; Walk_Run→Idle: 지상, Speed <10이다. 지상은 실습의 !bIsInAir이고 10 cm/s 경계이며 첫 Idle에 Entry가 연결된다.
도표 제목Idle 및 Jump의 방향별 전이
관계·조건 설명같은 세 상태를 전이 쌍별로 나눈 부분 그래프다. 각 단방향 전이마다 서로 다른 조건이 있다. Idle→Jump: 공중, bIsInAir; Jump→Idle: 지상, Speed <10이다. 지상은 실습의 !bIsInAir이고 10 cm/s 경계이며 첫 Idle에 Entry가 연결된다.
도표 제목Walk_Run 및 Jump의 방향별 전이
관계·조건 설명같은 세 상태를 전이 쌍별로 나눈 부분 그래프다. 각 단방향 전이마다 서로 다른 조건이 있다. Walk_Run→Jump: 공중, bIsInAir; Jump→Walk_Run: 지상, Speed ≥10이다. 지상은 실습의 !bIsInAir이고 10 cm/s 경계이며 첫 Idle에 Entry가 연결된다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
속도 입력

벡터 (300,400,1200)의 평면 크기는 500이다.

  • Size2D: 500
  • IsFalling: Jump 판단 입력
  • Z 1200을 평면 이동 속도에 더하지 않는다.
공중 상태

IsFalling이 참이면 Jump로 간다.

  • 상태: Jump
  • 조건: IsFalling=true
  • 이동 속도와 공중 여부의 역할을 구분한다.
착지 경계

착지해 IsFalling=false이고 Size2D=10 cm/s이면 Jump에서 Walk_Run으로 전환한다.

  • 착지 조건: IsFalling=false
  • Size2D: √(6²+8²)=10 cm/s
  • 선택 상태: Jump → Walk_Run
  • 10은 <10에 포함되지 않고 >=10에 포함되므로 지상 조건에서 Jump→Walk_Run이 참이다. 이후 지상 속도 0의 Idle 전이는 별도 후속 조건이다.

낙하 여부와 평면 속도로 목적 상태를 고릅니다

Idle/Walk_Run/Jump가 IsFalling과 평면 속도 10 경계로 전환한다. (300,400,1200)의 Size2D 500과 낙하, 착지 시 (6,8,0)의 10 경계로 실제 선택 상태를 보여 줄 수 있다.

UNREAL C++ · CH3-4

낙하 여부와 평면 속도로 목적 상태를 고릅니다

단순 실습의 Jump는 IsFalling 결과로 선택합니다. 지상에서는 CurrentSpeed의 10 경계를 기준으로 Idle과 Walk_Run 전이를 나눕니다.

세 그림은 같은 상태 머신의 전이선을 쌍별로 나눈 것입니다. 같은 상태 이름은 같은 상태를 가리킵니다. 지상은 이 실습의 !bIsInAir이며 수영·비행은 별도입니다.

Idle와Walk_Run의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Idle→Walk_Run:지상,Speed ≥10;Walk_Run→Idle:지상,Speed <10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.EntryIdle대기 포즈Walk_Run보행·달리기 포즈지상 · Speed ≥10지상 · Speed <10같은 상태 머신의 Idle ↔ Walk_Run 전이
Idle와Walk_Run의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Idle→Walk_Run:지상,Speed ≥10;Walk_Run→Idle:지상,Speed <10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Idle대기 포즈Walk_Run보행·달리기 포즈지상Speed ≥10지상Speed <10각 방향의 조건을 검사
Idle와Jump의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Idle→Jump:공중,bIsInAir;Jump→Idle:지상,Speed <10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Idle대기 포즈Jump공중 포즈공중 · bIsInAir지상 · Speed <10같은 상태 머신의 Idle ↔ Jump 전이
Idle와Jump의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Idle→Jump:공중,bIsInAir;Jump→Idle:지상,Speed <10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Idle대기 포즈Jump공중 포즈공중bIsInAir지상Speed <10각 방향의 조건을 검사
Walk_Run와Jump의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Walk_Run→Jump:공중,bIsInAir;Jump→Walk_Run:지상,Speed ≥10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Walk_Run보행·달리기 포즈Jump공중 포즈공중 · bIsInAir지상 · Speed ≥10같은 상태 머신의 Walk_Run ↔ Jump 전이
Walk_Run와Jump의 방향별 전이같은세상태를전이쌍별로나눈부분그래프이다. 각단방향전이마다서로다른조건. Walk_Run→Jump:공중,bIsInAir;Jump→Walk_Run:지상,Speed ≥10.지상은실습!bIsInAir;10cm/s경계이며첫Idle에Entry.Walk_Run보행·달리기 포즈Jump공중 포즈공중bIsInAir지상Speed ≥10각 방향의 조건을 검사
단순 지상·공중 상태의 전이 조건
목적 상태들어갈 조건원문 값의 의미
JumpbIsInAir가 trueIsFalling 결과; 모든 수영·비행 상태를 뜻하지 않음
IdlebIsInAir가 false이고 CurrentSpeed < 10XY 평면 속도; 기본 길이 단위에서는 cm/s
Walk_RunbIsInAir가 false이고 CurrentSpeed >= 10이 값으로 Blend Space의 보행·달리기 포즈 혼합
Jump
들어갈 조건: bIsInAir가 true
원문 값의 의미: IsFalling 결과; 모든 수영·비행 상태를 뜻하지 않음
Idle
들어갈 조건: bIsInAir가 false이고 CurrentSpeed < 10
원문 값의 의미: XY 평면 속도; 기본 길이 단위에서는 cm/s
Walk_Run
들어갈 조건: bIsInAir가 false이고 CurrentSpeed >= 10
원문 값의 의미: 이 값으로 Blend Space의 보행·달리기 포즈 혼합

전이선은 각 목적 상태에 연결합니다

Idle ↔ Walk_Run, Idle ↔ Jump, Walk_Run ↔ Jump의 연결을 직접 만듭니다. 표의 목적 상태 조건을 각 전이 규칙에서 검사합니다.

Entry는 Idle에 연결합니다. 이 조건표를 작성하는 것만으로 Anim Graph의 전이선이 생성되지는 않습니다.

10은 실습용 임계값입니다. Walk_Run의 포즈 혼합과 실제 이동 속도 설정은 별개입니다. Attack 플래그는 현재 코드에서 갱신되지 않으며 공격 전이도 아직 구현하지 않았습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
도표 제목Idle 및 Walk_Run의 방향별 전이
관계·조건 설명같은 세 상태를 전이 쌍별로 나눈 부분 그래프다. 각 단방향 전이마다 서로 다른 조건이 있다. Idle→Walk_Run: 지상, Speed ≥10; Walk_Run→Idle: 지상, Speed <10이다. 지상은 실습의 !bIsInAir이고 10 cm/s 경계이며 첫 Idle에 Entry가 연결된다.
도표 제목Idle 및 Jump의 방향별 전이
관계·조건 설명같은 세 상태를 전이 쌍별로 나눈 부분 그래프다. 각 단방향 전이마다 서로 다른 조건이 있다. Idle→Jump: 공중, bIsInAir; Jump→Idle: 지상, Speed <10이다. 지상은 실습의 !bIsInAir이고 10 cm/s 경계이며 첫 Idle에 Entry가 연결된다.
도표 제목Walk_Run 및 Jump의 방향별 전이
관계·조건 설명같은 세 상태를 전이 쌍별로 나눈 부분 그래프다. 각 단방향 전이마다 서로 다른 조건이 있다. Walk_Run→Jump: 공중, bIsInAir; Jump→Walk_Run: 지상, Speed ≥10이다. 지상은 실습의 !bIsInAir이고 10 cm/s 경계이며 첫 Idle에 Entry가 연결된다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
속도 입력

벡터 (300,400,1200)의 평면 크기는 500이다.

  • Size2D: 500
  • IsFalling: Jump 판단 입력
  • Z 1200을 평면 이동 속도에 더하지 않는다.
공중 상태

IsFalling이 참이면 Jump로 간다.

  • 상태: Jump
  • 조건: IsFalling=true
  • 이동 속도와 공중 여부의 역할을 구분한다.
착지 경계

착지해 IsFalling=false이고 Size2D=10 cm/s이면 Jump에서 Walk_Run으로 전환한다.

  • 착지 조건: IsFalling=false
  • Size2D: √(6²+8²)=10 cm/s
  • 선택 상태: Jump → Walk_Run
  • 10은 <10에 포함되지 않고 >=10에 포함되므로 지상 조건에서 Jump→Walk_Run이 참이다. 이후 지상 속도 0의 Idle 전이는 별도 후속 조건이다.

위 그림은 같은 스테이트 머신의 전이선을 두 상태씩 묶어 세 장으로 나눠 보여 줍니다. 같은 이름의 상태는 같은 상태이며, Entry는 처음 상태인 Idle에 연결됩니다. 첫 그림에서 Idle에서 Walk_Run으로 가는 전이는 지상이면서 Speed ≥ 10일 때, 반대 방향은 지상이면서 Speed < 10일 때 참이 됩니다. 둘째 그림에서 Idle에서 Jump로 가는 전이는 공중일 때(bIsInAir), Jump에서 Idle로 돌아오는 전이는 지상이면서 Speed < 10일 때 참이고, 셋째 그림의 Walk_Run과 Jump 사이도 같은 방식으로 공중이면 Jump로, 지상이면서 Speed ≥ 10이면 Walk_Run으로 갑니다.

그림의 표는 목적 상태마다 들어갈 조건과 값의 뜻을 정리합니다. Jump의 bIsInAir는 IsFalling 결과이므로 모든 수영, 비행 상태를 뜻하지는 않습니다. Idle과 Walk_Run의 CurrentSpeed는 XY 평면 속도로, 기본 길이 단위에서는 cm/s이며, Walk_Run에서는 이 값으로 블렌드 스페이스의 보행과 달리기 포즈를 섞습니다. 그림 아래의 설명처럼 이 조건표를 만드는 것만으로 애님 그래프에 전이선이 생기지는 않으므로 직접 연결해야 하고, 10은 실습용 기준값이며 공격 전이는 아직 구현하지 않았습니다.

단계 그림은 실제 값으로 상태가 고르는 과정을 따라갑니다. 속도 벡터가 (300, 400, 1200) cm/s라면 CurrentSpeed는 Size2D이므로 √(300² + 400²) = 500cm/s이고, 수직 성분 1200은 이 계산에 들어가지 않습니다. 그런데 이때 IsFalling이 참이면 지상의 Walk_Run 조건은 거짓이고 Jump로 가는 조건이 참입니다. 이동 키를 눌렀다는 사실이 아니라, 코드가 읽은 현재 속도와 낙하 상태가 상태를 고른다는 점이 중요합니다. 마지막 단계는 착지 직후의 경계값을 다루는데, 이 값은 연습 문제에서 직접 계산해 봅니다.

공격이나 피격, 착지 클립을 더할 때는 포즈를 고정할지와 전이의 우선순위를 따로 정합니다. 여러 전이가 동시에 참이면 Priority Order 값이 가장 작은 전이가 선택됩니다. 남은 재생 시간을 조건으로 쓸 때는 그 시퀀스와 반복 설정에 맞춰 써야 하며, 우선순위만 정한다고 상태가 빠르게 오가며 떨리는 문제가 모두 사라지는 것은 아닙니다.


캐릭터에 연결하고 확인하기

마지막으로 메시, 애님 클래스, 이벤트 그래프의 변수, 전이 규칙이 모두 같은 캐릭터 인스턴스를 바라보는지 확인합니다. BP_MyCharacter 블루프린트를 열고 ACharacter의 기본 Mesh 컴포넌트를 선택한 뒤, Skeletal Mesh 항목에 쓸 스켈레탈 메시를, Anim Class 항목에 ABP_MyCharacter를 지정합니다.

Play를 누르고 W, A, S, D로 움직이면 걷기와 달리기 애니메이션으로 전환되고, 스페이스 바를 누르면 점프 애니메이션이 재생되는지 확인합니다. 결과가 예상과 다르면 애니메이션 블루프린트 에디터의 디버그 대상(Debug Object)에서 지금 조종 중인 캐릭터의 애님 인스턴스를 고른 뒤, Pawn Owner와 캐스팅, C++ 값, 애니메이션 블루프린트 변수, 활성 전이를 차례로 확인합니다. 캐릭터의 Tick과 애니메이션 갱신 사이의 순서가 중요해지면 틱 의존성도 명시해야 합니다.


더 깊이: 이벤트 그래프를 게임 스레드에서 덜어 내기

애니메이션 블루프린트의 이벤트 그래프는 항상 게임 스레드에서 실행됩니다. 틱마다 각 애니메이션 블루프린트의 이벤트 그래프가 게임 스레드에서 차례로 평가되므로, 공식 문서는 대개 애니메이션 블루프린트에서 가장 비용이 큰 부분이 이벤트 그래프 로직이라고 설명합니다. 캐릭터가 몇 명뿐일 때는 드러나지 않지만, 같은 애니메이션 블루프린트를 쓰는 캐릭터가 많아지면 이 비용이 게임 스레드 시간에 그대로 쌓입니다.

그래서 언리얼은 이 계산을 스레드 안전(thread safe) 함수로 옮기는 방법을 제공합니다. Blueprint Thread Safe Update Animation을 재정의하고 그 안에서 스레드 안전 함수를 호출하면, 이 로직은 사용할 수 있는 워커 스레드에서 동시에 평가될 수 있습니다. 다만 스레드 안전 함수는 스레드 안전하지 않은 블루프린트와 컴포넌트에 직접 접근할 수 없습니다. 그래서 이 글처럼 Try Get Pawn Owner를 캐스팅해 캐릭터의 변수를 읽는 그래프는 그대로 옮길 수 없고, 함수 안에서 프로퍼티 액세스(Property Access) 노드로 필요한 값을 읽어야 합니다.

애님 그래프 쪽에도 비슷한 최적화가 있습니다. 노드의 입력 핀에 멤버 변수를 계산 없이 바로 연결하면 애니메이션 패스트 패스(Fast Path)가 적용되어, 엔진이 블루프린트 코드를 실행하지 않고 값을 내부에서 복사합니다. 그 사이에 계산 노드를 하나라도 넣으면 그 노드는 더 이상 패스트 패스를 쓰지 않습니다. 패스트 패스를 쓰는 노드는 컴파일 뒤 오른쪽 위에 번개 아이콘이 붙으므로 그래프에서 바로 확인할 수 있습니다. 블렌드 스페이스의 X축에 CurrentSpeed 변수를 그대로 연결한 이 글의 구성은 이 조건에 맞습니다.


정리

  • 스테이트 머신은 유한한 상태와 전이로 동작을 나누며, 애니메이션에서는 상태와 전환 조건을 따로 관리하게 해 줍니다.
  • 애니메이션 블루프린트의 이벤트 그래프는 게임플레이 값을 읽어 변수에 저장하고, 애님 그래프의 스테이트 머신은 그 변수로 포즈를 고릅니다.
  • C++는 Velocity.Size2D()로 평면 속도를, IsFalling()으로 낙하 상태를 계산해 BlueprintReadOnly 변수로 공개합니다.
  • 이벤트 그래프에서는 Try Get Pawn Owner와 캐스팅이 성공한 경로에서만 값을 읽습니다.
  • 전이는 한 방향이므로 방향마다 전이선을 만들고, 각 목적 상태의 조건을 검사합니다. 여러 전이가 함께 참이면 Priority Order가 가장 작은 전이가 선택됩니다.
  • 한 상태에 시퀀스를 여러 개 놓는다고 차례로 재생되지 않으므로, 순서가 있는 클립은 별도 상태로 나눕니다.

연습 문제

1. 착지 직후의 경계값

Jump 상태인 캐릭터가 착지해 IsFalling이 거짓이 되었고, 속도는 (6, 8, 0) cm/s입니다. 평면 속도는 얼마이며, Idle과 Walk_Run 가운데 어디로 돌아가는 조건이 참일까요? 그 뒤 속도가 0이 되고 낙하하지 않는다면 어느 전이가 가능할까요?

해설 보기

평면 속도는 √(6² + 8²) = 10cm/s입니다. 10은 < 10 조건에는 포함되지 않고 ≥ 10 조건에 포함되므로, Jump에서 Walk_Run으로 가는 전이가 참입니다.

그 뒤 지상에서 속도가 0이 되면 0 < 10이므로 Walk_Run에서 Idle로 가는 전이가 참이 됩니다. 실제 포즈에는 전이 블렌드와 클립, 블렌드 스페이스 설정도 함께 적용됩니다.

2. 공격 상태는 구현되었을까

헤더에 bIsAttacking을 선언했고, 애니메이션 블루프린트에서도 Is Attacking 값을 읽어 변수에 저장했습니다. 이것으로 공격 애니메이션이 재생될까요?

해설 보기

재생되지 않습니다. 이 글의 UpdateAnimationVariables에는 bIsAttacking을 갱신하는 코드가 없으므로 값은 계속 처음 값인 거짓으로 남습니다. 또 스테이트 머신에 Attack 상태와 그리로 가는 전이도 만들지 않았습니다.

공격을 구현하려면 공격 입력에서 bIsAttacking을 바꾸는 로직, Attack 상태와 전이선, 그리고 공격이 끝났을 때 돌아오는 조건을 모두 만들어야 합니다. 변수를 선언하고 읽는 것은 그 준비일 뿐입니다.

전이선의 방향과 bool 조건, 남은 재생 시간, 우선순위는 Epic의 Transition Rules 문서에서 확인할 수 있습니다.

이것으로 3장을 마칩니다. 3장에서는 Pawn, Character, Controller의 역할과 빙의, 향상된 입력으로 입력을 연결하는 방법, 컴포넌트로 카메라를 붙이고 회전을 나누는 방법, 그리고 C++ 값을 애니메이션 블루프린트의 스테이트 머신에 전달하는 방법까지 게임플레이의 기본 흐름을 익혔습니다. 다음 장의 씬 구성 요소 - Light, Camera, Mesh부터는 조명, 카메라, 메시로 월드를 꾸미고, 레벨 블루프린트와 C++의 역할, 액터의 생성과 관리, 레벨 스트리밍과 World Partition을 다룹니다.