
3. 관절 제어 확인
- date
- 2월 19, 2026
- status
- Public
- tags
- unrealRL
1. 소개#
저번에 이어서 이번에는 다리 관절 하나하나를 제어할 수 있는 구조인지 확인해보도록 하자. 왜냐하면 강화학습을 진행하기 위해서는 이후에 AI 모델이
- 앞다리 첫 관절을 들어라
- 4 번째 두번째 관절을 30도 접어라 같은
명령을 줄 수 있어야 하기 때문이다.
2. 관절 제약(Constraint) 구조 확인#
2.1. 관절 제약(Constraint) 구조 확인#
Physics Asset 열기 → 다리 하나를 기준으로 스켈레톤 트리 확인

스켈레톤 트리란?
스켈레톤의 계층 구조/물리 바디/프리미티브/컨스트레인트 출력 창
밑의 화면에는 컨스트레인트 그래프(Constraints Graph)도 확인할 수 있는데 이는,

Physics Asset 안에서 “어떤 물리 바디가 어떤 관절 제약으로 연결되어 있는지”를 도식으로 보여주는 창
- 노란색 박스 : 컨스트레인트
- 초록 박스들 : 물리 바디
로 이루어져 있고, 추후에 관절 하나하나에 모터처럼 목표 각도를 주고, 그걸 강화학습의 Action으로 제어하는 것이 목적이다. 이 그래프를 이용해 나중에
- 어떤 관절을 움직일 지
- 다리 1개에 관절이 몇 개 있는지
- 몸통과 다리가 어떻게 연결되어 있는지
를 파악한다.
여기서 최종적으로 제어 대상이 되는 것이 바로 이 노란 컨스트레인트들이다.
2.2. 코드로 관절 상세 정보 확인#
편집 → 플러그인 → Python Editor Script Plugin 설치

파이썬 코드 작성
dump_physics_constraints.py
import unreal
def find_selected_physics_asset():
selected_assets = unreal.EditorUtilityLibrary.get_selected_assets()
for asset in selected_assets:
if isinstance(asset, unreal.PhysicsAsset):
return asset
return None
def dump_constraints(physics_asset: unreal.PhysicsAsset) -> None:
constraints = physics_asset.get_constraints(False)
unreal.log("=" * 80)
unreal.log(f"[PhysicsAsset] {physics_asset.get_name()}")
unreal.log(f"[Constraint Count] {len(constraints)}")
unreal.log("=" * 80)
rows = []
for index, accessor in enumerate(constraints, start=1):
_, parent_body, child_body = (
unreal.ConstraintInstanceBlueprintLibrary.get_attached_body_names(accessor)
)
parent_name = str(parent_body)
child_name = str(child_body)
rows.append((parent_name, child_name))
unreal.log(f"{index:02d}. {parent_name} <-> {child_name}")
unreal.log("=" * 80)
unreal.log("[Grouped by parent body]")
grouped = {}
for parent_name, child_name in rows:
grouped.setdefault(parent_name, []).append(child_name)
for parent_name in sorted(grouped.keys()):
children = ", ".join(sorted(grouped[parent_name]))
unreal.log(f"- {parent_name}: {children}")
unreal.log("=" * 80)
def main():
physics_asset = find_selected_physics_asset()
if physics_asset is None:
unreal.log_error(
"Physics Asset을 콘텐츠 브라우저에서 먼저 선택한 뒤 다시 실행하세요."
)
return
dump_constraints(physics_asset)
main()피직스 에셋 선택 → 툴 창 → 파이썬 스크립트 실행

