BurningTimesAi/프로젝트/EerieVillage/기획/_build_skill_pptx.py

2293 lines
116 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
EerieVillage 스킬 시스템 개발 기획서 PPT 빌더
- 산출물: 스킬시스템_개발기획서_v1.pptx
- 대상: PD 검토 + 개발팀 구현 참고
- 통합 SOT: BT7-Plan + BT11-Plan + BT12-Dev v1 (7종 문서)
"""
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.oxml.ns import qn
from copy import deepcopy
from lxml import etree
# ============================================================
# 색상 팔레트 (Midnight Executive — 조선 퇴마 톤 정합)
# ============================================================
NAVY = RGBColor(0x1E, 0x27, 0x61) # 주: 야밤 네이비
ICE = RGBColor(0xCA, 0xDC, 0xFC) # 보조: 얼음 푸름
ACCENT = RGBColor(0xE8, 0xB4, 0x3C) # 액센트: 황금 부적색
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
DARK_TEXT = RGBColor(0x21, 0x29, 0x5C) # 본문 텍스트
GRAY = RGBColor(0x66, 0x66, 0x66) # 캡션
LIGHT_BG = RGBColor(0xF8, 0xF9, 0xFB) # 컨텐츠 배경
RED_HL = RGBColor(0xC0, 0x39, 0x2B) # 강조 (PD 결정 영역)
GREEN_OK = RGBColor(0x3D, 0x8B, 0x5A) # 완료·확정
PURPLE = RGBColor(0x6D, 0x2E, 0x46) # 각성·진화
# 폰트
HEADER_FONT = "맑은 고딕"
BODY_FONT = "맑은 고딕"
CODE_FONT = "Consolas"
# 슬라이드 크기 (16:9 와이드)
SLIDE_W = Inches(13.333)
SLIDE_H = Inches(7.5)
# ============================================================
# 헬퍼 함수
# ============================================================
def add_solid_bg(slide, color):
bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SLIDE_W, SLIDE_H)
bg.fill.solid()
bg.fill.fore_color.rgb = color
bg.line.fill.background()
bg.shadow.inherit = False
# 맨 뒤로 보내기
spTree = bg._element.getparent()
spTree.remove(bg._element)
spTree.insert(2, bg._element)
return bg
def add_text_box(slide, left, top, width, height, text, font_size=14,
bold=False, color=DARK_TEXT, align=PP_ALIGN.LEFT,
anchor=MSO_ANCHOR.TOP, font_name=BODY_FONT):
tb = slide.shapes.add_textbox(left, top, width, height)
tf = tb.text_frame
tf.word_wrap = True
tf.margin_left = Inches(0.05)
tf.margin_right = Inches(0.05)
tf.margin_top = Inches(0.02)
tf.margin_bottom = Inches(0.02)
tf.vertical_anchor = anchor
p = tf.paragraphs[0]
p.alignment = align
run = p.add_run()
run.text = text
run.font.name = font_name
run.font.size = Pt(font_size)
run.font.bold = bold
run.font.color.rgb = color
return tb
def add_multiline_text(slide, left, top, width, height, lines, font_size=12,
color=DARK_TEXT, font_name=BODY_FONT, line_spacing=1.15):
"""lines: list of dict {text, bold, color, size, bullet}"""
tb = slide.shapes.add_textbox(left, top, width, height)
tf = tb.text_frame
tf.word_wrap = True
tf.margin_left = Inches(0.05)
tf.margin_right = Inches(0.05)
for i, line in enumerate(lines):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = line.get("align", PP_ALIGN.LEFT)
p.line_spacing = line_spacing
text = line["text"]
if line.get("bullet"):
text = "• " + text
run = p.add_run()
run.text = text
run.font.name = font_name
run.font.size = Pt(line.get("size", font_size))
run.font.bold = line.get("bold", False)
run.font.color.rgb = line.get("color", color)
return tb
def add_shape(slide, shape_type, left, top, width, height,
fill=None, line=None, text=None, font_size=12,
text_color=WHITE, bold=False, font_name=BODY_FONT):
s = slide.shapes.add_shape(shape_type, left, top, width, height)
if fill is not None:
s.fill.solid()
s.fill.fore_color.rgb = fill
else:
s.fill.background()
if line is not None:
s.line.color.rgb = line
s.line.width = Pt(0.75)
else:
s.line.fill.background()
s.shadow.inherit = False
if text:
tf = s.text_frame
tf.word_wrap = True
tf.margin_left = Inches(0.06)
tf.margin_right = Inches(0.06)
tf.margin_top = Inches(0.04)
tf.margin_bottom = Inches(0.04)
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
run = p.add_run()
run.text = text
run.font.name = font_name
run.font.size = Pt(font_size)
run.font.bold = bold
run.font.color.rgb = text_color
return s
def add_table(slide, left, top, width, height, data, header=True,
header_fill=NAVY, header_color=WHITE, body_size=10, header_size=11,
col_widths=None):
rows = len(data)
cols = len(data[0])
tbl_shape = slide.shapes.add_table(rows, cols, left, top, width, height)
tbl = tbl_shape.table
if col_widths:
total = sum(col_widths)
for i, w in enumerate(col_widths):
tbl.columns[i].width = Emu(int(width * (w / total)))
for r in range(rows):
for c in range(cols):
cell = tbl.cell(r, c)
cell.margin_left = Inches(0.05)
cell.margin_right = Inches(0.05)
cell.margin_top = Inches(0.02)
cell.margin_bottom = Inches(0.02)
tf = cell.text_frame
tf.word_wrap = True
tf.clear()
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER if (r == 0 and header) else PP_ALIGN.LEFT
run = p.add_run()
txt = str(data[r][c])
run.text = txt
run.font.name = BODY_FONT
if r == 0 and header:
run.font.size = Pt(header_size)
run.font.bold = True
run.font.color.rgb = header_color
cell.fill.solid()
cell.fill.fore_color.rgb = header_fill
else:
run.font.size = Pt(body_size)
run.font.color.rgb = DARK_TEXT
# 홀짝 행 색
if r % 2 == 0:
cell.fill.solid()
cell.fill.fore_color.rgb = LIGHT_BG
else:
cell.fill.solid()
cell.fill.fore_color.rgb = WHITE
return tbl_shape
def add_slide_header(slide, title, slide_no, total, section=""):
"""모든 본문 슬라이드 공통 헤더"""
# 좌상단 제목
add_text_box(slide, Inches(0.5), Inches(0.25), Inches(10), Inches(0.55),
title, font_size=24, bold=True, color=NAVY, font_name=HEADER_FONT)
# 우상단 슬라이드 번호 + 부 표기
add_text_box(slide, Inches(11.3), Inches(0.3), Inches(2), Inches(0.4),
f"{section} | {slide_no}/{total}", font_size=10,
color=GRAY, align=PP_ALIGN.RIGHT)
# 헤더 하단 액센트 라인 (얇은 황금색)
line = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(0.5), Inches(0.85),
Inches(12.3), Pt(2))
line.fill.solid()
line.fill.fore_color.rgb = ACCENT
line.line.fill.background()
line.shadow.inherit = False
def add_footer(slide, slide_no, total):
add_text_box(slide, Inches(0.5), Inches(7.15), Inches(8), Inches(0.3),
"EerieVillage: Joseon Exorcist | 스킬 시스템 개발 기획서 v1",
font_size=8, color=GRAY)
add_text_box(slide, Inches(11.3), Inches(7.15), Inches(2), Inches(0.3),
f"BurningTimes 기획팀 | {slide_no}/{total}",
font_size=8, color=GRAY, align=PP_ALIGN.RIGHT)
# ============================================================
# 빌드 시작
# ============================================================
prs = Presentation()
prs.slide_width = SLIDE_W
prs.slide_height = SLIDE_H
BLANK = prs.slide_layouts[6]
TOTAL_SLIDES = 36
# ============================================================
# 슬라이드 1 — 표지
# ============================================================
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, NAVY)
# 액센트 도형 (좌측 황금 띠)
band = s.shapes.add_shape(MSO_SHAPE.RECTANGLE,
0, Inches(2.5), Inches(0.4), Inches(2.5))
band.fill.solid()
band.fill.fore_color.rgb = ACCENT
band.line.fill.background()
band.shadow.inherit = False
add_text_box(s, Inches(1.0), Inches(2.0), Inches(11), Inches(0.7),
"기묘한 고을 : 조선퇴마뎐", font_size=22,
color=ICE, font_name=HEADER_FONT)
add_text_box(s, Inches(1.0), Inches(2.6), Inches(11), Inches(1.2),
"스킬 시스템 개발 기획서", font_size=46, bold=True,
color=WHITE, font_name=HEADER_FONT)
add_text_box(s, Inches(1.0), Inches(3.9), Inches(11), Inches(0.5),
"EerieVillage: Joseon Exorcist — Skill System Spec v1",
font_size=16, color=ICE, font_name=BODY_FONT)
# 메타 정보
add_multiline_text(s, Inches(1.0), Inches(5.2), Inches(11), Inches(1.5),
[
{"text": "조직: BurningTimes 게임 개발 스튜디오 / 기획팀",
"size": 13, "color": ICE},
{"text": "대상: PD 직접 검토 + 개발팀(클라이언트·서버·QA) 구현 참고",
"size": 13, "color": ICE},
{"text": "통합 SOT: BT7-Plan(시스템·콘텐츠) + BT11-Plan(60종 명세) + BT12-Dev v1(개발 설계)",
"size": 13, "color": ICE},
{"text": "작성일: 2026-04-26 / 버전: v1.0",
"size": 13, "color": ICE},
])
# ============================================================
# 1부 — 개요 (슬라이드 2~7)
# ============================================================
# 슬라이드 2 — 시스템 정체성
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "시스템 정체성", 2, TOTAL_SLIDES, "1부 개요")
add_text_box(s, Inches(0.5), Inches(1.1), Inches(12), Inches(0.6),
"한 줄 요약", font_size=16, bold=True, color=NAVY)
quote = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(1.7), Inches(12), Inches(1.2))
quote.fill.solid()
quote.fill.fore_color.rgb = ICE
quote.line.fill.background()
quote.shadow.inherit = False
qtf = quote.text_frame
qtf.margin_left = Inches(0.3)
qtf.margin_right = Inches(0.3)
qtf.vertical_anchor = MSO_ANCHOR.MIDDLE
qp = qtf.paragraphs[0]
qp.alignment = PP_ALIGN.CENTER
qr = qp.add_run()
qr.text = "레벨업 픽으로 빌드를 쌓고, 조건 충족 시 발동하는 각성으로 카타르시스를 제공하는\n자동 발동형(VS 순수형) 스킬·진화 시스템"
qr.font.name = HEADER_FONT
qr.font.size = Pt(18)
qr.font.bold = True
qr.font.color.rgb = NAVY
add_text_box(s, Inches(0.5), Inches(3.2), Inches(12), Inches(0.5),
"VS 순수형 채택 근거 (BT7-Plan 확정 방향 2)",
font_size=15, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(3.7), Inches(12), Inches(3.5),
[
{"text": "공격 버튼 자체 제거 — 플레이어 입력은 이동·점프만", "bullet": True, "size": 13},
{"text": "장착한 액티브가 고유 주기·판정·대미지로 자동 발동 (Vampire Survivors 패턴)", "bullet": True, "size": 13},
{"text": "캐주얼 모바일 환경 정합 — 가상 스틱 1개로 5~10분 런 완결", "bullet": True, "size": 13},
{"text": "손맛은 \"타격 순간의 히트스탑·이펙트·사운드\"로 보장 (i-frame 0.6s 유지)", "bullet": True, "size": 13},
{"text": "조작 부담 경감 → 회피·포지셔닝·빌드 설계에 인지 자원 집중", "bullet": True, "size": 13},
])
add_footer(s, 2, TOTAL_SLIDES)
# 슬라이드 3 — 재미 축 3종 연결
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "재미 축 3종과의 연결 (P30 재미 우선 원칙)", 3, TOTAL_SLIDES, "1부 개요")
# 3개 컬럼 카드
cards = [
("축 1", "육성 롤러코스터의\n카타르시스",
"쌓기 → 폭발 → 낙차 리듬\n각성 발동 = 절정 포인트\n빌드 완성 순간의 전능감",
PURPLE),
("축 2", "자동 발동 감상\n(VS 순수형)",
"이동·점프만으로 포지셔닝\n장착 액티브가 화면을 채움\n\"내가 만든 군대\" 감각",
NAVY),
("축 3", "영속 성장의 위안\n(보조)",
"패시브 스탯 누적\n최대 하트 +1 패시브로\n런 내 안도감 형성",
GREEN_OK),
]
col_w = Inches(3.9)
gap = Inches(0.2)
start_x = Inches(0.5)
for i, (axis, title, body, color) in enumerate(cards):
x = start_x + (col_w + gap) * i
# 헤더 박스
head = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, Inches(1.2), col_w, Inches(0.7))
head.fill.solid()
head.fill.fore_color.rgb = color
head.line.fill.background()
head.shadow.inherit = False
htf = head.text_frame
htf.vertical_anchor = MSO_ANCHOR.MIDDLE
hp = htf.paragraphs[0]
hp.alignment = PP_ALIGN.CENTER
hr = hp.add_run()
hr.text = axis
hr.font.name = HEADER_FONT
hr.font.size = Pt(20)
hr.font.bold = True
hr.font.color.rgb = WHITE
# 본문 박스
body_box = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, Inches(1.9), col_w, Inches(4.3))
body_box.fill.solid()
body_box.fill.fore_color.rgb = LIGHT_BG
body_box.line.color.rgb = color
body_box.line.width = Pt(1.5)
body_box.shadow.inherit = False
btf = body_box.text_frame
btf.margin_left = Inches(0.2)
btf.margin_right = Inches(0.2)
btf.margin_top = Inches(0.2)
bp = btf.paragraphs[0]
bp.alignment = PP_ALIGN.CENTER
br = bp.add_run()
br.text = title
br.font.name = HEADER_FONT
br.font.size = Pt(15)
br.font.bold = True
br.font.color.rgb = NAVY
bp2 = btf.add_paragraph()
bp2.alignment = PP_ALIGN.CENTER
bp2.line_spacing = 1.5
br2 = bp2.add_run()
br2.text = "\n" + body
br2.font.name = BODY_FONT
br2.font.size = Pt(12)
br2.font.color.rgb = DARK_TEXT
add_text_box(s, Inches(0.5), Inches(6.4), Inches(12.3), Inches(0.5),
"스킬 시스템은 3축 모두에 기여 — 액티브(축 2) + 패시브(축 3) + 각성 발동(축 1) 통합 구조",
font_size=12, color=GRAY, align=PP_ALIGN.CENTER)
add_footer(s, 3, TOTAL_SLIDES)
# 슬라이드 4 — 핵심 KPI / 성공 지표
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "핵심 KPI / 성공 지표", 4, TOTAL_SLIDES, "1부 개요")
add_text_box(s, Inches(0.5), Inches(1.1), Inches(12), Inches(0.5),
"스킬 시스템 성공 판정 4축",
font_size=14, bold=True, color=NAVY)
kpi_data = [
["판정 축", "지표", "목표", "측정 방식"],
["다양성", "런당 빌드 분기 수", "8종 이상 시너지 빌드", "내부 플레이테스트·시너지 카드 그래프"],
["성장 체감", "1 스테이지 내 레벨업 횟수", "4~6회 (50~90초/회)", "balance/02 §3 곡선 검증"],
["각성 카타르시스", "런당 각성 발동 횟수", "1~2회 (Stage 3~4 첫 발동)", "보물상자·조건 충족 빈도 로그"],
["조작 부담", "1런 내 입력 채널", "이동·점프 2채널 한정", "InputSystem 액션 카탈로그 검증"],
["성능", "60 fps 유지율", "모바일 95%+ (광역 각성 시)", "Profiler·Frame Debugger 실측"],
["몰입 시간", "평균 런 길이", "5~10분 (캐주얼 정합)", "Stage 클리어·사망 timestamp"],
]
add_table(s, Inches(0.5), Inches(1.7), Inches(12.3), Inches(4.0),
kpi_data, body_size=11, header_size=12,
col_widths=[2, 3, 3.5, 3.8])
add_text_box(s, Inches(0.5), Inches(5.9), Inches(12), Inches(0.5),
"핵심 재미 가설",
font_size=14, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(6.4), Inches(12), Inches(0.8),
[
{"text": "각성 발동은 \"기다림이 보상받는\" 순간 — Lv.5 도달까지 동일 카드 5회 픽 투자가 각성 발동 시 카타르시스로 전환된다",
"bullet": True, "size": 12, "color": DARK_TEXT},
])
add_footer(s, 4, TOTAL_SLIDES)
# 슬라이드 5 — 본 문서 범위·비범위
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "본 문서 범위 (Scope) · 비범위 (Out of Scope)", 5, TOTAL_SLIDES, "1부 개요")
# 좌측 — 범위 (포함)
left_box = s.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(0.5), Inches(1.2), Inches(6), Inches(5.5))
left_box.fill.solid()
left_box.fill.fore_color.rgb = LIGHT_BG
left_box.line.color.rgb = GREEN_OK
left_box.line.width = Pt(2)
left_box.shadow.inherit = False
add_text_box(s, Inches(0.7), Inches(1.35), Inches(5.5), Inches(0.5),
"본 문서가 다루는 범위", font_size=15, bold=True, color=GREEN_OK)
add_multiline_text(s, Inches(0.9), Inches(1.95), Inches(5.5), Inches(4.7),
[
{"text": "스킬 3분류(액티브·패시브·각성) 시스템 구조", "bullet": True, "size": 12},
{"text": "60종 카드 카탈로그 (액티브 20·패시브 20·각성 20)", "bullet": True, "size": 12},
{"text": "픽 알고리즘·스택 업그레이드(Lv.1~5) 메커니즘", "bullet": True, "size": 12},
{"text": "각성 발동 3조건 동시 충족 판정 로직", "bullet": True, "size": 12},
{"text": "보물상자 체계·시너지 발동·하트 증가 메커니즘", "bullet": True, "size": 12},
{"text": "Unity C# 클래스·인터페이스·이벤트 구조", "bullet": True, "size": 12},
{"text": "BT.Framework Tier 1·2 활용 영역", "bullet": True, "size": 12},
{"text": "BT7-Dev 자산(PlayerAttack·Health 등) 통합 영역", "bullet": True, "size": 12},
{"text": "EditMode·PlayMode 검증 시나리오·어뷰징 가드", "bullet": True, "size": 12},
{"text": "Phase 분리 우선순위 (P0~P4)", "bullet": True, "size": 12},
])
# 우측 — 비범위 (제외)
right_box = s.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(6.8), Inches(1.2), Inches(6), Inches(5.5))
right_box.fill.solid()
right_box.fill.fore_color.rgb = LIGHT_BG
right_box.line.color.rgb = RED_HL
right_box.line.width = Pt(2)
right_box.shadow.inherit = False
add_text_box(s, Inches(7.0), Inches(1.35), Inches(5.5), Inches(0.5),
"본 문서가 다루지 않는 범위", font_size=15, bold=True, color=RED_HL)
add_multiline_text(s, Inches(7.2), Inches(1.95), Inches(5.5), Inches(4.7),
[
{"text": "구체 수치(대미지·확률·쿨다운) — balance-designer 이관", "bullet": True, "size": 12},
{"text": "narrative 한자 카드명 재매핑 — 현행 유지 (PD 후속)", "bullet": True, "size": 12},
{"text": "BM·과금 구조 — Phase 3-C 별도 설계", "bullet": True, "size": 12},
{"text": "스테이지·맵·적 배치 — level-designer 영역", "bullet": True, "size": 12},
{"text": "사운드·이펙트 아트 리소스 — 외부 아티스트 영역", "bullet": True, "size": 12},
{"text": "특성(Trait) 시스템 상세 — system/02 별도 문서", "bullet": True, "size": 12},
{"text": "보스 패턴·페이즈 설계 — content/03 별도", "bullet": True, "size": 12},
{"text": "다국어·로컬라이제이션 — 출시 단계 별도", "bullet": True, "size": 12},
])
add_footer(s, 5, TOTAL_SLIDES)
# 슬라이드 6 — 의존 시스템·문서 SOT
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "의존 시스템 · 문서 SOT 인벤토리", 6, TOTAL_SLIDES, "1부 개요")
add_text_box(s, Inches(0.5), Inches(1.1), Inches(12), Inches(0.4),
"본 기획서가 통합한 7종 SOT 문서",
font_size=14, bold=True, color=NAVY)
sot_data = [
["문서", "버전", "역할", "기획서 반영 부"],
["system/01_카드_시스템.md", "v0.2 (BT7-Plan)", "3분류 구조·픽 알고리즘·각성 조건·하트 증가", "2부 구조 / 4부 메커니즘"],
["content/01_카드_풀.md", "v0.2 (BT7-Plan)", "3분류 카드 풀 규모·태그 체계·시너지 빌드 예시", "3부 카테고리 / 5부 60종"],
["content/02_스킬_효과_컨셉.md", "v0.2 (BT11-Plan)", "60종 카드 단위 동작 컨셉·카테고리 분배", "3부 / 5부"],
["balance/01_전투_수치.md", "v0.2 (BT7-Plan)", "대미지 공식·BaseCooldown·BaseDamage·StackLevelFactor", "4부 메커니즘 / 6부 구현"],
["balance/02_레벨업_곡선.md", "v0.2 (BT7-Plan)", "경험치·레벨업 주기·픽 알고리즘 가중치", "4부 메커니즘"],
["ux/02_HUD_설계.md", "v0.2 (BT7-Plan)", "픽 UI·각성 발동 연출·HUD 슬롯", "4부 / 6부 UI 가이드"],
["개발/spec/스킬_시스템_설계_v1.md", "v1.0 (BT12-Dev)", "Unity C# 4계층 아키텍처·인터페이스 정의", "6부 구현 가이드 (전수)"],
]
add_table(s, Inches(0.5), Inches(1.6), Inches(12.3), Inches(4.5),
sot_data, body_size=10, header_size=11,
col_widths=[3.2, 2, 4.5, 2.6])
add_text_box(s, Inches(0.5), Inches(6.3), Inches(12), Inches(0.8),
"📌 본 기획서는 BT7-Plan(시스템·콘텐츠) + BT11-Plan(60종 명세) + BT12-Dev v1(개발 설계) 3종 통합 산출물로,\n"
" 각 SOT의 단일 진실 원천(SSOT)을 유지하며 본 문서는 통합 인덱스 역할을 한다.",
font_size=11, color=GRAY)
add_footer(s, 6, TOTAL_SLIDES)
# 슬라이드 7 — 1부 요약·전환
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, NAVY)
add_text_box(s, Inches(0.5), Inches(2.5), Inches(12.3), Inches(0.7),
"2부 — 시스템 구조", font_size=42, bold=True, color=ICE,
align=PP_ALIGN.CENTER, font_name=HEADER_FONT)
add_text_box(s, Inches(0.5), Inches(3.4), Inches(12.3), Inches(0.5),
"3분류 카드 구조 · 코어 루프 · 데이터 모델 · 태그 체계 · 빌드 슬롯",
font_size=18, color=ICE, align=PP_ALIGN.CENTER)
# 좌측 황금띠
band = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, Inches(2.0), Inches(0.4), Inches(2.5))
band.fill.solid()
band.fill.fore_color.rgb = ACCENT
band.line.fill.background()
band.shadow.inherit = False
add_text_box(s, Inches(0.5), Inches(6.8), Inches(12.3), Inches(0.4),
"Slide 7 / 36", font_size=10, color=ICE, align=PP_ALIGN.CENTER)
# ============================================================
# 2부 — 시스템 구조 (슬라이드 8~14)
# ============================================================
# 슬라이드 8 — 3분류 구조 다이어그램
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "카드 3분류 구조 — 액티브 + 패시브 = 각성", 8, TOTAL_SLIDES, "2부 구조")
# 액티브 박스
active_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.7), Inches(1.5), Inches(3.5), Inches(2.0))
active_box.fill.solid()
active_box.fill.fore_color.rgb = NAVY
active_box.line.fill.background()
active_box.shadow.inherit = False
atf = active_box.text_frame
atf.vertical_anchor = MSO_ANCHOR.MIDDLE
atf.margin_left = Inches(0.2)
atf.margin_right = Inches(0.2)
ap = atf.paragraphs[0]
ap.alignment = PP_ALIGN.CENTER
ar = ap.add_run()
ar.text = "액티브"
ar.font.name = HEADER_FONT
ar.font.size = Pt(24)
ar.font.bold = True
ar.font.color.rgb = WHITE
ap2 = atf.add_paragraph()
ap2.alignment = PP_ALIGN.CENTER
ar2 = ap2.add_run()
ar2.text = "\n자동 발동 무기·스킬\n주기·판정·대미지\n20종 (Lv.1~Lv.5)"
ar2.font.name = BODY_FONT
ar2.font.size = Pt(13)
ar2.font.color.rgb = ICE
# 더하기 기호
plus = s.shapes.add_shape(MSO_SHAPE.OVAL, Inches(4.5), Inches(2.2), Inches(0.6), Inches(0.6))
plus.fill.solid()
plus.fill.fore_color.rgb = ACCENT
plus.line.fill.background()
ptf = plus.text_frame
ptf.vertical_anchor = MSO_ANCHOR.MIDDLE
pp = ptf.paragraphs[0]
pp.alignment = PP_ALIGN.CENTER
pr = pp.add_run()
pr.text = "+"
pr.font.size = Pt(24)
pr.font.bold = True
pr.font.color.rgb = NAVY
# 패시브 박스
passive_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(5.4), Inches(1.5), Inches(3.5), Inches(2.0))
passive_box.fill.solid()
passive_box.fill.fore_color.rgb = GREEN_OK
passive_box.line.fill.background()
passive_box.shadow.inherit = False
ptf2 = passive_box.text_frame
ptf2.vertical_anchor = MSO_ANCHOR.MIDDLE
ptf2.margin_left = Inches(0.2)
pp_p = ptf2.paragraphs[0]
pp_p.alignment = PP_ALIGN.CENTER
pp_r = pp_p.add_run()
pp_r.text = "패시브"
pp_r.font.name = HEADER_FONT
pp_r.font.size = Pt(24)
pp_r.font.bold = True
pp_r.font.color.rgb = WHITE
pp_p2 = ptf2.add_paragraph()
pp_p2.alignment = PP_ALIGN.CENTER
pp_r2 = pp_p2.add_run()
pp_r2.text = "\n상시 스탯·효과 보정\n액티브 효율 증폭\n20종 (Lv.1~Lv.5)"
pp_r2.font.name = BODY_FONT
pp_r2.font.size = Pt(13)
pp_r2.font.color.rgb = WHITE
# 화살표 (조건 충족)
arrow = s.shapes.add_shape(MSO_SHAPE.RIGHT_ARROW,
Inches(9.2), Inches(2.3), Inches(0.8), Inches(0.4))
arrow.fill.solid()
arrow.fill.fore_color.rgb = ACCENT
arrow.line.fill.background()
arrow.shadow.inherit = False
add_text_box(s, Inches(9.05), Inches(1.85), Inches(1.2), Inches(0.4),
"조건 충족", font_size=10, color=GRAY, align=PP_ALIGN.CENTER)
# 각성 박스
awaken_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(10.2), Inches(1.5), Inches(2.7), Inches(2.0))
awaken_box.fill.solid()
awaken_box.fill.fore_color.rgb = PURPLE
awaken_box.line.fill.background()
awaken_box.shadow.inherit = False
awtf = awaken_box.text_frame
awtf.vertical_anchor = MSO_ANCHOR.MIDDLE
awp = awtf.paragraphs[0]
awp.alignment = PP_ALIGN.CENTER
awr = awp.add_run()
awr.text = "각성"
awr.font.name = HEADER_FONT
awr.font.size = Pt(24)
awr.font.bold = True
awr.font.color.rgb = WHITE
awp2 = awtf.add_paragraph()
awp2.alignment = PP_ALIGN.CENTER
awr2 = awp2.add_run()
awr2.text = "\n진화 카드\n(별도 풀)\n20종"
awr2.font.name = BODY_FONT
awr2.font.size = Pt(13)
awr2.font.color.rgb = WHITE
# 발동 조건 (하단)
add_text_box(s, Inches(0.5), Inches(4.0), Inches(12.3), Inches(0.5),
"각성 발동 3조건 동시 충족 (VS 원작 동일)",
font_size=15, bold=True, color=NAVY, align=PP_ALIGN.CENTER)
cond_data = [
["조건 1", "조건 2", "조건 3"],
["해당 액티브 카드 Lv.5 도달", "각성에 필요한 패시브 1+ 보유", "보물상자 획득"],
]
add_table(s, Inches(1.0), Inches(4.6), Inches(11.3), Inches(1.3),
cond_data, header_fill=PURPLE, body_size=12, header_size=13,
col_widths=[1, 1, 1])
add_text_box(s, Inches(0.5), Inches(6.2), Inches(12.3), Inches(0.7),
"총 카드 풀 규모: 60종 (20+20+20)\n"
"구 4티어 레어리티(Common/Uncommon/Rare/Epic) 폐기 -> VS 원작 계승 3분류 구조",
font_size=11, color=GRAY, align=PP_ALIGN.CENTER)
add_footer(s, 8, TOTAL_SLIDES)
# 슬라이드 9 — 코어 루프
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "코어 루프 — 픽 -> 스택 -> 각성 -> 클라이맥스", 9, TOTAL_SLIDES, "2부 구조")
# 7단계 플로우차트
loop_steps = [
("1", "레벨업 트리거", "경험치 만렙 도달", NAVY),
("2", "3장 무작위 드로우", "액티브·패시브 2축\n빌드 태그 가중", NAVY),
("3", "픽 UI 표시", "1장 선택 (강제)\n건너뛰기 없음", NAVY),
("4", "장착 / 스택 업그레이드", "신규 장착 또는\n동일 카드 Lv.N->N+1", NAVY),
("5", "보물상자 획득 (병행)", "스테이지 진행 중\n조건부 스폰", ACCENT),
("6", "각성 발동 판정", "3조건 동시 충족\n각성 풀에서 발동", PURPLE),
("7", "런 종료", "보스 클리어 / 사망\n카드 전손·빌드 초기화", RED_HL),
]
box_w = Inches(1.7)
box_h = Inches(2.3)
gap = Inches(0.05)
start_x = Inches(0.4)
top = Inches(1.5)
for i, (num, title, desc, color) in enumerate(loop_steps):
x = start_x + (box_w + gap) * i
box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, top, box_w, box_h)
box.fill.solid()
box.fill.fore_color.rgb = color
box.line.fill.background()
box.shadow.inherit = False
btf = box.text_frame
btf.margin_left = Inches(0.08)
btf.margin_right = Inches(0.08)
btf.margin_top = Inches(0.1)
btf.vertical_anchor = MSO_ANCHOR.TOP
bp = btf.paragraphs[0]
bp.alignment = PP_ALIGN.CENTER
br = bp.add_run()
br.text = num
br.font.name = HEADER_FONT
br.font.size = Pt(20)
br.font.bold = True
br.font.color.rgb = WHITE
bp2 = btf.add_paragraph()
bp2.alignment = PP_ALIGN.CENTER
bp2.line_spacing = 1.2
br2 = bp2.add_run()
br2.text = "\n" + title
br2.font.name = HEADER_FONT
br2.font.size = Pt(11)
br2.font.bold = True
br2.font.color.rgb = WHITE
bp3 = btf.add_paragraph()
bp3.alignment = PP_ALIGN.CENTER
bp3.line_spacing = 1.15
br3 = bp3.add_run()
br3.text = "\n" + desc
br3.font.name = BODY_FONT
br3.font.size = Pt(9)
br3.font.color.rgb = WHITE
add_text_box(s, Inches(0.5), Inches(4.2), Inches(12), Inches(0.5),
"주요 분기점·강조 사항",
font_size=14, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(4.7), Inches(12.3), Inches(2.3),
[
{"text": "픽 단계(3) — \"건너뛰기\" 옵션 미제공. 3장 모두 나쁜 경우는 \"빌드 위기\"로 설계 (긴장 요소)",
"bullet": True, "size": 12},
{"text": "스택 단계(4) — Lv.5 도달 시 각성 조건 1번 \"액티브 Lv.5\" 충족 (자동 마킹)",
"bullet": True, "size": 12},
{"text": "보물상자(5) — 스테이지 진행 병행 트리거. 각성 게이팅 조건이며 무관 시 대체 보상 (재화·회복·경험치)",
"bullet": True, "size": 12, "color": ACCENT},
{"text": "각성(6) — VS 원작 정합. 단순 자동 발동 아님 = \"기다림이 보상받는\" 카타르시스의 구조적 기반",
"bullet": True, "size": 12, "color": PURPLE},
{"text": "런 종료(7) — 모든 카드·빌드 전손. 메타 진행은 별도 시스템(특성·해금) 영역",
"bullet": True, "size": 12, "color": RED_HL},
])
add_footer(s, 9, TOTAL_SLIDES)
# 슬라이드 10 — 데이터 모델
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "데이터 모델 — ScriptableObject 3종 스키마", 10, TOTAL_SLIDES, "2부 구조")
add_text_box(s, Inches(0.5), Inches(1.05), Inches(12), Inches(0.4),
"공통 베이스 + 3종 파생 클래스 (Unity ScriptableObject)",
font_size=13, bold=True, color=NAVY)
base = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(1.5), Inches(12.3), Inches(1.4))
base.fill.solid()
base.fill.fore_color.rgb = LIGHT_BG
base.line.color.rgb = NAVY
base.line.width = Pt(1.5)
base.shadow.inherit = False
add_text_box(s, Inches(0.7), Inches(1.55), Inches(12), Inches(0.4),
"abstract class SkillDataAsset : ScriptableObject (공통 베이스)",
font_size=12, bold=True, color=NAVY, font_name=CODE_FONT)
add_multiline_text(s, Inches(0.7), Inches(1.95), Inches(12), Inches(0.95),
[
{"text": "string CardId | string DisplayName / EnglishName / Description | Sprite Icon",
"size": 10, "color": DARK_TEXT},
{"text": "AttributeTag[] AttributeTags ([Flags] enum: Physical·Fire·Frost·Lightning·Dark)",
"size": 10, "color": DARK_TEXT},
{"text": "TypeTag[] TypeTags ([Flags] enum: Melee·Ranged·Area·Persistent·Recovery·Defense)",
"size": 10, "color": DARK_TEXT},
], font_name=CODE_FONT)
derives = [
("ActiveSkillData", NAVY, [
"ActiveCategory Category (A·B·C·D·E·F)",
"ActiveTrigger Trigger (OnTime·OnHit·OnKill)",
"float BaseCooldown = 1.5f",
"int BaseDamage = 10",
"Vector2 HitboxSize",
"ProjectileTrajectory Trajectory",
"GameObject MinionPrefab",
"int DebuffStackLimit",
"float FireProbability (0~1, F전용)",
]),
("PassiveSkillData", GREEN_OK, [
"PassiveCategory Category (P-A~P-E)",
"bool IsAlwaysOn",
"StatType TargetStat",
"float BaseModifierValue",
"PassiveTriggerKind TriggerKind",
" - OnPlayerDamaged",
" - OnEnemyKilled",
" - OnTimer",
]),
("AwakeningSkillData", PURPLE, [
"AwakeningPattern Pattern",
" (ScaleUp·AddEffect·",
" MultiFire·GlobalSpread)",
"ActiveSkillData OriginalActive",
"PassiveSkillData[] RequiredPassives",
"int AwakeningBaseDamage = 25",
"int MultiFireCount = 3",
"GameObject AwakeningEffectPrefab",
]),
]
col_w = Inches(4.0)
gap = Inches(0.1)
start_x = Inches(0.5)
top = Inches(3.1)
for i, (name, color, fields) in enumerate(derives):
x = start_x + (col_w + gap) * i
box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, top, col_w, Inches(3.6))
box.fill.solid()
box.fill.fore_color.rgb = WHITE
box.line.color.rgb = color
box.line.width = Pt(2)
box.shadow.inherit = False
hd = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, top, col_w, Inches(0.5))
hd.fill.solid()
hd.fill.fore_color.rgb = color
hd.line.fill.background()
hd.shadow.inherit = False
htf = hd.text_frame
htf.vertical_anchor = MSO_ANCHOR.MIDDLE
hp = htf.paragraphs[0]
hp.alignment = PP_ALIGN.CENTER
hr = hp.add_run()
hr.text = name
hr.font.name = CODE_FONT
hr.font.size = Pt(13)
hr.font.bold = True
hr.font.color.rgb = WHITE
body_lines = [{"text": f, "size": 9, "color": DARK_TEXT, "font_name": CODE_FONT}
for f in fields]
add_multiline_text(s, x + Inches(0.15), top + Inches(0.6),
col_w - Inches(0.3), Inches(3.0),
body_lines, font_name=CODE_FONT)
add_footer(s, 10, TOTAL_SLIDES)
# 슬라이드 11 — 태그 체계
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "태그 체계 — 속성 5종 + 유형 6종 = 11종 (임시 일반 RPG 용어)", 11, TOTAL_SLIDES, "2부 구조")
add_text_box(s, Inches(0.5), Inches(1.05), Inches(6.0), Inches(0.5),
"속성 태그 (후보 5종)", font_size=14, bold=True, color=NAVY)
attr_data = [
["태그", "의미", "대표 카드"],
["[물리]", "순수 타격·관통", "A01·A05·A10·A12·A16"],
["[화염]", "지속 대미지·점화", "A02·A15 / P02"],
["[냉기]", "둔화·빙결", "A14·A19 / P03"],
["[번개]", "연쇄·즉발", "A04·A13 / P04"],
["[암흑]", "저주·DoT·약화", "A03·A06·A07·A08·A11 / P05"],
]
add_table(s, Inches(0.5), Inches(1.5), Inches(6.0), Inches(2.7),
attr_data, body_size=10, header_size=11,
col_widths=[1.5, 2.5, 3])
add_text_box(s, Inches(6.8), Inches(1.05), Inches(6.0), Inches(0.5),
"유형 태그 (후보 6종)", font_size=14, bold=True, color=NAVY)
type_data = [
["태그", "의미", "대표 카드"],
["[근접]", "플레이어 주변 판정", "A05·A10·A12·A16·A18·A20"],
["[원거리]", "투사체·원격", "A01·A02·A03·A14·A15"],
["[범위]", "AoE·다중 타격", "A04·A05·A07·A13·A19"],
["[지속]", "설치·오라·DoT", "A06·A09·A11·A17·A20"],
["[회복]", "쿼터 회복·실드", "P11·P16·P17"],
["[방어]", "피해 감소·회피·하트+1", "P12·P13·P14·P15·A09·A17"],
]
add_table(s, Inches(6.8), Inches(1.5), Inches(6.0), Inches(3.2),
type_data, body_size=10, header_size=11,
col_widths=[1.5, 2.5, 3])
syn_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(5.0), Inches(12.3), Inches(1.7))
syn_box.fill.solid()
syn_box.fill.fore_color.rgb = LIGHT_BG
syn_box.line.color.rgb = ACCENT
syn_box.line.width = Pt(1.5)
syn_box.shadow.inherit = False
add_text_box(s, Inches(0.7), Inches(5.1), Inches(11.5), Inches(0.4),
"시너지 발동 조건 — 태그 2축 설계",
font_size=13, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(5.5), Inches(11.5), Inches(1.2),
[
{"text": "동일 태그 카드 K장 이상 빌드 편입 시 패시브 효과 추가 발동 (K 수치는 balance-designer 영역)",
"bullet": True, "size": 11},
{"text": "속성 빌드(예: 화염 6장) + 유형 빌드(예: 원거리 5장) 동시 추구 = 교차 빌드 (제3의 방향)",
"bullet": True, "size": 11},
{"text": "각 카드는 1~2개 태그 보유. 2태그 카드는 두 시너지에 동시 기여 가능",
"bullet": True, "size": 11},
])
add_text_box(s, Inches(0.5), Inches(6.85), Inches(12.3), Inches(0.4),
"narrative-designer 후속: 한자·조선 퇴마 톤 일괄 재매핑 대기 (PD 직접 검토)",
font_size=10, color=RED_HL, align=PP_ALIGN.CENTER)
add_footer(s, 11, TOTAL_SLIDES)
# 슬라이드 12 — 카드 풀 규모
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "카드 풀 규모 — 총 60종 (액티브 20 · 패시브 20 · 각성 20)", 12, TOTAL_SLIDES, "2부 구조")
add_text_box(s, Inches(0.5), Inches(1.1), Inches(4.0), Inches(0.5),
"액티브 20종 — 6 카테고리", font_size=13, bold=True, color=NAVY)
active_cat = [
["카테고리", "종수"],
["A. 투사체", "6"],
["B. 근접·범위", "5"],
["C. 설치·지속", "3"],
["D. 소환", "3"],
["E. 상태이상", "1"],
["F. 특수 판정", "2"],
]
add_table(s, Inches(0.5), Inches(1.6), Inches(4.0), Inches(3.5),
active_cat, body_size=11, header_size=12, col_widths=[3, 1])
add_text_box(s, Inches(4.7), Inches(1.1), Inches(4.0), Inches(0.5),
"패시브 20종 — 5 카테고리", font_size=13, bold=True, color=GREEN_OK)
passive_cat = [
["카테고리", "종수"],
["P-A. 스탯 상승", "7"],
["P-B. 주기·증폭", "3"],
["P-C. 생존 강화", "5"],
["P-D. 회복", "2"],
["P-E. 자원 확장", "3"],
]
add_table(s, Inches(4.7), Inches(1.6), Inches(4.0), Inches(3.0),
passive_cat, header_fill=GREEN_OK, body_size=11, header_size=12, col_widths=[3, 1])
add_text_box(s, Inches(8.9), Inches(1.1), Inches(4.0), Inches(0.5),
"각성 20종 — 4 진화 패턴", font_size=13, bold=True, color=PURPLE)
awaken_cat = [
["진화 패턴", "종수"],
["1. 스케일업", "6"],
["2. 새 효과 추가", "6"],
["3. 다중 발동", "4"],
["4. 광역 확산", "4"],
]
add_table(s, Inches(8.9), Inches(1.6), Inches(4.0), Inches(2.6),
awaken_cat, header_fill=PURPLE, body_size=11, header_size=12, col_widths=[3, 1])
add_text_box(s, Inches(0.5), Inches(5.3), Inches(12), Inches(0.4),
"분배 근거 (재미 축 + 구현 부담 균형)",
font_size=13, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(5.7), Inches(12.3), Inches(1.5),
[
{"text": "투사체(6) — VS 원작 핵심. 가장 직관적 \"자동 발동 감상\" 체감",
"bullet": True, "size": 11},
{"text": "근접·범위(5) — 밀집 구간 연출 + 포지셔닝 유도. 냉기·번개·물리 속성 분산",
"bullet": True, "size": 11},
{"text": "P-A 스탯(7) — 범용 1 + 속성 4 + 크리티컬 2. 가장 직관적 강화",
"bullet": True, "size": 11},
{"text": "P-C 생존(5) — 실드·하트+1 2종·피해 감소·회피. 생존 빌드 풀 다양성",
"bullet": True, "size": 11},
{"text": "각성 4 패턴 = 원작 정합. \"같은 게 세짐 / 새 효과 / 2배 발동 / 화면 전체\" 4종 카타르시스",
"bullet": True, "size": 11, "color": PURPLE},
])
add_footer(s, 12, TOTAL_SLIDES)
# 슬라이드 13 — 빌드 슬롯 구조
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "빌드 슬롯 구조 — 액티브 6 / 패시브 6 (VS 원작 참조)", 13, TOTAL_SLIDES, "2부 구조")
add_text_box(s, Inches(0.5), Inches(1.1), Inches(6.0), Inches(0.5),
"PlayerSkillInventory 장착 슬롯",
font_size=13, bold=True, color=NAVY)
add_text_box(s, Inches(0.5), Inches(1.7), Inches(2), Inches(0.4),
"액티브 슬롯 (최대 6)", font_size=11, bold=True, color=NAVY)
for i in range(6):
x = Inches(0.5 + i * 0.95)
slot = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
x, Inches(2.15), Inches(0.85), Inches(0.85))
slot.fill.solid()
slot.fill.fore_color.rgb = NAVY if i < 4 else WHITE
slot.line.color.rgb = NAVY
slot.line.width = Pt(1.5)
slot.shadow.inherit = False
if i < 4:
slot.text_frame.text = f"A{i+1}"
for p_ in slot.text_frame.paragraphs:
p_.alignment = PP_ALIGN.CENTER
for r_ in p_.runs:
r_.font.size = Pt(12)
r_.font.bold = True
r_.font.color.rgb = WHITE
add_text_box(s, Inches(0.5), Inches(3.2), Inches(2), Inches(0.4),
"패시브 슬롯 (최대 6)", font_size=11, bold=True, color=GREEN_OK)
for i in range(6):
x = Inches(0.5 + i * 0.95)
slot = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
x, Inches(3.65), Inches(0.85), Inches(0.85))
slot.fill.solid()
slot.fill.fore_color.rgb = GREEN_OK if i < 5 else WHITE
slot.line.color.rgb = GREEN_OK
slot.line.width = Pt(1.5)
slot.shadow.inherit = False
if i < 5:
slot.text_frame.text = f"P{i+1}"
for p_ in slot.text_frame.paragraphs:
p_.alignment = PP_ALIGN.CENTER
for r_ in p_.runs:
r_.font.size = Pt(12)
r_.font.bold = True
r_.font.color.rgb = WHITE
add_text_box(s, Inches(0.5), Inches(4.7), Inches(6), Inches(0.4),
"각 슬롯 우하단에 Lv.1~5 표기 (UI)",
font_size=10, color=GRAY)
add_text_box(s, Inches(7.0), Inches(1.1), Inches(5.7), Inches(0.5),
"슬롯 운용 규칙",
font_size=13, bold=True, color=NAVY)
add_multiline_text(s, Inches(7.0), Inches(1.65), Inches(5.7), Inches(5.0),
[
{"text": "액티브·패시브 슬롯 분리 (BT7-Plan 정합)", "bullet": True, "size": 11, "bold": True},
{"text": "슬롯 가득 차도 동일 카드 재픽으로 Lv 업 가능", "bullet": True, "size": 11},
{"text": "신규 카드 픽 시 빈 슬롯 우선 편입", "bullet": True, "size": 11},
{"text": "각성 발동 시 원 액티브 슬롯 점유 유지 (대체)", "bullet": True, "size": 11, "color": PURPLE},
{"text": "런 종료 시 모든 슬롯 초기화 (전손)", "bullet": True, "size": 11, "color": RED_HL},
{"text": "", "size": 11},
{"text": "픽 알고리즘 처리:", "bullet": True, "size": 11, "bold": True},
{"text": " - 액티브·패시브 풀에서 가중 무작위 3장 드로우",
"size": 10, "color": DARK_TEXT},
{"text": " - 빌드 주력 태그 2개 가중치 보너스",
"size": 10, "color": DARK_TEXT},
{"text": " - 빌드 편입 카드는 후보 제외 (Lv.5 미만 업그레이드는 재등장)",
"size": 10, "color": DARK_TEXT},
{"text": " - 3장 액티브·패시브 혼합 필터 우선 (풀 고갈 시 해제)",
"size": 10, "color": DARK_TEXT},
{"text": "", "size": 11},
{"text": "각성 슬롯은 별도 풀 — 레벨업 드로우 풀에 미포함",
"bullet": True, "size": 11, "color": PURPLE},
])
add_footer(s, 13, TOTAL_SLIDES)
# 슬라이드 14 — 2부 종결 / 3부 전환
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, NAVY)
band = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, Inches(2.0), Inches(0.4), Inches(2.5))
band.fill.solid()
band.fill.fore_color.rgb = ACCENT
band.line.fill.background()
band.shadow.inherit = False
add_text_box(s, Inches(0.5), Inches(2.5), Inches(12.3), Inches(0.7),
"3부 — 카드 카테고리 명세", font_size=42, bold=True, color=ICE,
align=PP_ALIGN.CENTER, font_name=HEADER_FONT)
add_text_box(s, Inches(0.5), Inches(3.4), Inches(12.3), Inches(0.5),
"액티브 6 카테고리 · 패시브 5 카테고리 · 각성 4 패턴",
font_size=18, color=ICE, align=PP_ALIGN.CENTER)
add_text_box(s, Inches(0.5), Inches(6.8), Inches(12.3), Inches(0.4),
"Slide 14 / 36", font_size=10, color=ICE, align=PP_ALIGN.CENTER)
# ============================================================
# 3부 — 카드 카테고리 명세 (슬라이드 15~20)
# ============================================================
# 슬라이드 15 — 액티브 6 카테고리
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "액티브 6 카테고리 — 동작 방식·구현 복잡도", 15, TOTAL_SLIDES, "3부 카테고리")
active_data = [
["#", "카테고리", "동작 방식", "구현 복잡도", "대표 카드"],
["A", "투사체형", "주기마다 전방·유도 투사체 자동 발사, 단발 DPS", "낮음 (BT.Framework 풀링 활용)", "A01·A14·A15"],
["B", "근접·범위형", "플레이어 주변 즉시 판정 (원형/부채꼴)", "중간 (OverlapBox 다중)", "A05·A12·A13"],
["C", "설치·지속형", "이동 경로/위치에 지속 효과 구역 잔존", "중간~높음 (풀 + Trigger)", "A06·A09·A17"],
["D", "소환형", "독립 행동 소환물 생성, 자동 공격/어그로", "높음 (AI + 타이머 + 풀)", "A10·A11·A20"],
["E", "상태이상형", "스택 가능 디버프(저주·봉인·독) 부여", "중간 (적별 카운터)", "A03·A07·A08"],
["F", "특수 판정형", "확률 자동 발동 회심 / 무적 i-frame", "낮음 (Random 분기)", "A16·A18"],
]
add_table(s, Inches(0.5), Inches(1.1), Inches(12.3), Inches(4.5),
active_data, body_size=10, header_size=11,
col_widths=[0.5, 1.5, 4.8, 2.5, 3])
add_text_box(s, Inches(0.5), Inches(5.8), Inches(12), Inches(0.5),
"재미 축 연결",
font_size=13, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(6.3), Inches(12.3), Inches(1.0),
[
{"text": "A·B·C·F = 축 2 (자동 발동 감상) — 화면 인상 위주 카테고리",
"bullet": True, "size": 11},
{"text": "D·E = 축 1 (육성 카타르시스) — 누적·스택·전능감 체감",
"bullet": True, "size": 11},
])
add_footer(s, 15, TOTAL_SLIDES)
# 슬라이드 16 — 패시브 5 카테고리
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "패시브 5 카테고리 — 강화 대상·시너지", 16, TOTAL_SLIDES, "3부 카테고리")
passive_data = [
["#", "카테고리", "동작 방식", "주 시너지 액티브", "대표 카드"],
["P-A", "스탯 상승형", "장착 즉시 상시, 대미지/속도/크리 보정", "전체 (속성 매칭 시 강화)", "P01·P02~P05·P09·P10"],
["P-B", "주기·증폭형", "쿨다운 단축 / 판정 범위 / 투사체 수 증가", "A·B·E (DPS 증폭 라인)", "P06·P07·P08"],
["P-C", "생존 강화형", "최대 하트 +1 / 피해 감소 / 회피 / 실드", "C·D (오래 버틸 빌드)", "P11·P12·P13·P14·P15"],
["P-D", "회복형", "처치/시간 기반 쿼터 자동 회복", "B·E (처치 빈도 높음)", "P16·P17"],
["P-E", "자원 확장형", "이동·점프·경험치·보물상자 확률", "전체 (간접 가속)", "P18·P19·P20"],
]
add_table(s, Inches(0.5), Inches(1.1), Inches(12.3), Inches(4.0),
passive_data, header_fill=GREEN_OK, body_size=10, header_size=11,
col_widths=[0.5, 1.8, 4.5, 2.5, 3])
# 패시브-액티브 상호작용
add_text_box(s, Inches(0.5), Inches(5.4), Inches(12), Inches(0.5),
"패시브-액티브 상호작용 구조",
font_size=13, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(5.9), Inches(12.3), Inches(1.4),
[
{"text": "P-A 속성 강화 -> A·B·C·D·E 동일 속성 카드 대미지 배율 상승",
"bullet": True, "size": 11},
{"text": "P-B 발동 주기 단축 -> A 투사체 / B 근접·범위 / E 상태이상 (DPS·스택 가속)",
"bullet": True, "size": 11},
{"text": "P-C 생존 강화 -> C 설치·지속 / D 소환 (오래 버텨야 위력 발휘 카테고리 보조)",
"bullet": True, "size": 11},
{"text": "P-D 회복 -> B·E 처치 속도 빠른 카테고리 (P16 발동 빈도 가속)",
"bullet": True, "size": 11},
{"text": "P-E 자원 확장 -> 전체 간접 가속 (특히 P20 재물복 + 각성 빌드)",
"bullet": True, "size": 11, "color": ACCENT},
])
add_footer(s, 16, TOTAL_SLIDES)
# 슬라이드 17 — 각성 4 패턴
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "각성 4 진화 패턴 — 카타르시스 4종 분기", 17, TOTAL_SLIDES, "3부 카테고리")
# 4 패턴 그리드
patterns = [
("1. 스케일업", "ScaleUp",
"원 액티브 그대로 + 규모 대폭 증가\n대미지 / 범위 / 속도 1~2개 폭증",
"AW05 학익천진 (부채꼴 -> 360도)\nAW12 쾌검비경 (속도 극속)",
"낮음 (배율만 적용)", "밸런스 치우침"),
("2. 새 효과 추가", "AddEffect",
"기존 효과 유지 + 완전히 새 효과 부착\n부가 판정/속성/소환물",
"AW04 봉령결계 (봉인 + DoT)\nAW07 사령결계 (스택 즉시 최대)",
"중간 (이벤트 병렬)", "이벤트 순서 관리"),
("3. 다중 발동", "MultiFire",
"1회 -> N회 동시 발동\n투사체 2배 / 소환물 3~4기",
"AW10 분신만화 (1->3~4기)\nAW15 귀화유성 (무한 발사)",
"중간 (풀링 용량 ↑)", "파티클 상한 초과"),
("4. 광역 확산", "GlobalSpread",
"효과 화면 전체로 확산\n런 클라이맥스 연출",
"AW01 천부경문 (화면 전체)\nAW03 천뢰강림 (무한 연쇄)",
"높음 (성능 최적화 필수)", "성능 이슈 고위험"),
]
col_w = Inches(3.0)
row_h = Inches(2.7)
gap_x = Inches(0.1)
gap_y = Inches(0.15)
for i, (title, code, desc, examples, complexity, risk) in enumerate(patterns):
col = i % 4
x = Inches(0.5) + (col_w + gap_x) * col
y = Inches(1.1)
box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, col_w, row_h)
box.fill.solid()
box.fill.fore_color.rgb = WHITE
box.line.color.rgb = PURPLE
box.line.width = Pt(1.5)
box.shadow.inherit = False
# 제목 헤더
hd = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, col_w, Inches(0.5))
hd.fill.solid()
hd.fill.fore_color.rgb = PURPLE
hd.line.fill.background()
hd.shadow.inherit = False
htf = hd.text_frame
htf.vertical_anchor = MSO_ANCHOR.MIDDLE
hp = htf.paragraphs[0]
hp.alignment = PP_ALIGN.CENTER
hr = hp.add_run()
hr.text = title
hr.font.name = HEADER_FONT
hr.font.size = Pt(12)
hr.font.bold = True
hr.font.color.rgb = WHITE
# 본문
add_multiline_text(s, x + Inches(0.1), y + Inches(0.55),
col_w - Inches(0.2), Inches(2.1),
[
{"text": f"({code})", "size": 9, "color": GRAY, "font_name": CODE_FONT},
{"text": "", "size": 9},
{"text": desc, "size": 10, "color": DARK_TEXT},
{"text": "", "size": 9},
{"text": "예시:", "size": 9, "bold": True, "color": NAVY},
{"text": examples, "size": 9, "color": DARK_TEXT},
])
# 패턴별 분배·구현 복잡도 (하단)
add_text_box(s, Inches(0.5), Inches(4.0), Inches(12), Inches(0.5),
"패턴별 분배 · 구현 복잡도 · 주요 리스크",
font_size=13, bold=True, color=NAVY)
risk_data = [
["패턴", "종수", "구현 복잡도", "주요 리스크"],
["1. 스케일업", "6", "낮음 (기존 발동기 + 배율)", "밸런스 치우침"],
["2. 새 효과 추가", "6", "중간 (원 효과 + 추가 효과 병렬)", "이벤트 순서 관리"],
["3. 다중 발동", "4", "중간 (동시 N회 + 풀링 용량)", "파티클 상한 초과 (개발팀 확인)"],
["4. 광역 확산", "4", "높음 (화면 전체 AoE + 연쇄)", "성능 이슈 고위험 (최적화 필수)"],
]
add_table(s, Inches(0.5), Inches(4.6), Inches(12.3), Inches(2.4),
risk_data, header_fill=PURPLE, body_size=10, header_size=11,
col_widths=[2, 1, 4, 4])
add_footer(s, 17, TOTAL_SLIDES)
# 슬라이드 18 — 카테고리별 분배 표 (액티브)
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "액티브 카테고리별 카드 분배 (전체 20종 분류)", 18, TOTAL_SLIDES, "3부 카테고리")
# 좌측 — 분배 표
distrib_data = [
["카테고리", "종수", "카드 #", "근거"],
["A. 투사체", "6", "A01·A02·A03·A08·A14·A15", "VS 핵심 + 4속성 균등"],
["B. 근접·범위", "5", "A04·A05·A12·A13·A19", "밀집 연출 + 냉기/번개/물리"],
["C. 설치·지속", "3", "A06·A09·A17", "성능 부담 회피 (3종 제한)"],
["D. 소환", "3", "A10·A11·A20", "원거리 2 + 근접 1 분화"],
["E. 상태이상", "1", "A07", "순수 오라형 1종 (스택은 A 흡수)"],
["F. 특수 판정", "2", "A16·A18", "확률 로또감 (패턴화 회피)"],
]
add_table(s, Inches(0.5), Inches(1.1), Inches(12.3), Inches(3.5),
distrib_data, body_size=11, header_size=12,
col_widths=[2, 0.7, 4, 3])
# 하단 — 분배 차트 시각화 (간이 막대)
add_text_box(s, Inches(0.5), Inches(4.85), Inches(12), Inches(0.5),
"시각적 분배 (총 20종)",
font_size=13, bold=True, color=NAVY)
categories_chart = [("A 투사체", 6), ("B 근접범위", 5), ("C 설치지속", 3),
("D 소환", 3), ("E 상태이상", 1), ("F 특수판정", 2)]
chart_top = Inches(5.4)
chart_left = Inches(1.0)
bar_max_w = Inches(8.0)
bar_h = Inches(0.25)
gap_v = Inches(0.05)
for i, (label, count) in enumerate(categories_chart):
y = chart_top + (bar_h + gap_v) * i
# 라벨
add_text_box(s, Inches(0.5), y - Inches(0.02), Inches(2), bar_h + Inches(0.04),
label, font_size=10, color=DARK_TEXT)
# 바
bar_w = Emu(int(bar_max_w * (count / 6)))
bar = s.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(2.5), y, bar_w, bar_h)
bar.fill.solid()
bar.fill.fore_color.rgb = NAVY
bar.line.fill.background()
bar.shadow.inherit = False
# 카운트
add_text_box(s, Inches(2.5) + bar_w + Inches(0.05), y - Inches(0.02),
Inches(0.6), bar_h + Inches(0.04),
f"{count}종", font_size=10, bold=True, color=NAVY)
add_footer(s, 18, TOTAL_SLIDES)
# 슬라이드 19 — 패시브 카테고리별 분배
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "패시브 카테고리별 카드 분배 (전체 20종 분류)", 19, TOTAL_SLIDES, "3부 카테고리")
p_distrib_data = [
["카테고리", "종수", "카드 #", "근거"],
["P-A. 스탯 상승", "7", "P01 + P02·P03·P04·P05 + P09·P10", "범용 1 + 속성 4 + 크리 2"],
["P-B. 주기·증폭", "3", "P06·P07·P08", "DPS 증폭 축 응집"],
["P-C. 생존 강화", "5", "P11·P12·P13·P14·P15", "실드/하트+1 2종/감소/회피"],
["P-D. 회복", "2", "P16·P17", "처치+시간 기반 (어뷰징 가드)"],
["P-E. 자원 확장", "3", "P18·P19·P20", "이동/경험치/보물상자"],
]
add_table(s, Inches(0.5), Inches(1.1), Inches(12.3), Inches(3.0),
p_distrib_data, header_fill=GREEN_OK, body_size=11, header_size=12,
col_widths=[2.2, 0.7, 5.3, 4.1])
# 핵심 강조
add_text_box(s, Inches(0.5), Inches(4.4), Inches(12), Inches(0.5),
"핵심 패시브 — 각성 빌드 직결 카드",
font_size=13, bold=True, color=NAVY)
key_cards = [
("P12 생명의 꽃", "최대 하트 +1 (런 내). AW17 등 다수 각성 조건 패시브", PURPLE),
("P13 강철심장", "최대 하트 +1 (P12와 중첩 가능). 동일 효과 다른 카드 = 빌드 분기 의도", PURPLE),
("P20 재물복", "보물상자 발견 확률 증가. \"보물상자 각성 특화\" 빌드 핵심", ACCENT),
("P06 연사술", "모든 액티브 발동 주기 단축. 범용 고부가 + Lv.5 단축 가속", NAVY),
]
for i, (name, desc, color) in enumerate(key_cards):
y = Inches(4.95) + Inches(0.5) * i
icon = s.shapes.add_shape(MSO_SHAPE.OVAL,
Inches(0.7), y + Inches(0.05), Inches(0.3), Inches(0.3))
icon.fill.solid()
icon.fill.fore_color.rgb = color
icon.line.fill.background()
add_text_box(s, Inches(1.1), y, Inches(3.5), Inches(0.4),
name, font_size=11, bold=True, color=color)
add_text_box(s, Inches(4.6), y, Inches(8.2), Inches(0.4),
desc, font_size=10, color=DARK_TEXT)
add_footer(s, 19, TOTAL_SLIDES)
# 슬라이드 20 — 각성 패턴 분배 + 3부 종결
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "각성 카테고리·패턴 분배 (전체 20종)", 20, TOTAL_SLIDES, "3부 카테고리")
aw_distrib_data = [
["패턴", "종수", "각성 #", "원 액티브"],
["1. 스케일업", "6", "AW02·AW05·AW08·AW12·AW13·AW16", "A02·A05·A07·A12·A13·A16"],
["2. 새 효과 추가", "6", "AW04·AW07·AW11·AW14·AW17·AW18", "A03·A08·A11·A14·A17·A18"],
["3. 다중 발동", "4", "AW09·AW10·AW15·AW20", "A09·A10·A15·A20"],
["4. 광역 확산", "4", "AW01·AW03·AW06·AW19", "A01·A04·A06·A19"],
]
add_table(s, Inches(0.5), Inches(1.1), Inches(12.3), Inches(2.7),
aw_distrib_data, header_fill=PURPLE, body_size=11, header_size=12,
col_widths=[2, 0.7, 5.5, 4.1])
# 각성 발동 의미
mean_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(4.0), Inches(12.3), Inches(2.7))
mean_box.fill.solid()
mean_box.fill.fore_color.rgb = LIGHT_BG
mean_box.line.color.rgb = PURPLE
mean_box.line.width = Pt(2)
mean_box.shadow.inherit = False
add_text_box(s, Inches(0.7), Inches(4.15), Inches(12), Inches(0.5),
"각성 발동의 위상 — 한 런의 피날레",
font_size=14, bold=True, color=PURPLE)
add_multiline_text(s, Inches(0.7), Inches(4.65), Inches(12), Inches(2.0),
[
{"text": "빌드 완성자 — 각성 발동 이후 \"내 빌드가 완성됐다\" 감각. 이후 런은 \"완성된 빌드로 얼마나 버티는가\"",
"bullet": True, "size": 11},
{"text": "런 피날레 연출 — 풀스크린 연출 (시각·음향). 런의 분기점 명확히 알림",
"bullet": True, "size": 11},
{"text": "기다림의 보상 — 액티브 Lv.5까지 동일 카드 5번 픽 \"투자\" -> 각성 발동 시 보상의 카타르시스 곡선 완성",
"bullet": True, "size": 11},
{"text": "한 런 1회 발동 (중복 불가) — 다중 조건 충족 시 플레이어 1개 선택 UI 제공",
"bullet": True, "size": 11, "color": NAVY},
])
add_footer(s, 20, TOTAL_SLIDES)
# ============================================================
# 4부 — 동작 메커니즘 (슬라이드 21~25)
# ============================================================
# 슬라이드 21 — 픽 알고리즘
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "픽 알고리즘 — 가중 무작위 + 빌드 태그 가중", 21, TOTAL_SLIDES, "4부 메커니즘")
add_text_box(s, Inches(0.5), Inches(1.05), Inches(12), Inches(0.5),
"레벨업 트리거 시 5단계 처리",
font_size=14, bold=True, color=NAVY)
algo_data = [
["단계", "처리 내용", "관여 요소"],
["1", "현재 레벨·빌드 태그에 따라 액티브 vs 패시브 비율 가중 결정", "balance/02 §4 곡선"],
["2", "빌드 주력 태그 2개 추출 (현재 보유 카드의 최다 태그)", "PlayerSkillInventory"],
["3", "전체 풀(40종 = 액티브 20 + 패시브 20)에서 가중 무작위 3장 비복원 추출", "주력 태그 +α 가중"],
["4", "이미 빌드 편입 카드는 제외 / Lv.5 미만 동일 카드는 재등장 허용 (Lv 업)", "_cardIdToRuntime Dict"],
["5", "3장 액티브·패시브 혼합 필터 우선 적용 (풀 고갈 시 해제)", "혼합 안 되면 단일 종 가능"],
]
add_table(s, Inches(0.5), Inches(1.55), Inches(12.3), Inches(3.2),
algo_data, body_size=11, header_size=12,
col_widths=[0.5, 7, 4])
# 가중치 공식
add_text_box(s, Inches(0.5), Inches(5.0), Inches(12), Inches(0.5),
"가중치 공식 (개념 — 수치는 balance-designer)",
font_size=13, bold=True, color=NAVY)
formula_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(5.55), Inches(12.3), Inches(1.5))
formula_box.fill.solid()
formula_box.fill.fore_color.rgb = LIGHT_BG
formula_box.line.color.rgb = NAVY
formula_box.line.width = Pt(1)
formula_box.shadow.inherit = False
add_multiline_text(s, Inches(0.7), Inches(5.65), Inches(12), Inches(1.4),
[
{"text": "Weight(card) = BaseWeight × (1 + TagBonus × MatchedTagCount) × CategoryBonus",
"size": 11, "bold": True, "color": NAVY, "font_name": CODE_FONT},
{"text": " - BaseWeight: 카드 기본 출현 가중치 (1.0 균등 시작)",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " - TagBonus: 빌드 주력 태그 매칭 보너스 (예: 0.5)",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " - MatchedTagCount: 카드의 태그 중 주력 태그와 매칭된 수 (0~2)",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " - CategoryBonus: 레벨 구간별 액티브 vs 패시브 비율 보정",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
], font_name=CODE_FONT)
add_footer(s, 21, TOTAL_SLIDES)
# 슬라이드 22 — 스택 업그레이드 시스템
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "스택 업그레이드 — Lv.1~Lv.5 (VS 원작 정합)", 22, TOTAL_SLIDES, "4부 메커니즘")
# Lv 진행 시각화
lv_positions = [(1, 1.0), (2, 1.2), (3, 1.4), (4, 1.6), (5, 2.0)]
add_text_box(s, Inches(0.5), Inches(1.1), Inches(12), Inches(0.5),
"동일 카드 재픽 시 Lv 상승 (StackLevelFactor)",
font_size=14, bold=True, color=NAVY)
for i, (lv, factor) in enumerate(lv_positions):
x = Inches(1.0 + i * 2.3)
y = Inches(1.7)
color = ACCENT if lv == 5 else NAVY
box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
x, y, Inches(2.0), Inches(1.5))
box.fill.solid()
box.fill.fore_color.rgb = color
box.line.fill.background()
box.shadow.inherit = False
btf = box.text_frame
btf.vertical_anchor = MSO_ANCHOR.MIDDLE
bp = btf.paragraphs[0]
bp.alignment = PP_ALIGN.CENTER
br = bp.add_run()
br.text = f"Lv.{lv}"
br.font.name = HEADER_FONT
br.font.size = Pt(22)
br.font.bold = True
br.font.color.rgb = WHITE if lv != 5 else NAVY
bp2 = btf.add_paragraph()
bp2.alignment = PP_ALIGN.CENTER
br2 = bp2.add_run()
br2.text = f"\nFactor {factor}x"
br2.font.name = CODE_FONT
br2.font.size = Pt(12)
br2.font.color.rgb = WHITE if lv != 5 else NAVY
if lv == 5:
bp3 = btf.add_paragraph()
bp3.alignment = PP_ALIGN.CENTER
br3 = bp3.add_run()
br3.text = "각성 조건 1 충족"
br3.font.name = BODY_FONT
br3.font.size = Pt(9)
br3.font.bold = True
br3.font.color.rgb = NAVY
# 화살표 (Lv 사이)
for i in range(4):
ar_x = Inches(3.05 + i * 2.3)
ar = s.shapes.add_shape(MSO_SHAPE.RIGHT_ARROW,
ar_x, Inches(2.3), Inches(0.3), Inches(0.3))
ar.fill.solid()
ar.fill.fore_color.rgb = GRAY
ar.line.fill.background()
ar.shadow.inherit = False
# 규칙 + Lv.5 이후
add_text_box(s, Inches(0.5), Inches(3.5), Inches(12), Inches(0.5),
"스택 규칙 · Lv.5 이후 처리",
font_size=13, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(4.0), Inches(12.3), Inches(3.0),
[
{"text": "동일 카드 재픽마다 Lv.N -> Lv.N+1 (최대 Lv.5)",
"bullet": True, "size": 11},
{"text": "업그레이드는 \"수치 상향 또는 추가 트리거\" 중 하나 부여 (양자 동시 부여 금지)",
"bullet": True, "size": 11},
{"text": "Lv.5 도달 -> AwakeningManager.MarkActiveMaxed(data) 자동 호출 (각성 조건 1 마킹)",
"bullet": True, "size": 11, "color": PURPLE},
{"text": "Lv.5 이상 동일 카드 재픽 시: 재화 보상 또는 랜덤 변형 (balance-designer 결정)",
"bullet": True, "size": 11, "color": ACCENT},
{"text": "구 Lv.3 상한 폐기 (BT7-Plan 확정 방향 9) -> VS 원작 Lv.5 체계 계승",
"bullet": True, "size": 11, "color": GRAY},
])
add_footer(s, 22, TOTAL_SLIDES)
# 슬라이드 23 — 각성 발동 시퀀스 다이어그램
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "각성 발동 시퀀스 — 3 조건 동시 충족 판정 흐름", 23, TOTAL_SLIDES, "4부 메커니즘")
# 시퀀스 단계
seq_steps = [
("스테이지 진행", "보물상자 스폰 트리거 (적 처치 누적/위치/엘리트 처치)", NAVY),
("플레이어 보물상자 접촉", "TreasureChest.OnTriggerEnter2D -> Player.OnTreasureChestOpened()", NAVY),
("AwakeningManager.CheckAwakeningEligibility() 호출", "전체 AwakeningSkillData 순회", PURPLE),
("조건 1 검사: _maxedActiveIds.Contains(awData.OriginalActive.CardId)", "Lv.5 도달 액티브 ID HashSet 조회 (O(1))", PURPLE),
("조건 2 검사: RequiredPassives.Any(p => HasPassive(p.CardId))", "필요 패시브 1+ 보유 확인", PURPLE),
("조건 3: 보물상자는 이미 개봉됨 (진입 시점 보장)", "별도 검사 불요", GRAY),
("eligibleAwakenings 분기", "0건: 대체 보상 / 1건: 즉시 발동 / 2+건: 선택 UI", ACCENT),
("TriggerAwakening(awData)", "풀스크린 연출 + AwakeningSkillRuntime 교체 + alreadyAwakened 마킹", PURPLE),
]
step_h = Inches(0.6)
gap_v = Inches(0.05)
for i, (step, detail, color) in enumerate(seq_steps):
y = Inches(1.05) + (step_h + gap_v) * i
# 단계 번호 원
num = s.shapes.add_shape(MSO_SHAPE.OVAL,
Inches(0.5), y + Inches(0.1), Inches(0.4), Inches(0.4))
num.fill.solid()
num.fill.fore_color.rgb = color
num.line.fill.background()
num.shadow.inherit = False
ntf = num.text_frame
ntf.vertical_anchor = MSO_ANCHOR.MIDDLE
np_ = ntf.paragraphs[0]
np_.alignment = PP_ALIGN.CENTER
nr = np_.add_run()
nr.text = str(i + 1)
nr.font.size = Pt(11)
nr.font.bold = True
nr.font.color.rgb = WHITE
# 단계 이름
add_text_box(s, Inches(1.05), y + Inches(0.05), Inches(6.5), Inches(0.5),
step, font_size=11, bold=True, color=color)
# 디테일
add_text_box(s, Inches(1.05), y + Inches(0.32), Inches(11.5), Inches(0.3),
detail, font_size=9, color=DARK_TEXT, font_name=CODE_FONT)
add_footer(s, 23, TOTAL_SLIDES)
# 슬라이드 24 — 보물상자 체계
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "보물상자 체계 — 각성 게이팅 + 대체 보상", 24, TOTAL_SLIDES, "4부 메커니즘")
# 좌측 — 흐름 박스
add_text_box(s, Inches(0.5), Inches(1.1), Inches(6), Inches(0.5),
"보물상자 흐름",
font_size=13, bold=True, color=NAVY)
flow_steps = [
("스폰", "스테이지 진행 중 조건부 (적 처치 수·엘리트 처치·특정 위치)", NAVY),
("발견", "P20 재물복 패시브 시 발견 확률 증가", ACCENT),
("획득", "플레이어 접촉 시 OnTreasureChestOpened() 트리거", NAVY),
("판정", "각성 조건 3종 동시 충족 검사", PURPLE),
("분기", "충족 -> 각성 발동 / 불충족 -> 대체 보상", PURPLE),
]
for i, (step, desc, color) in enumerate(flow_steps):
y = Inches(1.6) + Inches(0.95) * i
box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), y, Inches(6), Inches(0.85))
box.fill.solid()
box.fill.fore_color.rgb = LIGHT_BG
box.line.color.rgb = color
box.line.width = Pt(1.5)
box.shadow.inherit = False
add_text_box(s, Inches(0.7), y + Inches(0.05), Inches(1.5), Inches(0.4),
step, font_size=12, bold=True, color=color)
add_text_box(s, Inches(0.7), y + Inches(0.4), Inches(5.7), Inches(0.4),
desc, font_size=9, color=DARK_TEXT)
# 우측 — 대체 보상
add_text_box(s, Inches(7.0), Inches(1.1), Inches(5.7), Inches(0.5),
"대체 보상 (조건 불충족 시)",
font_size=13, bold=True, color=NAVY)
reward_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(7.0), Inches(1.6), Inches(5.7), Inches(2.5))
reward_box.fill.solid()
reward_box.fill.fore_color.rgb = LIGHT_BG
reward_box.line.color.rgb = ACCENT
reward_box.line.width = Pt(2)
reward_box.shadow.inherit = False
add_multiline_text(s, Inches(7.2), Inches(1.7), Inches(5.5), Inches(2.4),
[
{"text": "조건 불충족 시 1종 무작위 보상:",
"size": 12, "bold": True, "color": ACCENT},
{"text": "", "size": 9},
{"text": "재화 (다음 런 메타 진행 자원)",
"bullet": True, "size": 11},
{"text": "회복 (1~2 쿼터 즉시 회복)",
"bullet": True, "size": 11},
{"text": "경험치 (다음 레벨업 가속)",
"bullet": True, "size": 11},
{"text": "확률·강도는 balance-designer 영역",
"bullet": True, "size": 10, "color": GRAY},
])
# 다중 각성 충족
add_text_box(s, Inches(7.0), Inches(4.3), Inches(5.7), Inches(0.5),
"다중 각성 동시 충족 처리",
font_size=13, bold=True, color=PURPLE)
multi_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(7.0), Inches(4.8), Inches(5.7), Inches(2.0))
multi_box.fill.solid()
multi_box.fill.fore_color.rgb = LIGHT_BG
multi_box.line.color.rgb = PURPLE
multi_box.line.width = Pt(2)
multi_box.shadow.inherit = False
add_multiline_text(s, Inches(7.2), Inches(4.9), Inches(5.5), Inches(1.9),
[
{"text": "한 보물상자 획득 -> 복수 각성 조건 동시 충족",
"bullet": True, "size": 11},
{"text": "플레이어 1개 선택 UI 제공",
"bullet": True, "size": 11, "color": PURPLE},
{"text": "동시 발동 금지 (밸런스 붕괴 방지)",
"bullet": True, "size": 11, "color": RED_HL},
{"text": "선택되지 않은 각성 조건은 다음 보물상자 획득 시 재판정",
"bullet": True, "size": 10, "color": GRAY},
])
add_footer(s, 24, TOTAL_SLIDES)
# 슬라이드 25 — HP/하트 메커니즘 + 시너지 발동
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "HP/하트 증가 · 시너지 발동 메커니즘", 25, TOTAL_SLIDES, "4부 메커니즘")
# 좌측 — HP/하트
add_text_box(s, Inches(0.5), Inches(1.1), Inches(6), Inches(0.5),
"HP/하트 분할 구조",
font_size=13, bold=True, color=NAVY)
hp_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(1.6), Inches(6), Inches(5.0))
hp_box.fill.solid()
hp_box.fill.fore_color.rgb = LIGHT_BG
hp_box.line.color.rgb = NAVY
hp_box.line.width = Pt(1.5)
hp_box.shadow.inherit = False
add_multiline_text(s, Inches(0.7), Inches(1.75), Inches(5.7), Inches(4.7),
[
{"text": "쿼터 단위 차감 구조 (BT7-Plan 확정 방향 7)",
"size": 11, "bold": True, "color": NAVY},
{"text": "", "size": 9},
{"text": "1 하트 = 4 쿼터 (Zelda BotW 방식)",
"bullet": True, "size": 11},
{"text": "최대 HP = maxHearts × 4 쿼터",
"bullet": True, "size": 11},
{"text": "i-frame 0.6s 보호 (연속 피격 방지)",
"bullet": True, "size": 11},
{"text": "", "size": 9},
{"text": "최대 하트 증가 (런 내):",
"size": 11, "bold": True, "color": NAVY},
{"text": "P12 생명의 꽃 +1",
"bullet": True, "size": 10},
{"text": "P13 강철심장 +1 (P12와 중첩 가능)",
"bullet": True, "size": 10},
{"text": "상한: 5개 (balance-designer 하드캡)",
"bullet": True, "size": 10, "color": GRAY},
{"text": "런 종료 시 기본 1개로 복귀 (전손)",
"bullet": True, "size": 10, "color": RED_HL},
{"text": "", "size": 9},
{"text": "Health.IncreaseMaxHearts(int delta) API",
"bullet": True, "size": 10, "color": GRAY, "font_name": CODE_FONT},
{"text": "Health.Heal(int quarters) API (P16·P17)",
"bullet": True, "size": 10, "color": GRAY, "font_name": CODE_FONT},
])
# 우측 — 시너지 발동
add_text_box(s, Inches(6.8), Inches(1.1), Inches(6), Inches(0.5),
"태그 시너지 발동",
font_size=13, bold=True, color=ACCENT)
syn_box2 = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(6.8), Inches(1.6), Inches(6), Inches(5.0))
syn_box2.fill.solid()
syn_box2.fill.fore_color.rgb = LIGHT_BG
syn_box2.line.color.rgb = ACCENT
syn_box2.line.width = Pt(1.5)
syn_box2.shadow.inherit = False
add_multiline_text(s, Inches(7.0), Inches(1.75), Inches(5.7), Inches(4.7),
[
{"text": "발동 조건",
"size": 11, "bold": True, "color": ACCENT},
{"text": "동일 태그 카드 K장 이상 빌드 편입 시 패시브 효과 추가 발동",
"bullet": True, "size": 11},
{"text": "K 임계값 = balance-designer 영역 (예: 화염 4장 이상)",
"bullet": True, "size": 10, "color": GRAY},
{"text": "", "size": 9},
{"text": "구현 흐름",
"size": 11, "bold": True, "color": ACCENT},
{"text": "PlayerSkillInventory.OnSkillEquipped 이벤트 시 태그 카운트 갱신",
"bullet": True, "size": 10},
{"text": "TagSynergyManager.CheckThreshold(tag) 호출",
"bullet": True, "size": 10, "font_name": CODE_FONT},
{"text": "K장 도달 시 SynergyEffect 활성화 (PlayerStats 보정)",
"bullet": True, "size": 10},
{"text": "이하 떨어지면 비활성화 (재진입 가능)",
"bullet": True, "size": 10},
{"text": "", "size": 9},
{"text": "교차 빌드의 가능성",
"size": 11, "bold": True, "color": ACCENT},
{"text": "속성 빌드 (예: 화염 6장) + 유형 빌드 (예: 원거리 5장) 동시 추구 = 제3의 방향",
"bullet": True, "size": 10},
{"text": "2태그 카드는 두 시너지에 동시 기여",
"bullet": True, "size": 10},
])
add_footer(s, 25, TOTAL_SLIDES)
# ============================================================
# 5부 — 60종 카드 명세 (슬라이드 26~28)
# ============================================================
# 슬라이드 26 — 액티브 20종 표
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "액티브 20종 명세 표 (CSV v0.3 기반)", 26, TOTAL_SLIDES, "5부 60종 명세")
active_table = [
["#", "카드명", "카테고리", "주 태그", "동작 요지"],
["A01", "진언부", "A 투사체", "[원거리][물리]", "전방 직선 부적 투척, 단일 적 타격"],
["A02", "화염부", "A 투사체", "[원거리][화염]", "직선 투척 + 적중 시 화염 DoT"],
["A03", "봉인부", "A 투사체", "[원거리][암흑]", "투척 + 적중 시 행동 정지 저주"],
["A04", "뇌격부", "B 근접·범위", "[범위][번개]", "주변 번개 연쇄, 밀집 다중 타격"],
["A05", "학익진", "B 근접·범위", "[근접][범위][물리]", "전방 부채꼴 자동 타격"],
["A06", "독안개", "C 설치·지속", "[지속][암흑]", "이동 경로 독 구역 잔존, 접촉 DoT"],
["A07", "혼백박멸", "E 상태이상", "[범위][암흑]", "주변 약화 오라, 공/방 저하"],
["A08", "저주문", "A 투사체", "[원거리][암흑][지속]", "자동 투척, 저주 스택 N개 시 폭발"],
["A09", "허수아비", "C 설치·지속", "[지속][방어]", "이동 경로 설치, 어그로 유도 + 완충"],
["A10", "분신술", "D 소환", "[근접][물리]", "분신 1기, 플레이어 공격 모방"],
["A11", "도깨비불소환", "D 소환", "[원거리][암흑][지속]", "도깨비불 2기, 랜덤 추적 + DoT"],
["A12", "쾌속검", "B 근접·범위", "[근접][물리]", "전방 연속 베기, 고빈도 단발"],
["A13", "천둥발", "B 근접·범위", "[범위][번개]", "원형 번개 판정, 다중 연쇄"],
["A14", "빙결창", "A 투사체", "[원거리][냉기]", "직선 투창, 적중 시 둔화"],
["A15", "귀화", "A 투사체", "[원거리][화염][지속]", "유도 화염구, 가장 가까운 적 추적"],
["A16", "일격필살", "F 특수 판정", "[근접][물리]", "확률 자동 회심 공격"],
["A17", "결계장벽", "C 설치·지속", "[방어][지속]", "전방 고정 실드 설치, 적 저지"],
["A18", "무영보", "F 특수 판정", "[방어][근접]", "순간 무적 대시 자동 발동"],
["A19", "풍백제", "B 근접·범위", "[근접][범위][냉기]", "회오리 생성, 밀어내기 + 지속 타격"],
["A20", "산군상", "D 소환", "[근접][물리][지속]", "호랑이 정령 소환, 돌진·물어뜯기"],
]
add_table(s, Inches(0.3), Inches(1.0), Inches(12.7), Inches(6.0),
active_table, body_size=8.5, header_size=10,
col_widths=[0.5, 1.5, 2, 2.7, 6])
add_footer(s, 26, TOTAL_SLIDES)
# 슬라이드 27 — 패시브 20종 표
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "패시브 20종 명세 표 (CSV v0.3 기반)", 27, TOTAL_SLIDES, "5부 60종 명세")
passive_table = [
["#", "카드명", "카테고리", "동작 요지", "주 시너지"],
["P01", "봉황격", "P-A 스탯", "모든 액티브 대미지 증가 (범용)", "전체 (범용 강화)"],
["P02", "화염강화", "P-A 스탯", "[화염] 태그 액티브 대미지 증가", "A02·A15"],
["P03", "냉기강화", "P-A 스탯", "[냉기] 태그 액티브 대미지 증가", "A14·A19"],
["P04", "뇌전강화", "P-A 스탯", "[번개] 태그 액티브 대미지 증가", "A04·A13"],
["P05", "어둠강화", "P-A 스탯", "[암흑] 태그 액티브 대미지 증가", "A03·A06·A07·A08·A11"],
["P06", "연사술", "P-B 주기", "모든 액티브 발동 주기 단축", "전체 (고부가 범용)"],
["P07", "광역확장", "P-B 주기", "판정 범위 + [지속] 잔존시간 연장", "B·C·A07·A19"],
["P08", "투사체증폭", "P-B 주기", "[원거리] 액티브 투사체 수 증가", "A 투사체 전체"],
["P09", "집중술", "P-A 스탯", "크리티컬 확률 상승", "A16 + 전체"],
["P10", "폭풍혼", "P-A 스탯", "크리티컬 대미지 배율 증가", "A16 + 전체"],
["P11", "호신부", "P-C 생존", "피격 시 1회 흡수 실드 (조건부)", "C·D"],
["P12", "생명의 꽃", "P-C 생존", "최대 하트 +1 (런 내)", "각성 조건 핵심"],
["P13", "강철심장", "P-C 생존", "최대 하트 +1 (P12와 중첩 가능)", "각성 조건 핵심"],
["P14", "부적방패", "P-C 생존", "받는 피해 % 감소", "C·D"],
["P15", "회피술", "P-C 생존", "회피 확률 + i-frame 연장", "F·전체"],
["P16", "단전수련", "P-D 회복", "적 처치 시 1/4 하트 회복 (누적 킬)", "B·E (처치 빈도)"],
["P17", "감로수", "P-D 회복", "일정 시간마다 1/4 하트 자동 재생", "전체 (시간 기반)"],
["P18", "질풍보", "P-E 자원", "이동 속도 상승", "전체 (포지셔닝)"],
["P19", "선견지명", "P-E 자원", "경험치 획득량 증가 (레벨업 가속)", "전체 (빌드 가속)"],
["P20", "재물복", "P-E 자원", "보물상자 스폰·발견 확률 증가", "전체 (각성 트리거 가속)"],
]
add_table(s, Inches(0.3), Inches(1.0), Inches(12.7), Inches(6.0),
passive_table, header_fill=GREEN_OK, body_size=8.5, header_size=10,
col_widths=[0.5, 1.5, 1.7, 5.0, 4.0])
add_footer(s, 27, TOTAL_SLIDES)
# 슬라이드 28 — 각성 20종 표
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "각성 20종 명세 표 (CSV v0.3 기반)", 28, TOTAL_SLIDES, "5부 60종 명세")
awaken_table = [
["#", "각성명", "패턴", "원 액티브", "필요 패시브 (1+)", "동작 요지"],
["AW01", "천부경문", "4 광역", "A01 진언부", "P01·P06·P08", "화면 전체 부적 전개, 전 적 강타"],
["AW02", "염라화염", "1 스케일업", "A02 화염부", "P02·P07", "범위 대폭 확장 + 점화 무제한"],
["AW03", "천뢰강림", "4 광역", "A04 뇌격부", "P04·P06·P08", "연쇄 번개 무제한 전파"],
["AW04", "봉령결계", "2 새효과", "A03 봉인부", "P05·P07", "행동 정지 + DoT 동시 부여"],
["AW05", "학익천진", "1 스케일업", "A05 학익진", "P01·P07", "부채꼴 -> 360도 전방위"],
["AW06", "독무유역", "4 광역", "A06 독안개", "P05·P07", "화면 전체 독무 잔존"],
["AW07", "사령결계", "2 새효과", "A08 저주문", "P05·P06", "스택 즉시 최대 + 폭발 증폭"],
["AW08", "혼령폭풍", "1 스케일업", "A07 혼백박멸", "P05·P07", "오라 -> 화면 전체 약화"],
["AW09", "허수군세", "3 다중발동", "A09 허수아비", "P01·P11", "1기 -> 4~5기 동시 설치"],
["AW10", "분신만화", "3 다중발동", "A10 분신술", "P01·P06", "1기 -> 3~4기 동시 소환"],
["AW11", "산신령강림", "2 새효과", "A11 도깨비불", "P05·P07", "도깨비불 2기 -> 강력 1기 상주"],
["AW12", "쾌검비경", "1 스케일업", "A12 쾌속검", "P01·P06", "연타 속도 극속, 무한 연격"],
["AW13", "천지진동", "1 스케일업", "A13 천둥발", "P04·P07", "원형 번개 화면 전체 확장"],
["AW14", "빙하시대", "2 새효과", "A14 빙결창", "P03·P07", "투창 + 낙하 영역 빙결"],
["AW15", "귀화유성", "3 다중발동", "A15 귀화", "P02·P08", "유도 화염구 무한 발사"],
["AW16", "일격절명", "1 스케일업", "A16 일격필살", "P09·P10", "회심 확률·배율 극한 (보스 외 즉사)"],
["AW17", "천지음양방호", "2 새효과", "A17 결계장벽", "P11·(P12 또는 P13)", "실드 + 무적 시간 상시 유지"],
["AW18", "귀보유영", "2 새효과", "A18 무영보", "P15·P18", "무적 대시 무한 + 피격 임박 자동"],
["AW19", "풍신질주", "4 광역", "A19 풍백제", "P03·P07", "회오리 -> 화면 폭풍, 빙결"],
["AW20", "산군포효", "3 다중발동", "A20 산군상", "P01·P05", "호랑이 1기 -> 3기, 포효 기절"],
]
add_table(s, Inches(0.2), Inches(1.0), Inches(12.9), Inches(6.0),
awaken_table, header_fill=PURPLE, body_size=7.5, header_size=9,
col_widths=[0.5, 1.5, 1.5, 1.6, 2.0, 5.5])
add_footer(s, 28, TOTAL_SLIDES)
# ============================================================
# 6부 — 구현 가이드 (슬라이드 29~33)
# ============================================================
# 슬라이드 29 — 4계층 아키텍처
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "Unity 구현 — 4계층 아키텍처", 29, TOTAL_SLIDES, "6부 구현")
layers = [
("계층 4 — 효과 발동", NAVY,
"AttackHitbox · ProjectileSpawner · AuraZone · MinionSpawner · DebuffApplier · Health.Decrement(int)",
"BT7-Dev 일부 재활용 + 신설 5종"),
("계층 3 — 스킬 런타임", GREEN_OK,
"ActiveSkillRuntime · PassiveSkillRuntime · AwakeningSkillRuntime · PlayerSkillInventory",
"각 카드당 1 인스턴스, 인벤토리 관리"),
("계층 2 — 스킬 데이터", PURPLE,
"SkillDataAsset (abstract) -> ActiveSkillData · PassiveSkillData · AwakeningSkillData",
"60종 .asset 파일 (ScriptableObject)"),
("계층 1 — 정적 SOT", ACCENT,
"02_스킬_효과_컨셉_v0.3.csv (60종 캐주얼 명명)",
"기획팀 편집 -> 에디터 툴 -> 자산 재생성"),
]
for i, (title, color, desc, sub) in enumerate(layers):
y = Inches(1.1) + Inches(1.4) * i
box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), y, Inches(12.3), Inches(1.25))
box.fill.solid()
box.fill.fore_color.rgb = color
box.line.fill.background()
box.shadow.inherit = False
btf = box.text_frame
btf.margin_left = Inches(0.3)
btf.margin_right = Inches(0.3)
btf.margin_top = Inches(0.15)
bp = btf.paragraphs[0]
br = bp.add_run()
br.text = title
br.font.name = HEADER_FONT
br.font.size = Pt(15)
br.font.bold = True
br.font.color.rgb = WHITE
bp2 = btf.add_paragraph()
bp2.line_spacing = 1.2
br2 = bp2.add_run()
br2.text = desc
br2.font.name = CODE_FONT
br2.font.size = Pt(11)
br2.font.color.rgb = WHITE
bp3 = btf.add_paragraph()
br3 = bp3.add_run()
br3.text = sub
br3.font.name = BODY_FONT
br3.font.size = Pt(10)
br3.font.color.rgb = ICE
# 위→아래 화살표
if i < len(layers) - 1:
arr_y = y + Inches(1.25) - Pt(2)
arr = s.shapes.add_shape(MSO_SHAPE.UP_ARROW,
Inches(6.5), arr_y, Inches(0.3), Inches(0.15))
arr.fill.solid()
arr.fill.fore_color.rgb = GRAY
arr.line.fill.background()
add_text_box(s, Inches(0.5), Inches(6.85), Inches(12.3), Inches(0.4),
"각 계층 분리 = 신규 카드 추가 시 ScriptableObject 인스턴스 생성만으로 확장 (BT12-Dev §1-1 자원 효율·코드 직관성·범용성)",
font_size=10, color=GRAY, align=PP_ALIGN.CENTER)
add_footer(s, 29, TOTAL_SLIDES)
# 슬라이드 30 — 인터페이스·이벤트 정의
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "주요 인터페이스 · 이벤트 정의 (Unity C#)", 30, TOTAL_SLIDES, "6부 구현")
# 좌측 — 인터페이스 트리
add_text_box(s, Inches(0.5), Inches(1.05), Inches(6), Inches(0.5),
"인터페이스 계약 4종",
font_size=13, bold=True, color=NAVY)
iface_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(1.55), Inches(6), Inches(5.3))
iface_box.fill.solid()
iface_box.fill.fore_color.rgb = LIGHT_BG
iface_box.line.color.rgb = NAVY
iface_box.line.width = Pt(1.5)
iface_box.shadow.inherit = False
add_multiline_text(s, Inches(0.7), Inches(1.65), Inches(5.7), Inches(5.1),
[
{"text": "ISkillRuntime (공통)",
"size": 12, "bold": True, "color": NAVY, "font_name": CODE_FONT},
{"text": " SkillDataAsset Data { get; }",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " int StackLevel { get; }",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " void OnEquip(PlayerSkillInventory)",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " void OnUnequip()",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " void Upgrade()",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": "", "size": 9},
{"text": "IActiveSkill : ISkillRuntime",
"size": 12, "bold": True, "color": NAVY, "font_name": CODE_FONT},
{"text": " float BaseCooldown / EffectiveCooldown",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " float CooldownRemaining",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " void Fire()",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " ActiveTrigger Trigger { get; }",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": "", "size": 9},
{"text": "IPassiveSkill : ISkillRuntime",
"size": 12, "bold": True, "color": GREEN_OK, "font_name": CODE_FONT},
{"text": " bool IsAlwaysOn",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " void ApplyModifier(PlayerStats)",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " void RemoveModifier(PlayerStats)",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " void OnTrigger(PassiveTriggerContext)",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": "", "size": 9},
{"text": "IAwakeningSkill : ISkillRuntime",
"size": 12, "bold": True, "color": PURPLE, "font_name": CODE_FONT},
{"text": " ActiveSkillData OriginalActive",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " PassiveSkillData[] RequiredPassives",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " void Awaken(PlayerSkillInventory)",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " AwakeningPattern Pattern { get; }",
"size": 9, "color": DARK_TEXT, "font_name": CODE_FONT},
])
# 우측 — 이벤트 + Enum
add_text_box(s, Inches(6.8), Inches(1.05), Inches(6), Inches(0.5),
"이벤트 · Enum 정의",
font_size=13, bold=True, color=NAVY)
ev_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(6.8), Inches(1.55), Inches(6), Inches(5.3))
ev_box.fill.solid()
ev_box.fill.fore_color.rgb = LIGHT_BG
ev_box.line.color.rgb = NAVY
ev_box.line.width = Pt(1.5)
ev_box.shadow.inherit = False
add_multiline_text(s, Inches(7.0), Inches(1.65), Inches(5.7), Inches(5.1),
[
{"text": "Simulation.Event 기반 발동 (BT7-Dev 일관)",
"size": 11, "bold": True, "color": NAVY},
{"text": " SkillFireEvent (액티브 발동)",
"size": 10, "font_name": CODE_FONT, "color": DARK_TEXT},
{"text": " AwakeningFireEvent (각성 발동)",
"size": 10, "font_name": CODE_FONT, "color": DARK_TEXT},
{"text": " AwakeningEffectEvent (풀스크린 연출)",
"size": 10, "font_name": CODE_FONT, "color": DARK_TEXT},
{"text": "", "size": 9},
{"text": "이벤트 핸들러 (PlayerSkillInventory)",
"size": 11, "bold": True, "color": NAVY},
{"text": " event Action<EnemyController> OnEnemyKilled",
"size": 10, "font_name": CODE_FONT, "color": DARK_TEXT},
{"text": " event Action<float> OnPlayerDamaged",
"size": 10, "font_name": CODE_FONT, "color": DARK_TEXT},
{"text": " void OnTreasureChestOpened()",
"size": 10, "font_name": CODE_FONT, "color": DARK_TEXT},
{"text": " void AddSkillByCardId(string)",
"size": 10, "font_name": CODE_FONT, "color": DARK_TEXT},
{"text": "", "size": 9},
{"text": "ActiveTrigger { OnTime · OnHit · OnKill }",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": "AwakeningPattern {",
"size": 10, "color": PURPLE, "font_name": CODE_FONT},
{"text": " ScaleUp · AddEffect · MultiFire · GlobalSpread }",
"size": 10, "color": PURPLE, "font_name": CODE_FONT},
{"text": "PassiveTriggerKind {",
"size": 10, "color": GREEN_OK, "font_name": CODE_FONT},
{"text": " OnPlayerDamaged · OnEnemyKilled · OnTimer }",
"size": 10, "color": GREEN_OK, "font_name": CODE_FONT},
{"text": "ActiveCategory { Projectile · MeleeArea ·",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " PlacementPersistent · Minion · Debuff · SpecialJudge }",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": "PassiveCategory { StatUp · CycleAmplify ·",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
{"text": " Survival · Recovery · ResourceExpand }",
"size": 10, "color": DARK_TEXT, "font_name": CODE_FONT},
])
add_footer(s, 30, TOTAL_SLIDES)
# 슬라이드 31 — BT.Framework / BT7-Dev 활용
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "BT.Framework Tier 1·2 + BT7-Dev 자산 활용", 31, TOTAL_SLIDES, "6부 구현")
# BT.Framework
add_text_box(s, Inches(0.5), Inches(1.05), Inches(6), Inches(0.5),
"BT.Framework Tier 1·2 활용 (P29 준수)",
font_size=13, bold=True, color=GREEN_OK)
bt_data = [
["자산", "활용 영역"],
["오브젝트 풀링 Tier 1", "ProjectileSpawner / MinionSpawner / AuraZone 인스턴스 관리"],
["Log Tier 1", "스킬 발동·각성 트리거 디버그 로그"],
["ValidationEx Tier 1", "런타임 데이터 무결성 검증 (ScriptableObject 누락 등)"],
["MathEx Tier 1", "확률 분포·가중치 계산"],
["EnumEx Tier 1", "AttributeTag·TypeTag Flags 연산"],
["FormatEx Tier 1", "Lv.N 표시·HUD 텍스트 렌더링"],
["SafeAreaBorder Tier 1", "각성 풀스크린 연출 Safe Area 보장"],
]
add_table(s, Inches(0.5), Inches(1.55), Inches(6), Inches(3.5),
bt_data, header_fill=GREEN_OK, body_size=9.5, header_size=11,
col_widths=[2, 4])
# BT7-Dev
add_text_box(s, Inches(6.8), Inches(1.05), Inches(6), Inches(0.5),
"BT7-Dev 기존 자산 통합 (C39 실측)",
font_size=13, bold=True, color=NAVY)
bt7_data = [
["자산", "통합 방식"],
["Health.cs (Decrement·Heal·IncreaseMaxHearts)", "P-C·P-D 패시브 효과 적용 API (변경 없음)"],
["Health.OnDamagedEvent (신설)", "Phase 2-C 확장 - OnHit 트리거 발화"],
["PlayerAttack : Simulation.Event", "기본 무기 발동 유지 (스킬 시스템 별도)"],
["PlayerAttackTicker", "기본 무기 주기 타이머 유지"],
["AttackHitbox.Fire(Vector2)", "B 카테고리 효과 발동기 재활용"],
["PlayerController.Facing", "투사체 발사 방향 / 근접 판정 방향"],
["Simulation.Schedule<T>()", "스킬 효과 발동 Dispatch 패턴 일관"],
]
add_table(s, Inches(6.8), Inches(1.55), Inches(6), Inches(3.5),
bt7_data, body_size=9.5, header_size=11,
col_widths=[2.5, 3.5])
# 하단 — 의존성
add_text_box(s, Inches(0.5), Inches(5.3), Inches(12), Inches(0.5),
"외부 의존성 (Unity 패키지)",
font_size=13, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(5.8), Inches(12.3), Inches(1.3),
[
{"text": "Input System (com.unity.inputsystem) — 이동·점프 입력 (Attack 액션 제거 상태 유지)",
"bullet": True, "size": 11},
{"text": "Cinemachine (com.unity.cinemachine) — 플레이어 추적 카메라",
"bullet": True, "size": 11},
{"text": "Universal RP (com.unity.render-pipelines.universal) — 2D 렌더링·이펙트",
"bullet": True, "size": 11},
{"text": "BT.Framework Tier 1 (코어코드/BT.Framework/) — 16/16 완결 자산",
"bullet": True, "size": 11, "color": GREEN_OK},
])
add_footer(s, 31, TOTAL_SLIDES)
# 슬라이드 32 — Phase 분리 우선순위
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "구현 Phase 분리 — P0~P4 우선순위 (BT12-Dev §7-2)", 32, TOTAL_SLIDES, "6부 구현")
phases = [
("P0", "코어 — 인터페이스·ScriptableObject 코드 생성", NAVY,
"ISkillRuntime / IActive / IPassive / IAwakening 4개\nSkillDataAsset 베이스 + 3 파생\nPlayerSkillInventory · PlayerStats · AwakeningManager · SkillRuntimeFactory\nSkillFireEvent · AwakeningFireEvent · AwakeningEffectEvent",
"Sonnet 클라이언트팀 단일 Task"),
("P1", "데이터 — CSV → ScriptableObject 파이프라인", PURPLE,
"SkillCsvImporter (EditorWindow)\n60종 .asset 파일 일괄 생성 (Active/Passive/Awakening 각 20)\n카테고리·패턴 enum 매핑 테이블 적용",
"에디터 툴 1회 실행 시 60종 자산 생성"),
("P2", "UI — 픽 UI · HUD 슬롯 · 각성 연출", ACCENT,
"CardPickCanvas (Additive) - 액티브/패시브 분류 표시\nHUD 액티브·패시브 슬롯 6+6 (Lv.5 황금 테두리)\n각성 발동 풀스크린 연출 (Safe Area 적용)",
"UX/02 v0.2 와이어프레임 기준"),
("P3", "각성 시스템 - 3 조건 매니저 + 패턴 Dispatcher", PURPLE,
"AwakeningManager.CheckAwakeningEligibility()\n4 패턴별 AwakeningFireEvent.Execute() 분기\n다중 조건 충족 시 선택 UI · 단일/복수 처리",
"Phase 1·2 완료 후"),
("P4", "보물상자 - 스폰·획득·대체 보상", ACCENT,
"TreasureChest 오브젝트 + OnTriggerEnter2D\nP20 재물복 패시브 가중치 반영\n조건 불충족 시 대체 보상 (재화·회복·경험치)",
"스테이지 구조 의존 (level-designer 연계)"),
]
for i, (phase, title, color, desc, note) in enumerate(phases):
y = Inches(1.1) + Inches(1.2) * i
# 좌측 P박스
pbox = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), y, Inches(1.0), Inches(1.05))
pbox.fill.solid()
pbox.fill.fore_color.rgb = color
pbox.line.fill.background()
pbox.shadow.inherit = False
ptf_ = pbox.text_frame
ptf_.vertical_anchor = MSO_ANCHOR.MIDDLE
pp_ = ptf_.paragraphs[0]
pp_.alignment = PP_ALIGN.CENTER
pr_ = pp_.add_run()
pr_.text = phase
pr_.font.name = HEADER_FONT
pr_.font.size = Pt(22)
pr_.font.bold = True
pr_.font.color.rgb = WHITE
# 우측 본문
bbox = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(1.6), y, Inches(11.2), Inches(1.05))
bbox.fill.solid()
bbox.fill.fore_color.rgb = LIGHT_BG
bbox.line.color.rgb = color
bbox.line.width = Pt(1)
bbox.shadow.inherit = False
add_text_box(s, Inches(1.75), y + Inches(0.05), Inches(10), Inches(0.35),
title, font_size=12, bold=True, color=color)
add_text_box(s, Inches(1.75), y + Inches(0.4), Inches(8.5), Inches(0.6),
desc, font_size=8.5, color=DARK_TEXT)
add_text_box(s, Inches(10.5), y + Inches(0.4), Inches(2.3), Inches(0.6),
note, font_size=8, color=GRAY, align=PP_ALIGN.RIGHT)
add_footer(s, 32, TOTAL_SLIDES)
# 슬라이드 33 — 시너지 빌드 예시
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "시너지 빌드 예시 — 8종 대표 빌드", 33, TOTAL_SLIDES, "6부 구현")
build_data = [
["빌드명", "핵심 액티브", "핵심 패시브", "각성 후보", "재미 축"],
["화염 폭격", "A02·A15", "P02·P07", "AW02·AW15", "축 1·2"],
["번개 연쇄", "A04·A13", "P04·P06·P08", "AW03·AW13", "축 2 (화면 포화)"],
["암흑 DoT", "A03·A06·A08", "P05·P07", "AW04·AW06·AW07", "축 1 (스택 기대감)"],
["소환 군주", "A09·A10·A11·A20", "P11·P12·P18", "AW09·AW10·AW11·AW20", "축 1·2 (전능감)"],
["냉기 제어", "A14·A19", "P03·P07", "AW14·AW19", "축 2 (필드 제어)"],
["회피 게릴라", "A12·A16·A18", "P09·P10·P15", "AW12·AW16·AW18", "축 1 (예상 밖 쾌감)"],
["철벽 생존", "A17", "P11·P12·P13·P14", "AW17", "축 3 (영속 위안)"],
["보물상자 각성", "어떤 액티브든 Lv.5", "P20·P19·P06", "조건 충족 각성", "축 1 (조기 각성)"],
]
add_table(s, Inches(0.3), Inches(1.0), Inches(12.7), Inches(5.5),
build_data, body_size=9, header_size=11,
col_widths=[2, 2.5, 2.5, 3, 2.7])
add_text_box(s, Inches(0.5), Inches(6.7), Inches(12.3), Inches(0.5),
"각 빌드는 실제 런 결과를 보장하지 않으며, 픽 단계 가중치·확률에 따라 달라진다 (balance-designer 영역)",
font_size=10, color=GRAY, align=PP_ALIGN.CENTER)
add_footer(s, 33, TOTAL_SLIDES)
# ============================================================
# 7부 — 검증·QA (슬라이드 34)
# ============================================================
# 슬라이드 34 — EditMode·PlayMode 테스트 + 어뷰징 가드
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "검증·QA — EditMode·PlayMode·성능·어뷰징 가드", 34, TOTAL_SLIDES, "7부 검증")
# 좌상 — EditMode
add_text_box(s, Inches(0.5), Inches(1.05), Inches(6), Inches(0.5),
"EditMode 테스트 (최소 10건)",
font_size=12, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(1.5), Inches(6), Inches(2.7),
[
{"text": "ScriptableObject 인스턴스 무결성 검증", "bullet": True, "size": 10},
{"text": "Factory.Create 분기 (Active/Passive/Awakening)", "bullet": True, "size": 10},
{"text": "StackLevelFactor 곡선 (Lv.1=1.0~Lv.5=2.0)", "bullet": True, "size": 10},
{"text": "Cooldown 누적 오버플로 방지 (BT7-Dev 패턴)", "bullet": True, "size": 10},
{"text": "AwakeningManager 3 조건 분기", "bullet": True, "size": 10},
{"text": "다중 조건 충족 → 선택 UI 진입 분기", "bullet": True, "size": 10},
{"text": "RequiredPassives.Any 매칭 로직", "bullet": True, "size": 10},
{"text": "이미 발동된 각성 중복 차단", "bullet": True, "size": 10},
{"text": "CSV → ScriptableObject 60종 변환", "bullet": True, "size": 10},
{"text": "PlayerStats 보정 누적·원복", "bullet": True, "size": 10},
])
# 우상 — PlayMode
add_text_box(s, Inches(6.8), Inches(1.05), Inches(6), Inches(0.5),
"PlayMode 통합 테스트",
font_size=12, bold=True, color=NAVY)
add_multiline_text(s, Inches(7.0), Inches(1.5), Inches(6), Inches(2.7),
[
{"text": "각 카테고리(A~F) 액티브 1종 실제 발동 확인", "bullet": True, "size": 10},
{"text": "Health.Decrement 호출 경로 추적", "bullet": True, "size": 10},
{"text": "픽 UI → 슬롯 편입 → 자동 발동 사이클", "bullet": True, "size": 10},
{"text": "Lv.5 도달 → MarkActiveMaxed 자동 호출", "bullet": True, "size": 10},
{"text": "보물상자 획득 → 각성 발동 (단일·다중)", "bullet": True, "size": 10},
{"text": "각성 발동 후 원 액티브 슬롯 유지 + 효과 변경", "bullet": True, "size": 10},
{"text": "P12+P13 동시 보유 시 최대 하트 +2", "bullet": True, "size": 10},
{"text": "P11 호신부 OnPlayerDamaged 트리거", "bullet": True, "size": 10},
{"text": "P16 단전수련 OnEnemyKilled 회복", "bullet": True, "size": 10},
])
# 좌하 — 성능 검증
add_text_box(s, Inches(0.5), Inches(4.4), Inches(6), Inches(0.5),
"성능 검증 포인트 (모바일 60fps 95%+)",
font_size=12, bold=True, color=ACCENT)
add_multiline_text(s, Inches(0.7), Inches(4.85), Inches(6), Inches(2.2),
[
{"text": "AW01·AW03·AW06·AW13·AW19 광역 확산 4종", "bullet": True, "size": 10, "color": RED_HL},
{"text": " - 파티클 상한·충돌 연산 배치 처리 필수", "size": 9, "color": GRAY},
{"text": "AW09·AW10·AW15·AW20 다중 발동 4종", "bullet": True, "size": 10, "color": RED_HL},
{"text": " - 동시 오브젝트 상한·풀링 용량 검증", "size": 9, "color": GRAY},
{"text": "A20 산군상 근접 돌진 소환물", "bullet": True, "size": 10},
{"text": " - 2D 지형 낙하·충돌 대응 (자율형 회피)", "size": 9, "color": GRAY},
{"text": "Cooldown 하드캡 0.5s 적용 확인", "bullet": True, "size": 10},
{"text": "Profiler GC 알로케이션 0 / 프레임", "bullet": True, "size": 10},
])
# 우하 — 어뷰징 가드
add_text_box(s, Inches(6.8), Inches(4.4), Inches(6), Inches(0.5),
"어뷰징 가드 (밸런스 보호)",
font_size=12, bold=True, color=RED_HL)
add_multiline_text(s, Inches(7.0), Inches(4.85), Inches(6), Inches(2.2),
[
{"text": "회복 스팸 차단 — P16·P17 회복량 감소 곡선·쿨다운", "bullet": True, "size": 10},
{"text": "최대 하트 하드캡 5개 (P12+P13 무한 중첩 차단)", "bullet": True, "size": 10},
{"text": "Cooldown 하드캡 0.5s (이펙트 중첩 혼잡 방지)", "bullet": True, "size": 10},
{"text": "MoveSpeed 상한 9.0 m/s (P18·P11 무한 가속 차단)", "bullet": True, "size": 10},
{"text": "Lv.5 이상 동일 카드 재픽 무한 루프 방지", "bullet": True, "size": 10},
{"text": "각성 1회 발동 후 alreadyAwakened 마킹", "bullet": True, "size": 10},
{"text": "P20 재물복 Lv.5 보물상자 확률 캡 (balance)", "bullet": True, "size": 10},
])
add_footer(s, 34, TOTAL_SLIDES)
# ============================================================
# 8부 — 후속·부록 (슬라이드 35~36)
# ============================================================
# 슬라이드 35 — PD 결정 대기 영역 + 후속 안건
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "PD 결정 대기 영역 · 후속 안건", 35, TOTAL_SLIDES, "8부 후속")
# PD 결정 대기
pd_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(1.1), Inches(12.3), Inches(2.8))
pd_box.fill.solid()
pd_box.fill.fore_color.rgb = LIGHT_BG
pd_box.line.color.rgb = RED_HL
pd_box.line.width = Pt(2)
pd_box.shadow.inherit = False
add_text_box(s, Inches(0.7), Inches(1.2), Inches(12), Inches(0.5),
"PD 결정 대기 영역 (본 기획서 범위 외)",
font_size=14, bold=True, color=RED_HL)
add_multiline_text(s, Inches(0.7), Inches(1.7), Inches(12), Inches(2.2),
[
{"text": "narrative 한자 카드명 재매핑 — 일반 RPG 용어 11종 -> 조선 퇴마 톤 일괄 재매핑 (BT7-Plan 11)",
"bullet": True, "size": 11, "color": DARK_TEXT},
{"text": "balance-designer 수치 확정 — 60종 BaseDamage·Cooldown·확률·스택·MultiFireCount·AwakeningBaseDamage",
"bullet": True, "size": 11, "color": DARK_TEXT},
{"text": "BM·과금 구조 (Phase 3-C) — Lv.5 이상 재픽 보상 방식 / 보물상자 재화 수치 / 메타 진행 자원",
"bullet": True, "size": 11, "color": DARK_TEXT},
{"text": "아트 톤 결정 — 카드 아이콘·각성 풀스크린 연출·이펙트 색상 가이드",
"bullet": True, "size": 11, "color": DARK_TEXT},
{"text": "기본 시작 무기 — 퇴마사 고유 무기 1종 (A01~A20 중 1 선택 또는 별도 카드)",
"bullet": True, "size": 11, "color": DARK_TEXT},
])
# 후속 작업 안건
suc_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(4.1), Inches(12.3), Inches(2.6))
suc_box.fill.solid()
suc_box.fill.fore_color.rgb = LIGHT_BG
suc_box.line.color.rgb = NAVY
suc_box.line.width = Pt(2)
suc_box.shadow.inherit = False
add_text_box(s, Inches(0.7), Inches(4.2), Inches(12), Inches(0.5),
"후속 작업 안건 (개발팀·기획팀 위임)",
font_size=14, bold=True, color=NAVY)
add_multiline_text(s, Inches(0.7), Inches(4.7), Inches(12), Inches(2.0),
[
{"text": "Phase 2 클라이언트팀 위임 (Sonnet) — Unity C# 스크립트 25종 + 테스트 10건 + 60종 .asset",
"bullet": True, "size": 11},
{"text": "Phase 3 개발팀장 검증 (Opus) — 6 검증 항목 (자원·구조·BT7-Dev 호환·BT.Framework 활용·테스트 green·balance 이관)",
"bullet": True, "size": 11},
{"text": "balance-designer 60종 수치 확정 Task — 기획팀장 경유 위임",
"bullet": True, "size": 11},
{"text": "narrative-designer 재매핑 Task — PD 후속 검토 후 일괄",
"bullet": True, "size": 11},
{"text": "content/01 v0.3 동기화 — A19·A20·AW19·AW20 신설 + P25→P20 재편 (기획팀장 연속 작업)",
"bullet": True, "size": 11},
{"text": "Unity 프로젝트 경로 paths.local.json 설정 — Phase 2 착수 차단 블로커 해소 필요",
"bullet": True, "size": 11, "color": RED_HL},
])
add_footer(s, 35, TOTAL_SLIDES)
# 슬라이드 36 — 변경 이력·참조·용어집·종결
s = prs.slides.add_slide(BLANK)
add_solid_bg(s, WHITE)
add_slide_header(s, "변경 이력 · 참조 SOT · 용어집", 36, TOTAL_SLIDES, "8부 부록")
# 변경 이력
add_text_box(s, Inches(0.5), Inches(1.05), Inches(6), Inches(0.5),
"변경 이력",
font_size=12, bold=True, color=NAVY)
hist_data = [
["일자", "버전", "변경"],
["2026-04-22", "BT6-Plan", "Phase 3-B 초안 (system·content)"],
["2026-04-24", "BT7-Plan", "3분류 구조·VS 순수형·Lv.5"],
["2026-04-24", "BT11-Plan", "60종 명세 (20+20+20)"],
["2026-04-24", "BT12-Dev v1", "Unity C# 4계층 설계"],
["2026-04-26", "본 PPT v1", "통합 기획서 36슬라이드"],
]
add_table(s, Inches(0.5), Inches(1.5), Inches(6), Inches(2.5),
hist_data, body_size=9.5, header_size=10,
col_widths=[1.5, 1.5, 3])
# 참조 SOT
add_text_box(s, Inches(6.8), Inches(1.05), Inches(6), Inches(0.5),
"참조 SOT 7종",
font_size=12, bold=True, color=NAVY)
add_multiline_text(s, Inches(7.0), Inches(1.5), Inches(6), Inches(2.5),
[
{"text": "system/01_카드_시스템.md v0.2", "size": 9, "font_name": CODE_FONT},
{"text": "content/01_카드_풀.md v0.2", "size": 9, "font_name": CODE_FONT},
{"text": "content/02_스킬_효과_컨셉.md v0.2", "size": 9, "font_name": CODE_FONT},
{"text": "content/02_스킬_효과_컨셉_v0.3.csv", "size": 9, "font_name": CODE_FONT},
{"text": "balance/01_전투_수치.md v0.2", "size": 9, "font_name": CODE_FONT},
{"text": "balance/02_레벨업_곡선.md v0.2", "size": 9, "font_name": CODE_FONT},
{"text": "ux/02_HUD_설계.md v0.2", "size": 9, "font_name": CODE_FONT},
{"text": "개발/spec/스킬_시스템_설계_v1.md", "size": 9, "font_name": CODE_FONT},
{"text": "개발/06_BT7-Plan_VS순수형_재구조.md", "size": 9, "font_name": CODE_FONT},
])
# 용어집
add_text_box(s, Inches(0.5), Inches(4.2), Inches(12.3), Inches(0.5),
"용어집",
font_size=12, bold=True, color=NAVY)
gloss_data = [
["용어", "정의"],
["액티브", "자동 발동 무기·스킬 (주기·판정·대미지). Lv.1~Lv.5 스택"],
["패시브", "장착 즉시 상시 적용 보정 (스탯·확률·지속). 일부는 조건부 트리거"],
["각성", "별도 풀의 진화 카드. 3 조건 동시 충족 시 발동. 한 런 1회"],
["보물상자", "각성 게이팅 트리거. 스테이지 진행 중 조건부 스폰. 불충족 시 대체 보상"],
["쿼터", "1 하트 = 4 쿼터. 피해는 쿼터 단위 차감 (Zelda BotW 방식)"],
["StackLevelFactor", "Lv 별 대미지 배율 (Lv.1=1.0 / Lv.5=2.0)"],
["VS 순수형", "Vampire Survivors 패턴 — 공격 버튼 없이 자동 발동 (BT7-Plan 확정 방향 2)"],
["i-frame", "피격 후 무적 시간 0.6s. 연속 피격 방지"],
]
add_table(s, Inches(0.5), Inches(4.7), Inches(12.3), Inches(2.4),
gloss_data, body_size=9, header_size=10,
col_widths=[2.5, 9.8])
add_footer(s, 36, TOTAL_SLIDES)
# ============================================================
# 저장
# ============================================================
import os
output_path = "스킬시스템_개발기획서_v1.pptx"
prs.save(output_path)
print(f"\n=== 빌드 완료 ===")
print(f"산출물: {os.path.abspath(output_path)}")
print(f"슬라이드 수: {len(prs.slides)}")
print(f"파일 크기: {os.path.getsize(output_path)} bytes")