- 핵심 질문
- 시작 전과 게임이 끝난 뒤에는 무엇을 보여 줄까요?
- 학습 방식
- 화면 준비 → 한 동작 연결 → 바로 실행
- 이전 학습
- Canvas · HUD · 점수와 시간
- 작업 Scene
- Week07_MicroLevel
- 오늘 완성할 것
- 시작 화면 · 결과 화면 · 재시작
- 다음 교시
- 데스크톱 빌드와 실행 확인
1교시에는 점수와 시간이 보이게 했습니다. 그런데 시간이 0이 되어도 레벨을 계속 플레이할 수 있었지요. 이번에는 시작 전 → 플레이 중 → 결과에 맞는 화면을 연결합니다. 2교시의 도착점은 Editor 안에서 완성한 UI이고, 3교시에 이 장면을 빌드합니다.
어떤 순서로 완성할까요?
각 단계에서 작동 원리를 먼저 읽고, 한 가지 동작을 연결할 때마다 Play로 확인합니다.
- 1~2 · 화면 준비
이전 HUD에 시작·결과 화면을 더합니다. 부모 Panel을 켜고 끄면서 화면을 묶는 이유를 확인합니다.
- 3~4 · Start 연결·확인
GameManager가 기억할 상태를 정하고 초기 화면을 확인합니다. Start 버튼이 상태와 화면을 함께 바꾸는 과정을 연결합니다.
- 5 · 수집과 결과 확인
보석 접촉 → HUD 갱신, 가시 접촉 → 시작점 복귀, 보석 3개 + 목표 도착 → 성공을 연결합니다. 시간 종료도 같은 결과 화면을 사용합니다.
- 6 · Restart 연결·확인
무엇을 초기화해야 하는지 살펴보고 Scene List에 장면을 등록합니다. 결과 화면에서 처음으로 돌아옵니다.
- 마무리 · 비교와 UI 저장
화면 비율과 결과 문구를 확인하고, 3교시 빌드에 사용할 장면을 저장합니다.
전체 코드는 접힌 자료에서 한 번 준비합니다. 본문의 짧은 코드는 현재 확인할 동작을 읽기 위한 부분입니다. 연결이 끝난 뒤에는 마지막 비교 활동에서 설정 하나를 바꿔 결과를 예측해 봅니다.
먼저 완성할 네 화면을 봅니다
각 화면에서 플레이어가 무엇을 읽고 어떤 행동을 할지 먼저 확인합니다.
아래는 5주차 Week5_MicroLevel에 6주차 애니메이션과 7주차 UI를 적용해 실행한 화면입니다. 공통 실습 패키지의 완성 참고 씬과 같은 레벨입니다. 자신의 작업 화면에서 문구·버튼·HUD가 나타나는 위치와 순서를 비교하세요. 이미지를 누르면 크게 볼 수 있습니다.
시작 화면에서는 목표와 시작 방법, HUD에서는 진행 상황, 결과 화면에서는 종료 이유와 다음 행동을 알 수 있어야 합니다. 네 모습을 만들지만 ResultPanel은 하나만 사용합니다.
1 · 1교시의 HUD와 기존 레벨을 이어 씁니다
보석 수집 수와 남은 시간이 보이는 Week07_HUD에서 출발합니다.
1교시에는 수집 값을 보여 주었습니다. 이번에는 언제 플레이를 시작하고, 어떤 조건에서 끝내며, 무엇을 초기화할지 정합니다. 보석 3개를 모은 뒤 오른쪽 목표 지점에 도착해야 성공이라는 5주차 규칙을 유지합니다.
Week07_MicroLevel로 별도 저장하기
Play를 끄고
Week07_HUD를Assets/Week7/Week07_MicroLevel.unity로 Save As합니다. Canvas와 HUD, 기존 레벨을 유지합니다. 이미 같은 작업 파일이 있다면 별도 백업 후 이어 작업합니다.화면에서 확인1교시 씬과 2교시 작업 씬이 각각 남아 있습니다.세 종류의 사건을 다시 확인하기
보석 접촉은 수집 수 증가, 가시 접촉은 수집 수를 유지한 시작점 복귀, 목표 접촉은 모든 보석을 모았는지 검사하는 사건입니다. 오브젝트의 위치·Collider·Trigger 설정은 유지합니다. 아직 기존 컴포넌트를 끄거나 제거하지 않습니다. 아래 GameManager 연결 단계에서 한 번에 교체합니다.
화면에서 확인원래 레벨이 작동하며, 새 UI를 만들 준비가 되어 있습니다.
자신의 1교시 파일을 복구해야 한다면 공통 패키지의 Reference/Week07_HUD_Complete.unity를 별도 작업 이름으로 저장해 이어갈 수 있습니다. 완성 참고 씬을 현재 씬에 합치지 않습니다.
2 · 시작 화면을 만들고 결과 화면으로 복제합니다
Panel을 켜면 그 안의 글자와 버튼이 함께 보입니다. 1교시에서 HUD 아래 두 Text를 묶었던 것과 같은 부모·자식 관계입니다.
Canvas 바로 아래에 HUD, StartPanel, ResultPanel을 둡니다. 시작 전에는 StartPanel, 플레이 중에는 HUD, 끝난 뒤에는 ResultPanel을 보여 줍니다. 게임 장면은 하나이고, 그 위에 보이는 UI가 바뀝니다.
세 부모는 Hierarchy에서 같은 깊이에 있는 형제입니다. StartPanel을 HUD의 자식으로 넣으면 HUD를 끌 때 시작 화면도 함께 꺼집니다. 각각 독립적으로 보여 주려면 세 부모를 모두 Canvas 바로 아래에 두어야 합니다.
1교시에서는 HUD 부모의 Image를 제거하고, 자식 Image로 숫자 뒤의 배경과 아이콘을 추가했습니다. 시작·결과 Panel에서는 부모의 Image를 화면 전체를 덮는 용도로 유지합니다. 전체 화면 덮개가 게임 장면을 어둡게 만들고, 그 앞의 작은 창 형태인 ContentPanel이 제목과 버튼을 받쳐 줍니다. 위쪽 초록색 띠에는 제목을, 아래쪽 밝은 영역에는 버튼을 놓습니다. Rect Transform은 배경이 차지할 범위, Image는 그 범위를 채울 색을 맡습니다.
배경은 화면 전체를 채우는 Stretch로, 제목과 버튼은 화면 중앙을 기준으로 배치합니다. 화면 비율이 바뀌어도 배경은 가장자리까지 이어지고, 조작할 버튼은 중앙에 남아 있어야 합니다. 1교시에서 배운 Anchor를 서로 다른 목적에 사용하는 것입니다.
Canvas Scaler는 1교시의 Scale With Screen Size, 기준 1920 × 1080, Match 0.5를 유지합니다. 1교시에서 Import한 Assets/Week7/UI/Sprites의 애셋을 사용합니다. 아직 준비하지 않았다면 같은 UI 패키지를 먼저 Import합니다. 아래 크기와 위치는 이 기준에서 입력할 값입니다. Play를 끈 상태에서 한 요소씩 추가하고 Game 뷰로 결과를 확인합니다.
배경부터 화면 전체에 채우기
Canvas를 우클릭해
UI → Panel을 만들고StartPanel로 이름을 바꿉니다. Inspector의 Rect Transform에서 Anchor Min·Max를 먼저 입력하고, 펼쳐진 Left·Right·Top·Bottom을 모두 0으로 둡니다.- Anchor Min / Max
- (0, 0) / (1, 1)
- Left · Right · Top · Bottom
- 모두 0
- Image → Color
- 어두운 색, Alpha는 배경이 조금 보이는 정도
예제는 Image의 Color를
#101820, Alpha를 약0.55(0~255 표시에서는 약 140)로 사용했습니다. Source Image는None으로 둡니다. 지금은 게임 장면 위에 어두운 덮개만 보이면 됩니다.화면에서 확인네 가장자리까지 배경이 이어집니다. 가운데만 작은 사각형이 보이면 Stretch와 네 여백을 다시 봅니다.
배경만 만든 단계입니다. 아직 제목과 버튼은 없습니다. 덮개 앞에 작은 게임 창 놓기
StartPanel 아래에
UI → Image를 만들고 이름을ContentPanel로 바꿉니다. PNG를 펼친 안쪽의Panel_CommonSprite를 Source Image에 넣습니다. 이 이미지는 화면 전체 덮개와 별개입니다. 위쪽에는 초록색 제목 띠, 아래쪽에는 밝은 연녹색 버튼 영역이 들어 있습니다. 제목과 버튼을 각각의 영역에 놓을 작은 창입니다.- Anchor Min · Max · Pivot
- 모두 (0.5, 0.5)
- Pos X / Pos Y
- 0 / 0
- Width / Height
- 760 / 360
- Image 설정
- Type Sliced · Color 흰색 · Raycast Target 끄기
Sliced는 Sprite에 지정된 테두리 영역을 보존하면서 가운데와 가장자리를 늘립니다. 이 애셋은 위쪽 제목 띠까지 Border에 포함되어 있습니다. 패키지의 설정을 유지하고, 크기를 바꾸었을 때 픽셀 모서리와 제목 띠의 높이가 유지되는지 봅니다. ContentPanel은 StartPanel의 첫 자식으로 두어 나중에 넣는 제목·버튼보다 뒤에 그려지게 합니다.
화면에서 확인어두운 게임 장면 앞 중앙에 작은 창이 보입니다. 위쪽 제목 띠와 아래쪽 버튼 영역은 구분되지만, 아직 글자와 버튼은 없습니다.
화면 전체를 덮는 StartPanel과 중앙의 ContentPanel은 서로 다른 Image입니다. 위쪽 띠 안에 목표 제목 넣기
Hierarchy에서 StartPanel을 선택한 뒤
UI → Text - TextMeshPro를 만듭니다. 이름은StartTitle, 내용은Collect 3 Gems로 바꿉니다. 제목은 StartPanel보다 한 단계 안쪽에 있어야 합니다.- Anchor Min · Max · Pivot
- 모두 (0.5, 0.5)
- Pos X / Pos Y
- 0 / 130
- Width / Height
- 640 / 80
- Font Size / Alignment
- 48 / 가로·세로 가운데
초록색 제목 띠 위에서 읽히도록 제목 색을 밝은 크림색
#F4EEDC로 지정합니다. StartTitle은 ContentPanel 다음 순서에 둡니다. Pos Y가 130이므로 작은 창의 위쪽 띠 안에 놓입니다. 글자가 잘리면 Rect Transform의 높이와 글자 크기, 자동 줄바꿈 여부를 확인합니다.화면에서 확인초록색 제목 띠 안에서 밝은 글자의 목표 문구가 한 줄로 읽힙니다. 버튼이 없어도 제목 위치부터 비교할 수 있습니다.
제목까지 만든 단계입니다. 다음에는 제목 아래에 시작할 행동을 놓습니다. 목표 설명과 조작 안내 넣기
StartTitle을 복제해
StartDetail로 바꾸고 StartPanel의 자식으로 둡니다. 내용은Then reach the goal., 중앙 Anchor·Pivot, Pos(0, 35), 크기620 × 48, Font Size28, 색#293A32입니다.하나 더 복제해
ControlsText로 바꿉니다. 내용은A / D or Arrows: Move Space: Jump Shift: Dash, Pos(0, -235), 크기1000 × 50, Font Size25, 색#F4EEDC입니다. 두 Text 모두 가운데 정렬, Raycast Target 끄기입니다.화면에서 확인창 안에는 목표에 도착하라는 설명, 창 아래에는 이동·점프·대시 조작법이 보입니다.제목 아래에 Start 버튼 놓기
다시 StartPanel을 선택한 뒤
UI → Button - TextMeshPro를 만듭니다. 이름을StartButton으로 바꿉니다. StartTitle과 StartButton은 같은 깊이에 두고, 버튼이 제목의 자식으로 들어가지 않았는지 확인합니다.- Anchor Min · Max · Pivot
- 모두 (0.5, 0.5)
- Pos X / Pos Y
- 0 / -50
- Width / Height
- 320 / 90
- 자식 Text (TMP)
- 내용 Start · Font Size 36 · 가운데 정렬
StartButton을 선택하면 버튼의 위치·크기·배경을, 그 아래 Text (TMP)를 선택하면 글자 내용·크기·색을 바꿉니다. 버튼 Image의 Source Image에
Button_Normal, Image Type에 Sliced, Color에 흰색을 지정합니다. 초록색 버튼 위의 자식 Text는 크림색#F4EEDC로 바꿉니다.Button의 Transition을 Sprite Swap으로 바꾸고 Target Graphic에 이 버튼의 Image를 연결합니다. Highlighted·Selected에는
Button_Highlighted, Pressed에는Button_Pressed, Disabled에는Button_Normal을 넣습니다. 포인터를 올리거나 누를 때 색이 다른 Sprite로 교체됩니다. 버튼 Image의 Raycast Target은 켜고 자식 Text의 Raycast Target은 끕니다.자식 Text의 Rect Transform은 버튼 안을 채우는 기본 배치를 유지합니다. 여기서는 버튼의 모양만 준비하고, 실제 시작 동작은 뒤의 Start 연결 단계에서 지정합니다.
화면에서 확인위쪽 띠에는 목표, 아래쪽 밝은 영역에는 Start 버튼이 보입니다. 제목과 버튼이 겹치지 않고 버튼 글자도 배경과 구분됩니다.
시작 화면의 배치가 완성됐습니다. 클릭 동작은 아직 연결하기 전입니다. 완성한 시작 화면을 결과 화면으로 복제하기
Hierarchy의 StartPanel 부모 전체를 복제하고 복제본 이름을
ResultPanel로 바꿉니다. StartPanel은 잠시 끄고 ResultPanel만 보며 아래 이름과 내용을 바꿉니다.- 복제한 StartTitle
- 이름 ResultText · 내용 Success
- 복제한 StartDetail
- 이름 ResultDetail · 내용 All 3 gems collected. Goal reached! · 크기 640 × 48 · Font Size 26
- 복제한 ControlsText
- ResultPanel의 복제본에서 삭제
- 복제한 StartButton
- 이름 RestartButton
- RestartButton의 자식 Text
- 내용 Restart
같은 위치와 크기를 유지하면 시작과 결과 화면에서 버튼을 찾는 위치가 같습니다. ResultText에
Time Up도 직접 입력해 한 줄로 읽히는지 확인한 뒤Success로 되돌립니다. 실행 중에는 GameManager가 이 문구를 바꿉니다.화면에서 확인제목 자리에는 결과가, 버튼에는 Restart가 보입니다. Success와 Time Up 모두 같은 영역 안에 들어갑니다.
배치와 크기는 그대로 사용하고 결과 문구와 버튼 이름표를 바꿉니다. 플레이 중 목표를 알려 줄 HintText 만들기
HUD 아래
UI → Image를 만들고HintBackground로 이름을 바꿉니다. HUD_Backplate를 Sliced로 넣고 크기는800 × 58, Anchor Min·Max·Pivot은(0.5, 0), Pos는(0, 28)입니다. Raycast Target은 끕니다.HUD 아래에 별도의
HintText를 만듭니다. 같은 Anchor·Pivot, Pos(0, 35), 크기760 × 44, Font Size25, 색#293A32, 가운데 정렬, Raycast Target 끄기입니다. 내용은Collect 3 gems, then reach the goal.로 두고 HintBackground보다 뒤 순서에 놓습니다.화면에서 확인HUD 하단에 목표 안내가 표시됩니다. 이후 목표 미달·복귀·수집 완료에 따라 GameManager가 문구를 바꿉니다.부모와 자식 이름을 확인하기
아래 구성도와 자신의 Hierarchy를 비교합니다. Canvas 아래의 순서는 HUD → StartPanel → ResultPanel입니다. 세 부모를 하나씩 켜서 확인한 뒤 StartPanel만 켜고 HUD·ResultPanel은 끕니다. 부모 아래의 Text와 Button 자체 체크는 켜 둡니다.
화면에서 확인세 부모가 형제로 놓여 있고, 각각에 필요한 Text와 Button이 들어 있습니다. 시작 화면만 보이는 상태에서 Scene을 저장합니다.
Canvas
├─ HUD
│ ├─ ScoreBackground / TimerBackground
│ ├─ ScoreIcon / TimerIcon
│ ├─ ScoreText / TimerText
│ ├─ HintBackground
│ └─ HintText
├─ StartPanel
│ ├─ ContentPanel
│ ├─ StartTitle
│ ├─ StartDetail
│ ├─ StartButton → Text (TMP)
│ └─ ControlsText
└─ ResultPanel
├─ ContentPanel
├─ ResultText
├─ ResultDetail
└─ RestartButton → Text (TMP)
EventSystem
GameManager ← 다음 단계에서 추가
부모 Panel을 켜고 끄면 그 자식 UI도 함께 보이거나 숨겨집니다. 이제 이 동작을 버튼과 게임 결과에 연결합니다.
한글이 네모로 보일 때
기본 TMP Font Asset에 한글 글리프가 없을 수 있습니다. 우선 Start · Success · Time Up · Restart로 확인합니다. 한글 문구는 해당 글자가 포함된 Font Asset을 연결한 뒤 바꿉니다.
2D UI 애셋 · 공통 패널과 버튼 3상태
Unity 패키지 내려받기 · PNG·설정 ZIP 내려받기 · 적용 안내 내려받기
글자가 없는 투명 배경 PNG 7종입니다. 패키지를 Import하면 Assets/Week7/UI/Sprites에 들어갑니다. PNG를 펼친 뒤 같은 이름의 Sprite를 Source Image에 연결합니다. 점수·시간·버튼 글자는 기존 TextMeshPro를 사용합니다.

