관리자 인증 확인 중...
B 또는 Space 로 복귀
LUNA CODE LAB · CLASS I Week 6 — 적 패턴과 점수 시스템 초4 · 중3 / 90분
1 / 9 📄 워크시트
WEEK 06 · 2026

맞히면 죽는다
점수가 쌓인다

지금까지 미사일과 적이 따로 놀았다. 이번 주는 둘이 만나는 순간 — 충돌 감지(AABB). 적 3종류 등장, 미사일 맞으면 적이 죽고 점수 +. 비행기에도 HP 바가 생깁니다.

Duration90 minutes
ToolClaude Code
Output충돌 + 점수 + HP
Phase비행기 게임 (3/4)
02 / Learning Goals⏱ 5분

오늘의 학습 목표

1
적 3종류 (빠른/느린/지그재그)
type 속성으로 적을 분류 — 빠른 적·느린 적·지그재그로 움직이는 적. switch문 첫 등장.
2
충돌 감지 (AABB)
미사일이 적에 닿았는지 어떻게 알까? 사각형 4개 좌표만 비교하면 끝.
3
점수 시스템
적 종류에 따라 점수 다르게 (10/20/50점). 화면 위에 큰 글씨로 표시.
4
HP 시스템 + 게임오버
비행기 HP 3개로 시작. 적과 부딪히면 -1. 0이 되면 게임오버 화면.
03 / Concept · AABB⏱ 10분

충돌 감지 = 사각형 4개 비교

게임의 가장 큰 비밀 — 두 사각형이 겹쳤는지 어떻게 알까? 답은 단순해. 한쪽이 다른 쪽보다 완전히 왼쪽·오른쪽·위·아래에 있으면 안 겹친 것. 그 반대만 충돌.

미사일
x, y, w, h
(왼쪽위 + 크기)
vs
x, y, w, h
(왼쪽위 + 크기)
💥
충돌?
4개 조건
모두 만족 시 YES
AABB 공식 (외울 필요 X, 한번 보면 끝)
a.x < b.x+b.w && a.x+a.w > b.x && a.y < b.y+b.h && a.y+a.h > b.y
네 조건 모두 true 면 두 사각형이 겹친 것. 마리오·앵그리버드·서바이버 다 이 공식.
04 / Enemy Design⏱ 10분

적 3종류 — type로 분류

SLOW · 10점
🟢 초록 큰 사각형
(40x40px)

속도: 1
점수: 10점
크기 커서 잘 맞음
FAST · 20점
🟡 노란 작은 사각형
(25x25px)

속도: 4
점수: 20점
빨라서 어려움
ZIGZAG · 50점
🔴 빨간 작은 사각형
(30x30px)

좌우 sin 흔들림
점수: 50점
가장 까다로움
💡 핵심: 적 객체에 type: 'slow' | 'fast' | 'zigzag' 속성을 추가하고, 매 프레임 switch로 분기하면 끝. 모든 게임 디자인의 시작은 "다른 타입에 다른 행동".
05 / Live Build · Step 1⏱ 12분

Step 1 — 적 3종류 구현

지난주 단일 적 코드를 3종류로 분기. 스폰 시 랜덤하게 골라서 다른 색·속도·움직임.

1
Claude에게 보낼 프롬프트
"지난주 적 스폰 코드를 3종류로 분기해줘. - slow: 초록색, 40x40, 속도 1, 직선 하강 - fast: 노란색, 25x25, 속도 4, 직선 하강 - zigzag: 빨간색, 30x30, 속도 2, x가 sin(y*0.05)*60으로 좌우 흔들림 - 스폰할 때 랜덤하게 셋 중 하나 선택 - 각 적에 hp, points, type 속성 추가"
핵심 코드 — type 분기
const ENEMY_TYPES = { slow: { w: 40, h: 40, speed: 1, points: 10, hp: 1, color: '#4A7A68' }, fast: { w: 25, h: 25, speed: 4, points: 20, hp: 1, color: '#C4935A' }, zigzag: { w: 30, h: 30, speed: 2, points: 50, hp: 1, color: '#B84A4A' } }; function spawnEnemy() { const types = ['slow', 'fast', 'zigzag']; const t = types[Math.floor(Math.random() * 3)]; enemies.push({ ...ENEMY_TYPES[t], type: t, x0: Math.random()*canvas.width, x: 0, y: -40 }); } // 매 프레임 — type별 다른 움직임 enemies.forEach(en => { en.y += en.speed; if (en.type === 'zigzag') en.x = en.x0 + Math.sin(en.y * 0.05) * 60; else en.x = en.x0; });
🎙
객체 안에 객체 "ENEMY_TYPES는 '적 종류 설명서'예요. 새 타입 추가하고 싶으면 여기에 한 줄만 더하면 돼. 보스도, UFO도, 미니언도 다 이렇게 만드는 거예요."
05 / Live Build · Step 2⏱ 15분

