테트리스 강좌

초등학생도 쉽게 따라할 수 있는 Unity 테트리스 게임 개발 강좌 자료

프로그램 처리 흐름 설계

📖 이 문서는?

테트리스 게임의 전체적인 프로그램 처리 흐름을 설명한 문서입니다.
각 클래스가 어떻게 상호작용하는지, 데이터가 어떻게 흐르는지 이해할 수 있습니다.


🎮 게임 전체 흐름

게임 루프 (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();

주의사항:


public 변수 연결

패턴: Inspector에서 직접 연결

예시:

// GameController.cs
public Text scoreText;  // Inspector에서 연결

// Inspector에서:
// ScoreText를 드래그 앤 드롭하여 연결

장점:


함수 호출

패턴: 직접 함수 호출

예시:

// 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초 후 다음 프레임)    │
└─────────────────────────────────────┘

중요 사항:

예시:

프레임 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 추가하여 추적    │
└──────────────┬──────────────────────┘
               ↓
┌─────────────────────────────────────┐
│      문제 해결!                      │
└─────────────────────────────────────┘

디버깅 팁:

  1. Debug.Log 활용:
    Debug.Log("현재 점수: " + score);
    Debug.Log("함수가 호출되었습니다!");
    
  2. 중단점 사용:
    • Visual Studio에서 코드 왼쪽 클릭
    • 중단점 설정 후 실행하면 그 지점에서 멈춤
  3. 단계별 확인:
    • 작은 단위로 나누어 하나씩 확인
    • 각 함수가 올바르게 작동하는지 확인

📈 성능 고려사항

최적화 포인트

  1. FindObjectOfType 최소화
    • Start()에서 한 번만 찾기
    • 변수에 저장하여 재사용
  2. Update() 최적화
    • 불필요한 계산 제거
    • 조건문으로 실행 제한
  3. 메모리 관리
    • 사용하지 않는 GameObject 삭제
    • 리스트 크기 관리

🎓 학습 포인트

이해해야 할 개념

  1. 게임 루프: 게임이 반복하는 기본 흐름
  2. 이벤트 기반: 특정 상황에서 함수 호출
  3. 상태 관리: 게임의 현재 상태 추적
  4. 클래스 간 통신: 클래스들이 서로 협력

📚 참고 자료


이 문서를 참고하여 프로그램의 전체 흐름을 이해하세요! 🔄