20. 애니메이션 기반 관절 움직임 추출 설계

date
4월 8, 2026
status
완료
tags
unrealRL

1. 소개#

앞 단계에서는 BP_SpiderAnimPreviewPawn, ABP_SpiderPreview, BS_SpiderMove를 구성하여 기존 거미 로봇 애니메이션을 Preview 레벨에서 재생할 수 있는 구조를 만들었다.

이 구조를 통해 Idle, WalkForward, WalkBackward, Walk_L, Walk_R, Landing 애니메이션의 자세와 다리 움직임을 확인할 수 있다.

이번 단계에서는 이 애니메이션에 포함된 움직임 정보를 강화학습용 관절 제어 기준 데이터로 변환하는 구조를 설계한다.

기존 이동 애니메이션은 로봇이 전진하거나 방향을 바꿀 때 각 다리 관절이 어떤 순서와 범위로 움직이는지를 포함하고 있다. 이 정보를 이용하면 강화학습에서 관절 Action을 완전히 무작위로 시작하는 대신, 기존 애니메이션의 움직임을 기준으로 초기 관절 목표값과 보행 Phase를 만들 수 있다.

다만 추출 대상은 단순히 관절 회전값만으로 제한하지 않는다.

강화학습에서 애니메이션을 기준 데이터로 사용하려면 관절 회전뿐 아니라 몸체 자세, 발 끝 위치, 발 높이 변화, Idle 대비 변화량, 보행 Phase까지 함께 확인해야 한다.

따라서 이번 단계에서는 애니메이션 데이터를 다음과 같은 Motion Data로 분류하여 추출한다.

flowchart TD
    Anim[기존 이동 애니메이션] --> Motion[Animation Motion Data]
    Motion --> Body[Body 데이터<br/>Root_M / Top_M]
    Motion --> Joint[Joint 데이터<br/>24개 다리 관절]
    Motion --> Foot[Foot 데이터<br/>6개 발 끝 위치]
    Motion --> Phase[Phase 데이터<br/>time / phase]
    Body --> Reward[자세 안정 기준]
    Joint --> Target[기준 Joint Target]
    Joint --> Range[관절별 움직임 범위]
    Foot --> Gait[보행 Phase / Swing / Stance 분석]
    Target --> Controller[SpiderJointControllerComponent]
    Range --> Config[DA_SpiderJointConfig]
    Gait --> RL[PPO Action 보정]
    Controller --> RL
    Config --> RL

최종적으로 추출된 데이터는 DA_SpiderJointConfig, SpiderJointControllerComponent, PPO Action 구조에 연결하기 위한 기준 데이터로 사용한다.


2. 구현#

이번 구현에서는 기존 애니메이션을 직접 학습에 연결하지 않고, 먼저 애니메이션에서 Motion Data를 추출하는 구조를 만든다.

구현 순서는 다음과 같이 진행한다.

flowchart TD
    Step1[추출 대상 데이터 분류] --> Step2[추출 대상 Animation Sequence 선정]
    Step2 --> Step3[C++ 추출용 Function Library 생성]
    Step3 --> Step4[Motion Data JSON 구조 정의]
    Step4 --> Step5[Editor Utility에서 추출 함수 실행]
    Step5 --> Step6[JSON 출력 구조 확인]
    Step6 --> Step7[실제 Animation Sequence 샘플링 구현]
    Step7 --> Step8[Idle 대비 Delta Transform 계산]
    Step8 --> Step9[관절별 Min / Max 및 Foot 궤적 요약]
    Step9 --> Step10[DA_SpiderJointConfig 반영값 검토]

2.1. 추출 대상 데이터 분류#

애니메이션을 강화학습용 기준 데이터로 사용하기 위해 추출 대상을 다음 네 그룹으로 나눈다.

그룹대상사용 목적
Body 데이터Root_M, Top_M몸체 위치, 높이, Roll/Pitch 안정성 기준
Joint 데이터24개 다리 관절 Bone기준 관절 자세, 움직임 범위, ActionScale 후보
Foot 데이터6개 발 끝 Bone발 높이, 발 궤적, Swing/Stance 분석
Phase 데이터sample_index, time, phase보행 주기 기준, Phase 기반 TargetPose 생성

초기에는 관절 Rotation만 추출하는 방식도 가능하지만, 이 경우 발이 언제 들리고 내려가는지, 몸체가 얼마나 흔들리는지, 보행 Phase가 어떻게 형성되는지를 확인하기 어렵다.

따라서 처음부터 전체 Motion Data를 추출할 수 있는 구조로 설계한다.


2.2. Body 데이터#