Step 2 — 충돌 감지 함수

미사일 모두 × 적 모두 → 이중 forEach로 비교. AABB 공식 한 번 짜면 평생 쓴다.

2
Claude에게 보낼 프롬프트
"미사일이 적에 닿으면 둘 다 사라지게 해줘. - 매 프레임 missiles × enemies 이중 루프 - AABB 공식으로 충돌 확인 - 충돌하면 둘 다 .dead = true로 표시 - 매 프레임 마지막에 dead가 true인 것 filter로 제거 - 충돌 함수는 따로 isColliding(a, b)로 빼서 재사용 가능하게"
핵심 코드 — AABB 함수
function isColliding(a, b) { return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y; } // 매 프레임 — 모든 미사일 × 모든 적 missiles.forEach(m => { enemies.forEach(en => { if (isColliding(m, en)) { m.dead = true; en.dead = true; } }); }); // 죽은 것들 제거 missiles = missiles.filter(m => !m.dead && m.y > 0); enemies = enemies.filter(en => !en.dead && en.y < canvas.height);
🎙
왜 따로 함수로 빼나? "isColliding 함수는 이제 비행기 vs 적 충돌에도 쓸 거예요. 한 번 만들어두면 모든 충돌에 다 써먹어요. 이게 함수의 존재 이유 — 재사용."
05 / Live Build · Step 3⏱ 12분

Step 3 — 점수 시스템 + UI

적 죽일 때마다 점수 +. 화면 좌상단에 큰 글씨로 표시. 적 종류별로 점수 다르게.

3
Claude에게 보낼 프롬프트
"점수 시스템 추가해줘. - let score = 0 전역 변수 - 충돌 시 score += enemy.points (slow 10, fast 20, zigzag 50) - 화면 좌상단에 큰 글씨(36px)로 'SCORE 0' 표시 - 폰트는 픽셀 게임 느낌나는 monospace 굵게 - 최고점도 함께 표시 (localStorage 사용)"
핵심 코드 — 점수 + 그리기
let score = 0; let hiScore = parseInt(localStorage.getItem('hiScore') || '0'); // 충돌 시 if (isColliding(m, en)) { score += en.points; // 적 종류별 점수 if (score > hiScore) { hiScore = score; localStorage.setItem('hiScore', score); } m.dead = true; en.dead = true; } // 매 프레임 그리기 (canvas) ctx.fillStyle = '#3D5A80'; ctx.font = 'bold 36px monospace'; ctx.fillText(`SCORE ${score}`, 20, 50); ctx.font = '14px monospace'; ctx.fillText(`BEST ${hiScore}`, 20, 75);
🎙
localStorage "localStorage는 브라우저에 저장하는 작은 메모장. 여기 적어두면 페이지 새로고침해도 안 사라져요. 그래서 최고점이 평생 남아요."
05 / Live Build · Step 4⏱ 12분

Step 4 — 비행기 HP 바

적과 비행기가 부딪히면 HP -1. HP는 변수, 바는 fillRect 2개로 그린다. 검정 배경 + 빨간 채움.

4
Claude에게 보낼 프롬프트
"비행기 HP 시스템 추가해줘. - let hp = 3, maxHp = 3 전역 - 매 프레임 비행기 × 적 충돌 검사 → 충돌 시 hp--, 적 사라짐 - 화면 우상단에 HP 바 그리기 (200x20px) · 배경: 어두운 회색 · 채움: 현재 HP에 비례한 빨간 사각형 · 텍스트: HP 3/3 - 무적 시간 1초 — 한번 맞으면 깜빡거리며 무적"
핵심 코드 — HP + 무적 + UI
let hp = 3, maxHp = 3, invincibleUntil = 0; // 매 프레임 충돌 검사 enemies.forEach(en => { if (isColliding(plane, en) && Date.now() > invincibleUntil) { hp--; en.dead = true; invincibleUntil = Date.now() + 1000; // 1초 무적 } }); // 무적이면 깜빡임 (50% 시간만 그림) const blink = Date.now() < invincibleUntil && Math.floor(Date.now()/100) % 2; if (!blink) drawPlane(); // HP 바 그리기 우상단 const barX = canvas.width - 220, barY = 20; ctx.fillStyle = '#333'; ctx.fillRect(barX, barY, 200, 20); // 배경 ctx.fillStyle = '#B84A4A'; ctx.fillRect(barX, barY, 200 * (hp/maxHp), 20); // 비율 채움 ctx.fillStyle = '#fff'; ctx.font = 'bold 12px monospace'; ctx.fillText(`HP ${hp}/${maxHp}`, barX + 80, barY + 14);
🎙
무적 시간이 왜 필요한가 "무적 안 만들면 적 한 마리에 닿는 순간 HP 3이 한 프레임에 다 까져요. 0.016초 만에 게임 끝. 모든 액션 게임이 'i-frame'이라는 무적 시간을 둬요."
05 / Live Build · Step 5⏱ 9분