결과 확인 : 창 → 출력 로그
Cmd: py "../../../../../../WorkSpace/Project/UnrealProject/SpiderRL/SpiderRL/Content/Scripts/dump_physics_constraints.py"
LogPython: ================================================================================
LogPython: [PhysicsAsset] SK_ISO_Mech_PhysicsAsset
LogPython: [Constraint Count] 29
LogPython: ================================================================================
LogPython: 01. Root_M <-> BackLeg1_R
LogPython: 02. BackLeg1_R <-> BackLeg2_R
LogPython: 03. BackLeg2_R <-> BackLeg3_R
LogPython: 04. BackLeg3_R <-> BackLeg4_R
LogPython: 05. Root_M <-> MiddleLeg1_R
LogPython: 06. MiddleLeg1_R <-> MiddleLeg2_R
LogPython: 07. MiddleLeg2_R <-> MiddleLeg3_R
LogPython: 08. MiddleLeg3_R <-> MiddleLeg4_R
LogPython: 09. Root_M <-> FrontLeg1_R
LogPython: 10. FrontLeg1_R <-> FrontLeg2_R
LogPython: 11. FrontLeg2_R <-> FrontLeg3_R
LogPython: 12. FrontLeg3_R <-> FrontLeg4_R
LogPython: 13. Root_M <-> Top_M
LogPython: 14. Top_M <-> ShotgunTop_R
LogPython: 15. Top_M <-> ShotgunBot_R
LogPython: 16. Top_M <-> ShotgunTop_L
LogPython: 17. Top_M <-> ShotgunBot_L
LogPython: 18. Root_M <-> BackLeg1_L
LogPython: 19. BackLeg1_L <-> BackLeg2_L
LogPython: 20. BackLeg2_L <-> BackLeg3_L
LogPython: 21. BackLeg3_L <-> BackLeg4_L
LogPython: 22. Root_M <-> MiddleLeg1_L
LogPython: 23. MiddleLeg1_L <-> MiddleLeg2_L
LogPython: 24. MiddleLeg2_L <-> MiddleLeg3_L
LogPython: 25. MiddleLeg3_L <-> MiddleLeg4_L
LogPython: 26. Root_M <-> FrontLeg1_L
LogPython: 27. FrontLeg1_L <-> FrontLeg2_L
LogPython: 28. FrontLeg2_L <-> FrontLeg3_L
LogPython: 29. FrontLeg3_L <-> FrontLeg4_L
LogPython: ================================================================================
LogPython: [Grouped by parent body]
LogPython: - BackLeg1_L: BackLeg2_L
LogPython: - BackLeg1_R: BackLeg2_R
LogPython: - BackLeg2_L: BackLeg3_L
LogPython: - BackLeg2_R: BackLeg3_R
LogPython: - BackLeg3_L: BackLeg4_L
LogPython: - BackLeg3_R: BackLeg4_R
LogPython: - FrontLeg1_L: FrontLeg2_L
LogPython: - FrontLeg1_R: FrontLeg2_R
LogPython: - FrontLeg2_L: FrontLeg3_L
LogPython: - FrontLeg2_R: FrontLeg3_R
LogPython: - FrontLeg3_L: FrontLeg4_L
LogPython: - FrontLeg3_R: FrontLeg4_R
LogPython: - MiddleLeg1_L: MiddleLeg2_L
LogPython: - MiddleLeg1_R: MiddleLeg2_R
LogPython: - MiddleLeg2_L: MiddleLeg3_L
LogPython: - MiddleLeg2_R: MiddleLeg3_R
LogPython: - MiddleLeg3_L: MiddleLeg4_L
LogPython: - MiddleLeg3_R: MiddleLeg4_R
LogPython: - Root_M: BackLeg1_L, BackLeg1_R, FrontLeg1_L, FrontLeg1_R, MiddleLeg1_L, MiddleLeg1_R, Top_M
LogPython: - Top_M: ShotgunBot_L, ShotgunBot_R, ShotgunTop_L, ShotgunTop_R
LogPython: ================================================================================
LogAutomationController: Ignoring very large delta of 24.93 seconds in calls to FAutomationControllerManager::Tick() and not penalizing unresponsive tests
LogStatusBar: Looking status bar with open content browser drawer...
LogStatusBar: Using status bar: LevelEditor.StatusBar to dock content browser
LogEOSSDK: LogEOS: Updating Product SDK Config, Time: 3910.609863
LogEOSSDK: LogEOS: SDK Config Product Update Request Completed - No Change
LogEOSSDK: LogEOS: ScheduleNextSDKConfigDataUpdate - Time: 3910.842529, Update Interval: 311.902222- 데이터를 확인해 보면 로봇의 전체 구조를 확인할 수 있다
로봇의 전체 구조#
스켈레톤 트리
flowchart TD
Root[Root_M<br/>머리/전방 헤드 쪽 Body] --> Top[Top_M<br/>실제 몸통/상부 섀시 Body]
Root --> FL[FrontLeg1_L/R]
Root --> ML[MiddleLeg1_L/R]
Root --> BL[BackLeg1_L/R]
Top --> Gun1[ShotgunTop_L/R]
Top --> Gun2[ShotgunBot_L/R]- 우선적으로는 보행 1차 단계에서는 다리만 제어하기로 하고 나중에 전투쪽에서 고도화할 때 한 번 넣어보자
2.3. 다리 관절 1개만 수동 제어 해보기#
1. Physics Asset 창으로 다시 이동#
컨스트레인트 그래프에서 아래 관절을 하나 고르자
FrontLeg1_R <-> FrontLeg2_R오른쪽 앞다리의 첫 번째 굽힘 관절
2. 해당 Constraint 선택#
그래프에서
FrontLeg1_R - FrontLeg2_R노란색 컨스트레인트 박스를 클릭하면, 오른쪽 디테일 패널이 컨스트레인트 설정으로 바뀐다
2.4. 컨스트레인트 디테일 패널 분석#