Body 데이터는 로봇의 중심 자세와 상부 몸체 움직임을 확인하기 위해 사용한다.

추출 대상은 다음과 같다.

Root_M
Top_M

각 Body Bone에서 추출할 값은 다음과 같다.

항목설명
Local Location부모 Bone 기준 위치
Local Rotation부모 Bone 기준 회전
Local ScaleLocal Scale
Component LocationSkeletal Mesh Component 기준 위치
Component RotationSkeletal Mesh Component 기준 회전
Idle 대비 Delta Location기본 자세 대비 위치 변화
Idle 대비 Delta Rotation기본 자세 대비 회전 변화
HeightComponent Space 기준 Z 높이
Roll / Pitch / Yaw자세 안정 분석용 회전값

Root_M은 다리 6개가 붙어 있는 하부 중심 Body로 보고, 전방 이동이나 몸체 중심 기준을 분석할 때 사용한다.

Top_M은 상부 차체의 기울기와 자세 안정성을 분석하는 기준으로 사용한다.


2.3. Joint 데이터#

Joint 데이터는 실제 관절 제어 기준값을 만들기 위해 사용한다.

현재 거미 로봇은 6개의 다리를 가지고 있으며, 각 다리는 4개의 주요 관절로 구성되어 있다.

따라서 처음에는 다음 24개 Bone을 Joint 데이터 추출 대상으로 사용한다.

FrontLeg1_L
FrontLeg2_L
FrontLeg3_L
FrontLeg4_L

MiddleLeg1_L
MiddleLeg2_L
MiddleLeg3_L
MiddleLeg4_L

BackLeg1_L
BackLeg2_L
BackLeg3_L
BackLeg4_L

FrontLeg1_R
FrontLeg2_R
FrontLeg3_R
FrontLeg4_R

MiddleLeg1_R
MiddleLeg2_R
MiddleLeg3_R
MiddleLeg4_R

BackLeg1_R
BackLeg2_R
BackLeg3_R
BackLeg4_R

각 Joint Bone에서 추출할 값은 다음과 같다.

항목설명
Local Location부모 Bone 기준 위치
Local Rotation부모 Bone 기준 회전
Local ScaleBone Scale
Component LocationSkeletal Mesh Component 기준 위치
Component RotationSkeletal Mesh Component 기준 회전
Idle 대비 Delta Location기본 자세 대비 위치 변화
Idle 대비 Delta Rotation기본 자세 대비 회전 변화

이 Joint 데이터는 이후 SpiderJointControllerComponent에서 사용하는 24개의 Continuous Action과 대응된다.

다만 Bone Rotation과 Physics Constraint의 Target Orientation은 좌표계가 다를 수 있으므로, 추출 결과를 바로 Constraint Target으로 사용하지는 않는다.

먼저 각 관절이 어느 축으로 얼마나 움직이는지 확인하고, 그 결과를 BaseTargetOrientation, ActionScale, ControlledAxis 후보로 사용한다.


2.4. Foot 데이터#

Foot 데이터는 보행 Phase와 발 궤적을 분석하기 위해 사용한다.

우선 각 다리의 끝 관절인 Leg4 계열 Bone을 발 끝 기준으로 사용한다.

FrontLeg4_L
MiddleLeg4_L
BackLeg4_L

FrontLeg4_R
MiddleLeg4_R
BackLeg4_R

각 Foot Bone에서 추출할 값은 다음과 같다.

항목설명
Component LocationSkeletal Mesh Component 기준 발 위치
Height발 끝의 Z 높이
Idle 대비 Delta Location기본 자세 대비 발 위치 변화
Foot Velocity이전 샘플 대비 발 위치 변화량
Min Height / Max Height보행 중 발 높이 범위
Swing / Stance 후보발이 들리는 구간과 낮게 유지되는 구간

발 끝 위치를 확인하면 단순히 관절이 몇 도 움직였는지보다 더 직접적으로 보행 패턴을 분석할 수 있다.

예를 들어 발 끝 높이가 올라가는 구간은 Swing Phase 후보가 되고, 발 끝 높이가 낮게 유지되는 구간은 Stance Phase 후보가 된다.

발 끝 높이 증가
→ Swing Phase 후보

발 끝 높이 낮게 유지
→ Stance Phase 후보

이 정보는 이후 발 접촉 보상, 보행 Phase Observation, Gait Reward를 설계할 때 사용할 수 있다.


2.5. Phase 데이터#

각 샘플에는 애니메이션 시간과 Phase 값을 함께 저장한다.

Phase는 애니메이션 전체 길이에 대한 현재 시간의 비율이다.

Phase = SampleTime / AnimationLength