기존 StartPanel·ResultPanel의 어두운 덮개를 유지하고, 그 아래 중앙에 Panel_Common Image를 넣습니다. 제목 띠와 버튼 영역이 나뉜 창형 패널이며, 제목·버튼보다 뒤에 그려지도록 앞 순서에 둡니다. 제목은 위쪽 초록색 띠에 놓고, 제목과 버튼 글자는 모두 크림색 #F4EEDC로 바꿉니다. 패널과 버튼은 Image Type을 Sliced, Color를 흰색으로 설정합니다.
Start·Restart 버튼에는 같은 3개 Sprite를 사용합니다. Image의 Source Image는 Button_Normal, Button의 Transition은 Sprite Swap입니다. Highlighted·Selected에는 Button_Highlighted, Pressed에는 Button_Pressed를 연결합니다. 장식 패널의 Raycast Target은 끄고 버튼 Image의 Raycast Target은 켜 둡니다. 버튼 글자와 On Click 함수 연결은 유지합니다.
2-1 · 부모를 끄면 그 안의 UI도 함께 꺼집니다
코드를 연결하기 전에, 손으로 화면을 바꿔 보며 부모·자식 관계를 확인합니다.
StartPanel에는 전체 화면 덮개 Image가 있고, 자식으로 창형 ContentPanel·제목 Text·Start 버튼이 들어 있습니다. Inspector 맨 위에서 StartPanel 오브젝트의 체크를 끄면 자식까지 비활성 상태가 되어 배경·제목·버튼이 함께 사라집니다. 다시 켜면 자식 중 자체 체크가 켜져 있던 것들이 돌아옵니다.
StartPanel의 Image 컴포넌트 체크만 끄면 전체 화면의 어두운 덮개만 사라집니다. 자식 ContentPanel·제목·버튼은 남습니다. 화면 한 묶음을 전환할 때는 부모 오브젝트를, 배경 표현만 바꿀 때는 Image를 조절합니다. 코드에서도 SetActive로 같은 활성 상태를 바꿀 수 있습니다.
배경과 화면 전체를 구분해 끄기
Play를 끈 상태에서 StartPanel만 켭니다. 먼저 Image 컴포넌트만 껐다 켜 보고, 다음에는 오브젝트 맨 위 체크를 껐다 켭니다. 어느 경우에 제목과 버튼까지 사라지는지 비교합니다.
화면에서 확인StartPanel의 Image를 끄면 어두운 덮개만 사라집니다. 부모 오브젝트를 끄면 창형 패널과 제목·버튼까지 함께 사라집니다.실행 전 배치로 되돌리기
StartPanel의 Image와 자식 ContentPanel·Text·Button은 모두 켜 둡니다. 세 부모 중 StartPanel만 켜고 HUD·ResultPanel은 끈 뒤 Scene을 저장합니다. Canvas와 EventSystem은 계속 켜 둡니다.
화면에서 확인게임 장면 위에 시작 화면만 보입니다. 세 UI 부모는 Canvas 바로 아래에 있습니다.
HUD를 꺼도 다른 오브젝트의 이동이나 타이머 코드가 자동으로 멈추지는 않습니다. 다음 단계에서는 GameManager가 진행 상태, 게임 시간, 보이는 화면을 함께 정하게 만듭니다.
3 · 화면과 게임 상태를 관리할 담당을 연결합니다
HUD는 유지하고, 한 판의 값을 기억하는 역할을 Week07GameManager로 옮깁니다.
1교시에는 PlayerProgress가 수집 수를 기억하고 HUDDisplay가 표시했습니다. 이제 Week07GameManager가 수집 수·남은 시간·진행 상태를 함께 관리합니다. 기존 GameManager.cs라는 파일을 덮어쓰지 않습니다. 이 수업의 파일 이름은 Week07GameManager.cs이고, Hierarchy의 오브젝트 이름은 GameManager입니다.
| 현재 상태 | 조건 | 보이는 UI |
|---|---|---|
| Ready | Start를 기다림. 이동·점프·대시·시간 정지. | StartPanel |
| Playing | 수집·복귀·목표 검사를 처리. 시간 감소. | HUD |
| Won | 보석 3개를 모은 뒤 목표 지점에 도착. | ResultPanel · Success |
| Lost | 목표에 도착하기 전에 시간 0. | ResultPanel · Time Up |
RoundState는 네 상태의 이름을 모은 enum이고, CurrentState에는 현재 이름 하나가 들어갑니다. 6주차 Animator의 State가 캐릭터 동작을 구분했다면, 여기서는 한 판의 진행 단계를 구분합니다.
Week07GameManager.cs 전체 코드
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace GameEngineI.Week07
{
public enum RoundState { Ready, Playing, Won, Lost }
// Week 5's collect -> goal / hazard -> start rules, with Week 7 screen states.
[DefaultExecutionOrder(-200)]
public sealed class Week07GameManager : MonoBehaviour
{
[Header("Rules")]
[SerializeField, Min(1)] int targetScore = 3;
[SerializeField, Min(1)] float roundDuration = 30f;
[Header("Player")]
[SerializeField] Rigidbody2D playerBody;
[SerializeField] PlayerController movement;
[SerializeField] PlayerDash dash;
[SerializeField] PlayerAnimation animationDriver;
[Header("UI")]
[SerializeField] GameObject hud;
[SerializeField] GameObject startPanel;
[SerializeField] GameObject resultPanel;
[SerializeField] TMP_Text scoreText;
[SerializeField] TMP_Text timerText;
[SerializeField] TMP_Text hintText;
[SerializeField] TMP_Text resultText;
[SerializeField] TMP_Text resultDetail;
public RoundState CurrentState { get; private set; }
public int Score { get; private set; }
public int TargetScore => targetScore;
public float TimeRemaining { get; private set; }
public Vector2 SpawnPosition { get; private set; }
float hintUntil;
bool restarting;
void Awake()
{
SpawnPosition = playerBody.position;
Score = 0;
TimeRemaining = roundDuration;
CurrentState = RoundState.Ready;
SetPlayerControl(false);
Time.timeScale = 0;
hud.SetActive(false);
startPanel.SetActive(true);
resultPanel.SetActive(false);
RefreshHud();
}
void Update()
{
if (CurrentState != RoundState.Playing) return;
TimeRemaining = Mathf.Max(0, TimeRemaining - Time.deltaTime);
RefreshHud();
if (TimeRemaining <= 0) Finish(RoundState.Lost);
else if (playerBody.position.y < -6) Respawn();
}
public void StartRound()
{
if (CurrentState != RoundState.Ready) return;
CurrentState = RoundState.Playing;
startPanel.SetActive(false);
resultPanel.SetActive(false);
hud.SetActive(true);
Time.timeScale = 1;
SetPlayerControl(true);
RefreshHud();
}
public bool TryCollect()
{
if (CurrentState != RoundState.Playing || Score >= targetScore) return false;
Score++;
hintUntil = 0;
RefreshHud();
// All gems unlock the goal; collecting the last gem does not end Week 5's level.
return true;
}
public void TryGoal()
{
if (CurrentState != RoundState.Playing) return;
if (Score < targetScore)
{
ShowHint($"Collect all {targetScore} gems before reaching the goal.");
return;
}
Finish(RoundState.Won);
}
public void Respawn()
{
if (CurrentState != RoundState.Playing) return;
SetPlayerControl(false);
playerBody.position = SpawnPosition;
playerBody.transform.position = new Vector3(SpawnPosition.x, SpawnPosition.y, playerBody.transform.position.z);
playerBody.angularVelocity = 0;
Physics2D.SyncTransforms();
SetPlayerControl(true);
ShowHint("Back at the start. Your collected gems are kept.");
}
public void RestartScene()
{
if (restarting || (CurrentState != RoundState.Won && CurrentState != RoundState.Lost)) return;
restarting = true;
Time.timeScale = 1;
SceneManager.LoadScene(SceneManager.GetActiveScene().path);
}
void Finish(RoundState state)
{
if (CurrentState != RoundState.Playing) return;
CurrentState = state;
SetPlayerControl(false);
Time.timeScale = 0;
hud.SetActive(false);
startPanel.SetActive(false);
resultPanel.SetActive(true);
resultText.text = state == RoundState.Won ? "Success" : "Time Up";
resultDetail.text = state == RoundState.Won
? $"All {targetScore} gems collected. Goal reached!"
: "Find your route and try again.";
}
void SetPlayerControl(bool active)
{
movement.enabled = active;
dash.enabled = active;
animationDriver.enabled = active;
if (!active) playerBody.linearVelocity = Vector2.zero;
}
void ShowHint(string message)
{
hintUntil = Time.unscaledTime + 2.5f;
hintText.text = message;
}
void RefreshHud()
{
scoreText.text = $"{Score} / {targetScore}";
timerText.text = $"{TimeRemaining:0.0}";
if (Time.unscaledTime >= hintUntil)
hintText.text = Score >= targetScore
? "All gems collected. Reach the goal!"
: $"Collect {targetScore} gems, then reach the goal.";
}
void OnDestroy()
{
// A reloaded scene establishes its own Ready pause in Awake.
// The old manager must not unpause that new scene during teardown.
if (!restarting) Time.timeScale = 1;
}
}
}
7주차 코드 전체 ZIP · 공통 패키지에는 이 파일이 이미 있습니다. 직접 준비할 때는 Assets/Week7/Scripts에 저장합니다. 아래 짧은 코드들은 전체 코드의 일부를 읽는 자료이므로 추가로 붙이지 않습니다.
HUDDisplay의 역할을 GameManager에 넘기기
Play를 끄고
HUDDisplay를 비활성화합니다.HudController와AddScoreButton도 계속 꺼 둡니다. 두 스크립트가 동시에 값을 쓰지 않게 하는 단계입니다. Hierarchy 최상위에 빈 GameManager를 만들고 Week07GameManager를 붙입니다.화면에서 확인GameManager는 Canvas나 비활성 Panel의 자식이 아닙니다.GameManager의 UI 참조와 Player 참조 연결하기
아래 표대로 Player 4개와 UI 8개, 총 12개 참조를 채웁니다. 같은 Player를 여러 칸에 넣어도 각 칸은 다른 컴포넌트를 가리킵니다. Target Score는
3, Round Duration은30으로 둡니다.화면에서 확인12개 칸에 None·Missing이 없습니다. 결과 화면 참조도 첫 실행 전에 채웁니다.
| Hierarchy에서 가져올 대상 | Inspector에서 넣을 칸 |
|---|---|
| Player | Player BodyRigidbody2D |
| Player | MovementPlayerController |
| Player | DashPlayerDash |
| Player / Visual | Animation DriverPlayerAnimation |
| Canvas / HUD | HudGameObject |
| Canvas / StartPanel | Start PanelGameObject |
| Canvas / ResultPanel | Result PanelGameObject |
| Canvas / HUD / ScoreText | Score TextTMP_Text |
| Canvas / HUD / TimerText | Timer TextTMP_Text |
| Canvas / HUD / HintText | Hint TextTMP_Text |
| Canvas / ResultPanel / ResultText | Result TextTMP_Text |
| Canvas / ResultPanel / ResultDetail | Result DetailTMP_Text |
Text에는 문구를 쓰고, 세 UI 부모에는 SetActive를 호출합니다. Movement·Dash·Animation Driver는 Ready와 결과 화면에서 입력 및 동작을 멈추기 위한 참조입니다. 참조 연결과 Button의 On Click 연결은 서로 별개입니다.
기존 레벨에서 무엇을 바꿀까요?
첫 Play 전에 아래 교체를 모두 마칩니다. 컴포넌트 메뉴의 Remove Component로 기존 스크립트를 제거하고 같은 오브젝트에 새 스크립트를 붙입니다. 오브젝트·Collider·Sprite와 원본 .cs 파일은 유지합니다. Prefab 인스턴스라면 변경을 원본에 Apply하지 않습니다.
| 대상 | 기존 처리 | 새 연결 |
|---|---|---|
| Player | PlayerProgress 제거 | PlayerController·PlayerDash와 Visual의 PlayerAnimation 유지 |
| 보석 3개 각각 | 기존 Collectible 제거 | Week07Collectible 추가 → Game Manager에 GameManager |
| 가시 구역 | 기존 HazardZone 제거 | Week07HazardZone 추가 → Game Manager에 GameManager |
| 오른쪽 목표 구역 | 기존 GoalZone 제거 | Week07GoalZone 추가 → Game Manager에 GameManager |
| HUDDisplay | 오브젝트 비활성화 | Text 참조는 Week07GameManager에 연결 |
수집 코드 · 가시 코드 · 목표 코드 · 패키지를 Import했다면 이미 준비되어 있습니다. 기존 스크립트를 체크만 끈 상태로 남기지 않습니다. Trigger 메시지는 비활성 MonoBehaviour에도 전달될 수 있어 중복 처리를 피하려면 컴포넌트를 제거해야 합니다.
보석·가시·목표 GameObject는 모두 활성화되어 있습니다. 각 오브젝트에는 새 Week07 컴포넌트 하나가 있고, 같은 GameManager를 참조합니다.
3-1 · 시작 전에는 입력과 시간을 함께 멈춥니다
시작 화면이 보이는 동안 캐릭터가 움직이거나 입력을 미리 쌓지 않게 합니다.
Unity가 GameManager의 Awake()를 호출하면 시작 위치·점수 0·시간 30을 준비하고 Ready로 정합니다. Awake는 실행 준비 함수이고, 화면의 Start 버튼이 호출할 StartRound()는 사용자의 시작 요청을 처리하는 함수입니다.
CurrentState = RoundState.Ready;
SetPlayerControl(false);
Time.timeScale = 0;
hud.SetActive(false);
startPanel.SetActive(true);
resultPanel.SetActive(false);
RefreshHud();Time.timeScale = 0은 물리와 게임 시간 진행을 멈추지만 모든 Update나 입력을 끄지는 않습니다. 그래서 SetPlayerControl(false)로 이동·대시·애니메이션 스크립트도 비활성화합니다. PlayerController와 PlayerDash의 OnDisable은 대기 중인 점프·대시 요청을 지웁니다. EventSystem은 켜 두어 UI 버튼을 누를 수 있게 합니다.
시작 화면에서 입력해 보기
Scene을 저장하고 Play합니다. Start를 누르기 전에 A/D·Space·Shift를 눌렀다가 놓습니다. 캐릭터 위치가 유지되는지 봅니다. HUD 아래 ScoreText·TimerText를 Inspector로 보면
0 / 3,30.0이어야 합니다.화면에서 확인Ready에서 위치와 시간이 바뀌지 않습니다. StartPanel만 보입니다.입력 스크립트의 활성 상태 보기
Play 중 Player의 PlayerController·PlayerDash와 Visual의 PlayerAnimation 체크가 꺼져 있는지 확인하고 Play를 끕니다. 직접 체크를 다시 켜지 않습니다. 실행 상태에 따라 GameManager가 조절합니다.
화면에서 확인화면을 가리는 처리, 게임 시간, 플레이 입력을 각각 제어한다는 차이가 보입니다.
Play → Awake → Ready는 준비입니다. Start 버튼 → StartRound → Playing이 실제 플레이의 시작입니다.
4 · Start를 누르고 HUD를 확인합니다
버튼 하나를 연결하고 곧바로 실행해 첫 화면 전환을 확인합니다.
편집 중에는 부모 오브젝트의 체크를 바꾸어 화면을 켜고 껐습니다. 코드에서는 SetActive(true)가 켜기, SetActive(false)가 끄기입니다. Start 버튼이 StartRound()를 호출하면 시작 화면을 숨기고 HUD를 보여 줍니다.
버튼 자식 Text에 “Start”라고 쓰는 일은 버튼의 이름표를 만드는 작업입니다. 실제 동작은 Button 컴포넌트의 On Click에 연결합니다. On Click은 클릭을 마쳤을 때 호출할 함수를 지정하는 자리입니다. 대상은 장면 속 GameManager, 실행할 함수는 StartRound입니다.
StartRound의 첫 부분은 현재 상태가 Ready인지 검사합니다. 준비 상태일 때만 시작을 받아들인 뒤 상태를 Playing으로 바꿉니다. 이어서 시간 배율을 1로 돌리고 HUD를 켭니다. 상태 변경 → 시간 진행 → 화면 전환이 한 함수 안에서 이어집니다.
public void StartRound()
{
if (CurrentState != RoundState.Ready) return;
CurrentState = RoundState.Playing;
startPanel.SetActive(false);
resultPanel.SetActive(false);
hud.SetActive(true);
Time.timeScale = 1;
SetPlayerControl(true);
RefreshHud();
}return;은 여기서 함수를 끝낸다는 뜻입니다. 이미 Playing인데 시작 요청이 다시 들어와도 아래 처리를 반복하지 않습니다. 점수와 시간은 앞의 ShowReady에서 준비했으므로, 이 단계에서는 준비한 한 판을 진행합니다.
StartButton의 On Click 연결하기
Hierarchy에서 StartPanel을 펼쳐 StartButton 자체를 선택합니다. 자식 Text (TMP)가 선택되면 Button 컴포넌트가 보이지 않습니다. Inspector의 Button 아래
On Click()에서 +를 눌러 이벤트 한 줄을 만듭니다.왼쪽 대상 칸에 Hierarchy의 GameManager 오브젝트를 드래그합니다. 그다음 오른쪽
No Function을 열어Week07GameManager → StartRound()를 선택합니다. Project의 .cs 파일을 넣지 않습니다.연결이 끝나면 대상과 함수가 같은 줄에 있어야 합니다. 함수 목록에 StartRound가 없다면 대상 오브젝트에 GameManager 컴포넌트가 붙어 있는지, 컴파일 오류가 없는지 확인합니다.
화면에서 확인StartButton의 On Click에 GameManager와 StartRound가 한 줄 연결되어 있습니다.Play에서 Start 눌러 보기
Scene을 저장하고 Play를 누릅니다. 시작 화면만 보이는 상태에서 Start를 누릅니다. 시작 화면이 사라지고 HUD의 점수
0 / 3과30에서 줄어드는 시간이 나타나는지 봅니다. 이동·점프·대시도 확인합니다. 시작 전에 눌렀다가 놓은 Space·Shift가 자동으로 실행되지 않아야 합니다.5초 정도 확인한 뒤 Editor의 Play를 끕니다. 지금은 시작 화면에서 플레이 화면으로 넘어가는 동작을 확인하면 됩니다.
화면에서 확인Start를 누르면 HUD가 보이고 시간이 줄어듭니다. Play를 끈 상태에서 다음 단계로 갑니다.
Start 버튼 → 시작 화면 숨기기 → HUD 표시와 시간 진행이 연결되었습니다. 이제 보석 접촉으로 점수를 올려 봅니다.
Start 버튼이 반응하지 않을 때
On Click의 대상과 함수, GameManager의 Player 4개·UI 8개 참조, Console의 첫 오류를 확인합니다. 1교시의 EventSystem과 InputSystemUIInputModule, Canvas의 Graphic Raycaster도 있어야 합니다. 입력 모듈의 Actions Asset과 Point·Left Click이 비어 있다면 모듈 메뉴의 Reset으로 기본 연결을 복원합니다.
게임 시간이 멈춰 있어도 Start를 누를 수 있는 이유
Time.timeScale은 게임 시간의 배율입니다. 0으로 두면 시간에 따라 움직이는 처리와 물리 진행을 멈출 수 있지만, 모든 코드나 입력이 비활성화되는 것은 아닙니다. 이번 EventSystem의 UI 버튼은 계속 클릭할 수 있습니다.
GameManager는 Update가 호출되어도 Playing이 아니면 바로 돌아갑니다. 시간 배율은 시간이 흐르는 속도를, 상태 검사는 이 동작을 처리할지를 정합니다. 화면을 덮는 것만으로도, 시간 배율만으로도 게임의 모든 규칙이 정해지지는 않습니다.
5 · 실제 보석 접촉을 HUD에 반영합니다
보석은 접촉을 알리고, GameManager는 현재 상태를 확인해 수집을 받아들입니다.
앞 단계에서 교체한 Week07Collectible의 호출을 따라갑니다. OnTriggerEnter2D는 Player 태그인지 확인한 뒤 gameManager.TryCollect()를 호출합니다. 반환값이 true일 때만 해당 보석을 숨깁니다.
Week07Collectible.cs 전체 코드
using UnityEngine;
namespace GameEngineI.Week07
{
public sealed class Week07Collectible : MonoBehaviour
{
[SerializeField] Week07GameManager gameManager;
bool collected;
void OnTriggerEnter2D(Collider2D other)
{
if (collected || !other.CompareTag("Player")) return;
if (!gameManager.TryCollect()) return;
collected = true;
gameObject.SetActive(false);
}
}
}
보석에서 GameManager 연결하기
세 보석을 차례로 선택해
Week07Collectible → Game Manager가 같은 Hierarchy의 GameManager를 가리키는지 확인합니다. 기존 Collider의 Is Trigger와 Player 태그, Rigidbody2D는 유지합니다. 보석의 위치와 모양도 5주차 그대로입니다.화면에서 확인세 보석 모두 참조가 채워져 있으며 기존 Collectible은 남아 있지 않습니다.한 개, 세 개 순서로 수집하기
Play → Start 후 한 개를 모으면
1 / 3이고 그 보석만 사라져야 합니다. 나머지 두 개도 모읍니다. 세 번째 보석을 먹은 직후에는 HUD가 유지되고 시간이 계속 줄어야 합니다. 하단 안내는All gems collected. Reach the goal!로 바뀝니다.화면에서 확인수집 완료는 목표에 갈 준비가 된 상태입니다. 아직 Success가 아닙니다.
if (CurrentState != RoundState.Playing || Score >= targetScore) return false;
Score++;
hintUntil = 0;
RefreshHud();
return true;collected는 한 보석의 중복 처리를 막고, CurrentState 검사는 플레이 중인 수집만 허용합니다. 수집 함수가 곧바로 Won으로 바꾸지 않으므로 원래 레벨의 목표 지점이 계속 필요합니다.
보석 → TryCollect → Score 증가 → RefreshHud → 해당 보석 숨기기 순서입니다.
5-1 · 가시는 복귀, 목표 지점은 성공 여부를 확인합니다
같은 Trigger 접촉이어도 호출할 함수와 유지할 값이 다릅니다.
가시에 닿았을 때: 진행을 유지하며 시작점으로
Week07HazardZone은 Player 접촉을 Respawn()에 알립니다. GameManager는 입력을 잠시 끄고 속도를 지운 뒤 저장된 시작 위치로 옮깁니다. 그다음 입력을 다시 켭니다. Score와 TimeRemaining은 초기화하지 않습니다. 이미 수집한 보석도 숨겨진 채 유지합니다.
Week07HazardZone.cs 전체 코드
using UnityEngine;
namespace GameEngineI.Week07
{
public sealed class Week07HazardZone : MonoBehaviour
{
[SerializeField] Week07GameManager gameManager;
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player")) gameManager.Respawn();
}
}
}
보석 하나를 모은 뒤 가시에 닿기
Start 후 한 개를 모아 1 / 3을 확인하고 가시에 닿습니다. 복귀 안내가 보이는지, 출발 위치에서 다시 움직일 수 있는지 확인합니다.
화면에서 확인위치만 돌아갑니다. 수집 수는 1 / 3이고 시간은 줄어든 값에서 계속 갑니다.
목표에 닿았을 때: 수집 조건을 먼저 검사
Week07GoalZone은 오른쪽 목표 구역에서 TryGoal()을 호출합니다. 보석이 부족하면 안내만 보여 주고 Playing을 유지합니다. 모두 모았다면 Finish(RoundState.Won)을 호출합니다.
Week07GoalZone.cs 전체 코드
using UnityEngine;
namespace GameEngineI.Week07
{
public sealed class Week07GoalZone : MonoBehaviour
{
[SerializeField] Week07GameManager gameManager;
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player")) gameManager.TryGoal();
}
void OnTriggerStay2D(Collider2D other)
{
if (gameManager.Score >= gameManager.TargetScore && other.CompareTag("Player"))
gameManager.TryGoal();
}
}
}
OnTriggerEnter2D는 들어오는 순간을 확인합니다. OnTriggerStay2D는 목표 안에 머물러 있는 동안 수집 조건이 충족된 경우도 처리합니다. 결과 상태가 된 뒤에는 GameManager의 상태 검사로 반복 처리를 막습니다.
보석을 덜 모은 채 목표에 가 보기
새로 Play → Start 후 일부 보석을 남기고 오른쪽 목표 구역에 닿습니다. 안내 문구를 확인한 뒤 계속 이동합니다.
화면에서 확인부족한 보석을 모으라는 안내가 나오고 HUD·이동·타이머가 유지됩니다.세 개를 모은 뒤 목표에 도착하기
보석 3 / 3을 만든 뒤 목표 구역에 닿습니다. 이번에는 ResultPanel의 Success가 나타나는지 봅니다.
화면에서 확인보석 수집 완료 + 목표 도착 두 조건이 함께 충족되어 Won이 됩니다.
| 동작 | 위치 | 점수·보석 | 시간·상태 |
|---|---|---|---|
| 가시 → Respawn | 시작점 | 유지 | 남은 시간 유지 · Playing |
| 결과 버튼 → RestartScene | 시작점 | 0 / 3 · 보석 3개 복원 | 30.0 · Ready |
가시가 결과 화면을 열거나, 세 번째 보석만으로 성공하면 연결을 다시 확인합니다.
5-2 · 성공과 시간 종료를 같은 결과 화면에 표시합니다
끝나는 이유는 둘이지만, 결과를 보여 주고 다시 시작할 행동을 안내하는 자리는 하나입니다.
목표의 TryGoal은 Finish(RoundState.Won), 시간이 0이 된 Update는 Finish(RoundState.Lost)를 호출합니다. Finish는 Playing일 때만 한 번 처리하며, 입력·시간을 멈추고 HUD 대신 ResultPanel을 보여 줍니다.
if (CurrentState != RoundState.Playing) return;
CurrentState = state;
SetPlayerControl(false);
Time.timeScale = 0;
hud.SetActive(false);
startPanel.SetActive(false);
resultPanel.SetActive(true);
resultText.text = state == RoundState.Won ? "Success" : "Time Up";ResultText에는 결과 제목을, ResultDetail에는 “모든 보석을 모아 목표에 도착했다” 또는 “경로를 찾아 다시 시도하라”는 설명을 씁니다. 제목과 설명은 별도 Text이므로 같은 칸에 연결하지 않습니다.
성공 후 입력이 멈추는지 확인하기
보석 세 개를 모아 목표에 도착합니다. Success 상태에서 A/D·Space·Shift를 눌러도 캐릭터가 움직이지 않아야 합니다. 숨겨진 HUD의 시간도 더 줄지 않는지 봅니다.
화면에서 확인결과 문구와 Restart 버튼이 보이고 한 판의 진행은 멈춥니다.시간 종료도 확인하기
Play를 끄고 Round Duration을 잠시 5로 바꾼 뒤 실행합니다. Start 후 목표를 완료하지 않고 기다립니다. Time Up을 확인하고 Play를 끈 뒤 30으로 복원합니다.
화면에서 확인같은 ResultPanel에서 결과 제목과 설명이 달라집니다.