해당 디테일 패널은 선택한 선택한 컨스트레인트
FrontLeg2_R ↔ FrontLeg1_R하나의 물리 성질을 전부 조정하는 곳쉽게 말하면,
“이 관절이 어디에 있고, 얼마나 움직일 수 있고, 흔들리면 어떻게 보정하고, 모터로 어떻게 움직일지”
를 설정하는 패널
상세 정보#
1. 조인트 이름 / 자손 본 / 부모 본 이름 ⇒ 부모/자식 스텔레톤

| 항목 | 의미 |
|---|---|
| 조인트 이름 | 이 컨스트레인트의 이름 |
| 자손 본 이름 | 이 관절이 제어하는 아래쪽 본. 지금은 FrontLeg2_R |
| 부모 본 이름 | 기준이 되는 위쪽 본. 지금은 FrontLeg1_R |
FrontLeg1_R을 기준으로 FrontLeg2_R이 어떻게 움직일지 정하는 설정
2. Constraint Transforms ⇒ 관절 위치/회전축 방향

관절의 기준 위치와 기준 회전축을 정하는 곳
| 항목 | 의미 |
|---|---|
| 자손 위치 / 회전 | 자손 본 쪽에서 본 컨스트레인트 기준점 |
| 부모 위치 / 회전 | 부모 본 쪽에서 본 컨스트레인트 기준점 |
쉽게 비유하면
“경첩이 정확히 어디에 달려 있고, 어느 축으로 접히는가?”
를 정하는 값
예를 들어 무릎 관절이라면, 이 축이 실제 무릎이 접히는 방향과 맞아야 자연스럽게 움직임
축이 잘못되어 있으면 옆으로 꺾이거나 비틀리는 이상한 관절이 될 수 있음. Physics Asset에서 컨스트레인트의 위치와 회전은 실제 관절 중심과 회전축을 정하는 핵심 설정
3. Constraint Behavior ⇒ 관절 자체의 일반적인 동작 방식 정의

관절 자체의 일반적인 동작 방식을 정하는 영역
3-1. 콜리전 비활성화#
부모 본과 자손 본이 서로 충돌할지 말지 정의
- 체크 ON: 이 관절로 연결된 두 바디끼리는 충돌하지 않음
- 체크 OFF: 서로 충돌 가능
보통 연결된 뼈끼리는 충돌을 끄는 경우가 많음
무릎 윗마디와 아랫마디가 붙어 있는데 서로 계속 충돌까지 하면 덜덜 떨 수 있음
3-2. 부모 도미네이트#
부모 바디가 자손 바디의 움직임에 영향을 덜 받게 하는 옵션
- 체크 OFF: 부모와 자손이 서로 힘을 주고받음
- 체크 ON: 자손이 흔들려도 부모는 상대적으로 영향을 덜 받음
예를 들어, 몸통 ↔ 다리 관절에서 부모 도미네이트를 켜면, 다리가 흔들려도 몸통이 덜 흔들릴 수 있음
다만 너무 남용하면 물리 반응이 부자연스러워질 수 있음. 공식 문서도 부모 도미네이트가 켜지면 자식의 운동이 부모에 영향을 주지 않는다고 설명(unreal.ConstraintProfileProperties — Unreal Python 5.7 (Experimental) documentation)
4. 프로젝션