Step 5 — 게임오버 화면

HP가 0이 되는 순간 — 전체 화면 어둡게, 가운데에 "GAME OVER" + 최종 점수 + 재시작 버튼.

5
Claude에게 보낼 프롬프트
"게임오버 시스템 추가해줘. - hp가 0 이하가 되면 gameOver = true - gameOver이면 게임 루프 멈춤 (update/spawn 안 함) - 화면 위에 반투명 검정 오버레이 - 가운데에 'GAME OVER' (대형 빨간 글씨) - 그 밑에 'SCORE: 1240 / BEST: 2100' - '스페이스를 누르면 재시작' 안내 - 스페이스 누르면 score=0, hp=3, 배열 비우고 다시 시작"
핵심 코드 — 게임오버 + 재시작
let gameOver = false; function update() { if (hp <= 0) gameOver = true; if (gameOver) return; // 게임 멈춤 // ... 평소 게임 로직 } function draw() { // ... 평소 그리기 if (gameOver) { ctx.fillStyle = 'rgba(0,0,0,0.75)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#B84A4A'; ctx.font = 'bold 72px monospace'; ctx.textAlign = 'center'; ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 40); ctx.fillStyle = '#fff'; ctx.font = '20px monospace'; ctx.fillText(`SCORE: ${score} / BEST: ${hiScore}`, canvas.width/2, canvas.height/2 + 20); ctx.fillText('Press SPACE to restart', canvas.width/2, canvas.height/2 + 60); } } // 재시작 키 document.addEventListener('keydown', e => { if (gameOver && e.code === 'Space') { score = 0; hp = 3; missiles = []; enemies = []; gameOver = false; } });
🎙
오늘의 마무리 "이제 진짜 게임이 됐어요. 점수가 있고, HP가 있고, 죽으면 다시 시작. 다음 주는 마지막 — 보스전과 BGM. 시간을 음악과 함께 마무리해요."
10 / Checkpoint⏱ 5분

여기까지 됐나? 확인하기

잘 됐다면
  • 3종류 (초록·노랑·빨강) 다 등장
  • 빨간 적은 좌우로 흔들면서 내려옴
  • 미사일 맞으면 적이 사라지고 점수 +
  • 비행기 HP 바 우상단 표시
  • HP 0이 되면 GAME OVER + 재시작 가능
⚠️자주 막히는 부분
  • 미사일 통과 → isColliding 4개 조건 부등호 방향 확인
  • 지그재그가 직선 → Math.sin(en.y * 0.05) 빠짐
  • HP 한순간에 0 → invincibleUntil 무적시간 빠짐
  • 죽어도 계속 진행 → update에 if(gameOver) return 빠짐
  • 재시작 안 됨 → missiles=[], enemies=[] 초기화 빠짐
11 / Challenge⏱ 10분

더 해보고 싶다면 도전

💥
충돌 시 파티클 폭발 효과 — 작은 점들이 사방으로 튀게. 0.5초 후 사라짐.
난이도 ★
🛡
적이 2발 맞아야 죽는 종류 추가 — type "tank", HP 2, 노란 큰 사각형, 점수 100점.
난이도 ★★
점수에 따라 비행기 모양 변신 — 100점에 노란 테두리, 500점에 무지개 테두리.
난이도 ★★★
12 / Student Gallery⏱ 7분

작년 친구들 작품

13 / Next Week⏱ 3분

다음 시간 예고

WEEK 07 · COMING UP

보스전과 게임 BGM —
비행기 게임 완성

드디어 마지막 — 100점 도달하면 보스 등장. 보스는 좌우로 움직이면서 탄막을 쏘고, HP 바도 있습니다. 그리고 Suno로 8비트 BGM을 직접 만들어 게임에 깐다. 4주짜리 작품 완성.

PREPARE · 준비물
  • 이번 주 완성한 게임 (충돌·점수·HP·게임오버까지)
  • 좋아하는 게임 보스전 영상 1분 (참고용)
  • "어떤 분위기 BGM 깔고 싶은지" 한 단어 (긴장? 신남? 잔잔?)