언리얼 엔진 C++ UPROPERTY 리플렉션 메타데이터 자동 주석 생성 AI 스크립트

언리얼 엔진 5 C++ 헤더의 UPROPERTY와 선언부를 분석해 AI로 한국어 ToolTip 리플렉션 메타데이터를 생성하고 삽입하는 Python 스크립트를 소개합니다. 기존 meta 보존과 UHT·빌드·에디터 검증까지 다룹니다.

✓ 복사 완료!
챕터Unreal Engine 코어 & C++
난이도중급
상태● 검토 완료
대상 엔진
Unreal Engine
최종 업데이트
언리얼 엔진 C++ UPROPERTY 리플렉션 메타데이터 자동 주석 생성 AI 스크립트

핵심 요약

언리얼 엔진의 UPROPERTY(meta=(ToolTip="...")) 메타데이터는 에디터 Details 패널에 표시되는 프로퍼티 설명을 제어합니다. Python 스크립트가 헤더의 UPROPERTY와 선언부를 수집한 뒤 AI에게 짧은 설명을 요청하고 ToolTip을 삽입하면 반복적인 주석 작업을 자동화할 수 있습니다. 단, *.generated.h는 수정하지 않고 원본 헤더만 변경한 뒤 UnrealHeaderTool과 컴파일 결과를 반드시 확인해야 합니다.

UPROPERTY 리플렉션 메타데이터란 무엇인가?

UPROPERTY는 UnrealHeaderTool이 C++ 프로퍼티를 언리얼 리플렉션 시스템에 등록하도록 만드는 매크로입니다. EditAnywhere, BlueprintReadWrite, VisibleInstanceOnly 같은 프로퍼티 지정자와 meta=(...) 형태의 메타데이터를 함께 사용해 에디터 노출 방식과 블루프린트 동작을 설정할 수 있습니다.

예를 들어 다음 코드는 Details 패널에서 MaxHealth를 편집할 수 있게 하고 마우스 오버 설명을 추가합니다.

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Stats", meta=(ClampMin="0.0", ToolTip="캐릭터가 가질 수 있는 최대 체력입니다."))
float MaxHealth = 100.0f;

ToolTip은 C++ 런타임 로직을 바꾸는 값이 아니라 에디터가 읽는 메타데이터입니다. 따라서 설명을 자동 생성할 때도 타입과 기본값만 보고 단정하지 말고 변수명, 카테고리, 주변 주석, 사용 맥락을 함께 전달해야 합니다.

구성 요소역할자동화 시 확인할 내용
UPROPERTY프로퍼티를 리플렉션 대상으로 등록매크로가 선언부와 연결되어 있는가
지정자편집 권한과 블루프린트 노출을 설정EditAnywhere, BlueprintReadWrite 등의 의미
meta=(...)에디터 동작과 추가 표시 정보를 설정기존 메타데이터를 보존하는가
ToolTipDetails 패널의 설명 문구짧고 사실에 근거한 문장인가
Category에디터에서 프로퍼티를 그룹화팀의 분류 규칙과 일치하는가

왜 UPROPERTY 주석을 AI로 생성하는가?

대규모 프로젝트에서는 프로퍼티 수가 늘어날수록 다음 문제가 반복됩니다.

  • 변수명만으로 의미를 알기 어려운 프로퍼티가 생깁니다.
  • 디자이너가 Details 패널에서 단위와 범위를 다시 확인해야 합니다.
  • 기존 meta=(ClampMin=...) 값을 실수로 덮어쓸 수 있습니다.
  • 코드 리뷰에서 설명 문구의 품질이 작성자마다 달라집니다.

AI는 변수명, C++ 타입, 기본값, Category, 주변 주석을 바탕으로 초안을 만드는 데 적합합니다. 반면 실제 게임 규칙이나 네트워크 복제 정책을 추론해 확정하는 역할까지 맡기면 안 됩니다. 생성 결과는 문서 초안으로 취급하고 컴파일과 코드 리뷰를 통과시켜야 합니다.

AI 스크립트는 어떤 순서로 동작하는가?

자동화 파이프라인은 다음 네 단계로 구성할 수 있습니다.

flowchart LR
    A[헤더 파일 검색] --> B[UPROPERTY와 선언 분석]
    B --> C[AI에 구조화된 설명 요청]
    C --> D[ToolTip 메타데이터 삽입]
    D --> E[diff 검토와 UHT 컴파일]
  1. Source 아래의 .h 파일을 검색합니다.
  2. UPROPERTY(...) 블록과 바로 뒤의 프로퍼티 선언을 읽습니다.
  3. AI에게 사실 기반의 한 줄 설명을 JSON으로 요청합니다.
  4. 기존 ToolTip이 없는 경우에만 원본 헤더에 메타데이터를 추가합니다.
  5. Git diff를 확인하고 UnrealHeaderTool 및 C++ 컴파일을 실행합니다.