예를 들어 SampleCount = 20이면 다음과 같은 phase 값을 사용한다.

phase = 0.00
phase = 0.05
phase = 0.10
...
phase = 1.00

각 샘플에는 다음 값이 포함된다.

항목설명
sample_index샘플 번호
time애니메이션 시간
phase0.0 ~ 1.0 범위의 보행 주기 값

Phase 데이터는 이후 다음 구조에 사용된다.

AnimationTargetPose = SampleAnimationPose(Phase)
FinalTarget = AnimationTargetPose + RLAction * ActionScale

즉 기존 애니메이션은 Phase에 따른 기준 관절 목표값을 제공하고, PPO Action은 그 기준값을 물리 환경에 맞게 보정하는 역할을 한다.


2.6. 추출 대상 애니메이션 선정#

처음부터 모든 애니메이션을 처리하지 않고, 가장 기본이 되는 두 개의 애니메이션부터 추출한다.

A_ISO_Mech_Idle_01
A_ISO_Mech_WalkForward_01

각 애니메이션의 역할은 다음과 같다.

애니메이션역할
A_ISO_Mech_Idle_01기본 자세 기준
A_ISO_Mech_WalkForward_01전진 보행 중 관절, 몸체, 발 궤적 기준

Idle 애니메이션은 각 Bone의 기본 Transform을 얻기 위해 사용한다.

WalkForward 애니메이션은 전진 보행 중 각 Bone이 시간에 따라 어떻게 움직이는지 확인하기 위해 사용한다.

처음에는 전진 보행만 처리하고, 구조가 정리된 뒤 다음 애니메이션으로 확장한다.

A_ISO_Mech_WalkBackward_01
A_ISO_Mech_Walk_L_01
A_ISO_Mech_Walk_R_01
A_ISO_Mech_Landing_01

2.7. C++ 추출용 Function Library 생성#

애니메이션의 특정 시간 지점에서 여러 Bone의 Transform을 반복적으로 추출해야 하므로, C++ 기반의 추출용 Function Library를 만든다.

생성할 클래스 이름은 다음과 같이 정한다.

SpiderAnimationExtractionLibrary

현재 프로젝트에서는 다음 파일이 생성된다.

Source/SpiderRL/SpiderAnimationExtractionLibrary.h
Source/SpiderRL/SpiderAnimationExtractionLibrary.cpp

이 클래스의 역할은 다음과 같다.

Animation Sequence 입력
→ Body / Joint / Foot Bone 목록 기준으로 시간별 Transform 추출
→ Idle 대비 Delta Transform 계산
→ 관절별 Min / Max 및 발 높이 요약
→ JSON 파일로 저장

초기 함수 이름은 Rotation 전용 이름이 아니라 Motion Data 전체를 의미하도록 다음과 같이 정한다.

UFUNCTION(BlueprintCallable, CallInEditor, Category = "Spider|Animation Extraction")
static bool ExportSpiderAnimationMotionData(
    UAnimSequence* IdleAnimation,
    UAnimSequence* TargetAnimation,
    const FString& OutputRelativePath,
    int32 SampleCount
);

각 입력값의 의미는 다음과 같다.

입력값의미
IdleAnimation기준 자세로 사용할 Idle 애니메이션
TargetAnimation샘플링할 대상 애니메이션
OutputRelativePath결과 JSON 저장 경로
SampleCount애니메이션을 나눌 샘플 개수

예상 호출 값은 다음과 같다.

IdleAnimation = A_ISO_Mech_Idle_01
TargetAnimation = A_ISO_Mech_WalkForward_01
OutputRelativePath = SpiderAnimExtract/walk_forward_motion.json
SampleCount = 20

2.8. Motion Data JSON 구조 정의#

추출 결과는 JSON 파일로 저장한다.

최종 목표 출력 구조는 다음과 같다.

