언리얼 엔진에서 모션 매칭을 세팅하다 보면
포즈 서치 스키마(Pose Search Schema)의 내부 데이터를 확인해야 하는 순간이 온다.
채널별 가중치는 얼마인지, 샘플 레이트는 몇으로 잡혀 있는지, 그룹 채널 아래 서브 채널은 몇 개인지...
에셋을 하나하나 열어서 눈으로 확인하는 건 가능하다.
하지만 스키마(데이터)가 늘어나는 순간, 수작업 확인은 실수를 부르고 시간을 잡아먹는다.
엑셀 시트 하나에 전부 정리해서 비교할 수 있다면, 밸런싱 작업의 효율은 완전히 달라진다.
내부 프로퍼티를 파이썬으로 추출하고, 최종적으로 버튼 하나로 엑셀 파일을 생성하는 자동화 도구를 만든 전체 과정을 담고 있다.

준비부터 자동화까지
Step 1. 파이썬 실행 환경 활성화
언리얼 에디터에서 파이썬을 쓰려면 먼저 플러그인을 켜야 한다.
Plugins 메뉴에서 다음 두 가지가 활성화되어 있는지 확인한다.
- Python Editor Script Plugin — 에디터 내에서 파이썬 코드를 실행할 수 있게 해주는 핵심 플러그인이다.
- Editor Scripting Utilities — 에디터 전용 유틸리티 함수들을 파이썬과 블루프린트에서 사용할 수 있도록 열어주는 플러그인이다.
활성화 후, Output Log(출력 로그) 창 하단의 입력 모드를 Cmd에서 Python으로 변경한다.
이제 이 입력란에 파이썬 코드를 직접 타이핑하거나, py "스크립트경로" 명령으로 외부 .py 파일을 실행할 수 있다.
Step 2. 추출 전략 수립 — 데이터 구조 파악
포즈 서치 스키마의 데이터는 단일 구조가 아니다.
항목에 따라 추출 로직이 달라야 한다.
스크립트를 작성하기 전에, 어떤 데이터가 어떤 방식으로 접근 가능한지 먼저 정리했다.
| 데이터 항목 | 추출 전략 | 특이사항 |
| 일반 변수 (sample_rate 등) | get_editor_property()로 직접 읽기 | 단일 값이므로 즉시 추출 가능 |
| 궤적 채널 (Trajectory Channel) | weight 속성을 직접 보유 | 수치 데이터로 바로 저장 가능 |
| 그룹 채널 (Group Channel) | 자체 가중치가 없음 | sub_channels 개수를 파악하는 예외 처리 필요 |
| 데이터 역할 (Data Role) | sample_role 등의 변수명 사용 | 애니메이션 내 태그 정보를 수집 |
- 핵심은 그룹 채널이다.
- 궤적 채널은 weight 프로퍼티를 직접 갖고 있어서 숫자를 바로 읽으면 된다.
- 그런데 그룹 채널은 weight가 없다.
- 대신 내부에 sub_channels라는 하위 채널 목록을 갖고 있다.
- 이걸 구분하지 않고 일괄적으로 weight를 읽으려고 하면 에러가 난다.
따라서 스크립트에서 try/except 구문으로 분기 처리를 해야 한다.
Step 3. 스크립트 작성 — 핵심 코드 전문
복잡한 로직은 Output Log에 직접 입력하기보다 외부 .py 파일로 관리하는 것이 오류가 적다.
아래가 완성된 추출 스크립트 전문이다.
# export_schema.py
import unreal
import os
# 1. 저장 경로 설정
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
file_path = os.path.join(desktop_path, "SchemaExport.csv")
# 2. 선택한 에셋 가져오기
selected_assets = unreal.EditorUtilityLibrary.get_selected_assets()
# 3. 데이터 필터링 (포즈 서치 스키마만 골라내기)
valid_assets = [a for a in selected_assets if isinstance(a, unreal.PoseSearchSchema)]
invalid_count = len(selected_assets) - len(valid_assets)
# 4. 팝업 메시지 및 예외 처리
if not selected_assets:
unreal.EditorDialog.show_message(
"알림",
"선택된 에셋이 없습니다.\n콘텐츠 브라우저에서 스키마 에셋을 선택하세요.",
unreal.AppMsgType.OK
)
unreal.log_warning("추출 실패: 선택된 에셋 없음")
elif not valid_assets:
unreal.EditorDialog.show_message(
"에러: 대상 아님",
"선택한 에셋 중 '포즈 서치 스키마'가 하나도 없습니다.\n올바른 에셋을 선택했는지 확인하세요.",
unreal.AppMsgType.OK,
unreal.AppReturnType.OK
)
unreal.log_error("추출 실패: 포즈 서치 스키마 에셋 미포함")
else:
# 데이터 추출 진행
with open(file_path, "w", encoding="utf-8-sig") as f:
f.write("AssetName,SampleRate,ChannelCount,WeightsPerChannel\n")
for asset in valid_assets:
asset_name = asset.get_name()
sample_rate = asset.get_editor_property("sample_rate")
channels = asset.get_editor_property("channels")
weights = []
for ch in channels:
try:
w = ch.get_editor_property("weight")
except:
w = (
f"Group({len(ch.get_editor_property('sub_channels'))} subs)"
if hasattr(ch, "sub_channels")
else "N/A"
)
weights.append(str(w))
f.write(f"{asset_name},{sample_rate},{len(channels)},{' | '.join(weights)}\n")
# 성공 메시지
success_msg = (
f"성공적으로 {len(valid_assets)}개의 에셋을 추출했습니다.\n"
f"바탕화면의 SchemaExport.csv를 확인하세요."
)
if invalid_count > 0:
success_msg += f"\n\n(참고: 스키마가 아닌 에셋 {invalid_count}개는 제외되었습니다.)"
unreal.EditorDialog.show_message("추출 완료", success_msg, unreal.AppMsgType.OK)
unreal.log(f"--- 최종 완료! 파일 저장됨: {file_path} ---")
- 코드에서 짚어둘 포인트 세 가지가 있다.
- os.path.expanduser("~")로 경로를 자동 지정한다. 바탕화면 절대 경로를 하드코딩하면 다른 사람의 PC에서 깨진다. os 라이브러리가 현재 사용자의 홈 디렉토리를 자동으로 잡아주므로, 누가 실행해도 그 사람의 바탕화면에 파일이 생성된다.
- encoding="utf-8-sig"는 필수다. 일반 utf-8로 저장하면 엑셀에서 한글이 깨진다. utf-8-sig는 파일 앞에 BOM(Byte Order Mark)이라는 표식을 붙여서, 엑셀이 인코딩을 올바르게 인식하도록 한다.
- try/except로 그룹 채널을 분기 처리한다. 앞서 분석한 대로, 그룹 채널은 weight가 없다. try 블록에서 weight 읽기를 시도하고, 실패하면 except 블록에서 서브 채널 개수를 대신 기록한다. 이 분기가 없으면 그룹 채널을 만나는 순간 스크립트가 멈춘다.
Step 4. 실행 — 콘텐츠 브라우저에서 바로 추출