AI에게 어떤 프롬프트를 보내야 하는가?

프롬프트에는 출력 형식을 제한하는 규칙을 넣어야 합니다. 특히 AI가 존재하지 않는 기능을 설명하지 않도록 추론 금지와 정보 부족 시 보수적 표현을 명시하는 것이 중요합니다.

당신은 Unreal Engine 5 C++ 코드 리뷰어입니다.
다음 UPROPERTY 선언을 보고 Details 패널용 한국어 ToolTip을 작성하세요.

규칙:
- 반드시 JSON 객체 하나만 반환합니다.
- key는 tooltip 하나만 사용합니다.
- 80자 이내의 자연스러운 한 문장으로 작성합니다.
- 변수명, 타입, 기본값, Category와 제공된 주석에서 확인되는 사실만 사용합니다.
- 단위가 코드에 없으면 임의로 초 또는 미터라고 쓰지 않습니다.
- 네트워크 복제, 저장, 런타임 동작은 입력에 명시된 경우에만 설명합니다.
- 설명을 확정할 수 없으면 변수의 역할을 보수적으로 설명합니다.

입력:
{property_context}

응답은 반드시 {"tooltip":"..."}처럼 제한하는 편이 좋습니다. 이후 스크립트에서 JSON 파싱에 실패하거나 tooltip이 문자열이 아니면 해당 프로퍼티를 건너뛰도록 합니다.

Python으로 UPROPERTY ToolTip을 자동 삽입하는 방법

아래 예시는 외부 AI HTTP 엔드포인트를 호출하는 최소 구현입니다. 프로젝트에 맞는 모델 공급자의 URL과 인증 방식을 AI_URL, AI_TOKEN 환경 변수로 주입합니다. 실제 토큰을 소스 파일에 기록하지 않는 것이 핵심입니다.

from __future__ import annotations

import json
import os
import re
import urllib.request
from pathlib import Path

ROOT = Path(os.environ.get("UNREAL_PROJECT_ROOT", "."))
SOURCE_DIR = ROOT / "Source"
AI_URL = os.environ["AI_URL"]
AI_TOKEN = os.environ["AI_TOKEN"]

PROPERTY_START_RE = re.compile(r"\bUPROPERTY\s*\(")
TOOLTIP_RE = re.compile(r"\bToolTip\s*=", re.IGNORECASE)
META_OPEN_RE = re.compile(r"\bmeta\s*=\s*\(")

PROMPT = """당신은 Unreal Engine 5 C++ 코드 리뷰어입니다.
UPROPERTY 선언을 보고 Details 패널용 한국어 ToolTip을 작성하세요.
JSON 객체 하나만 반환하고 key는 tooltip 하나만 사용하세요.
80자 이내의 한 문장으로 작성하며 입력에 없는 단위나 런타임 동작을 추측하지 마세요.
정보가 부족하면 변수의 역할을 보수적으로 설명하세요.

입력:
{context}
"""


def find_matching_parenthesis(source: str, opening: int) -> int | None:
    depth = 0
    quote = None
    escaped = False

    for index in range(opening, len(source)):
        char = source[index]
        if quote is not None:
            if escaped:
                escaped = False
            elif char == "\\":
                escaped = True
            elif char == quote:
                quote = None
            continue

        if char in ('"', "'"):
            quote = char
        elif char == "(":
            depth += 1
        elif char == ")":
            depth -= 1
            if depth == 0:
                return index

    return None


def scan_properties(source: str):
    for match in PROPERTY_START_RE.finditer(source):
        opening = source.find("(", match.start())
        closing = find_matching_parenthesis(source, opening)
        if closing is None:
            continue

        declaration_match = re.match(r"\s*(?P<decl>[^;]+;)", source[closing + 1 :], re.DOTALL)
        if declaration_match is None:
            continue

        declaration = declaration_match.group("decl").strip()
        end = closing + 1 + declaration_match.end()
        spec = source[opening + 1 : closing]
        yield match.start(), end, spec, declaration


def ask_ai(context: str) -> str:
    payload = json.dumps(
        {"prompt": PROMPT.format(context=context), "temperature": 0.1},
        ensure_ascii=False,
    ).encode("utf-8")
    request = urllib.request.Request(
        AI_URL,
        data=payload,
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {AI_TOKEN}",
        },
        method="POST",
    )

    with urllib.request.urlopen(request, timeout=30) as response:
        result = json.loads(response.read().decode("utf-8"))

    candidate = result.get("tooltip") or result.get("output")
    if isinstance(candidate, str):
        try:
            candidate = json.loads(candidate).get("tooltip")
        except json.JSONDecodeError:
            pass

    if not isinstance(candidate, str):
        raise ValueError("AI 응답에 tooltip 문자열이 없습니다.")
    return " ".join(candidate.split()).strip()