보석을 모두 모았어도 목표에 도착하기 전에 시간이 끝나면 Lost입니다.
6 · Restart로 처음부터 다시 시작합니다
재시작이 필요한 순간에 Scene List와 Scene 다시 불러오기를 연결합니다.
지금까지는 Editor의 Play를 껐다 켜서 처음으로 돌아왔습니다. 완성한 게임에서는 Restart 버튼으로 저장한 Scene을 다시 열어 같은 일을 합니다. 그러면 숨겨진 보석과 점수·시간이 초기 상태로 돌아오고 Start를 다시 기다립니다.
StartPanel만 다시 켜면 화면은 처음처럼 보일 수 있습니다. 하지만 Player는 도착한 위치에 남고, 모은 보석은 계속 꺼져 있으며, 각 보석의 collected 값도 true입니다. 가시 복귀도 이 진행 상태를 유지하므로 전체 재시작을 대신할 수 없습니다. 점수만 0으로 바꾸어도 이 문제는 남습니다. 재시작은 화면과 함께 한 판의 진행 내용을 초기화하는 작업입니다.
이번에는 저장한 Scene을 다시 불러오는 방법을 사용합니다. Player와 보석은 저장된 배치로 새로 준비되고, 새 GameManager의 Awake가 점수·시간·화면을 초기화합니다. 장면의 배치를 복원하는 일과 초기 값을 쓰는 일이 이어져 처음 상태가 됩니다.
RestartScene()은 게임 시간 배율을 복원한 뒤 현재 Scene을 다시 불러옵니다. 이 코드가 현재 Scene의 경로를 사용하며 빌드에 포함된 Scene을 불러오므로 먼저 Scene List에 현재 장면을 등록합니다.
restarting = true;
Time.timeScale = 1;
SceneManager.LoadScene(SceneManager.GetActiveScene().path);GetActiveScene().path는 현재 씬의 경로입니다. 그 경로로 저장된 장면을 다시 불러오므로 이름이 같은 다른 씬과 혼동하지 않습니다. Project에 파일이 있는 것과 활성 빌드의 Scene List에 포함한 것은 구분합니다.
재시작 과정에서 시간 배율을 1로 돌려도 곧바로 플레이가 시작되지는 않습니다. 새 장면의 Awake가 다시 배율을 0으로 두고 StartPanel을 보여 줍니다. 사용자가 Start를 눌러야 Playing과 배율 1로 넘어갑니다.
저장한 Scene을 Scene List에 등록하기
Play를 끄고 Scene을 저장합니다.
File → Build Profiles를 열고 현재 데스크톱 플랫폼의Scene List에서Add Open Scenes를 누릅니다. Week07_MicroLevel이 목록에 있고 체크되어 있는지 확인합니다.별도 빌드 프로파일을 사용하고 Scene List가 안 보이면
Add Settings → Scene List로 추가해 그 목록에도 현재 Scene을 포함합니다. 여기서는 재시작에 필요한 등록을 하고, 실행 파일을 만드는 과정은 3교시에서 이어갑니다.화면에서 확인실제로 사용하는 Scene List에 Week07_MicroLevel이 포함되어 있습니다.RestartButton의 On Click 연결하기
RestartButton의 Button 컴포넌트에서
On Click()의 +를 누릅니다. 대상에 Hierarchy의 GameManager를 넣고Week07GameManager → RestartScene()을 선택합니다. 연결한 뒤 Scene을 저장합니다.이 버튼의 함수 칸에는 StartRound가 아닌 RestartScene이 표시되어야 합니다. 버튼 글자만 Restart로 바꾸어도 함수 연결은 자동으로 바뀌지 않습니다.
화면에서 확인RestartButton에는 RestartScene, StartButton에는 앞서 연결한 StartRound가 있습니다.게임 안에서 처음으로 돌아가기
Play → Start를 누르고 보석 세 개를 모아 오른쪽 목표 지점에 도착합니다. Success에서 Restart를 누르면 시작 화면으로 돌아와야 합니다. 다시 Start를 눌러 점수
0 / 3, 초기 시간30, 보석 세 개를 확인합니다.이번에는 수집하지 않고 기다립니다. Time Up에서도 Restart를 눌러 시작 화면으로 돌아오는지 확인합니다.
화면에서 확인Editor의 Play를 끄지 않고 성공과 시간 종료에서 모두 다시 시작할 수 있습니다.
Start → Won → Restart → Start → Lost → Restart가 이어집니다. 시작·플레이·결과·재시작 UI가 연결되었습니다.
7 · 같은 레벨에서 전체 흐름을 확인합니다
UI만 확인하지 않고, 원래의 수집·복귀·목표 규칙까지 한 판으로 이어 봅니다.
| 순서 | 기대 결과 |
|---|---|
| Ready에서 이동·점프·대시 입력 후 놓기 | 위치·시간 유지. Start 후 미리 누른 입력이 실행되지 않음. |
| Start 후 보석 하나 수집 | HUD 표시, 1 / 3, 해당 보석만 사라짐. |
| 가시 접촉 | 시작점 복귀, 1 / 3 유지, 시간 계속 감소. |
| 보석이 부족한 채 목표 접촉 | 안내 표시, Playing 유지. |
| 3 / 3 달성 | HUD와 시간 유지, 목표에 도착하라는 안내. |
| 이후 목표 접촉 | Success, 입력·시간 정지. |
| Restart → Start → 시간 종료 → Restart | 각 결과에서 Ready로 복귀, 위치·점수·시간·보석 모두 초기화. |
Console의 빨간 오류가 없고 두 종류의 결과에서 재시작할 수 있어야 합니다.
설정 하나를 바꾸고 결과를 예측합니다
기본 동작을 완성했다면 아래 중 하나를 골라 비교합니다. 바꾸기 전, 언제 어떤 화면이 나올지 먼저 말해 봅니다.
UI 표현과 게임 규칙 중 하나를 골라 바꿉니다. 배경은 읽기 쉬운 정도를, 목표와 제한 시간은 결과가 나오는 조건을 바꿉니다. 비교 뒤에는 최종 배치와 기본 규칙으로 복원해 저장합니다.
UI 비교 · 창형 패널이 옅어지면 제목은 어떻게 보일까요?
Play를 끄고 StartPanel 아래 ContentPanel의 Image Color에서 Alpha 값을 기록합니다. 전체 화면 덮개와 제목은 그대로 두고, 창형 패널의 Alpha만 낮춰 뒤의 게임 장면이 더 많이 비치게 합니다. 밝은 지형과 보석이 비치는 자리에서도 제목과 버튼을 읽을 수 있는지 비교합니다.
배경을 잘 보여 주는 것과 안내를 잘 읽게 하는 것 사이에서 적절한 값을 고릅니다. 부모 오브젝트를 끄거나 제목의 Alpha까지 바꾸지 않습니다. 비교 후 기록한 값 또는 더 읽기 쉬웠던 값으로 정리하고, ResultPanel 아래 ContentPanel에도 같은 기준을 적용해 저장합니다.
비교 A · 보석은 세 개 그대로 두고 목표만 2로 바꾸면?
Play를 끄고 GameManager의 Target Score를 2로 바꿔 Scene을 저장합니다. Play → Start를 누르면 점수 표시는 0 / 2에서 시작합니다. 두 개를 모아도 Playing이 유지됩니다. 그 뒤 목표 지점에 도착하면 세 번째 보석이 남아 있어도 Success가 나와야 합니다.
성공 조건은 Target Score 충족과 목표 지점 도착입니다. 목표와 결과 안내의 숫자는 코드가 Target Score에서 읽습니다. Restart로 돌아온 뒤에도 보석 세 개가 복원되는지 확인합니다.
StartTitle의 Collect 3 Gems는 직접 적은 문구라서 목표를 바꾸어도 자동으로 갱신되지 않습니다. 실제 게임의 목표를 바꿀 때는 안내 문구도 함께 맞춰야 합니다. 이 비교를 마치면 Play를 끄고 Target Score를 3으로 복원해 저장합니다.
비교 B · 제한 시간을 5초로 바꾸고 Start 전에 기다리면?
Play를 끄고 Round Duration을 5로 바꿔 Scene을 저장합니다. Play를 누른 뒤 Start 화면에서 먼저 5초 이상 기다립니다. 이 기다림 때문에 Time Up이 나오면 안 됩니다. Start를 누른 다음부터 시간이 줄어들고, 수집하지 않으면 약 5초 뒤 결과 화면이 나타납니다.
제한 시간을 세는 구간은 Playing입니다. 비교 후 Play를 끄고 Round Duration을 30으로 복원해 저장합니다. 다음 교시 빌드에는 기본 설정을 사용합니다.
“어떤 사건이 어느 함수를 호출했고, 그 함수가 어떤 값과 화면을 바꾸었는가?”를 Start, 수집, Restart 중 하나로 설명해 봅니다. 이 연결을 따라가면 동작이 멈춘 지점도 좁혀 볼 수 있습니다.
화면 흐름을 한 번 더 짚어봅니다
필요하면 아래 모형에서 수집·목표 도착·가시 복귀의 차이를 눌러 확인합니다.
시작·성공·실패·재시작 모형 펼치기
네 상태는 어떤 화면을 보여 줄지 구분하는 이름입니다. 아래 점수와 시간은 내부 값이며, 실제 Unity의 HUD는 Playing에서만 보입니다. 5초 경과는 남은 시간을 줄이고, Time Out은 시간 종료를 즉시 확인합니다. 자동으로 흐르는 타이머가 아니라 사건별 상태를 확인하는 모형입니다.
상태 전환을 직접 확인하기
현재 상태에서 허용된 사건만 누를 수 있습니다. 세 번 수집한 뒤 Reach Goal을 눌러야 Won입니다. 가시 복귀는 점수와 시간을 유지합니다. 목표 도착 전에 시간이 끝나면 Lost입니다.
시작 화면을 보여 주고 게임 시간은 멈춰 있습니다.
- score
- 0 / 3
- time
- 30.0초
- state
- Ready
현재 Ready입니다. 다음에 허용될 사건을 예측하세요.
같은 레벨을 빌드할 준비가 되었습니다
설정을 복원하고 최종 작업 씬을 저장합니다.
- GameManager의 Target Score 3, Round Duration 30과 Player 4개·UI 8개 참조를 확인합니다.
- HudController·AddScoreButton·HUDDisplay는 비활성화되어 있습니다. 기존 PlayerProgress·Collectible·HazardZone·GoalZone은 작업 씬에서 제거했고 새 Week07 컴포넌트가 연결되어 있습니다.
- StartButton은 Week07GameManager.StartRound, RestartButton은 Week07GameManager.RestartScene을 한 번씩 호출합니다.
- 기존 지형·보석 위치·가시·목표·다섯 애니메이션을 유지합니다. 16:9와 4:3에서 UI와 목표 안내를 확인합니다.
- StartPanel만 켜고 HUD·ResultPanel은 끈 상태로
Assets/Week7/Week07_MicroLevel.unity를 저장합니다.
보석에 닿아도 점수가 오르지 않습니다
Start를 눌러 Playing인지, Player 태그와 보석의 Is Trigger가 유지되어 있는지 확인합니다. Week07Collectible의 Game Manager 참조를 채우고 기존 Collectible이 함께 남아 있지 않은지 봅니다. Console에 오류가 있다면 첫 오류의 오브젝트부터 확인합니다.
3 / 3인데 결과가 나오지 않습니다
보석을 모두 모은 것만으로는 정상적으로 Playing이 유지됩니다. 오른쪽 목표 구역에 도착해야 합니다. 그래도 바뀌지 않으면 GoalZone의 기존 Collider와 Week07GoalZone → Game Manager 참조를 확인합니다.
Ready에서 움직이거나 Start 직후 점프합니다
GameManager의 Movement·Dash·Animation Driver가 실제 Player의 컴포넌트를 참조하는지 확인합니다. PlayerController·PlayerDash의 OnDisable에서 대기 입력을 초기화하는 공통 코드가 적용되어 있어야 합니다. 시간 배율만 0으로 두는 것으로는 입력 요청을 지우지 못합니다.
Restart가 처음 상태로 돌아오지 않습니다
활성 Scene List에 저장한 Week07_MicroLevel이 포함되어 있는지 확인합니다. RestartButton의 On Click은 Week07GameManager.RestartScene 한 줄이어야 합니다. 새 장면의 Awake가 Ready를 준비하므로 이전 GameManager를 DontDestroyOnLoad로 남겨 두지 않습니다.
완성 참고는 공통 패키지의 Reference/Week07_Complete.unity입니다. 비교할 때도 씬 하나만 열어 GameManager와 EventSystem이 중복되지 않게 합니다.
보석 3개 → 목표 도착 → 성공, 가시 → 진행 유지 복귀, Restart → 전체 초기화가 모두 작동합니다.
Unity 공식 자료
화면 표시와 버튼 연결, 재시작 동작을 확인할 때 참고합니다.
- Transition Options ↗Button의 포인터·누름 상태와 Sprite Swap
- GameObject.SetActive ↗Unity 6.6 · UI 부모 오브젝트 활성화
- Button ↗uGUI 2.0 · On Click에 함수 연결
- 게임 내 시간 및 실시간 ↗Time.timeScale로 게임 시간 조절
- SceneManager.LoadScene ↗저장한 Scene 다시 불러오기
- 빌드의 씬 관리 ↗재시작할 Scene을 목록에 포함
- Input System의 UI 입력 ↗EventSystem의 입력 연결