에피소드 10 (PLAY) 첫 발을 내딛는 날
에피소드 10 (PLAY) — 첫 발을 내딛는 날
“이제… 내려왔으니까, 걸어야지.”
리온이 모니터를 보며 말했다. 신대륙 A-01, 바람등대 언덕 위에 조그만 코러호가 얌전히 앉아 있었다. 하늘에서 볼 때랑은 완전히 다른 느낌. 지금은, 진짜로 땅이 가까이 있었다.
딕이 손을 번쩍 들었다. “나 내려서 걷고 싶어! 여기저기 돌아다니고, 숲도 들어가 보고, 언덕도 올라가 보고!” 세이가 웃었다. “좋아. 근데 그러려면 먼저… ‘사람’을 만들어야지.”
리온이 고개를 끄덕였다. “그래. 비행기는 비행기고, 지상에서는 캐릭터가 필요해.” 딕이 바로 외쳤다. “당연히 주인공은 나지?” “잠깐만.” 세이가 말렸다. “일단 아바타부터 만들고, 누가 조종할지 나중에 정하자.”
오늘의 미션은 딱 한 줄로 정리됐다. “신대륙 A-01 지상 위에, 첫 한 걸음을 내딛게 하라.”
1. MAKE → PLAY — “하늘 모드”와 “지상 모드” 나누기
세이는 태블릿에 간단한 다이어그램을 그렸다.
SKY_MODE → GROUND_MODE
(비행기) → (캐릭터)
“이제부터 게임은 두 개의 세상으로 나뉘어.” 세이가 설명했다. “하늘에서 비행기 조종하는 모드, 그리고 땅에서 걸어 다니는 모드.”
리온이 고개를 끄덕였다. “착륙이 끝나면, 비행기 상태는 LANDED였지. 그다음에는 모드를 SKY에서 GROUND로 바꾸면 돼.”
const GAME_MODE = {
SKY: "sky",
GROUND: "ground"
};
let gameMode = GAME_MODE.SKY;
// 착륙 성공 시
function onLandingComplete() {
planeState = PLANE_STATE.LANDED;
gameMode = GAME_MODE.GROUND;
}
딕이 물었다. “그럼 SKY 모드일 때는 비행기만 그리는 거고, GROUND 모드일 때는 캐릭터만 그리는 거야?” 세이가 답했다. “기본적으로는 그래. 필요하면 둘 다 그릴 수도 있지만, 지금은 단순하게 시작하자.”
리온이 정리했다. “좋아. 하늘은 일단 잠깐 쉬고, 지금부터는 지상 모드 PLAY다.”
2. PLAY — 첫 캐릭터 만들기
“누구 기준으로 만들까?” 세이가 물었다. 딕이 잽싸게 외쳤다. “당연히 나지! 난 벌써 머릿속에 캐릭터 설정까지 다 해놨어!” 리온이 웃었다. “일단 기본 아바타부터 만들고, 나중에 스킨 고르는 거 어때?” “오… 좋다. 스킨 시스템!” 딕이 신났다.
리온은 지상 모드 전용 캐릭터 데이터를 만들었다.
const player = {
x: landingZonePx.x + landingZonePx.w / 2,
y: landingZonePx.y + landingZonePx.h / 2,
w: 20,
h: 28,
speed: 2.0,
vx: 0,
vy: 0
};
세이가 말했다. “착륙지 중앙에 캐릭터를 스폰하는 거네. 딱 좋다.” 딕이 궁금해했다. “근데 왜 w랑 h가 비행기랑 조금 달라?” 리온이 설명했다. “비행기는 가로로 긴 느낌, 사람은 세로로 조금 더 긴 느낌이 좋잖아. 간단한 네모라도 느낌이 달라져.”
그리고 지상 모드에서 캐릭터를 그리는 함수를 만들었다.
function drawPlayer() {
ctx.fillStyle = "#f97316"; // 주황색 캐릭터
ctx.fillRect(player.x - player.w/2, player.y - player.h/2, player.w, player.h);
}
딕이 화면을 보며 외쳤다. “오! 저 네모가… 우리 첫 사람인가?!” 세이는 웃으며 말했다. “그래. 지금은 네모지만, 나중엔 머리도 달고, 눈도 달고, 옷도 바꿀 수 있을 거야.”
3. PLAY — 키보드로 걷기
“이제 움직여야지.” 세이가 말했다. “하늘에서는 화살표 키랑 WASD로 비행기 움직였잖아? 지상에서도 그대로 쓰자.”
const keys = {
left: false,
right: false,
up: false,
down: false
};
document.addEventListener("keydown", (e) => {
if (gameMode !== GAME_MODE.GROUND) return;
if (e.code === "ArrowLeft" || e.code === "KeyA") keys.left = true;
if (e.code === "ArrowRight" || e.code === "KeyD") keys.right = true;
if (e.code === "ArrowUp" || e.code === "KeyW") keys.up = true;
if (e.code === "ArrowDown" || e.code === "KeyS") keys.down = true;
});
document.addEventListener("keyup", (e) => {
if (e.code === "ArrowLeft" || e.code === "KeyA") keys.left = false;
if (e.code === "ArrowRight" || e.code === "KeyD") keys.right = false;
if (e.code === "ArrowUp" || e.code === "KeyW") keys.up = false;
if (e.code === "ArrowDown" || e.code === "KeyS") keys.down = false;
});
그리고 지상 모드 업데이트에서 이렇게 속도를 계산했다.
function updatePlayer() {
let vx = 0;
let vy = 0;
if (keys.left) vx -= 1;
if (keys.right) vx += 1;
if (keys.up) vy -= 1;
if (keys.down) vy += 1;
// 대각선 속도 보정
if (vx !== 0 && vy !== 0) {
vx *= Math.sqrt(0.5);
vy *= Math.sqrt(0.5);
}
player.x += vx * player.speed;
player.y += vy * player.speed;
}
딕이 테스트를 시작했다. “왼쪽… 오른쪽… 오, 움직인다! 와, 진짜 내가 이 땅 위를 걷고 있는 것 같아!”
세이가 말했다. “이제 우리는 하늘뿐 아니라, 땅 위에서도 ‘게임’이 되는 순간을 만든 거야.”
4. PLAY — 물에 빠지지 않게, 경계를 만들자
딕이 열심히 움직이다가 갑자기 소리쳤다. “어, 나 바다 위도 걸을 수 있는데?” 화면 속 캐릭터 네모가 바다 타일까지 스윽스윽 지나가고 있었다.
세이가 말했다. “음… 이건 게임이 아니라 버그네.” 리온도 고개를 끄덕였다. “그래. 지상 모드에서는 ‘걸을 수 있는 곳’과 ‘걸을 수 없는 곳’을 나눠야 해.”
세이는 타일맵을 다시 열어 간단한 규칙을 정했다.
- 0: 바다 → 못 걷는다
- 1: 땅 → 걸을 수 있다
- 2: 숲 → 천천히 걷거나 막을 수도 있다 (지금은 막기)
그리고 움직이기 전에 “미리 가볼 자리”를 계산하도록 수정했다.
function canWalkTo(x, y) {
const col = Math.floor(x / TILE_SIZE);
const row = Math.floor(y / TILE_SIZE);
if (col < 0 || col >= MAP_COLS || row < 0 || row >= MAP_ROWS) {
return false;
}
const tile = worldMap[row][col];
if (tile === 0) return false; // 바다
if (tile === 2) return false; // 숲 (지금은 통과 불가)
return true;
}
function updatePlayer() {
let vx = 0;
let vy = 0;
if (keys.left) vx -= 1;
if (keys.right) vx += 1;
if (keys.up) vy -= 1;
if (keys.down) vy += 1;
if (vx !== 0 && vy !== 0) {
vx *= Math.sqrt(0.5);
vy *= Math.sqrt(0.5);
}
const nextX = player.x + vx * player.speed;
const nextY = player.y + vy * player.speed;
if (canWalkTo(nextX, player.y)) player.x = nextX;
if (canWalkTo(player.x, nextY)) player.y = nextY;
}
딕이 다시 테스트를 했다. 이번에는 바닥 경계선에서 멈추는 모습이 눈에 보였다.
“오! 이제 더 이상 물 위를 걸을 수 없어!” 세이가 미소 지었다. “세계에 규칙이 생겼다는 뜻이야.”
5. PLAY — ‘여기가 어디인지’ 알려주는 땅 위 메시지
“이제 한 가지가 더 필요해.” 세이가 말했다. “우리는 지금 바람등대 언덕 위를 걷고 있잖아? 그럼 게임도 우리에게 알려줘야 해. ‘여기가 어디인지’.”
리온이 regions 데이터를 떠올렸다. 에피소드 08에서 만든 영역 정보들. 바람등대 언덕, 첫 번째 그림자 숲…
const regions = [
{
name: "바람등대 언덕",
area: { x: 6, y: 2, w: 4, h: 3 }
},
{
name: "첫 번째 그림자 숲",
area: { x: 7, y: 3, w: 3, h: 2 }
}
];
세이는 간단한 함수를 만들었다.
let currentRegionName = "";
function updateRegionName() {
const col = Math.floor(player.x / TILE_SIZE);
const row = Math.floor(player.y / TILE_SIZE);
currentRegionName = "";
for (let i = 0; i < regions.length; i++) {
const r = regions[i];
const ax = r.area.x;
const ay = r.area.y;
const aw = r.area.w;
const ah = r.area.h;
if (col >= ax && col < ax + aw &&
row >= ay && row < ay + ah) {
currentRegionName = r.name;
break;
}
}
}
그리고 화면 구석에 현재 지역 이름을 그렸다.
function drawRegionName() {
if (!currentRegionName) return;
ctx.fillStyle = "rgba(0,0,0,0.6)";
ctx.fillRect(10, 10, 220, 30);
ctx.fillStyle = "#ffffff";
ctx.font = "14px 'Noto Sans KR'";
ctx.fillText("현재 위치: " + currentRegionName, 18, 30);
}
딕이 캐릭터를 살짝 움직여보자, 화면 상단에 작은 글씨가 떴다.
“현재 위치: 바람등대 언덕”
딕이 조용히 말했다. “와… 나 진짜 이 세계 위에 서 있는 느낌이야.”
세이는 살짝 웃었다. “숫자와 타일 뒤에, 이제 이름과 의미가 붙은 거지.”
6. Q&A / F&A
| Q | A |
|---|---|
| Q. 왜 하늘 모드와 지상 모드를 나눠야 하나요? | A. 조종 방식·카메라·충돌 규칙이 완전히 달라지기 때문이에요. 분리하지 않으면 코드가 쉽게 꼬이고 유지보수가 힘들어집니다. |
| Q. 캐릭터를 원보다 네모로 만든 이유는? | A. 네모가 충돌 계산이 훨씬 쉽고, 나중에 스프라이트로 바꿀 때도 편해요. 처음에는 단순한 네모로 시작하는 게 좋아요. |
| Q. 대각선 이동 속도 보정은 꼭 해야 하나요? | A. 안 하면 대각선 이동이 가로/세로보다 빠르게 느껴져서 움직임이 어색해져요. 공정한 조작감을 위해 보정하는 것이 일반적입니다. |
| Q. 바다와 숲을 아예 통과 못 하게 막아야 하나요? | A. 처음에는 단순히 막는 게 좋고, 나중에 ‘스킬’이나 ‘장비’로 통과 조건을 열어 주면 성장감을 줄 수 있어요. |
| F&A. 처음부터 카메라 스크롤까지 넣는 건 무리일까요? | A. 가능하지만, 처음부터 다 넣으면 디버깅이 어려워져요. 지금처럼 작은 섬 안에서 먼저 걷는 감각을 완성한 뒤, 카메라를 추가하는 걸 추천해요. |
7. 오늘의 정리
오늘 에피소드 10 (PLAY)에서는 신대륙 A-01 위에 처음으로 ‘걷는 사람’을 등장시켰다.
- 하늘 모드와 지상 모드를 나누고
- 착륙이 끝나면 지상 모드로 전환하고
- 첫 캐릭터(아바타)를 스폰하고
- 키보드로 걸을 수 있게 만들고
- 바다·숲 경계에 충돌 규칙을 붙이고
- 현재 위치의 이름을 화면에 보여줬다.
코러호는 이제 하늘을 나는 탈것일 뿐만 아니라, 새로운 땅으로 친구들을 데려다주는 스타팅 포인트가 되었다.
세 아이는 모니터를 바라보며 가만히 서 있었다. 화면 속 작은 네모 캐릭터가 바람등대 언덕 위에서 첫 발을 내딛는 그 순간, 셋 모두 마음속으로 똑같이 생각하고 있었다.
“이제, 진짜 모험이 시작되는구나.”
다음 편은 에피소드 11 (PLAY) — 언덕 끝에서 본 풍경 신대륙 A-01의 지상을 조금 더 멀리, 그리고 넓게 바라보는 이야기로 이어진다.
8. English Summary (about 500 chars)
In Episode 10 (PLAY), the kids switch from sky mode to ground mode after landing on continent A-01. They spawn a simple player avatar on the landing zone, add keyboard movement, and prevent walking into sea or forest tiles. The game now shows the current region name, making the world feel real. With this first step on land, Corer One is no longer just a plane — it’s the start point of a true adventure.
9. TAG
#에피소드10 #PLAY단계 #지상모드 #첫발걸음 #신대륙A01 #DIY게임만들기 #코러호 #리온세이딕 #타일맵이동 #엔딕프로젝트

Leave a Comment