====== 게임: 이앤씨의 꿈 퀴즈 어드벤처 ====== 이 문서는 p5.js와 Tone.js를 기반으로 제작된 '이앤씨의 꿈 퀴즈 어드벤처' 게임의 핵심 로직과 알고리즘을 설명합니다. === 1. 게임 설계: 기본 설정 및 전역 변수 === 게임의 가장 기본적인 규칙과 상태를 관리하는 변수들을 정의하는 영역입니다. 이 변수들을 통해 게임의 난이도, 퀴즈 내용, 시각적 테마 등을 손쉽게 조정할 수 있습니다. {{wiki:it:dream_of_enc:frontend:screenshot-1.png?400}} ==== 1.1. 캔버스 및 퀴즈 설정 ==== 게임의 기준 해상도와 한 판에 등장할 퀴즈의 종류 및 개수를 설정합니다. // Game Settings const BASE_WIDTH = 960; // 기준 너비 const BASE_HEIGHT = 576; // 기준 높이 let scaleFactor = 1; // 화면 크기에 따른 스케일 비율 // NEW: Question Selection Settings const SELECTED_QUIZ_KEYWORD = null; // 특정 키워드의 퀴즈만 로드 (null이면 전체) const NUMBER_OF_QUIZZES_TO_LOAD = 20; // 가져올 문제의 개수 ==== 1.2. 게임 상태 및 주요 변수 ==== 게임의 현재 상태(gameState)를 문자열로 관리하여, 상태에 따라 다른 로직과 화면을 보여줍니다. 플레이어, 퀴즈, 아이템 등 핵심 객체들은 배열로 관리됩니다. // Game State and Variables let gameState = "AUDIO_PROMPT"; // 게임의 현재 상태 (예: "START_SCREEN", "PLAYING", "GAME_OVER") let player; // 플레이어 객체 let quizzes = [], particles = [], obstacles = []; // 각종 객체를 담을 배열 let highScore = 0; // 최고 점수 let feverModeActive = false; // 피버 모드 활성화 여부 ==== 1.3. 색상 및 시각 요소 ==== 게임에 사용되는 모든 색상을 미리 변수로 정의합니다. 이를 통해 낮/밤 테마 변경 시 lerpColor() 함수로 두 색상 사이를 부드럽게 전환하고, 일관된 디자인을 유지합니다. // Color Definitions let DAY_SKY_TOP, DAY_SKY_BOT, NIGHT_SKY_TOP, NIGHT_SKY_BOT; let DAY_MOUNTAIN, NIGHT_MOUNTAIN, DAY_HILL, NIGHT_HILL, DAY_GROUND, NIGHT_GROUND; // ... (이하 생략) === 2. 게임의 생명주기: p5.js 핵심 함수 === p5.js 프레임워크의 생명주기에 따라 게임을 준비하고, 실행하고, 상호작용하는 핵심 함수들입니다. ==== 2.1. preload() ==== 게임이 시작되기 전, 가장 먼저 실행되는 함수입니다. 용량이 큰 이미지나 폰트 등 외부 리소스를 미리 불러와 메모리에 올려두는 역할을 합니다. 이를 통해 게임 시작 시 로딩 지연을 방지합니다. function preload() { bgFarImage = loadImage('assets/background_far.png'); // 원경 이미지 bgGroundImage = loadImage('assets/background_ground.png'); // ... startScreenBackgroundImage = loadImage('assets/background_start.gif'); } ==== 2.2. setup() ==== preload()가 끝난 후, 게임 시작에 필요한 모든 초기 설정을 단 한 번 실행하는 함수입니다. 캔버스를 생성하고, 색상 변수를 초기화하며, 플레이어와 각종 효과음(SFX) 객체를 생성합니다. function setup() { let canvas = createCanvas(windowWidth, windowHeight); canvas.parent('game-container'); // 최초 게임 크기 조절 resizeGame(); // 색상 변수 초기화 DAY_SKY_TOP = color(135, 206, 235); // ... // 효과음 신디사이저 생성 (Tone.js) jumpSound = new Tone.Synth(...).toDestination(); // ... // 게임 시작 상태로 초기화 resetGame(); } ==== 2.3. draw() ==== setup()이 끝난 후, 게임이 실행되는 동안 1초에 약 60번씩 계속해서 호출되는 메인 루프입니다. 이 함수 안에서 모든 게임의 로직 업데이트와 화면 그리기가 반복적으로 이루어집니다. function draw() { // 1. 매 프레임 게임의 모든 로직(움직임, 충돌 등)을 먼저 계산 updateGame(); // 2. 계산된 최신 상태를 바탕으로 화면을 그림 push(); // 현재 좌표계 저장 scale(scaleFactor); // 전체 캔버스 스케일링 clear(); // 이전 프레임 지우기 // 현재 게임 상태(gameState)에 따라 적절한 화면을 그림 if (gameState === "START_SCREEN") { drawStartScreen(); } else if (gameState === "PLAYING") { // 인게임 화면 그리기 drawBackground(); player.draw(); // ... } // ... pop(); // 저장했던 좌표계 복원 } === 3. 게임의 두뇌: 핵심 로직과 상태 관리 === draw() 함수 내부에서 호출되는 updateGame() 함수와, 각 게임 상태를 처리하는 핸들러 함수들이 게임의 실제 동작을 제어합니다. ==== 3.1. updateGame(): 게임 월드의 지휘자 ==== 매 프레임마다 게임의 모든 요소를 업데이트하는 총괄 함수입니다. 카메라 이동: 플레이어의 위치에 따라 카메라의 x좌표를 업데이트하여, 화면이 자연스럽게 따라가도록 합니다. 플레이어 업데이트: 사용자의 입력을 받아 플레이어의 움직임을 계산하고, 중력을 적용합니다. 상태별 로직 실행: 현재 gameState에 맞는 핸들러 함수(예: handlePlayingState)를 호출하여 해당 상태에 필요한 로직만 실행합니다. ==== 3.2. handlePlayingState(): 인게임 로직 ==== gameState가 "PLAYING"일 때 실행됩니다. 객체 생성: 정해진 시간 간격에 따라 새로운 퀴즈, 아이템, 장애물을 생성하여 배열에 추가합니다. 객체 업데이트: 화면에 있는 모든 퀴즈, 아이템, 장애물 객체의 update() 메소드를 호출하여 각자 움직이도록 합니다. 충돌 처리: 플레이어와 다른 객체들 간의 충돌을 검사하고, 충돌 시 체력 감소, 점수 획득, 퀴즈 화면 전환 등의 이벤트를 처리합니다. 게임 종료 판정: 플레이어의 체력이 0이 되거나, 모든 퀴즈를 맞혔을 경우 gameState를 "GAME_OVER" 또는 "GAME_CLEAR"로 변경합니다. === 4. 게임의 구성 요소: 주요 클래스 정의 === 게임에 등장하는 플레이어, 퀴즈, 장애물 등 핵심 객체들의 설계도(클래스)입니다. ==== 4.1. Player 클래스 ==== 플레이어의 위치, 체력, 점수 등 모든 상태와 행동(점프, 이동, 충돌 처리)을 정의합니다. update() 메소드에서 물리 법칙(중력)을 계산하고, draw() 메소드에서 현재 상태(걷기, 점프 등)에 맞는 애니메이션을 그립니다. class Player { constructor() { this.pos = createVector(120, BASE_HEIGHT * 0.85); // 위치 this.vel = createVector(0, 0); // 속도 this.hp = 3; this.score = 0; // ... } handleInput() { /* 키보드 및 터치 입력 처리 */ } update() { /* 중력 적용 및 위치 업데이트 */ } draw() { /* 플레이어 캐릭터 그리기 */ } collidesWith(other) { /* 다른 객체와의 충돌 판정 */ } } ==== 4.2. Quiz 클래스 ==== 퀴즈 몬스터의 생성, 움직임, 플레이어와의 상호작용을 정의합니다. 각 퀴즈 객체는 자신의 문제 인덱스(qIdx)와 답변 완료 여부(answered) 상태를 가집니다. class Quiz { constructor(qIdx, x, type = 0) { this.qIdx = qIdx; // questions 배열의 인덱스 this.pos = createVector(x, ...); this.velX = -QUIZ_SPEED; this.answered = false; // 답변 완료 여부 // ... } // ... } ==== 4.3. Obstacle 클래스 ==== 바위, 함정, 레이저 등 각종 장애물의 종류와 행동을 정의합니다. constructor에서 type에 따라 다른 크기와 속도를 가지도록 설정됩니다. class Obstacle { constructor(x, type) { this.type = type; // 'ground_rock', 'pit_trap' 등 this.pos = createVector(x, ...); this.hit = false; // 플레이어와 충돌했는지 여부 // ... } // ... } === 5. 랭킹 시스템 (서버 연동) === 게임이 끝난 후, 점수를 서버에 저장하고 명예의 전당을 불러오는 기능입니다. ==== 5.1. saveRankingToServer() ==== fetch API를 사용하여, 플레이어의 닉네임과 점수, 클리어 시간 등의 정보를 백엔드 Flask 서버(https://api.dreamofenc.com/api/game/add_ranking)로 전송(POST)합니다. async function saveRankingToServer(nickname, score, hearts, timeMillis) { const newEntry = { nickname: nickname, score: score, // ... }; try { const response = await fetch(https://api.dreamofenc.com/api/game/add_ranking, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(newEntry), }); // ... } catch (error) { // ... } } ==== 5.2. loadRankingsFromServer() ==== 마찬가지로 fetch를 사용하여, 서버에서 전체 랭킹 데이터를 받아와 화면에 표시할 rankings 배열에 저장합니다. async function loadRankingsFromServer() { try { const response = await fetch(https://api.dreamofenc.com/api/game/get_rankings); const data = await response.json(); rankings = data; sortRankings(); } catch (error) { // 서버 연결 실패 시 로컬 저장소 사용 } }