{
  "meta": {
    "idle_animation": "A_ISO_Mech_Idle_01",
    "target_animation": "A_ISO_Mech_WalkForward_01",
    "sample_count": 20,
    "animation_length": 1.23
  },
  "bone_groups": {
    "body": ["Root_M", "Top_M"],
    "joints": [
      "FrontLeg1_L",
      "FrontLeg2_L"
    ],
    "feet": [
      "FrontLeg4_L",
      "MiddleLeg4_L"
    ]
  },
  "samples": [
    {
      "sample_index": 0,
      "time": 0.0,
      "phase": 0.0,
      "body": {
        "Root_M": {
          "local": {
            "location": {"x": 0.0, "y": 0.0, "z": 0.0},
            "rotation": {"roll": 0.0, "pitch": 0.0, "yaw": 0.0},
            "scale": {"x": 1.0, "y": 1.0, "z": 1.0}
          },
          "component": {
            "location": {"x": 0.0, "y": 0.0, "z": 0.0},
            "rotation": {"roll": 0.0, "pitch": 0.0, "yaw": 0.0}
          },
          "delta_from_idle": {
            "location": {"x": 0.0, "y": 0.0, "z": 0.0},
            "rotation": {"roll": 0.0, "pitch": 0.0, "yaw": 0.0}
          }
        }
      },
      "joints": {
        "FrontLeg1_L": {
          "local": {
            "location": {"x": 0.0, "y": 0.0, "z": 0.0},
            "rotation": {"roll": 0.0, "pitch": 0.0, "yaw": 0.0},
            "scale": {"x": 1.0, "y": 1.0, "z": 1.0}
          },
          "component": {
            "location": {"x": 0.0, "y": 0.0, "z": 0.0},
            "rotation": {"roll": 0.0, "pitch": 0.0, "yaw": 0.0}
          },
          "delta_from_idle": {
            "location": {"x": 0.0, "y": 0.0, "z": 0.0},
            "rotation": {"roll": 0.0, "pitch": 0.0, "yaw": 0.0}
          }
        }
      },
      "feet": {
        "FrontLeg4_L": {
          "component_location": {"x": 0.0, "y": 0.0, "z": 0.0},
          "height": 0.0,
          "delta_from_idle": {
            "location": {"x": 0.0, "y": 0.0, "z": 0.0}
          }
        }
      }
    }
  ],
  "summary": {
    "joints": {
      "FrontLeg1_L": {
        "dominant_rotation_axis": "pitch",
        "min_delta_rotation": {"roll": -5.0, "pitch": -20.0, "yaw": -3.0},
        "max_delta_rotation": {"roll": 5.0, "pitch": 30.0, "yaw": 4.0}
      }
    },
    "feet": {
      "FrontLeg4_L": {
        "min_height": 0.0,
        "max_height": 15.0
      }
    }
  }
}

이 구조를 사용하면 단순 관절 회전값뿐 아니라 몸체 자세, 발 궤적, 관절별 움직임 범위까지 함께 확인할 수 있다.


2.9. Editor Utility에서 추출 함수 실행#

SpiderAnimationExtractionLibrary의 빌드가 완료되면, 에디터에서 해당 함수를 호출할 수 있는 Editor Utility Blueprint를 만든다.

Content Browser에서 Scripts 폴더로 이동한 뒤 Editor Utility Blueprint를 생성한다.

Content Browser
→ Content/Scripts
→ 우클릭
→ Editor Utilities
→ Editor Utility Blueprint

부모 클래스는 다음으로 선택한다.

EditorUtilityObject

에셋 이름은 다음과 같이 지정한다.

EUO_ExportSpiderAnimationData

EUO_ExportSpiderAnimationData를 열고 함수 하나를 만든다.

Functions
→ + Function
→ 이름: RunExport

RunExport 그래프에서 ExportSpiderAnimationMotionData 노드를 추가한다.

입력값은 다음과 같이 지정한다.

입력핀
Idle AnimationA_ISO_Mech_Idle_01
Target AnimationA_ISO_Mech_WalkForward_01
Output Relative PathSpiderAnimExtract/walk_forward_motion.json
Sample Count20

Blueprint 연결 구조는 다음과 같다.

RunExport / 실행 출력핀
→ ExportSpiderAnimationMotionData / 실행 입력핀

반환값은 BranchPrint String으로 확인한다.

ExportSpiderAnimationMotionData / Return Value
→ Branch / Condition

성공 시에는 다음 문자열을 출력한다.

Export Success

실패 시에는 다음 문자열을 출력한다.

Export Failed

2.10. 초기 JSON 출력 테스트#

처음 구현에서는 실제 Animation Sequence 샘플링을 바로 넣지 않고, 먼저 JSON 구조가 정상적으로 출력되는지 확인한다.

이 단계에서 확인할 항목은 다음과 같다.

C++ Function Library가 정상적으로 호출되는가
Editor Utility에서 Animation Sequence를 입력할 수 있는가
OutputRelativePath 기준으로 JSON 파일이 생성되는가
body / joints / feet 그룹이 출력되는가
sample_count에 맞게 samples 배열이 생성되는가
phase 값이 0.0 ~ 1.0 범위로 생성되는가

실행 후 Output Log에는 다음과 같은 로그가 출력되어야 한다.

[SpiderAnimExtract] Exported JSON: .../Saved/SpiderAnimExtract/walk_forward_motion.json