def add_tooltip_to_spec(spec: str, tooltip: str) -> str:
    escaped = json.dumps(tooltip, ensure_ascii=False)
    meta_match = META_OPEN_RE.search(spec)

    if meta_match:
        insert_at = meta_match.end()
        return spec[:insert_at] + f"ToolTip={escaped}, " + spec[insert_at:]
    return spec.rstrip() + f", meta=(ToolTip={escaped})"


def process_file(path: Path) -> bool:
    source = path.read_text(encoding="utf-8")
    replacements = []

    for start, end, spec, declaration in scan_properties(source):
        if TOOLTIP_RE.search(spec):
            continue

        context = f"UPROPERTY({spec})\\n{declaration}"
        try:
            tooltip = ask_ai(context)
        except Exception as error:
            print(f"SKIP: {declaration}\\n  {error}")
            continue

        if not tooltip or len(tooltip) > 120:
            print(f"SKIP: 설명 길이 검증 실패 - {declaration}")
            continue

        new_spec = add_tooltip_to_spec(spec, tooltip)
        replacements.append((start, end, f"UPROPERTY({new_spec})\\n{declaration}"))

    for start, end, replacement in reversed(replacements):
        source = source[:start] + replacement + source[end:]

    if not replacements:
        return False

    path.write_text(source, encoding="utf-8")
    print(f"UPDATED: {path}")
    return True


if __name__ == "__main__":
    for header in SOURCE_DIR.rglob("*.h"):
        if header.name.endswith(".generated.h"):
            continue
        process_file(header)

스크립트 사용 예

PowerShell에서는 프로젝트 루트와 AI 엔드포인트를 환경 변수로 지정한 뒤 실행합니다.

$env:UNREAL_PROJECT_ROOT = "D:\Projects\MyGame"
$env:AI_URL = "https://your-ai-gateway.example/v1/generate"
$env:AI_TOKEN = "개발 환경의 보안 저장소에서 주입"
python .\tools\generate_uproperty_tooltips.py

이 예시는 공급자마다 다른 요청과 응답 형식을 일반화한 골격입니다. OpenAI 호환 게이트웨이를 사용한다면 ask_ai() 내부의 payload와 결과 추출 경로를 해당 게이트웨이 계약에 맞게 바꿔야 합니다. 중요한 점은 AI SDK를 파일 전체에 퍼뜨리지 않고 단일 함수로 격리하는 것입니다.

기존 meta 값을 안전하게 보존하려면?

자동 삽입 시 가장 위험한 실수는 기존 메타데이터를 덮어쓰는 것입니다. 다음 규칙을 적용하면 변경 범위를 줄일 수 있습니다.

  • 이미 ToolTip이 있으면 기본적으로 건너뜁니다.
  • ClampMin, ClampMax, Units, EditCondition, EditConditionHides 등 기존 값을 보존합니다.
  • *.generated.h, 플러그인 캐시, Intermediate 폴더는 검색 대상에서 제외합니다.
  • 한 번에 모든 파일을 저장하지 말고 먼저 dry-run diff를 출력합니다.
  • 설명이 변경된 프로퍼티는 자동으로 덮어쓰지 말고 명시적 --update-existing 옵션으로 분리합니다.

예를 들어 입력이 다음과 같다면:

UPROPERTY(EditAnywhere, Category="Movement", meta=(ClampMin="0.0", Units="cm/s"))
float MoveSpeed = 600.0f;

결과는 기존 제한과 단위를 유지해야 합니다.

UPROPERTY(EditAnywhere, Category="Movement", meta=(ToolTip="캐릭터의 이동 속도를 설정합니다.", ClampMin="0.0", Units="cm/s"))
float MoveSpeed = 600.0f;

meta의 순서는 팀 스타일에 맞춰 통일할 수 있지만 기능상 중요한 것은 기존 키와 값을 보존하는 것입니다.

언리얼 엔진 Details 패널에서 UPROPERTY ToolTip이 표시되는 예시

정규식 파싱만으로 충분한가?

정규식은 단순한 선언을 빠르게 처리하기에는 편리하지만 C++ 문법 전체를 이해하지는 못합니다. 위 예시는 UPROPERTY 괄호를 직접 순회해 중첩된 meta=(...)를 처리하지만 주석과 전처리기 조건문까지 완전하게 해석하는 C++ 파서는 아닙니다.

프로젝트 규모가 커지면 다음 순서로 안정성을 높이는 것이 좋습니다.

  1. 초기 단계에서는 대상 코딩 스타일을 좁혀 제한된 스캐너로 처리합니다.
  2. 스크립트에 --check-only와 변경 diff 출력을 추가합니다.
  3. 복잡한 선언은 자동 수정하지 않고 로그에 남깁니다.
  4. 장기적으로는 Clang 기반 AST 도구나 컴파일러 프론트엔드를 사용해 FieldDecl과 매크로 위치를 분석합니다.

