Files
Northbound/DataTools/generate_csharp_classes.py
dal4segno dc4d71d4b6 데이터 파이프라인 추가 및 수정 + 크립 및 크립 캠프 배치 자동화
Hierachy > System > MapGenerator 에서 크립 관련 변수 설정 가능
Kaykit PlantWarrior 애셋 추가
2026-02-02 19:50:30 +09:00

202 lines
7.0 KiB
Python

# DataTools/generate_csharp_classes.py
"""노션 스키마 → Unity C# ScriptableObject 클래스 생성 (리스트 지원 버전)"""
import json
from pathlib import Path
from collections import defaultdict
GAMEDATA_DIR = Path(__file__).parent.parent / "GameData"
UNITY_OUTPUT_DIR = Path(__file__).parent.parent / "Assets" / "Data" / "Scripts" / "DataClasses"
def get_csharp_type(field_type):
"""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',
'number': 'float',
'string': 'string',
'bool': 'bool',
'boolean': 'bool'
}
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"""
components = snake_str.split('_')
return components[0] + ''.join(x.title() for x in components[1:])
def to_pascal_case(snake_str):
"""snake_case → PascalCase"""
return ''.join(x.title() for x in snake_str.split('_'))
def group_fields_by_condition(schema):
"""필드를 조건별로 그룹화"""
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# 클래스 코드 생성"""
class_name = schema_name + "Data"
field_groups = group_fields_by_condition(schema)
condition_field = None
for field in schema:
if field.get('condition'):
condition_field = field['condition']['field']
break
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("{")
lines.append(f' [CreateAssetMenu(fileName = "{class_name}", menuName = "Northbound/{schema_name} Data")]')
lines.append(f" public partial class {class_name} : ScriptableObject")
lines.append(" {")
# 공통 필드
if 'common' in field_groups and field_groups['common']:
lines.append(" [Header(\"기본 정보\")]")
for field in field_groups['common']:
add_field(lines, field, nullable=False)
lines.append("")
# 조건부 필드 (그룹별)
for condition_value, fields in field_groups.items():
if condition_value == 'common':
continue
header_name = condition_value.capitalize()
lines.append(f" [Header(\"{header_name} 전용\")]")
for field in fields:
add_field(lines, field, nullable=True)
lines.append("")
# 유틸리티 메서드
if condition_field:
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("")
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 "List<" in field_type:
default_value = f"new {field_type}()"
elif field_type == 'int':
default_value = '0'
elif field_type == 'float':
default_value = '0f'
elif field_type == 'bool':
default_value = 'false'
else:
default_value = '""'
lines.append(f" public {field_type} {method_name}() => {field_name} ?? {default_value};")
lines.append(" }")
lines.append("}")
return "\n".join(lines)
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'):
description = field['description'].replace('\\n', ' ').replace('\n', ' ')
lines.append(f" /// <summary>{description}</summary>")
# 필드 선언
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};")
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("⚠️ 스키마 파일을 찾을 수 없습니다.")
return
print("=" * 60)
print("🔧 C# 클래스 자동 생성 (List 지원)")
print("=" * 60)
generated_count = 0
for schema_file in schema_files:
schema_name = schema_file.stem.replace("_schema", "").lstrip(".")
try:
with open(schema_file, 'r', encoding='utf-8') as f:
schema = json.load(f)
csharp_code = generate_class(schema_name, schema)
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")
generated_count += 1
except Exception as e:
print(f"{schema_name} 실패: {e}")
print("=" * 60)
print(f"🎉 완료: {generated_count}개 클래스 생성")
if __name__ == "__main__":
generate_all_classes()