관절이 계산 오차로 벌어지거나 틀어졌을 때 강제로 다시 맞춰주는 보정 기능
| 항목 | 의미 |
|---|---|
| 투영 활성화 | Projection 보정 사용 여부 |
| 투영 선형 허용치 | 위치 오차가 이 거리보다 커지면 강제로 붙임 |
| 투영 각도 허용치 | 각도 오차가 이 각도보다 커지면 강제로 바로잡음 |
| 투영 선형 알파 | 작은 위치 오차를 얼마나 강하게 보정할지 |
| 투영 각도 알파 | 작은 회전 오차를 얼마나 강하게 보정할지 |
비유하면:
“관절이 계산상 조금 벌어졌으면, 그냥 두지 말고 다시 붙여라.”
라는 안전장치(Physics Constraint Reference | Unreal Engine 4.27 Documentation | Epic Developer Community)
주의#
Projection은 관절을 단단하게 유지하는 데 도움을 주지만, 충돌이 복잡한 체인에서 너무 세게 쓰면 튐이나 지터를 유발할 수 있음. 공식 문서도 여러 제약이나 충돌과 함께 쓰면 예상치 못한 결과가 날 수 있다고 설명(unreal.ConstraintProfileProperties — Unreal Python 5.4 (Experimental) documentation)
지금은?#
체크돼 있는 상태 그대로 둬도 돼.
5. 쇼크 프로퍼게이션

이건 긴 관절 사슬이 흐물흐물해지는 걸 줄이기 위한 보정
예를 들어 다리가
몸통 → 허벅지 → 종아리 → 발처럼 이어질 때, 가장 끝쪽에서 힘이 걸리면 전체 체인이 불안정하게 흔들릴 수 있음
이때 부모 쪽을 일시적으로 더 무겁게 취급해서 관절 체인을 단단하게 만드는 옵션이 Shock Propagation
| 항목 | 의미 |
|---|---|
| 쇼크 프로퍼게이션 활성화 | 해당 보정 사용 여부 |
| 알파 | 효과 강도 |
주의#
이것도 너무 세면 에너지가 관절 체인으로 역류해서 오히려 흔들림이나 지터가 커질 수 있음
6. 질량 컨디셔닝 활성화
이 옵션은 서로 연결된 바디들의 질량 차이가 너무 클 때 계산을 안정화하는 용도
예를 들어:
- 몸통은 매우 무겁고
- 다리 끝은 매우 가볍다면
관절 계산이 불안정해질 수 있는데,
질량 컨디셔닝은 그런 경우에 솔버가 더 안정적으로 처리하도록 돕는 기능
공식 제약 프로퍼티에는 질량 비율 문제를 완화하는 안정화 옵션들이 포함(unreal.ConstraintProfileProperties — Unreal Python 5.7 (Experimental) documentation)
7. 선형 조인트 솔버 사용
이건 관절 계산 방식에 관련된 고급 옵션
- 선형 솔버: 더 빠름, 대신 정확도는 낮음
- 비선형 솔버: 더 느리지만 수렴성과 정확도가 나음
공식 5.7 Python API 문서에서도 use_linear_joint_solver는 빠르지만 덜 정확한 솔버라고 설명(unreal.ConstraintProfileProperties — Unreal Python 5.7 (Experimental) documentation)
8. Linear Limits

두 바디가 서로 직선 방향으로 얼마나 떨어질 수 있는지 정하는 설정
축별 의미#
| 상태 | 의미 |
|---|---|
| Free | 자유롭게 이동 가능 |
| Limited | 정한 범위 안에서만 이동 |
| Locked | 직선 이동 금지 |
| 항목 | 의미 |
|---|---|
| X 모션 | X축 방향 직선 이동 |
| Y 모션 | Y축 방향 직선 이동 |
| Z 모션 | Z축 방향 직선 이동 |
지금 화면에서는:
X Locked
Y Locked
Z Locked이므로, 이 관절은 서로 떨어지거나 미끄러지는 직선 이동은 금지되어 있음.
9. Angular Limits