즉 간단한 스캐너는 자동화의 시작점이지 C++ 파서의 대체품은 아닙니다. 게임플레이 모듈 전체를 대상으로 실행하기 전에는 대표적인 헤더 샘플로 템플릿, 포인터, 배열, TMap, 조건부 컴파일 구문을 검증해야 합니다.

생성된 메타데이터를 어떻게 검증할까?

AI가 만든 설명은 문법 검증과 의미 검증을 나누어 확인합니다.

1단계: 정적 검사

다음 항목을 검사합니다.

  • ToolTip 값이 큰따옴표와 괄호를 올바르게 닫는가
  • 문자열 안의 백슬래시와 큰따옴표가 C++ 문자열 규칙에 맞는가
  • 동일한 UPROPERTY에 ToolTip이 중복되지 않는가
  • 금지된 추정 표현이나 과도한 길이가 포함되지 않았는가

2단계: UnrealHeaderTool과 C++ 빌드

에디터에서 프로젝트를 다시 빌드하거나 IDE의 Unreal Engine 빌드 구성을 사용해 UHT와 C++ 컴파일을 실행합니다. 메타데이터 문자열이 잘못 이스케이프되면 UHT 단계에서 오류가 날 수 있으므로 일반 C++ 컴파일만 통과했다고 끝내지 않아야 합니다.

3단계: 에디터 표시 확인

Details 패널에서 다음을 확인합니다.

  • 설명이 원하는 프로퍼티에 표시되는가
  • EditCondition과 편집 가능 상태가 그대로 동작하는가
  • 카테고리와 정렬이 바뀌지 않았는가
  • BlueprintReadOnly 또는 BlueprintReadWrite 노출에 영향이 없는가

CI에서는 생성 스크립트를 무조건 실행해 파일을 수정하기보다 --check-only 모드로 저장소의 기대 결과와 차이가 있는지 검사하는 편이 안전합니다.

운영 규칙은 어떻게 정하면 좋은가?

AI 호출 비용과 변경 노이즈를 줄이려면 생성 규칙을 코드 리뷰 기준으로 명문화합니다.

규칙권장 기준
언어프로젝트 기본 언어를 사용하고 용어집을 따름
길이한 문장, 80자 안팎
단위Units 메타데이터나 코드 주석에 있는 경우만 표기
범위ClampMin, ClampMax를 확인할 때만 설명에 포함
네트워크Replicated 등 선언에 명시된 경우에만 언급
기본값선언부의 실제 초기화 값만 설명에 사용
재생성기존 ToolTip은 수동 작성으로 간주하고 보존

파일 해시와 프로퍼티 문맥을 함께 저장하는 캐시도 유용합니다.
선언이나 주변 주석이 바뀐 경우에만 다시 생성하면 매 실행마다 같은 요청을 반복하지 않을 수 있습니다.

자주 묻는 질문(FAQ)

UPROPERTY의 C++ 주석만으로 Details ToolTip이 자동 표시되나요?

항상 그렇지는 않습니다. 일반 C++ 주석은 문서화나 IDE 표시에는 활용될 수 있지만 Details 패널의 표시 문구를 확실하게 제어하려면 meta=(ToolTip="...")를 명시하는 방식이 안전합니다.

ToolTip을 추가하면 패키지 빌드나 런타임 성능에 영향이 있나요?

ToolTip은 주로 에디터 리플렉션 메타데이터로 사용됩니다.
그러나 프로젝트의 패키징 설정과 엔진 버전에 따라 메타데이터 처리 방식이 다를 수 있으므로 최종 빌드 크기와 패키징 결과는 실제 타깃 빌드에서 확인해야 합니다.
일반적인 게임플레이 실행 경로에 알고리즘을 추가하는 기능은 아닙니다.

AI가 잘못된 설명을 생성하면 어떻게 막나요?

ToolTip이 없는 프로퍼티만 처리하고 JSON 스키마 검증, 금지어 검사, 길이 제한, Git diff 리뷰를 적용하세요.
특히 단위, 네트워크 복제, 저장 여부처럼 코드에서 직접 확인할 수 없는 정보는 프롬프트에서 추측하지 못하게 제한해야 합니다.

정리

UPROPERTY 메타데이터 자동화의 핵심은 AI 자체가 아니라 안전한 코드 변경 경계입니다. 원본 헤더만 대상으로 삼고 기존 meta 값을 보존하며 ToolTip이 없는 선언에만 보수적인 설명을 추가하면 에디터 문서 품질을 높이면서도 수동 작업을 줄일 수 있습니다.

마지막에는 항상 diff, UnrealHeaderTool, C++ 빌드, Details 패널을 순서대로 검증하세요.