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 Scale | Local Scale |
| Component Location | Skeletal Mesh Component 기준 위치 |
| Component Rotation | Skeletal Mesh Component 기준 회전 |
| Idle 대비 Delta Location | 기본 자세 대비 위치 변화 |
| Idle 대비 Delta Rotation | 기본 자세 대비 회전 변화 |
| Height | Component 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 Scale | Bone Scale |
| Component Location | Skeletal Mesh Component 기준 위치 |
| Component Rotation | Skeletal 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 Location | Skeletal 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 | 애니메이션 시간 |
phase | 0.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_012.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 = 202.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_ExportSpiderAnimationDataEUO_ExportSpiderAnimationData를 열고 함수 하나를 만든다.
Functions
→ + Function
→ 이름: RunExportRunExport 그래프에서 ExportSpiderAnimationMotionData 노드를 추가한다.
입력값은 다음과 같이 지정한다.
| 입력핀 | 값 |
|---|---|
Idle Animation | A_ISO_Mech_Idle_01 |
Target Animation | A_ISO_Mech_WalkForward_01 |
Output Relative Path | SpiderAnimExtract/walk_forward_motion.json |
Sample Count | 20 |
Blueprint 연결 구조는 다음과 같다.
RunExport / 실행 출력핀
→ ExportSpiderAnimationMotionData / 실행 입력핀반환값은 Branch와 Print String으로 확인한다.
ExportSpiderAnimationMotionData / Return Value
→ Branch / Condition성공 시에는 다음 문자열을 출력한다.
Export Success실패 시에는 다음 문자열을 출력한다.
Export Failed2.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 Delta | Idle 대비 변화량이 계산되는가 |
| Joint Min / Max | 관절별 움직임 범위가 정리되는가 |
| Foot Height | 발 끝 높이 변화가 확인되는가 |
| Body Rotation | Root_M, Top_M의 Roll/Pitch 변화가 확인되는가 |
특히 다음 항목을 중점적으로 본다.
각 관절이 어느 축을 중심으로 움직이는가
좌우 다리의 움직임이 대칭적인가
Front / Middle / Back 다리의 phase 차이가 있는가
발 끝 높이가 주기적으로 올라갔다 내려오는가
Top_M이 보행 중 얼마나 흔들리는가
특정 Bone의 Delta 값이 비정상적으로 큰가이 확인이 끝난 뒤에야 DA_SpiderJointConfig에 값을 반영한다.
2.13. 이후 DA_SpiderJointConfig 반영 방향#
추출 결과는 최종적으로 다음 값들을 정하는 데 사용한다.
| 항목 | 반영 기준 |
|---|---|
BaseTargetOrientation | Idle 기준 Joint Rotation |
ActionScale | Joint별 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_SpiderJointConfig의 BaseTargetOrientation, ActionScale, PhaseOffset을 조정하고, SpiderJointControllerComponent에서 phase 기반 TargetPose를 적용하는 구조로 확장한다.