관절이 어느 방향으로 얼마나 회전할 수 있는지 정하는 핵심 영역
세 가지 회전 축#
| 항목 | 대략적 의미 |
|---|---|
| Swing 1 | 한 방향으로 젖힘 |
| Swing 2 | 다른 평면 방향으로 젖힘 |
| Twist | 축을 중심으로 비틀기 |
각각도( Free / Limited / Locked) 중 하나로 설정가능
현재 선택한 관절은:
Swing 1: Limited 45°
Swing 2: Limited 45°
Twist: Limited 45°즉, 세 방향 모두 최대 45도 범위 안에서 회전 가능한 상태. 공식 레퍼런스도 Swing1, Swing2, Twist 각각에 대해 모션 타입과 제한 각도를 둔다고 설명(Set Angular Limits | Unreal Engine 5.7 Documentation | Epic Developer Community)
10. 스윙 한계 / 트위스트 한계 아래의 소프트 컨스트레인트

- 소프트 컨스트레인트
- 강성
- 댐핑
- 복원력
은 각도 제한 끝에 도달했을 때 딱딱하게 막을지, 스프링처럼 부드럽게 밀어낼지를 조정하는 값
예시#
- 하드 리밋: 45도에 닿으면 딱 멈춤
- 소프트 리밋: 45도 근처에서 점점 저항이 커짐
지금은 소프트 컨스트레인트가 켜져 있고:
강성 50
댐핑 5이니까, 제한 범위를 넘어서려 할 때 약간 스프링처럼 저항하는 방식이라고 이해하면 됨
11. 선형 모터
12. 각 모터

우리가 지금 실제로 만질 곳
이건 관절을 목표 각도나 목표 각속도로 움직이게 하는 회전 모터
강화학습에서 나중에 액션 값을 넣을 핵심 제어부도 사실상 이쪽을 대상으로 수행해야 함
12-1. 각 드라이브 모드#
| 모드 | 의미 |
|---|---|
| SLERP | 목표 회전으로 가장 짧은 회전 경로를 따라감 |
| Twist And Swing | Twist와 Swing 축을 나눠서 제어 |
SLERP는 단순하게 쓰기 좋지만, 회전 축 중 하나라도 Locked면 동작하지 않는다는 제약이 있음. 지금 관절은 세 축 모두 Limited라 SLERP 테스트가 가능
12-2. Acceleration Mode#
이 옵션을 켜면 모터의 회전 가속도가 관절 주변 물체의 관성에 덜 영향을 받도록 동작함
즉, 질량이나 관성 차이 때문에 모터 반응이 지나치게 둔해지는 걸 완화하는 방향
12-3. 타깃 오리엔테이션#
이건
“이 관절을 어느 목표 회전 자세로 가져갈 것인가?”
를 의미
관절을 현재 기준에서 특정 회전 상태로 보내 보려는 것. 공식 API도 Angular Orientation Target을 목표 회전값으로 설정한다고 설명(Set Angular Limits | Unreal Engine 5.7 Documentation | Epic Developer Community)
12-4. 드라이브 체크박스#
어느 방식의 회전 드라이브를 켤지 정하는 체크
SLERP 모드일 때#
- 스피어… 체크를 켜는 게 핵심
Twist And Swing 모드일 때#
- 트위스트
- 스윙
을 각각 씀
그래서 지금 SLERP 모드에서는 스피어…만 체크
12-5. 세기#
Position Strength, 즉 목표 각도로 끌고 가려는 힘의 강도
- 낮으면: 거의 안 움직임
- 높으면: 목표 각도를 강하게 유지하려 함
공식 API에서는 position_strength를 드라이브의 강성(stiffness)으로 설명(unreal.PhysicsConstraintComponent — Unreal Python 5.0 (Experimental) documentation)
12-6. 타깃 속도#
이건 목표 각도 대신
“이 속도로 계속 회전해라”
에 가까운 제어
지금 우리가 하는 건 목표 각도로 움직이는 테스트이므로 건드리지 않아도 됨
12-7. 감쇠#
공식 API의 velocity_strength, 즉 댐핑
- 낮으면: 목표 각도를 지나쳐서 흔들릴 수 있음
- 높으면: 진동을 억제하고 덜 출렁임
관절이 로봇 다리처럼 빨리 반응하면서도 덜 떨리게 하려면 나중에 중요해
12-8. 최대 힘#
모터가 사용할 수 있는 힘의 상한
- 낮으면: 목표 각도까지 못 버틸 수 있음
- 높으면: 더 강하게 밀 수 있음
공식 API에서는 force_limit이 모터가 적용할 수 있는 최대 힘이라고 설명
지금 화면에서는 0.0으로 보이는데, 언리얼 설정에서 이 값은 제한 없음으로 취급되는 경우가 일반적이지만, 이 부분은 프로젝트 물리 설정과 엔진 구현 맥락에 따라 확인이 필요해서 지금은 건드리지 않고 두는 게 안전
지금 여기서 우리가 할 조작은 각 모터를 이용해서 다리를 직접 조작해보는 것이다
2.5. 관절 조작 테스트#
이 관절이 모터로 실제 움직이는지 확인하는 1차 테스트
관절 중, 컨스트레인트 FrontLeg2_R ↔ FrontLeg1_R 선택