파일은 다음 경로에 생성된다.

Saved/SpiderAnimExtract/walk_forward_motion.json

이 테스트가 성공하면 C++ Function Library, Editor Utility 실행 흐름, JSON 저장 구조가 정상적으로 준비된 것이다.


2.11. 실제 Animation Sequence 샘플링 구현 방향#

초기 JSON 출력 구조가 확인되면, 다음 단계에서 실제 Animation Sequence 샘플링을 구현한다.

구현해야 할 내용은 다음과 같다.

IdleAnimation에서 Body / Joint / Foot Bone의 기준 Transform 추출
TargetAnimation을 SampleCount 기준으로 phase별 샘플링
각 phase에서 Body / Joint / Foot Bone의 Local Transform 추출
Local Transform을 Component Space Transform으로 변환
Idle 대비 Delta Transform 계산
Foot Height와 Foot Velocity 계산
Joint별 Min / Max Delta Rotation 계산
Foot별 Min / Max Height 계산

즉 현재 0.0으로 채워둔 Transform 값들을 실제 애니메이션에서 추출한 값으로 교체한다.


2.12. 추출 결과 확인 기준#

JSON 파일이 생성되면 바로 DA_SpiderJointConfig에 반영하지 않고, 먼저 값이 타당한지 확인한다.

확인할 항목은 다음과 같다.

확인 항목기준
Body 데이터Root_M, Top_M이 모두 출력되는가
Joint 데이터24개 다리 관절이 모두 출력되는가
Foot 데이터6개 발 끝 Bone이 모두 출력되는가
Sample 개수지정한 SampleCount만큼 출력되는가
Phase 값0.0 ~ 1.0 범위로 정규화되는가
Idle DeltaIdle 대비 변화량이 계산되는가
Joint Min / Max관절별 움직임 범위가 정리되는가
Foot Height발 끝 높이 변화가 확인되는가
Body RotationRoot_M, Top_M의 Roll/Pitch 변화가 확인되는가

특히 다음 항목을 중점적으로 본다.

각 관절이 어느 축을 중심으로 움직이는가
좌우 다리의 움직임이 대칭적인가
Front / Middle / Back 다리의 phase 차이가 있는가
발 끝 높이가 주기적으로 올라갔다 내려오는가
Top_M이 보행 중 얼마나 흔들리는가
특정 Bone의 Delta 값이 비정상적으로 큰가

이 확인이 끝난 뒤에야 DA_SpiderJointConfig에 값을 반영한다.


2.13. 이후 DA_SpiderJointConfig 반영 방향#

추출 결과는 최종적으로 다음 값들을 정하는 데 사용한다.

항목반영 기준
BaseTargetOrientationIdle 기준 Joint Rotation
ActionScaleJoint별 Min / Max Delta Rotation
ControlledAxis가장 크게 움직이는 회전 축
PhaseOffset발 끝 위치와 관절 움직임을 기준으로 한 다리별 보행 시점 차이
ConstraintName기존 Physics Constraint와 Bone의 연결 관계
ReferenceFootHeight보행 중 발 높이 기준
ReferenceBodyPitch/Roll보행 중 허용 가능한 몸체 기울기

예를 들어 특정 관절의 Delta Rotation 범위가 다음과 같다면:

Pitch: -30도 ~ 45도
Yaw: -5도 ~ 6도
Roll: -3도 ~ 2도

해당 관절은 Pitch 중심으로 움직이는 관절로 볼 수 있다.

이 경우 ActionScale은 Pitch 값을 중심으로 설정하고, Yaw와 Roll은 작게 제한하거나 초기 단계에서는 사용하지 않을 수 있다.


2.14. 이번 구현의 완료 기준#

이번 구현의 완료 기준은 로봇이 실제로 걷는 것이 아니다.

완료 기준은 다음과 같다.

A_ISO_Mech_Idle_01에서 Body / Joint / Foot 기준 Transform을 추출할 수 있다.
A_ISO_Mech_WalkForward_01에서 phase별 Motion Data를 추출할 수 있다.
Idle 대비 Delta Transform을 계산할 수 있다.
Joint별 Min / Max Delta Rotation을 계산할 수 있다.
Foot별 높이 변화와 궤적을 확인할 수 있다.
결과를 JSON 파일로 저장할 수 있다.

이 단계가 완료되면 다음 단계에서 JSON 결과를 기반으로 DA_SpiderJointConfigBaseTargetOrientation, ActionScale, PhaseOffset을 조정하고, SpiderJointControllerComponent에서 phase 기반 TargetPose를 적용하는 구조로 확장한다.

Be Your Own Lamp