데이터파이프 라인에서 리스트 타입 지원
list:int, list:bool 등
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# DataTools/generate_csharp_classes.py
|
||||
"""노션 스키마 → Unity C# ScriptableObject 클래스 생성"""
|
||||
"""노션 스키마 → Unity C# ScriptableObject 클래스 생성 (리스트 지원 버전)"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
@@ -10,7 +10,18 @@ UNITY_OUTPUT_DIR = Path(__file__).parent.parent / "Assets" / "Data" / "Scripts"
|
||||
|
||||
|
||||
def get_csharp_type(field_type):
|
||||
"""Python 타입 → C# 타입 변환"""
|
||||
"""Python 타입 → C# 타입 변환 (list 지원)"""
|
||||
# 리스트 여부 확인 (예: list:int, list:string)
|
||||
is_list = field_type.lower().startswith('list')
|
||||
|
||||
# 기본 타입 추출
|
||||
base_type = field_type.lower()
|
||||
if is_list and ':' in base_type:
|
||||
base_type = base_type.split(':')[1]
|
||||
elif is_list:
|
||||
# 'list'만 적혀있을 경우 기본적으로 string 리스트로 간주
|
||||
base_type = 'string'
|
||||
|
||||
type_map = {
|
||||
'int': 'int',
|
||||
'float': 'float',
|
||||
@@ -19,87 +30,54 @@ def get_csharp_type(field_type):
|
||||
'bool': 'bool',
|
||||
'boolean': 'bool'
|
||||
}
|
||||
return type_map.get(field_type.lower(), 'string')
|
||||
|
||||
csharp_base = type_map.get(base_type, 'string')
|
||||
|
||||
if is_list:
|
||||
return f"List<{csharp_base}>"
|
||||
return csharp_base
|
||||
|
||||
|
||||
def to_camel_case(snake_str):
|
||||
"""
|
||||
snake_case → camelCase
|
||||
|
||||
예시:
|
||||
- tower_type → towerType
|
||||
- damage → damage
|
||||
- attack_speed → attackSpeed
|
||||
"""
|
||||
"""snake_case → camelCase"""
|
||||
components = snake_str.split('_')
|
||||
return components[0] + ''.join(x.title() for x in components[1:])
|
||||
|
||||
|
||||
def to_pascal_case(snake_str):
|
||||
"""
|
||||
snake_case → PascalCase
|
||||
|
||||
예시:
|
||||
- tower_type → TowerType
|
||||
- damage → Damage
|
||||
"""
|
||||
"""snake_case → PascalCase"""
|
||||
return ''.join(x.title() for x in snake_str.split('_'))
|
||||
|
||||
|
||||
def group_fields_by_condition(schema):
|
||||
"""
|
||||
필드를 조건별로 그룹화
|
||||
|
||||
반환:
|
||||
{
|
||||
'common': [공통 필드들],
|
||||
'attack': [attack 타입 필드들],
|
||||
'defense': [defense 타입 필드들],
|
||||
...
|
||||
}
|
||||
"""
|
||||
"""필드를 조건별로 그룹화"""
|
||||
groups = defaultdict(list)
|
||||
|
||||
for field in schema:
|
||||
if field.get('condition'):
|
||||
condition_value = field['condition']['value']
|
||||
groups[condition_value].append(field)
|
||||
else:
|
||||
groups['common'].append(field)
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
def generate_class(schema_name, schema):
|
||||
"""
|
||||
스키마 → C# 클래스 코드 생성
|
||||
|
||||
schema: [
|
||||
{'name': 'id', 'type': 'int', 'condition': None, 'description': '...'},
|
||||
{'name': 'damage', 'type': 'float', 'condition': {...}, 'description': '...'},
|
||||
...
|
||||
]
|
||||
"""
|
||||
"""스키마 → C# 클래스 코드 생성"""
|
||||
class_name = schema_name + "Data"
|
||||
|
||||
# 필드 그룹화
|
||||
field_groups = group_fields_by_condition(schema)
|
||||
|
||||
# 조건 필드명 찾기 (예: tower_type, enemy_type)
|
||||
condition_field = None
|
||||
for field in schema:
|
||||
if field.get('condition'):
|
||||
condition_field = field['condition']['field']
|
||||
break
|
||||
|
||||
# C# 코드 생성 시작
|
||||
lines = []
|
||||
|
||||
# 파일 헤더
|
||||
lines.append("// 이 파일은 자동 생성되었습니다. 직접 수정하지 마세요!")
|
||||
lines.append("// 생성 스크립트: DataTools/generate_csharp_classes.py")
|
||||
lines.append("")
|
||||
lines.append("using UnityEngine;")
|
||||
lines.append("using System.Collections.Generic; // 리스트 지원을 위해 추가")
|
||||
lines.append("")
|
||||
lines.append("namespace Northbound.Data")
|
||||
lines.append("{")
|
||||
@@ -121,10 +99,8 @@ def generate_class(schema_name, schema):
|
||||
|
||||
header_name = condition_value.capitalize()
|
||||
lines.append(f" [Header(\"{header_name} 전용\")]")
|
||||
|
||||
for field in fields:
|
||||
add_field(lines, field, nullable=True)
|
||||
|
||||
lines.append("")
|
||||
|
||||
# 유틸리티 메서드
|
||||
@@ -132,30 +108,27 @@ def generate_class(schema_name, schema):
|
||||
lines.append(" // ===== 유틸리티 메서드 =====")
|
||||
lines.append("")
|
||||
|
||||
# 타입 체크 프로퍼티
|
||||
for condition_value in field_groups.keys():
|
||||
if condition_value == 'common':
|
||||
continue
|
||||
|
||||
property_name = f"Is{to_pascal_case(condition_value)}"
|
||||
field_name = to_camel_case(condition_field)
|
||||
|
||||
lines.append(f' public bool {property_name} => {field_name} == "{condition_value}";')
|
||||
|
||||
lines.append("")
|
||||
|
||||
# Nullable 필드 Getter 메서드
|
||||
for condition_value, fields in field_groups.items():
|
||||
if condition_value == 'common':
|
||||
continue
|
||||
|
||||
for field in fields:
|
||||
field_name = to_camel_case(field['name'])
|
||||
method_name = f"Get{to_pascal_case(field['name'])}"
|
||||
field_type = get_csharp_type(field['type'])
|
||||
|
||||
# 기본값 결정
|
||||
if field_type == 'int':
|
||||
# 리스트 여부에 따른 기본값 처리
|
||||
if "List<" in field_type:
|
||||
default_value = f"new {field_type}()"
|
||||
elif field_type == 'int':
|
||||
default_value = '0'
|
||||
elif field_type == 'float':
|
||||
default_value = '0f'
|
||||
@@ -166,7 +139,6 @@ def generate_class(schema_name, schema):
|
||||
|
||||
lines.append(f" public {field_type} {method_name}() => {field_name} ?? {default_value};")
|
||||
|
||||
# 클래스 종료
|
||||
lines.append(" }")
|
||||
lines.append("}")
|
||||
|
||||
@@ -177,15 +149,18 @@ def add_field(lines, field, nullable=False):
|
||||
"""필드 정의 추가"""
|
||||
field_name = to_camel_case(field['name'])
|
||||
field_type = get_csharp_type(field['type'])
|
||||
|
||||
# 주석 (설명이 있으면)
|
||||
is_list = "List<" in field_type
|
||||
|
||||
if field.get('description'):
|
||||
# 줄바꿈을 공백으로 변환 (C# 주석은 한 줄)
|
||||
description = field['description'].replace('\\n', ' ').replace('\n', ' ')
|
||||
lines.append(f" /// <summary>{description}</summary>")
|
||||
|
||||
# 필드 선언
|
||||
if nullable:
|
||||
if is_list:
|
||||
# 리스트는 인스펙터에서 바로 보이도록 초기화
|
||||
lines.append(f" public {field_type} {field_name} = new {field_type}();")
|
||||
elif nullable and field_type not in ['string']:
|
||||
# string은 원래 nullable이므로 제외, 값 타입만 ? 적용
|
||||
lines.append(f" public {field_type}? {field_name};")
|
||||
else:
|
||||
lines.append(f" public {field_type} {field_name};")
|
||||
@@ -193,65 +168,34 @@ def add_field(lines, field, nullable=False):
|
||||
|
||||
def generate_all_classes():
|
||||
"""모든 스키마에 대해 C# 클래스 생성"""
|
||||
|
||||
# 출력 폴더 생성
|
||||
UNITY_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 스키마 파일 찾기
|
||||
schema_files = list(GAMEDATA_DIR.glob(".*_schema.json"))
|
||||
|
||||
if not schema_files:
|
||||
print("⚠️ 스키마 파일을 찾을 수 없습니다.")
|
||||
print("💡 먼저 sync_from_notion.py를 실행하세요.")
|
||||
print("⚠️ 스키마 파일을 찾을 수 없습니다.")
|
||||
return
|
||||
|
||||
print("=" * 60)
|
||||
print("🔧 C# 클래스 자동 생성")
|
||||
print("🔧 C# 클래스 자동 생성 (List 지원)")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
generated_count = 0
|
||||
|
||||
for schema_file in schema_files:
|
||||
# ".Towers_schema.json" → "Towers"
|
||||
schema_name = schema_file.stem.replace("_schema", "").lstrip(".")
|
||||
|
||||
print(f"📋 {schema_name} 처리 중...")
|
||||
|
||||
# 스키마 로드
|
||||
try:
|
||||
with open(schema_file, 'r', encoding='utf-8') as f:
|
||||
schema = json.load(f)
|
||||
except Exception as e:
|
||||
print(f" ❌ 스키마 로드 실패: {e}")
|
||||
continue
|
||||
|
||||
# C# 코드 생성
|
||||
try:
|
||||
csharp_code = generate_class(schema_name, schema)
|
||||
except Exception as e:
|
||||
print(f" ❌ 코드 생성 실패: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
# 파일 저장
|
||||
output_file = UNITY_OUTPUT_DIR / f"{schema_name}Data.cs"
|
||||
try:
|
||||
output_file = UNITY_OUTPUT_DIR / f"{schema_name}Data.cs"
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(csharp_code)
|
||||
|
||||
print(f" ✅ 생성: {schema_name}Data.cs ({len(schema)}개 필드)")
|
||||
print(f" ✅ 생성: {schema_name}Data.cs")
|
||||
generated_count += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 파일 저장 실패: {e}")
|
||||
print(f" ❌ {schema_name} 실패: {e}")
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f"🎉 완료: {generated_count}개 클래스 생성")
|
||||
print(f"📂 위치: {UNITY_OUTPUT_DIR}")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user