5단계: 입력 처리
🎯 이 단계에서 배울 것
- 키보드 입력 감지
- InputHandler 클래스 구현
- 쿨다운 시스템
- 현재 조각 찾기
🔷 구분하기: 내가 만드는 것 vs 엔진(API)이 주는 것
| 구분 | 이번 단계 예시 | 설명 |
|---|---|---|
| 내가 만드는 것 | InputHandler 클래스, MoveLeft/MoveRight/Rotate 호출, 쿨다운 로직 | 우리가 정의하는 “어떤 키에 어떤 동작을 붙일지”.입력을 게임 로직에 연결하는 건 우리 코드. |
| 엔진(API)이 주는 것 | Input.GetKey, KeyCode, FindObjectOfType, Update() | Unity가 제공. FindObjectOfType은 씬에서 컴포넌트를 찾는 API. |
왜 엔진 것을 쓰나?
키 입력은 Input이 감지하고, 우리는 “눌렸을 때 MoveLeft() 호출”만 연결합니다. FindObjectOfType으로 씬에서 블록을 찾습니다.
📝 실습 내용
📐 예시 구조
InputHandler 빈 오브젝트를 씬 루트에 두고 스크립트만 붙이면 됩니다.
씬 (Tetris)
├─ Main Camera
├─ GameBoard
├─ Spawner
│ └─ (Spawn Point 등)
├─ InputHandler ← 여기, 루트 레벨에 추가. 빈 오브젝트 + Input Handler (Script). 연결할 칸 없음.
└─ (기타)
InputHandler가 FindObjectOfType<TetrisBlock>() 로 블록을 찾아 MoveLeft 등을 호출합니다. → 시스템 설계 개요
📌 작업 씬 Tetris인지 확인. 아니면 → 씬 이름 변경. 발음 → 영어 용어 표기
0. 5단계 시작 전 점검 (누락 방지)
키로 블록이 움직이려면 4단계까지 아래가 있어야 합니다. InputHandler 붙이기 전에 확인하세요.
| 확인 항목 | 필요한 것 | 없을 때 |
|---|---|---|
| 작업 씬 | 상단 씬 탭이Tetris | 1단계 씬 이름 변경 방법대로 Tetris로 변경 |
| GameBoard | Hierarchy에GameBoard 오브젝트, Game Board (Script) 붙어 있음 (2단계) | TetrisBlock이 gameBoard를 못 찾아 ValidMove()에서 에러 → 이동 실패 |
| Spawner | Hierarchy에Spawner 오브젝트, Spawner (Script) 붙어 있음. Spawn Point 연결, Block Prefab 연결 (4단계) | Play 시 블록이 스폰되지 않음 |
| 블록 프리팹 | 사용하는 프리팹(예: I_Block)루트 오브젝트에 TetrisBlock 스크립트 붙어 있음. ValidMove, MoveLeft, MoveRight, MoveDown, Rotate 구현됨 (4단계) | FindObjectOfType<TetrisBlock>()이 null → 키를 눌러도 이동 호출 안 됨 |
체크 방법
- GameBoard: [Hierarchy > GameBoard] 클릭 → [Inspector > Game Board (Script)] 있는지
- Spawner: [Hierarchy > Spawner] 클릭 → [Inspector > Spawner (Script)] 에 Spawn Point, Block Prefab 채워져 있는지
- 블록 프리팹: [Project > Assets > Prefabs > I_Block] (등) 더블클릭 → 프리팹 루트 선택 → [Inspector > Tetris Block (Script)] 있는지
맞으면 테스트 전 필수 설정 확인 후 1. InputHandler 생성으로 가세요.
테스트 전 필수 설정 (카메라·게임 보드 표현)
Play 시 블록·보드가 보여야 합니다. 안 보이면 아래 설정.
카메라 이상 시 → 카메라 설정 가이드
<span id="테스트-전-필수-설정-카메라게임-보드-표현">
카메라 세팅
- [Hierarchy > Main Camera] 클릭
- [Inspector > Main Camera > Transform]
- Position: (5, 10, -10) — 10×20 보드의 가로 중앙(5), 세로 중간(10) 을 바라보고, Z는 음수로 두어 2D 화면 앞에 오도록
- Rotation: (0, 0, 0)
- [Inspector > Main Camera > Camera]
- Projection: Orthographic(읽는 법: 오소그래픽) (2D 테트리스에 적합)
- Size: 10 ~ 12 — 값이 작을수록 확대, 클수록 넓게 보입니다. 10이면 세로로 보드가 잘 보이고, 12면 여백이 더 보입니다.
- 상단 Game 탭에서 Free Aspect 또는 16:9 로 두고 Play → 보드 전체(스폰 위치 포함)가 보이는지 확인
게임 보드의 표현 (보드가 안 보일 때)
GameBoard는 빈 오브젝트라 화면에 안 보입니다. Position (0,0,0)이면 보드는 x 0~10, y 0~20. 카메라 (5, 10, -10), Size 10~12면 그 구간이 보입니다.
보드 시각화(선택) → 6단계 게임 보드 시각화
정리: 카메라 Position (5, 10, -10), Size 10~12로 맞추면 됩니다.
맨 아래 줄이 반쯤 잘리면 → 4단계 바닥 트러블슈팅 (Size 10.5)
1. InputHandler(읽는 법: 인풋 핸들러) 클래스 생성
Assets/Scripts/InputHandler.cs 생성:
- [Project > Assets > Scripts] 선택 → 우클릭 → Create → C# Script → 이름 InputHandler
- 결과: [Project > Assets > Scripts > InputHandler.cs]
- [Project > Assets > Scripts > InputHandler.cs] 더블클릭 → 아래 코드로 전체 교체 후 저장 (Ctrl+S)
using UnityEngine;
public class InputHandler : MonoBehaviour // [MonoBehaviour 상속]
{
private float moveCooldown = 0.1f; // [우리 멤버]
private float moveTimer = 0f; // [우리 멤버]
void Update() // [Unity가 호출] 상속받은 메소드 — 매 프레임 엔진이 호출
{
moveTimer += Time.deltaTime; // Time.deltaTime: [Unity 프로퍼티]
if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A)) // Input.GetKey, KeyCode: [Unity API]
{
if (moveTimer >= moveCooldown) { MoveLeft(); moveTimer = 0f; } // MoveLeft: [우리 메소드]
}
if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D))
{
if (moveTimer >= moveCooldown) { MoveRight(); moveTimer = 0f; } // MoveRight: [우리 메소드]
}
if (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S))
{
TetrisBlock current = FindObjectOfType<TetrisBlock>(); // [Unity API]
if (current != null && moveTimer >= moveCooldown)
{
current.MoveDown(); // MoveDown: [우리 메소드] TetrisBlock 쪽
moveTimer = 0f;
}
}
if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.W)) // [Unity API]
{
Rotate(); // [우리 메소드]
}
}
void MoveLeft() // [우리 메소드]
{
TetrisBlock current = FindObjectOfType<TetrisBlock>(); // [Unity API]
if (current != null)
{
current.MoveLeft(); // MoveLeft: [우리 메소드] TetrisBlock 쪽
}
}
void MoveRight() // [우리 메소드]
{
TetrisBlock current = FindObjectOfType<TetrisBlock>();
if (current != null)
{
current.MoveRight();
}
}
void Rotate() // [우리 메소드]
{
TetrisBlock current = FindObjectOfType<TetrisBlock>();
if (current != null)
{
current.Rotate(); // Rotate: [우리 메소드] TetrisBlock 쪽
}
}
}
씬에 InputHandler 연결하는 방법
개념: InputHandler는 씬의 “어디”에 넣는가
- InputHandler는 화면에 보이는 오브젝트가 아닙니다. 카메라나 블록처럼 위치·크기가 있는 게 아니라, “키 입력을 감지해서 TetrisBlock에 전달하는” 역할만 하는 스크립트를 담는 빈 오브젝트입니다.
- 그래서 Hierarchy에서 어느 부모 아래에 두든, Transform 위치가 어디든, 입력 동작에는 영향이 없습니다. 씬에 한 번만 있으면 됩니다.
- 다만 씬을 정리하기 위해 보통 GameBoard, Spawner, Main Camera와 같은 “루트 레벨” 에 둡니다. 즉, 다른 오브젝트의 자식으로 넣지 않고, Hierarchy 맨 위쪽(부모 없음) 에 두는 방식을 씁니다.
씬에서의 위치 (Hierarchy 구조 예)
- Hierarchy에서 빈 곳을 우클릭 → Create Empty를 하면, 새 오브젝트는 부모가 없는 “루트” 로 만들어집니다. Main Camera, GameBoard, Spawner와 같은 최상위 레벨에 추가됩니다.
- InputHandler는 그 루트 레벨에 두면 됩니다. GameBoard나 Spawner 안(자식) 에 넣지 않습니다.
예시 구조:
Hierarchy
├── Main Camera
├── GameBoard
├── Spawner
│ └── (Spawn Point 등)
├── InputHandler ← 여기, 루트 레벨에 추가
└── (기타)
연결 과정 (순서대로)
- 빈 오브젝트 만들기
- [Hierarchy] 빈 공간 우클릭 → Create Empty
- 루트에 GameObject 생김 → [Hierarchy > GameObject]
- 이름 바꾸기
- 오브젝트 선택 후 F2 또는 [Inspector] 맨 위 이름 칸에 InputHandler 입력 → Enter
- 결과: [Hierarchy > InputHandler]
- InputHandler 스크립트 붙이기
- [Project > Assets > Scripts > InputHandler.cs] 를 [Hierarchy > InputHandler] 위로 드래그
- [Inspector]에 Input Handler (Script) 보이면 연결됨
- 확인
- [Hierarchy > InputHandler] 클릭 시 [Inspector]에 Input Handler (Script) 있으면 OK
- InputHandler는 Spawner처럼 연결 칸 없음. 스크립트만 붙이면 됨.
- 씬 저장
- Ctrl+S 또는 File → Save
- 안 하면 나중에 씬 열었을 때 InputHandler가 없을 수 있음
정리: Hierarchy 루트에 InputHandler 빈 오브젝트 → InputHandler.cs 드래그로 붙이기 → Ctrl+S 저장. (위치 표기: 1단계)
언제 스폰된 블록이 움직이나요?
이동은 Play 모드에서 일어납니다.
- ▶ Play를 눌러야 함. 그때부터 Spawner가 블록 하나 스폰하고 동작 시작.
- 자동 낙하 (4단계)
TetrisBlock Update() →fallTimer쌓임 → fallSpeed마다 MoveDown() → 키 안 눌러도 한 칸씩 내려감. - 키로 이동 (5단계)
InputHandler Update() → 화살표/WASD 누르면 TetrisBlock 찾아서 MoveLeft(), MoveRight(), Rotate(), MoveDown() 호출.
정리: Play 누른 뒤 → 자동 낙하 + 키 입력으로 좌우·회전·빠른 낙하.
키를 눌러도 이동이 안 될 때
화살표/WASD 눌러도 블록이 안 움직이면 순서대로 확인.
- Game 뷰 한 번 클릭 (포커스). 다른 창에 포커스 있으면 키가 게임에 안 감.
- InputHandler
[Hierarchy > InputHandler] 있고 [Inspector]에 Input Handler (Script) 붙어 있는지. - 블록 프리팹 루트에 TetrisBlock
프리팹 루트(예: I_Block)에 TetrisBlock 붙어 있어야 함. 자식에만 있으면 찾지 못함. - GameBoard
[Hierarchy]에 GameBoard + Game Board (Script). 없으면 ValidMove()에서 NullReferenceException. - Console
Window → General → Console에서 빨간 에러 확인 → 3·4 다시 점검.
정리: Game 뷰 포커스 → InputHandler·GameBoard 씬에 있는지 → 프리팹 루트에 TetrisBlock → Console. 맨 아래 줄 반쯤 잘리면 → 4단계 바닥/좌표 (카메라 Size 10.5).
회전은 2번 되는데, 그 다음엔 아무것도 안 될 때
증상: 위/ W 로 회전은 한두 번 되다가, 그 다음부터는 회전·좌우 이동·아래 모두 안 됨.
- Game 뷰 한 번 클릭 (포커스).
- 블록 한 개만: 수동 블록 제거, Spawner 블록만 두기.
- 회전 후 보드 밖: Spawn Point (5, 18, 0), GameBoard Width 10 / Height 20. Play 멈추고 위치 조정 후 다시 Play.
- Console 빨간 에러 → GameBoard 있는지, 씬 Tetris인지 확인.
나중에 블록이 여러 개 나오나요?
네. 나중에 나옵니다.
- 지금(4·5단계): Spawner는 Start()에서 블록 한 개만 스폰합니다. 그래서 Play하면 블록 하나만 떨어집니다.
- 6단계에서: 블록이 바닥에 닿으면 LockPiece()가 호출되고, 그 안에서 Spawner.SpawnNext()를 부릅니다. 그래서 한 블록이 고정될 때마다 다음 블록이 스폰됩니다. 한 번에 하나씩 떨어지고, 고정되면 그다음 블록이 나오는 식으로 이어집니다.
- 8단계에서: Spawner에 프리팹 배열과 랜덤 선택을 넣으면 블록 종류(I, O, T 등)가 여러 개 나오게 할 수 있습니다.
정리: 6단계에서 LockPiece() → SpawnNext() 로 이어지면 블록이 차례로 나오고, 8단계에서 여러 종류의 블록이 나오게 할 수 있습니다.
🔗 이전 단계와의 연결
이 섹션은 실습이 아닙니다. 따로 할 일이나 체크할 항목은 없습니다. “이 단계가 이전 단계와 어떻게 이어지는지”만 참고로 정리한 것이므로, 궁금할 때만 읽으면 됩니다.
이 단계에서는 4단계에서 만든 TetrisBlock의 이동 함수들을 사용합니다:
MoveLeft(): 4단계에서 구현MoveRight(): 4단계에서 구현MoveDown(): 4단계에서 구현Rotate(): 4단계에서 구현
작동 방식:
- 사용자가 키를 누름
- InputHandler가 입력을 감지
- 현재 TetrisBlock를 찾아서 해당 함수 호출
- TetrisBlock가 이동/회전 수행
💡 참고: 현재 블록 찾기
- 이 강의에서는
FindObjectOfType<TetrisBlock>()로 현재 블록을 찾는 방식으로 설명합니다. - 더 효율적으로 하려면 한 번 찾아 변수에 저장해 재사용할 수 있습니다:
// 실제 코드에서는 Spawner나 GameController가 현재 블록을 관리할 수 있습니다 private TetrisBlock currentBlock; void Update() { if (currentBlock == null) { currentBlock = FindObjectOfType<TetrisBlock>(); } // 입력 처리... } FindObjectOfType은 매 프레임마다 호출하면 성능에 영향을 줄 수 있으므로, 한 번 찾아서 캐싱하는 것이 좋습니다- 실제 프로젝트에서는 Spawner가 현재 블록을 관리하고, InputHandler가 Spawner를 참조하는 구조를 사용할 수 있습니다.
📌 복기: 변수에 한 번 저장해 두고 쓰는 것 = 캐싱. 변수에서 다시 실습해 보세요.
💡 이번 단계에서 익히는 개념
- 메소드 = 클래스 안에 들어간 함수 (이름만 다름).
예:
MoveLeft(),MoveRight(),Rotate()는 TetrisBlock 클래스 안의 메소드입니다. 역할은 “그 클래스의 저장소(위치 등)를 다루는 함수”입니다.
📌 복기: 메소드 = 클래스 안의 함수. 한 번에 이해 못했어도 멤버와 메소드에서 다시 실습해 보세요.
📖 기초 개념 학습 (복기 → 점진적 실습)
📌 복기로 위 개념을 떠올린 뒤, 아래 기초 개념 문서에서 점진적으로 확장된 실습을 진행하세요. 단계가 올라갈수록 같은 개념이 조금씩 확장되므로, 차근차근 따라 하면 됩니다.
| 개념 문서 | 이번 단계와의 연결 |
|---|---|
| 멤버와 메소드 | 메소드 = 클래스 안의 함수. 한눈에 정리 + AddScore 실습 |
| 함수 | 매개변수·반환값. AddNumbers, GetTen 실습 |
| FindObjectOfType | currentPiece 찾기 |
📐 이 단계 확장 실습 (전용 씬에서 점진적 연습): 5단계 확장 실습 01 - Input과 쿨다운 → 단계별 확장 실습 전체 목차: 단계별 확장 실습 (목차)
✅ 확인 사항
- 시작 전 점검: 위 0. 5단계 시작 전 점검에서 GameBoard, Spawner, 블록 프리팹(루트에 TetrisBlock)이 모두 갖춰져 있는가?
- 테스트 전 설정: 테스트 전 필수 설정 (카메라·게임 보드 표현) 을 적용했는가? (블록이 안 보이면 카메라 Position/Size 확인)
- 화살표 키로 블록이 이동하는가?
- WASD 키도 작동하는가?
- 회전 키가 작동하는가?
- 아래 화살표(또는 S)로 빠른 낙하가 작동하는가?
✅ 다음 단계 전 점검: 작업 씬이 Tetris인지 재차 확인하세요. Tetris가 아니면 씬 이름 변경 방법을 따라 Tetris로 바꾼 뒤 다음 단계로 넘어가세요.
| ← 이전 단계: 4단계 - 블록 이동 구현 | 다음 단계: 6단계 - 행 제거 시스템 → |