실행 순서는 다음과 같다.
- 콘텐츠 브라우저에서 추출하고 싶은 스키마 에셋들을 Ctrl+클릭으로 다중 선택한다.
- Output Log 창에서 py "스크립트파일경로"를 입력하고 엔터를 누른다. 또는 툴 메뉴의 Python 탭에서 직접 실행한다.
- 바탕화면에 SchemaExport.csv 파일이 생성된 것을 확인한다.

스키마가 아닌 에셋을 실수로 함께 선택해도, 필터가 자동으로 걸러내고 팝업으로 몇 개가 제외되었는지 알려준다. 아무것도 선택하지 않았을 때, 스키마가 하나도 포함되지 않았을 때 등 각 예외 상황마다 별도의 안내 팝업이 뜨도록 처리했다.
Step 5. 자동화 — 에디터 유틸리티 위젯으로 버튼화
스크립트가 잘 동작하는 것을 확인했으면, 매번 경로를 입력하는 대신 버튼 하나로 실행할 수 있도록 만드는 것이 다음 단계다.
언리얼의 에디터 유틸리티 위젯(Editor Utility Widget — 에디터 안에서 커스텀 UI를 만들 수 있는 도구)을 사용한다.
1단계: 위젯 생성


- 콘텐츠 브라우저에서 우클릭 → 에디터 유틸리티(Editor Utilities) → 에디터 유틸리티 위젯(Editor Utility Widget) 선택.
- 루트 위젯 클래스를 선택하는 창이 뜨면 Button을 선택한다. 이름은 EUW_SchemaExporter로 짓는다.
2단계: UI 디자인

- Text를 버튼 안으로 드래그한다. 오른쪽 디테일 패널에서 텍스트 내용을 “엑셀로 내보내기”로 수정한다.
3단계: 파이썬 실행 로직 연결

- 이 단계가 핵심이다. 버튼을 눌렀을 때 파이썬 코드가 실행되도록 연결해야 한다.
- 배치한 버튼을 선택하고, 디테일 패널 하단의 On Clicked 옆의 + 버튼을 누른다.
- 그래프 화면으로 이동하면 On Clicked 노드가 생긴다.
- 이 노드에서 선을 끌어 Execute Python Command 노드를 검색해 생성한다.
- Command 칸에 위에서 완성한 파이썬 코드를 import unreal부터 마지막 줄까지 통째로 붙여넣는다.
4단계: 실행