디테일 패널의 각 모터 부분 → 타깃오리엔테이션 → 스피어 체크박스 on 후, y축 30도 설정

시뮬레이트 실행
이제 실제로 키보드 입력으로 직접 관절을 까딱거리게 만들어보자.
예를 들면:
Q누르면 관절 목표 각도+30도E누르면 관절 목표 각도30도
이렇게 해서 플레이 중 실제 로봇 다리 한 마디가 움직이는지 확인해서 강화학습 모델이 실제로 움직임을 제어할 수 있는지를 테스트 해보도록 해보자
2.6. 블루프린트 액터 만들기#
콘텐츠 브라우저 - 로봇 폴더 선택 후 우클릭 → 블루프린트 클래스 → Actor 선택

이름은 BP_SpiderRobot으로 하자

더블 클릭으로 연 후, 왼쪽 컴포넌트 창에 DefaultSceneRoot만 있는 것을 확인

왼쪽 컴포넌트 패널에서 추가 클릭 → Skeletal Mesh 검색 후 추가 → 이름 SpiderMesh로 변경

실제 로봇 메시 할당하기 위해 추가한 메시 컴포넌트 선택 → 디테일 패널에서 스켈레탈 매시 에셋에 실제 거미 로봇 메시 할당하면 뷰포트에 로봇 스켈레톤 매시 보여짐

2.7. 매쉬 컴포넌트에 피직스 설정 적용#
디테일 패널 → 피직스→ 피직스 시뮬레이트 적용

디테일 패널 → 콜리전 → 콜리전 프리셋을 NoCollision에서 PhsicsActor로 변경

왜
PhysicsActor냐?#현재 생성한
SpiderMesh는 지금물리 시뮬레이션을 하고
중력을 받고
바닥과 부딪혀야 하는
물리 액터임
PhysicsActor프리셋은 이런 물리 오브젝트에 쓰기 적합한 기본 프리셋이고, 보통:Collision Enabled = Query and Physics
Object Type = PhysicsBody
월드 지형 같은
WorldStatic과 충돌
하는 방향으로 설정되어 있음. 공식 충돌 레퍼런스에서도
PhysicsActor는 물리 시뮬레이션 액터용 프리셋으로 정리되어 있음(Collision Response Reference in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community)변경 후 컴파일, 저장
2.6.3. 블루프린트 모델을 맵에 배치
- 기존 맵으로 돌아가서 블루프린트 모델을 맵에다 드래그 앤 드롭 후 플레이 실행 시, 푹 꺼지면 블루프린트 내부에서 물리 시뮬레이션이 정상동작한다는 것을 의미



