에피소드 05 (PLAY) 구름이 다가온다
에피소드 05 (PLAY) — 구름이 다가온다
“어… 얘들아. 우리 하늘, 너무 깨끗한 거 아니야?”
리온의 말에 세이와 딕이 동시에 모니터를 바라봤다. 캔버스 위 파란 하늘, 그 안에 코러호가 한 대. 위아래로, 왼쪽 오른쪽으로 마음대로 움직이긴 하는데…
딕이 고개를 갸웃했다. “응? 멋있긴 한데… 뭐랄까… 심심한데?” 세이가 바로 받아쳤다. “그냥 자유 비행 모드지. 게임 같진 않아.”
나는 의자를 빙그르르 돌리며 말했다. “맞아. 우리 이제부터는 ‘장애물’을 만들어야 해. 그냥 하늘이 아니라, 날아가야 할 이유가 있는 하늘 말이야.”
딕의 눈이 반짝였다. “장애물? 그럼 구름 몬스터부터 가야지!” 세이가 웃으면서도 손을 들었다. “일단 몬스터 말고, 진짜 ‘구름’부터. 그래야 나중에 몬스터가 나와도 더 설득력 있지 않겠어?”
나는 노트에 크게 적었다. 오늘의 미션: 코러호 앞길에 구름을 띄워라.
1. MAKE — 하늘 위에 떠 있는 ‘구름 조각’ 만들기
“일단 구름도 비행기처럼 좌표가 있는 물체야.” 세이가 태블릿에 간단한 그림을 그렸다. 네모 하늘, 그 안에 동그란 구름 몇 개. 각 구름 옆에는 (x, y) 숫자들이 붙어 있었다.
“코러호도 x, y가 있잖아? 구름도 x, y를 주면, ‘어디에 떠 있는지’ 우리가 알 수 있어. 그리고 나중에… ‘부딪혔는지’도 알 수 있고.”
딕이 고개를 끄덕였다. “그러니까… 구름도 캐릭터처럼 다루는 거네?” “맞아.” 내가 말했다. “다만 비행기는 우리가 조종하고, 구름은 천천히 다가오거나 흘러가는 역할인 거지.”
나는 코드 편집기를 열고, 천천히 타이핑을 시작했다.
// 구름 여러 개를 담을 배열
const clouds = [];
// 구름 하나를 만드는 함수
function createCloud(x, y, speed) {
return {
x: x,
y: y,
w: 60,
h: 30,
speed: speed
};
}
// 처음에 구름 몇 개 만들기
clouds.push(createCloud(420, 80, 2));
clouds.push(createCloud(520, 140, 1.5));
clouds.push(createCloud(600, 40, 1.8));
“잠깐만.” 세이가 손가락을 들었다. “구름 가로 크기 w, 세로 h를 같이 넣어준 건 좋다. 나중에 충돌 계산할 때 써먹기 딱 좋겠어.”
딕이 화면에 코러호와 구름을 번갈아 보면서 말했다. “근데 저렇게 오른쪽에서만 나오면 좀… 심심하지 않아? 랜덤하게도 나왔으면 좋겠다. 어떤 건 높이 떠 있고, 어떤 건 아래로 내려와 있고.”
나는 웃으며 말했다. “당연히 랜덤 써야지. 우리 이제부터 하늘의 기분을 만들 거니까.”
// 랜덤 구름 생성
function spawnRandomCloud() {
const x = canvas.width + Math.random() * 200; // 화면 오른쪽 밖
const y = Math.random() * (canvas.height - 60); // 랜덤 높이
const speed = 1 + Math.random() * 2; // 1 ~ 3 사이 속도
clouds.push(createCloud(x, y, speed));
}
“이제 가끔씩 spawnRandomCloud()를 불러주면,
하늘 어딘가에서 천천히 구름이 모습을 드러내겠지.”
나는 말하면서도 내가 만든 하늘을 살짝 상상했다.
딕이 웃으며 말했다. “좋아. 이제 우리 하늘, 점점 게임 냄새가 나는데?”
2. PLAY — 구름이 ‘진짜’ 다가오게 만들기
“이제 움직여야지.” 세이가 말했다. “구름은 스스로 움직이는 거잖아. 플레이어가 직접 조종하는 게 아니라, 세상이 흘러가는 느낌으로.”
나는 update 함수 안에 구름을 움직이는 코드를 넣었다.
function updateClouds() {
for (let i = 0; i < clouds.length; i++) {
const c = clouds[i];
c.x -= c.speed; // 왼쪽으로 이동
// 화면 왼쪽 밖으로 완전히 나가면 제거
if (c.x + c.w < 0) {
clouds.splice(i, 1);
i--;
}
}
}
“이제 매 프레임마다 updateClouds()를 호출하면, 구름이 그냥 ‘우리 쪽으로 천천히 다가오는 느낌’이 되는 거야.”
딕이 마우스를 잡았다. “자, 테스트 들어갑니다아~ 코러호 출격!” 코러호가 위아래로 움직이는 동안, 오른쪽에서 하얀 구름들이 조금씩 밀려왔다.
세이가 화면을 유심히 보다가 말했다. “오, 느낌 있다. 이제야 하늘을 난다 같은 기분이네.” “그래도 아직은 그냥 피하기만 하는 느낌이야.” 딕이 말했다. “부딪히면… 어떻게 할 건데?”
3. PLAY — 구름과의 충돌을 느끼는 순간
“좋아, 이제 오늘의 핵심.” 내가 말했다. “코러호와 구름이 부딪히는지 계산해보자.”
우리는 이미 비행기의 위치를 x, y, w, h로 가지고 있다. 구름도 x, y, w, h로 만들었다. 이제 남은 건 간단한 네모끼리의 충돌 체크다.
function isHitPlaneAndCloud(plane, cloud) {
return (
plane.x < cloud.x + cloud.w &&
plane.x + plane.w > cloud.x &&
plane.y < cloud.y + cloud.h &&
plane.y + plane.h > cloud.y
);
}
세이가 말했다. “서로의 범위가 겹치면 true, 안 겹치면 false. 이건 네모 충돌의 기본 공식 같은 거야.”
딕은 이해한 듯 고개를 끄덕이다가, 갑자기 물었다. “근데… 부딪히면 뭐가 일어나는 게 좋을까?” 나는 잠깐 생각하다가 말했다. “우선은 간단하게, 비행기가 ‘아얏!’ 하고 반짝이는 것부터 해보자.”
let isHit = false;
let hitTimer = 0;
function checkHit() {
isHit = false;
for (let i = 0; i < clouds.length; i++) {
if (isHitPlaneAndCloud(plane, clouds[i])) {
isHit = true;
hitTimer = 10; // 잠깐 효과
break;
}
}
}
그리고 그 결과를 그릴 때 반영했다.
function drawPlane() {
if (hitTimer > 0) {
ctx.fillStyle = "#FFD700"; // 맞은 순간 황금빛 반짝
hitTimer--;
} else {
ctx.fillStyle = "#FF4500"; // 평소 색
}
ctx.fillRect(plane.x, plane.y, plane.w, plane.h);
}
“자, 테스트!” 내가 말했다. 딕이 키보드를 잡고 코러호를 구름 사이로 몰고 갔다. 그리고 일부러 하나의 구름에 정면으로 돌진시켰다.
찰나의 반짝임. 코러호가 황금빛으로 번쩍 빛나더니, 다시 원래 색으로 돌아왔다.
“와… 방금 그거 뭐야? 너무 멋있었는데?” 딕이 외쳤다. 세이가 설명했다. “충돌이 일어나면 isHit가 true가 되고, hitTimer가 잠깐 켜진 거지. 그동안은 색을 바꿔서 ‘맞았음’을 보여주는 거야.”
나는 화면을 보며 조용히 말했다. “이제 이건 그냥 움직이는 네모가 아니야. 세상에 반응하는 비행기야.”
4. PLAY — 난이도와 ‘억울함’을 조절하는 법
구름과 충돌이 잘 되는 걸 본 뒤, 우리는 또 새로운 불만을 찾기 시작했다. 딕이 먼저 입을 열었다.
“근데 말이야… 이거 너무 어렵게도 만들 수 있겠는데?” “어떻게?” 내가 물었다. “구름을 너무 많이, 너무 빠르게, 너무 낮게, 너무 높게, 막 쏟아지게 하면 되지!”
세이가 손을 번쩍 들며 말했다. “잠깐. 너무 어렵기만 한 게임은 재미없어. ‘억울하다’고 느끼지 않도록 조절해 줘야 해.”
나는 고개를 끄덕였다. “그래. 게임을 만드는 사람은 한 가지를 꼭 기억해야 해. 플레이어는 실패하더라도 ‘다음엔 될 것 같다’고 느껴야 한다는 것.”
그래서 우리는 구름 생성 빈도와 속도를 조절하기로 했다.
let cloudSpawnTimer = 0;
function update() {
// ... 비행기 움직임 코드 ...
updateClouds();
checkHit();
cloudSpawnTimer--;
if (cloudSpawnTimer <= 0) {
spawnRandomCloud();
cloudSpawnTimer = 60 + Math.random() * 60; // 1~2초 사이
}
}
“이렇게 하면 구름이 계속 나오긴 하지만, 완전 미친 속도로 쏟아지진 않아.” 세이가 설명했다. 딕이 다시 테스트를 시작했다.
이번에는 조금 더 여유가 느껴졌다. 구름들이 순서대로 나타났다 사라졌고, 사이사이에 숨을 고를 공간도 생겼다.
“오, 이건 진짜 ‘할 만하다’ 느낌이네.” 딕이 말했다. “한 번 부딪혀도 왠지 다음엔 피할 수 있을 것 같은데?”
나는 미소를 지었다. “그게 바로 PLAY 단계의 중요한 감각이야. 우리가 숫자를 조금씩 바꿔가면서 ‘이 정도면 재밌다’ 하는 지점을 찾는 거지.”
5. DREAM — 구름 뒤에 숨어 있는 것들
테스트를 마친 뒤, 우리는 한동안 말없이 화면을 바라봤다. 파란 하늘, 그 안을 가로지르는 코러호, 그리고 오른쪽에서 천천히 다가오는 구름들.
세이가 조용히 말했다. “이제, 구름 뒤에 뭔가 숨길 수도 있겠다.” 딕이 바로 반응했다. “아이템! 코인! 비밀 문!” 나는 웃었다. “또는… 신대륙으로 가는 길의 힌트일 수도 있고.”
세이는 태블릿에 새로운 메모를 적었다. 구름 뒤 아이템 / 구름 뒤 위험 / 구름 뒤 스토리 “우리가 오늘 만든 건 그냥 장애물이 아니라, ‘무언가를 숨길 수 있는 레이어’야.”
딕이 창밖을 보며 말했다. “진짜 하늘도 그렇지 않나? 구름 뒤에 비가 숨어 있기도 하고, 무지개가 숨어 있기도 하고.”
나는 마지막으로 게임 화면을 한번 더 바라봤다. 코러호는 구름 사이를 조심스럽게 날고 있었다. “우리가 만든 건 단지 움직임이 아니야. 기대할 수 있는 하늘이야.”
그리고 노트 한쪽에 다음 문장을 적었다. 에피소드 06 (PLAY) — 폭풍이 오는 날
6. Q&A / F&A
| Q | A |
|---|---|
| Q. 구름이 너무 많이 나와서 게임이 불가능해요. | A. cloudSpawnTimer 값을 늘려서 생성 간격을 길게 해 보세요. 예: 60~120 대신 90~150. |
| Q. 구름 속도가 너무 빨라요. | A. spawnRandomCloud()에서 speed 범위를 줄여 보세요. 예: 1 + Math.random()처럼. |
| Q. 구름과 부딪혀도 아무 일도 안 일어나요. | A. 충돌 함수 isHitPlaneAndCloud가 제대로 호출되는지, 좌표와 크기가 올바른지 확인해 보세요. |
| Q. 구름이 화면에 안 보여요. | A. 캔버스 밖(x가 너무 크거나 y가 0~canvas.height 범위를 벗어난 경우)에서만 생성되고 있을 수 있어요. 초기 위치를 다시 점검해 보세요. |
| F&A. 꼭 구름이 아니어도 되나요? | A. 물론이에요! 새, 풍선, 드론, 떠다니는 섬 등 뭐든지 될 수 있어요. 중요한 건 “움직이는 장애물”이라는 역할이죠. |
7. 오늘의 정리
오늘 우리는 코러호 앞길에 구름을 띄웠다. 그냥 장식용이 아니라, 속도와 위치를 가진 진짜 오브젝트로.
구름은 이제 단순한 배경이 아니다. 코러호와 부딪히고, 피해야 하고, 때로는 그 뒤에 무언가를 숨길 수 있는 이야기의 도구가 되었다.
우리는 숫자를 조금씩 바꾸어 가며 “너무 어렵지도, 너무 쉽지도 않은” 그 사이의 지점을 찾아보았다.
그 과정에서 깨달은 것은, 게임을 만든다는 건 단순히 코드만 짜는 일이 아니라, 누군가의 감정을 설계하는 일이라는 것.
내일, 우리의 하늘에는 구름 말고도 더 많은 것이 생길 것이다. 바람, 비, 번개, 그리고… 신대륙의 그림자도.
8. English Summary (about 500 chars)
In Episode 05 (PLAY), Leon, Sey, and Dic add moving clouds to the sky of their tiny airplane game, Corer One. The clouds are not just decorations; each has its own position, size, and speed, created and updated with JavaScript arrays. The kids test collisions so the plane briefly flashes when it hits a cloud, then adjust spawn timing and speed to avoid unfair difficulty. By the end, they realize they are not only coding obstacles, but designing a sky that players can expect, fear, and look forward to. Next, the calm clouds will slowly turn into a storm.
9. TAG
#에피소드05 #MAKEPLAYDREAM #PLAY단계 #DIY게임만들기 #Canvas게임 #코딩교육 #리온세이딕 #자바스크립트코딩 #구름장애물 #엔딕프로젝트

Leave a Comment