- 위젯 창 상단의 Compile과 Save를 누른다.
- 콘텐츠 브라우저에서 EUW_SchemaExporter를 우클릭하고, 맨 위의 Run Editor Utility Widget을 선택한다.
- 별도의 창이 뜬다. 추출할 에셋들을 콘텐츠 브라우저에서 선택한 뒤, 위젯의 “엑셀로 내보내기” 버튼을 누르면 된다.
- 팝업이 뜨며 바탕화면에 CSV 파일이 즉시 생성된다.

결과 및 개선 효과
| 항목 | Before | After |
| 스키마 1개 데이터 확인 | 에셋을 열어 눈으로 확인 | 버튼 클릭 1회 (딸깍) |
| 스키마 20개 비교 분석 | 에셋 20개를 순서대로 열어 수동 기록 | 전체 선택 후 버튼 1회 |
데이터를 엑셀에 나란히 놓고 보면, 채널 간 가중치 불균형이나 의도하지 않은 설정값이 한눈에 드러난다.
수작업으로는 발견하기 어려운 밸런싱 이슈를 구조적으로 잡을 수 있게 된 것이다.
💡 확장 가능성
현재 스크립트는 채널의 최상위 레벨만 추출한다.
그룹 채널의 경우 서브 채널 개수만 기록하고 있다.
여기서 한 단계 더 들어가, 서브 채널 각각의 가중치까지 루프를 돌려 추출하면 더욱 정교한 밸런싱 시트를 만들 수 있다.
또한 sample_role 등의 데이터 역할 정보까지 함께 추출하면, 어떤 애니메이션이 어떤 역할로 태그되어 있는지를 일괄 확인할 수 있어서 대규모 애니메이션 데이터베이스 관리에 유용하다.
에디터 유틸리티 위젯에 체크박스를 추가해서, 추출할 항목을 선택하거나 저장 경로를 지정하는 UI로 확장해도 좋겠다!
Importer
csv로 추출이 가능하면 당연히 입력도 되지 않을까?
csv 양식만 동일하면 데이터 값을 입력해줄 수 있게 위젯을 만들어주었다.
import unreal
import csv
import os
# 1. 파일 경로 (기존에 내보낸 파일과 동일한 경로)
file_path = os.path.join(os.path.expanduser("~"), "Desktop", "SchemaExport_Final.csv")
if not os.path.exists(file_path):
unreal.EditorDialog.show_message("에러", "CSV 파일을 찾을 수 없습니다.", unreal.AppMsgType.OK)
else:
with open(file_path, "r", encoding="utf-8-sig") as f:
reader = csv.DictReader(f)
for row in reader:
asset_name = row['AssetName']
# 에셋 경로 찾기 (프로젝트 구조에 맞게 수정 필요)
# 여기서는 선택한 에셋들 중에서 이름이 같은 것을 찾아 수정하는 예시입니다.
selected_assets = unreal.EditorUtilityLibrary.get_selected_assets()
for asset in selected_assets:
if asset.get_name() == asset_name:
# 1. Sample Rate 수정
new_rate = float(row['SampleRate'])
asset.set_editor_property("sample_rate", new_rate)
# 2. 가중치 수정 (단순 궤적 채널 예시)
# 주의: 복잡한 그룹 채널은 내부 인덱스를 파싱하는 추가 로직이 필요함
channels = asset.get_editor_property("channels")
new_weights = row['WeightsPerChannel'].split(" | ")
for i, ch in enumerate(channels):
if i < len(new_weights) and "Group" not in new_weights[i]:
try:
ch.set_editor_property("weight", float(new_weights[i]))
except: pass
unreal.log(f"업데이트 완료: {asset_name}")
unreal.EditorDialog.show_message("완료", "엑셀 데이터가 에셋에 반영되었습니다.", unreal.AppMsgType.OK)



이 워크플로우는 포즈 서치 스키마에 한정되지 않는다. 블루프린트에서 접근이 안 되는 어떤 에셋이든, 변수명만 알면 같은 방법으로 데이터를 꺼낼 수 있다.
'Dev. > UE 언리얼 엔진' 카테고리의 다른 글
| UE5 Motion Matching 1편 :: 모든 캐릭터가 이동 시스템을 공유하는 구조 (2) | 2026.03.12 |
|---|---|
| [GAS] Aim Offset 트러블슈팅... 고개 하나 돌리는 데 4개의 버그를 만났다 (0) | 2026.02.12 |
| 언리얼 엔진5 :: 모션 매칭(Motion Matching)에 대해서 (0) | 2026.02.10 |
| [GAS] 모션매칭 SandboxCharacter를 MyCharacter로 전환하기 (0) | 2026.02.07 |
| [GAS] 스켈레탈 메쉬 크래시 해결: EXCEPTION_ACCESS_VIOLATION (0) | 2026.02.03 |