프로그램 처리 흐름 설계
📖 이 문서는?
테트리스 게임의 전체적인 프로그램 처리 흐름을 설명한 문서입니다.
각 클래스가 어떻게 상호작용하는지, 데이터가 어떻게 흐르는지 이해할 수 있습니다.
🎮 게임 전체 흐름
게임 루프 (Game Loop)
시각적 흐름도:
┌─────────────────────────────────────────┐
│ 게임 시작 │
└──────────────┬──────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ [초기화 단계] │
│ ├─ GameBoard 생성 (10x20 그리드) │
│ ├─ GameController 생성 │
│ │ - score = 0 │
│ │ - lines = 0 │
│ │ - level = 1 │
│ ├─ Spawner 생성 │
│ └─ InputHandler 생성 │
└──────────────┬──────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ [첫 블록 생성] │
│ Spawner.NewTetris() │
│ └─ TetrisBlock 생성 │
│ └─ 랜덤 모양 선택 │
│ └─ 보드 상단 중앙에 배치 │
└──────────────┬──────────────────────────┘
↓
┌──────┴──────┐
│ 게임 루프 시작 │
└──────┬──────┘
↓
┌──────────────────────┐
│ [매 프레임 반복] │
└──────────┬─────────────┘
↓
┌─────────────────────────────────────┐
│ [자동 낙하] │
│ TetrisBlock.Update() │
│ ├─ fallTimer += Time.deltaTime │
│ └─ fallTimer >= fallTime? │
│ └─ MoveDown() 호출 │
└──────────┬───────────────────────────┘
↓
┌─────────────────────────────────────┐
│ [사용자 입력] │
│ InputHandler.Update() │
│ ├─ 키 입력 확인 │
│ ├─ ←/A: MoveLeft() │
│ ├─ →/D: MoveRight() │
│ ├─ ↓/S: MoveDown() │
│ ├─ ↑/W: Rotate() │
│ └─ Space: HardDrop() │
└──────────┬───────────────────────────┘
↓
┌─────────────────────────────────────┐
│ [블록 고정 확인] │
│ MoveDown()에서 │
│ └─ 더 이상 내려갈 수 없으면? │
│ └─ LockPiece() 호출 │
└──────────┬───────────────────────────┘
│
├─ 내려갈 수 있으면 → 게임 루프로
│
└─ 내려갈 수 없으면 ↓
┌─────────────────────────────────────┐
│ [블록 고정] │
│ LockPiece() │
│ ├─ 1. 블록을 GameBoard에 저장 │
│ │ gameBoard.AddToGrid() │
│ ├─ 2. 행 제거 확인 │
│ │ gameBoard.CheckForLines() │
│ ├─ 3. 점수 추가 │
│ │ gameController.AddScore() │
│ ├─ 4. 다음 블록 생성 │
│ │ spawner.NewTetris() │
│ └─ 5. 현재 블록 삭제 │
│ Destroy(gameObject) │
└──────────┬───────────────────────────┘
↓
┌─────────────────────────────────────┐
│ [행 제거] │
│ GameBoard.CheckForLines() │
│ ├─ 아래에서 위로 행 확인 │
│ ├─ 가득 찬 행 찾기 │
│ ├─ 행 제거 (블록 삭제) │
│ ├─ 위의 행들을 아래로 이동 │
│ └─ 제거한 행 개수 반환 │
└──────────┬───────────────────────────┘
↓
┌─────────────────────────────────────┐
│ [점수 추가] │
│ GameController.AddScore() │
│ ├─ 점수 계산 │
│ │ score += points × level │
│ ├─ 줄 수 누적 │
│ │ lines += linesCleared │
│ ├─ 레벨 계산 │
│ │ level = (lines / 10) + 1 │
│ └─ UI 업데이트 │
│ UpdateUI() │
└──────────┬───────────────────────────┘
↓
┌─────────────────────────────────────┐
│ [다음 블록 생성] │
│ Spawner.NewTetris() │
│ ├─ 게임 오버 확인 │
│ │ gameBoard.IsGameOver() │
│ ├─ 게임 오버면 종료 │
│ └─ 아니면 새 TetrisBlock 생성 │
└──────────┬───────────────────────────┘
↓
┌──────┴──────┐
│ 게임 오버? │
└──────┬──────┘
│
┌──────┴──────┐
│ │
아니오 예
│ │
↓ ↓
[게임 루프로] [게임 종료]
│
└─→ (자동 낙하로 돌아감)
🏗️ 클래스 구조 및 관계
클래스 다이어그램
┌─────────────────┐
│ GameController │
│ (게임 전체 제어)│
│ - 점수 관리 │
│ - 레벨 관리 │
│ - UI 업데이트 │
└────────┬────────┘
│
┌────────┴────────┐
│ │
│ 참조 │ 참조
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ GameBoard │ │ UI Text │
│ (게임 보드 관리)│ │ (화면 표시) │
│ - 그리드 관리 │ │ - ScoreText │
│ - 행 제거 │ │ - LinesText │
│ - 충돌 감지 │ │ - LevelText │
└────────┬────────┘ └─────────────────┘
│
│ 사용
↓
┌─────────────────┐
│ Spawner │
│ (블록 생성) │
│ - 다음 블록 생성│
└────────┬────────┘
│
│ 생성
↓
┌─────────────────┐
│ TetrisBlock │
│ (블록 제어) │
│ - 이동, 회전 │
│ - 낙하 │
│ - 고정 │
└────────┬────────┘
│
┌────────┴────────┐
│ │
│ 사용 │ 제어
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ InputHandler │ │ GameBoard │
│ (입력 처리) │ │ (충돌 감지) │
│ - 키 입력 감지 │ │ - 위치 확인 │
│ - TetrisBlock 제어│ │ - 셀 확인 │
└─────────────────┘ └─────────────────┘
클래스 간 통신 흐름
[InputHandler] → [TetrisBlock] → [GameBoard]
↓ ↓ ↓
키 입력 블록 이동 충돌 확인
↓
[GameController]
↓
점수/레벨 업데이트
↓
[UI]
데이터 흐름 다이어그램
┌─────────────┐
│ 사용자 입력 │
└──────┬──────┘
↓
┌─────────────┐ ┌─────────────┐
│InputHandler │ ───→ │ TetrisBlock │
└─────────────┘ └──────┬───────┘
│
↓
┌─────────────┐
│ GameBoard │
│ (충돌 확인) │
└──────┬───────┘
│
↓
┌─────────────┐
│GameController│
│ (점수 계산) │
└──────┬───────┘
│
↓
┌─────────────┐
│ UI │
│ (화면 표시) │
└─────────────┘
🔄 주요 처리 흐름
1. 게임 시작 흐름
1. Unity가 씬 로드
↓
2. 각 GameObject의 Awake() 실행
- GameBoard.Awake(): 그리드 초기화
- GameController.Awake(): 변수 초기화
↓
3. 각 GameObject의 Start() 실행
- GameController.Start(): UI 초기화
- Spawner.Start(): 첫 블록 생성
- TetrisBlock.Start(): 블록 초기화
↓
4. 게임 루프 시작 (Update 함수들 실행)
2. 블록 이동 흐름
사용자가 키 입력 (← 또는 →)
↓
InputHandler.Update()
- Input.GetKey() 감지
↓
TetrisBlock.MoveLeft() 또는 MoveRight()
↓
ValidMove() 호출
↓
GameBoard.IsValidPosition() 확인
↓
GameBoard.IsCellEmpty() 확인
↓
이동 가능하면:
- position 업데이트
- UpdateVisualPosition() 호출
- 화면에 반영
3. 블록 고정 흐름
TetrisBlock.Update()
- fallTimer >= fallTime
↓
MoveDown() 호출
↓
ValidMove() 확인
↓
더 이상 내려갈 수 없으면:
↓
LockPiece() 호출
↓
1. 블록을 GameBoard에 저장
- gameBoard.AddToGrid() 호출
↓
2. 행 제거 확인
- gameBoard.CheckForLines() 호출
↓
3. 점수 추가
- gameController.AddScore() 호출
↓
4. 다음 블록 생성
- spawner.NewTetris() 호출
↓
5. 현재 블록 삭제
- Destroy(gameObject)
4. 행 제거 흐름
GameBoard.CheckForLines() 호출
↓
아래에서 위로 행 확인 (for 루프)
↓
각 행에 대해:
↓
HasLine() 확인
├─ 가득 찬 행이면:
│ ↓
│ DeleteLine() 호출
│ ↓
│ 1. 행의 모든 블록 삭제
│ - Destroy() 호출
│ - grid[x, y] = null
│ ↓
│ 2. 위의 행들을 아래로 이동
│ - grid[x, y] = grid[x, y+1]
│ - position += Vector3.down
│ ↓
│ clearedRows 증가
│
└─ 가득 차지 않았으면:
다음 행 확인
↓
제거한 행 개수 반환
5. 점수 계산 흐름
GameController.AddScore(linesCleared) 호출
↓
점수 배열에서 기본 점수 가져오기
- points[linesCleared]
↓
점수 계산
- score += points[linesCleared] * level
↓
줄 수 누적
- lines += linesCleared
↓
레벨 계산
- newLevel = (lines / 10) + 1
- level = newLevel (레벨 업 시)
↓
UI 업데이트
- UpdateUI() 호출
- scoreText.text = "Score: " + score
- linesText.text = "Lines: " + lines
- levelText.text = "Level: " + level
📊 데이터 흐름
변수와 데이터의 흐름
[GameController]
score, lines, level
↓ (AddScore 호출 시)
점수 계산 및 업데이트
↓
UI Text 업데이트
↓
화면에 표시
[TetrisBlock]
position, cells, shapeIndex
↓ (이동 시)
ValidMove() 확인
↓
GameBoard.IsValidPosition()
GameBoard.IsCellEmpty()
↓
이동 가능 여부 반환
↓
이동 또는 정지
[GameBoard]
grid[width, height]
↓ (블록 고정 시)
AddToGrid() 호출
↓
grid에 블록 저장
↓
행 제거 확인
↓
CheckForLines() 호출
↓
제거한 행 개수 반환
🔗 클래스 간 통신
FindObjectOfType 사용
패턴: 다른 클래스를 찾아서 사용
예시:
// TetrisBlock에서 GameBoard 찾기
gameBoard = FindObjectOfType<GameBoard>();
// TetrisBlock에서 GameController 찾기
GameController gc = FindObjectOfType<GameController>();
int level = gc.GetLevel();
주의사항:
- FindObjectOfType은 느릴 수 있음
- Start()에서 한 번만 찾아서 변수에 저장하는 것이 좋음
public 변수 연결
패턴: Inspector에서 직접 연결
예시:
// GameController.cs
public Text scoreText; // Inspector에서 연결
// Inspector에서:
// ScoreText를 드래그 앤 드롭하여 연결
장점:
- FindObjectOfType보다 빠름
- 명확하게 연결됨
함수 호출
패턴: 직접 함수 호출
예시:
// TetrisBlock에서
gameBoard.CheckForLines(); // 직접 호출
// InputHandler에서
currentPiece.MoveLeft(); // 직접 호출
⏱️ 시간 흐름 (프레임별)
한 프레임의 처리 순서
프레임 처리 흐름도:
┌─────────────────────────────────────┐
│ 프레임 시작 (약 0.016초) │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 1. InputHandler.Update() │
│ ├─ 키 입력 확인 │
│ ├─ ←/A 키? → MoveLeft() │
│ ├─ →/D 키? → MoveRight() │
│ ├─ ↓/S 키? → MoveDown() │
│ ├─ ↑/W 키? → Rotate() │
│ └─ Space 키? → HardDrop() │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. TetrisBlock.Update() │
│ ├─ fallTimer += Time.deltaTime │
│ └─ fallTimer >= fallTime? │
│ └─ MoveDown() 호출 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. GameController.Update() │
│ (현재는 없음 - 필요시 추가) │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. 렌더링 (화면 그리기) │
│ ├─ 모든 GameObject 그리기 │
│ ├─ UI 그리기 │
│ └─ 화면에 표시 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 프레임 종료 │
│ (약 0.016초 후 다음 프레임) │
└─────────────────────────────────────┘
중요 사항:
- Update(): 매 프레임마다 실행됨 (초당 약 60회)
- Time.deltaTime: 이전 프레임과 현재 프레임 사이의 시간 (약 0.016초)
- 실행 순서: InputHandler → TetrisBlock → GameController → 렌더링
- 동시 실행: 같은 프레임 내에서 순서대로 실행됨
예시:
프레임 1:
- InputHandler: 키 입력 없음
- TetrisBlock: fallTimer = 0.016초 (아직 fallTime 미도달)
- 렌더링: 현재 상태 그리기
프레임 2:
- InputHandler: ← 키 입력 → MoveLeft() 호출
- TetrisBlock: fallTimer = 0.032초
- 렌더링: 이동한 블록 그리기
프레임 60:
- InputHandler: 키 입력 없음
- TetrisBlock: fallTimer = 0.96초 (아직 fallTime 미도달)
- 렌더링: 현재 상태 그리기
프레임 61:
- InputHandler: 키 입력 없음
- TetrisBlock: fallTimer = 0.976초 → fallTime(1초) 도달 → MoveDown() 호출
- 렌더링: 아래로 이동한 블록 그리기
🎯 이벤트 흐름
주요 이벤트와 반응
[이벤트: 블록이 바닥에 닿음]
↓
LockPiece() 호출
↓
[이벤트: 행 제거]
↓
CheckForLines() 호출
↓
[이벤트: 점수 추가]
↓
AddScore() 호출
↓
[이벤트: UI 업데이트]
↓
UpdateUI() 호출
↓
[이벤트: 다음 블록 생성]
↓
NewTetris() 호출
🔍 디버깅 흐름
문제 발생 시 확인 순서
디버깅 체크리스트:
┌─────────────────────────────────────┐
│ 문제 발생 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 1단계: Console 창 확인 │
│ ├─ 에러 메시지 확인 (빨간색) │
│ ├─ 경고 메시지 확인 (노란색) │
│ └─ Debug.Log 확인 (흰색) │
│ │
│ 해결: 에러 메시지 읽고 수정 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2단계: Inspector 확인 │
│ ├─ 변수 값 확인 │
│ │ - public 변수 값 │
│ │ - private 변수는 확인 불가 │
│ ├─ 연결 상태 확인 │
│ │ - Text 연결 여부 │
│ │ - GameObject 연결 여부 │
│ └─ 컴포넌트 존재 확인 │
│ │
│ 해결: 값이 올바른지, 연결되었는지│
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3단계: Hierarchy 확인 │
│ ├─ GameObject 존재 확인 │
│ │ - GameController 있나? │
│ │ - GameBoard 있나? │
│ │ - Canvas 있나? │
│ ├─ 컴포넌트 존재 확인 │
│ │ - 스크립트가 추가되었나? │
│ └─ 부모-자식 관계 확인 │
│ │
│ 해결: 없으면 생성, 있으면 확인 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4단계: 코드 확인 │
│ ├─ 함수 호출 순서 확인 │
│ │ - Start()가 호출되었나? │
│ │ - Update()가 호출되는가? │
│ ├─ 변수 값 확인 │
│ │ - Debug.Log로 값 출력 │
│ └─ 조건문 확인 │
│ - if 조건이 올바른가? │
│ │
│ 해결: Debug.Log 추가하여 추적 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 문제 해결! │
└─────────────────────────────────────┘
디버깅 팁:
- Debug.Log 활용:
Debug.Log("현재 점수: " + score); Debug.Log("함수가 호출되었습니다!"); - 중단점 사용:
- Visual Studio에서 코드 왼쪽 클릭
- 중단점 설정 후 실행하면 그 지점에서 멈춤
- 단계별 확인:
- 작은 단위로 나누어 하나씩 확인
- 각 함수가 올바르게 작동하는지 확인
📈 성능 고려사항
최적화 포인트
- FindObjectOfType 최소화
- Start()에서 한 번만 찾기
- 변수에 저장하여 재사용
- Update() 최적화
- 불필요한 계산 제거
- 조건문으로 실행 제한
- 메모리 관리
- 사용하지 않는 GameObject 삭제
- 리스트 크기 관리
🎓 학습 포인트
이해해야 할 개념
- 게임 루프: 게임이 반복하는 기본 흐름
- 이벤트 기반: 특정 상황에서 함수 호출
- 상태 관리: 게임의 현재 상태 추적
- 클래스 간 통신: 클래스들이 서로 협력
📚 참고 자료
- 개념설명.md - 게임 루프, 이벤트 섹션
- 각 클래스의 주석 - 상세한 설명
- 단계별학습가이드.md - 실습 가이드
이 문서를 참고하여 프로그램의 전체 흐름을 이해하세요! 🔄