diff --git a/.DS_Store b/.DS_Store index cd077a4..a85f9e8 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/assets/.DS_Store b/assets/.DS_Store index bd9b06f..3dd8d70 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/beach.png b/assets/beach.png new file mode 100644 index 0000000..c71abd4 Binary files /dev/null and b/assets/beach.png differ diff --git a/assets/boat.png b/assets/boat.png new file mode 100644 index 0000000..6cf570f Binary files /dev/null and b/assets/boat.png differ diff --git a/assets/coconut/.DS_Store b/assets/coconut/.DS_Store new file mode 100644 index 0000000..7e3d3d0 Binary files /dev/null and b/assets/coconut/.DS_Store differ diff --git a/assets/coconut/coconut_0.png b/assets/coconut/coconut_0.png index 6498470..d28247e 100644 Binary files a/assets/coconut/coconut_0.png and b/assets/coconut/coconut_0.png differ diff --git a/assets/coconut/coconut_1.png b/assets/coconut/coconut_1.png index 8737795..05850c7 100644 Binary files a/assets/coconut/coconut_1.png and b/assets/coconut/coconut_1.png differ diff --git a/assets/coconut/coconut_2.png b/assets/coconut/coconut_2.png index c545bd2..22f4d24 100644 Binary files a/assets/coconut/coconut_2.png and b/assets/coconut/coconut_2.png differ diff --git a/assets/coconut/coconut_3.png b/assets/coconut/coconut_3.png index 0d671a4..cd4a719 100644 Binary files a/assets/coconut/coconut_3.png and b/assets/coconut/coconut_3.png differ diff --git a/assets/coconut/coconut_4.png b/assets/coconut/coconut_4.png index 829325f..7cbf8d3 100644 Binary files a/assets/coconut/coconut_4.png and b/assets/coconut/coconut_4.png differ diff --git a/assets/effect/.DS_Store b/assets/effect/.DS_Store new file mode 100644 index 0000000..463121a Binary files /dev/null and b/assets/effect/.DS_Store differ diff --git a/assets/effect/particles0.png b/assets/effect/particles0.png new file mode 100644 index 0000000..eede3c8 Binary files /dev/null and b/assets/effect/particles0.png differ diff --git a/assets/effect/particles1.png b/assets/effect/particles1.png new file mode 100644 index 0000000..c923f34 Binary files /dev/null and b/assets/effect/particles1.png differ diff --git a/assets/effect/particles2.png b/assets/effect/particles2.png new file mode 100644 index 0000000..3284d92 Binary files /dev/null and b/assets/effect/particles2.png differ diff --git a/assets/kakamora-characters/.DS_Store b/assets/kakamora-characters/.DS_Store new file mode 100644 index 0000000..5ca1bea Binary files /dev/null and b/assets/kakamora-characters/.DS_Store differ diff --git a/assets/kakamora-characters/Kakamora-TikiRob-Claw-Edition.png b/assets/kakamora-characters/Kakamora-TikiRob-Claw-Edition.png deleted file mode 100644 index eb0d385..0000000 Binary files a/assets/kakamora-characters/Kakamora-TikiRob-Claw-Edition.png and /dev/null differ diff --git a/assets/kakamora-characters/Kokomoro-TikiRob-Fishhead-Edition.webp b/assets/kakamora-characters/Kokomoro-TikiRob-Fishhead-Edition.webp deleted file mode 100644 index 5233c75..0000000 Binary files a/assets/kakamora-characters/Kokomoro-TikiRob-Fishhead-Edition.webp and /dev/null differ diff --git a/assets/kakamora-characters/Kokomoro-TikiRob-Mickey-Mouse-Edition-1-250x250.png b/assets/kakamora-characters/Kokomoro-TikiRob-Mickey-Mouse-Edition-1-250x250.png deleted file mode 100644 index f5b09f3..0000000 Binary files a/assets/kakamora-characters/Kokomoro-TikiRob-Mickey-Mouse-Edition-1-250x250.png and /dev/null differ diff --git a/assets/kakamora-characters/Kokomoro-TikiRob-Seashell-Edition.png b/assets/kakamora-characters/Kokomoro-TikiRob-Seashell-Edition.png deleted file mode 100644 index 8f7efe9..0000000 Binary files a/assets/kakamora-characters/Kokomoro-TikiRob-Seashell-Edition.png and /dev/null differ diff --git a/assets/kakamora-characters/Kokomoro-TikiRob-Starfish-Edition-250x250.png b/assets/kakamora-characters/Kokomoro-TikiRob-Starfish-Edition-250x250.png deleted file mode 100644 index bff8b56..0000000 Binary files a/assets/kakamora-characters/Kokomoro-TikiRob-Starfish-Edition-250x250.png and /dev/null differ diff --git a/assets/kakamora-characters/co_ani_1.png b/assets/kakamora-characters/char1/co_ani_1.png similarity index 100% rename from assets/kakamora-characters/co_ani_1.png rename to assets/kakamora-characters/char1/co_ani_1.png diff --git a/assets/kakamora-characters/co_ani_2.png b/assets/kakamora-characters/char1/co_ani_2.png similarity index 100% rename from assets/kakamora-characters/co_ani_2.png rename to assets/kakamora-characters/char1/co_ani_2.png diff --git a/assets/kakamora-characters/co_ani_3.png b/assets/kakamora-characters/char1/co_ani_3.png similarity index 100% rename from assets/kakamora-characters/co_ani_3.png rename to assets/kakamora-characters/char1/co_ani_3.png diff --git a/assets/kakamora-characters/co_ani_4.png b/assets/kakamora-characters/char1/co_ani_4.png similarity index 100% rename from assets/kakamora-characters/co_ani_4.png rename to assets/kakamora-characters/char1/co_ani_4.png diff --git a/assets/kakamora-characters/co_ani_5.png b/assets/kakamora-characters/char1/co_ani_5.png similarity index 100% rename from assets/kakamora-characters/co_ani_5.png rename to assets/kakamora-characters/char1/co_ani_5.png diff --git a/assets/kakamora-characters/char2/Frame 10.png b/assets/kakamora-characters/char2/Frame 10.png new file mode 100644 index 0000000..4c7e168 Binary files /dev/null and b/assets/kakamora-characters/char2/Frame 10.png differ diff --git a/assets/kakamora-characters/char2/Frame 6.png b/assets/kakamora-characters/char2/Frame 6.png new file mode 100644 index 0000000..2339fc7 Binary files /dev/null and b/assets/kakamora-characters/char2/Frame 6.png differ diff --git a/assets/kakamora-characters/char2/Frame 7.png b/assets/kakamora-characters/char2/Frame 7.png new file mode 100644 index 0000000..98293e3 Binary files /dev/null and b/assets/kakamora-characters/char2/Frame 7.png differ diff --git a/assets/kakamora-characters/char2/Frame 8.png b/assets/kakamora-characters/char2/Frame 8.png new file mode 100644 index 0000000..893ebde Binary files /dev/null and b/assets/kakamora-characters/char2/Frame 8.png differ diff --git a/assets/kakamora-characters/char2/Frame 9.png b/assets/kakamora-characters/char2/Frame 9.png new file mode 100644 index 0000000..8bfce0e Binary files /dev/null and b/assets/kakamora-characters/char2/Frame 9.png differ diff --git a/assets/kakamora-characters/char3/Frame 11.png b/assets/kakamora-characters/char3/Frame 11.png new file mode 100644 index 0000000..14979d1 Binary files /dev/null and b/assets/kakamora-characters/char3/Frame 11.png differ diff --git a/assets/kakamora-characters/char3/Frame 12.png b/assets/kakamora-characters/char3/Frame 12.png new file mode 100644 index 0000000..220e76c Binary files /dev/null and b/assets/kakamora-characters/char3/Frame 12.png differ diff --git a/assets/kakamora-characters/char3/Frame 13.png b/assets/kakamora-characters/char3/Frame 13.png new file mode 100644 index 0000000..f44e184 Binary files /dev/null and b/assets/kakamora-characters/char3/Frame 13.png differ diff --git a/assets/kakamora-characters/char3/Frame 14.png b/assets/kakamora-characters/char3/Frame 14.png new file mode 100644 index 0000000..18c8d24 Binary files /dev/null and b/assets/kakamora-characters/char3/Frame 14.png differ diff --git a/assets/kakamora-characters/char3/Frame 15.png b/assets/kakamora-characters/char3/Frame 15.png new file mode 100644 index 0000000..cb7c671 Binary files /dev/null and b/assets/kakamora-characters/char3/Frame 15.png differ diff --git a/assets/kakamora-characters/char4/Frame 16.png b/assets/kakamora-characters/char4/Frame 16.png new file mode 100644 index 0000000..f42da03 Binary files /dev/null and b/assets/kakamora-characters/char4/Frame 16.png differ diff --git a/assets/kakamora-characters/char4/Frame 17.png b/assets/kakamora-characters/char4/Frame 17.png new file mode 100644 index 0000000..b3282b9 Binary files /dev/null and b/assets/kakamora-characters/char4/Frame 17.png differ diff --git a/assets/kakamora-characters/char4/Frame 18.png b/assets/kakamora-characters/char4/Frame 18.png new file mode 100644 index 0000000..3f9be28 Binary files /dev/null and b/assets/kakamora-characters/char4/Frame 18.png differ diff --git a/assets/kakamora-characters/char4/Frame 19.png b/assets/kakamora-characters/char4/Frame 19.png new file mode 100644 index 0000000..113a5e7 Binary files /dev/null and b/assets/kakamora-characters/char4/Frame 19.png differ diff --git a/assets/kakamora-characters/char4/Frame 20.png b/assets/kakamora-characters/char4/Frame 20.png new file mode 100644 index 0000000..4e5e3e5 Binary files /dev/null and b/assets/kakamora-characters/char4/Frame 20.png differ diff --git a/assets/kakamora-characters/char4/Frame 21.png b/assets/kakamora-characters/char4/Frame 21.png new file mode 100644 index 0000000..434aa9f Binary files /dev/null and b/assets/kakamora-characters/char4/Frame 21.png differ diff --git a/assets/kakamora-characters/char5/Frame 22.png b/assets/kakamora-characters/char5/Frame 22.png new file mode 100644 index 0000000..388a70e Binary files /dev/null and b/assets/kakamora-characters/char5/Frame 22.png differ diff --git a/assets/kakamora-characters/char5/Frame 23.png b/assets/kakamora-characters/char5/Frame 23.png new file mode 100644 index 0000000..c3bbcde Binary files /dev/null and b/assets/kakamora-characters/char5/Frame 23.png differ diff --git a/assets/kakamora-characters/char5/Frame 24.png b/assets/kakamora-characters/char5/Frame 24.png new file mode 100644 index 0000000..aee3b82 Binary files /dev/null and b/assets/kakamora-characters/char5/Frame 24.png differ diff --git a/assets/kakamora-characters/char5/Frame 25.png b/assets/kakamora-characters/char5/Frame 25.png new file mode 100644 index 0000000..db45563 Binary files /dev/null and b/assets/kakamora-characters/char5/Frame 25.png differ diff --git a/assets/kakamora-characters/char5/Frame 26.png b/assets/kakamora-characters/char5/Frame 26.png new file mode 100644 index 0000000..6f5086d Binary files /dev/null and b/assets/kakamora-characters/char5/Frame 26.png differ diff --git a/assets/kakamora-characters/char6/Frame 27.png b/assets/kakamora-characters/char6/Frame 27.png new file mode 100644 index 0000000..cee2404 Binary files /dev/null and b/assets/kakamora-characters/char6/Frame 27.png differ diff --git a/assets/kakamora-characters/char6/Frame 28.png b/assets/kakamora-characters/char6/Frame 28.png new file mode 100644 index 0000000..7b8217c Binary files /dev/null and b/assets/kakamora-characters/char6/Frame 28.png differ diff --git a/assets/kakamora-characters/char6/Frame 29.png b/assets/kakamora-characters/char6/Frame 29.png new file mode 100644 index 0000000..23b2ec7 Binary files /dev/null and b/assets/kakamora-characters/char6/Frame 29.png differ diff --git a/assets/kakamora-characters/char6/Frame 30.png b/assets/kakamora-characters/char6/Frame 30.png new file mode 100644 index 0000000..64b24f2 Binary files /dev/null and b/assets/kakamora-characters/char6/Frame 30.png differ diff --git a/assets/kakamora-characters/char6/Frame 31.png b/assets/kakamora-characters/char6/Frame 31.png new file mode 100644 index 0000000..47930b3 Binary files /dev/null and b/assets/kakamora-characters/char6/Frame 31.png differ diff --git a/assets/kakamora-characters/char7/Frame 32.png b/assets/kakamora-characters/char7/Frame 32.png new file mode 100644 index 0000000..94f2185 Binary files /dev/null and b/assets/kakamora-characters/char7/Frame 32.png differ diff --git a/assets/kakamora-characters/char7/Frame 33.png b/assets/kakamora-characters/char7/Frame 33.png new file mode 100644 index 0000000..7f07bed Binary files /dev/null and b/assets/kakamora-characters/char7/Frame 33.png differ diff --git a/assets/kakamora-characters/char7/Frame 34.png b/assets/kakamora-characters/char7/Frame 34.png new file mode 100644 index 0000000..5373976 Binary files /dev/null and b/assets/kakamora-characters/char7/Frame 34.png differ diff --git a/assets/kakamora-characters/char7/Frame 35.png b/assets/kakamora-characters/char7/Frame 35.png new file mode 100644 index 0000000..1b887dd Binary files /dev/null and b/assets/kakamora-characters/char7/Frame 35.png differ diff --git a/assets/kakamora-characters/char7/Frame 36.png b/assets/kakamora-characters/char7/Frame 36.png new file mode 100644 index 0000000..41e3e13 Binary files /dev/null and b/assets/kakamora-characters/char7/Frame 36.png differ diff --git a/assets/knife.png b/assets/knife.png new file mode 100644 index 0000000..fcf50cc Binary files /dev/null and b/assets/knife.png differ diff --git a/assets/sound/beach sound.wav b/assets/sound/beach sound.wav new file mode 100644 index 0000000..0fd4174 Binary files /dev/null and b/assets/sound/beach sound.wav differ diff --git a/assets/sound/slice.wav b/assets/sound/slice.wav new file mode 100644 index 0000000..93fbb5e Binary files /dev/null and b/assets/sound/slice.wav differ diff --git a/index.html b/index.html index c303a62..9399b49 100644 --- a/index.html +++ b/index.html @@ -4,14 +4,18 @@ Kakamora Game + + - + + + - - - + \ No newline at end of file diff --git a/sketch.js b/sketch.js index f183326..1a39c9c 100644 --- a/sketch.js +++ b/sketch.js @@ -5,57 +5,77 @@ let flashAlpha = 0; let score = 0; let bgBlue = 0; -let treeImgs = {}; -let treeHits = 0; -const TREE_COUNT = 8; -let waveTime = 0; -let idleSprite; -let idleAni; +let beachImg; +let moanaFont; +let beachSound, sliceSound; + +let charFrames = []; +let slotChars = new Array(10).fill(-1); +let slotOffsets = new Array(10).fill(0); +const ANI_FPS = 18; + +const CHAR_SCALES = [0.9, 1.5, 1.2, 1.5, 1.5, 1.5, 1.5]; + +let charOrder = []; +let charOrderIdx = 0; + +// ====== GAME STATE ====== +let gameState = 'name'; // 'name' | 'intro' | 'playing' | 'departure' +let playerName = ''; +let nameInput, nameConfirmBtn; +const COCONUT_TIME_MS = 5000; +let coconutTimerEnd = 0; // millis() when current coconut expires +let slotPowers = []; // 0–100, speed bonus per slot let coconut = { x: 0, y: 0, r: 0, state: 0, - shakeX: 0, + slices: 0, + shakeX: 0, shakeY: 0, shakeTimer: 0, + baseAngle: 0, flying: false, flyVX: 0, flyVY: 0, flyAlpha: 255, - flyRotation: 0, flyRotSpeed: 0 + flyRotation: 0, flyRotSpeed: 0, + entering: false, + enterT: 0, + enterFromX: 0, enterFromY: 0 }; let coconutImgs = []; -let kakomoraImgs = []; let flyingKakamoraList = []; +let knifeImg; +let effectImgs = []; +let imgParticles = []; +let slashLingerAlpha = 0; +let lingerTrail = []; +let lingerVelocities = []; // ====== PRELOAD ====== function preload() { for (let i = 0; i < 5; i++) { coconutImgs[i] = loadImage(`assets/coconut/coconut_${i}.png`); } - kakomoraImgs[0] = loadImage('assets/kakamora-characters/Kakamora-TikiRob-Claw-Edition.png'); - kakomoraImgs[1] = loadImage('assets/kakamora-characters/Kokomoro-TikiRob-Fishhead-Edition.webp'); - kakomoraImgs[2] = loadImage('assets/kakamora-characters/Kokomoro-TikiRob-Mickey-Mouse-Edition-1-250x250.png'); - kakomoraImgs[3] = loadImage('assets/kakamora-characters/Kokomoro-TikiRob-Seashell-Edition.png'); - kakomoraImgs[4] = loadImage('assets/kakamora-characters/Kokomoro-TikiRob-Starfish-Edition-250x250.png'); - treeImgs.full = [ - loadImage('assets/tree/tree_full_1.png'), - loadImage('assets/tree/tree_full_2.png'), - ]; - treeImgs.half = [ - loadImage('assets/tree/tree_half_1.png'), - loadImage('assets/tree/tree_half_2.png'), - ]; - treeImgs.one = [loadImage('assets/tree/tree_one_1.png')]; - treeImgs.empty = [ - loadImage('assets/tree/tree_empty_1.png'), - loadImage('assets/tree/tree_empty_2.png'), - loadImage('assets/tree/tree_empty_3.png'), - ]; + // char1 + charFrames[0] = []; + for (let i = 1; i <= 5; i++) charFrames[0].push(loadImage(`assets/kakamora-characters/char1/co_ani_${i}.png`)); + // char2–7: Frame N.png + const charRanges = [[6,10],[11,15],[16,21],[22,26],[27,31],[32,36]]; + for (let c = 0; c < charRanges.length; c++) { + let [start, end] = charRanges[c]; + charFrames[c + 1] = []; + for (let n = start; n <= end; n++) charFrames[c + 1].push(loadImage(`assets/kakamora-characters/char${c + 2}/Frame ${n}.png`)); + } + + beachImg = loadImage('assets/beach.png'); + moanaFont = loadFont('assets/fonts/Moanas.ttf'); + knifeImg = loadImage('assets/knife.png'); + for (let i = 0; i < 3; i++) effectImgs.push(loadImage(`assets/effect/particles${i}.png`)); - idleAni = loadAni('assets/kakamora-characters/co_ani_1.png', 5); } // ====== SETUP ====== @@ -64,18 +84,81 @@ function setup() { resetCoconutHome(); world.gravity.y = 0; - idleSprite = new Sprite(); - idleSprite.addAni('idle', idleAni); - idleSprite.collider = 'none'; - idleSprite.visible = false; // p5play auto-draw 비활성 — 수동으로 그림 - idleSprite.ani.frameDelay = 8; + shuffleCharOrder(); + + beachSound = new Howl({ + src: ['assets/sound/beach%20sound.wav'], + loop: true, + volume: 0.45, + fade: true + }); + sliceSound = new Howl({ + src: ['assets/sound/slice.wav'], + volume: 0.75 + }); + + // HTML name input + nameInput = createInput(''); + nameInput.attribute('placeholder', 'Enter your name...'); + nameInput.attribute('maxlength', '16'); + nameInput.style('font-size', '18px'); + nameInput.style('font-family', 'Google Sans, Nunito, sans-serif'); + nameInput.style('background', 'rgba(14, 20, 36, 0.92)'); + nameInput.style('color', '#e8f4ff'); + nameInput.style('border', '2px solid rgba(90, 185, 255, 0.55)'); + nameInput.style('border-radius', '2px'); + nameInput.style('padding', '10px 16px'); + nameInput.style('width', '260px'); + nameInput.style('outline', 'none'); + nameInput.style('letter-spacing', '1px'); + nameInput.style('position', 'absolute'); + nameInput.elt.addEventListener('keydown', e => { if (e.key === 'Enter') confirmName(); }); + + nameConfirmBtn = createButton('CONFIRM'); + nameConfirmBtn.style('font-size', '16px'); + nameConfirmBtn.style('font-family', 'Google Sans, Nunito, sans-serif'); + nameConfirmBtn.style('font-weight', '700'); + nameConfirmBtn.style('letter-spacing', '2px'); + nameConfirmBtn.style('background', 'rgba(18, 80, 150, 0.95)'); + nameConfirmBtn.style('color', '#cce8ff'); + nameConfirmBtn.style('border', '2px solid rgba(90, 185, 255, 0.7)'); + nameConfirmBtn.style('border-radius', '2px'); + nameConfirmBtn.style('padding', '10px 36px'); + nameConfirmBtn.style('cursor', 'pointer'); + nameConfirmBtn.style('position', 'absolute'); + nameConfirmBtn.mousePressed(confirmName); + + positionNameUI(); +} + +function positionNameUI() { + let pw = min(windowWidth * 0.5, 440); + let ph = 240; + let px = (windowWidth - pw) / 2; + let py = (windowHeight - ph) / 2; + let inputW = pw * 0.65; + let inputX = px + (pw - inputW) / 2; + nameInput.position(inputX, py + 104); + nameInput.style('width', inputW + 'px'); + nameConfirmBtn.position(px + pw / 2 - 80, py + 164); +} + +function shuffleCharOrder() { + charOrder = [0, 1, 2, 3, 4, 5, 6]; + for (let i = charOrder.length - 1; i > 0; i--) { + let j = floor(random(i + 1)); + [charOrder[i], charOrder[j]] = [charOrder[j], charOrder[i]]; + } + charOrderIdx = 0; } function windowResized() { resizeCanvas(windowWidth, windowHeight); resetCoconutHome(); + if (gameState === 'name') positionNameUI(); } + function resetCoconutHome() { coconut.r = min(windowWidth, windowHeight) * 0.13; coconut.x = width / 2; @@ -84,100 +167,41 @@ function resetCoconutHome() { // ====== DRAW ====== function draw() { - background(lerpColor(color(30, 50, 80), color(25, 90, 200), bgBlue / 255)); - if (bgBlue > 0) bgBlue = max(0, bgBlue - 4); + imageMode(CORNER); + image(beachImg, 0, 0, width, height); + if (bgBlue > 0) { + fill(25, 90, 200, bgBlue * 0.4); + noStroke(); + rect(0, 0, width, height); + bgBlue = max(0, bgBlue - 4); + } - drawBackground(); - drawTrees(); - updateShake(); - updateFlyAway(); - drawCoconut(); - drawTrail(); + if (gameState === 'name') { drawNameEntry(); return; } + + if (gameState === 'playing') { + checkCoconutTimer(); + updateShake(); + updateEnter(); + updateFlyAway(); + drawCoconut(); + drawCoconutTimer(); + drawTrail(); + updateImgParticles(); + drawImgParticles(); + } drawParticles(); drawFlash(); - drawScore(); - drawKakamoraSlots(); - updateFlyingKakamoraList(); - drawFlyingKakamoraList(); -} - -// ====== BACKGROUND ====== -function drawBackground() { - const horizonY = height * 0.60; - const sandY = height * 0.70; - waveTime += 0.016; - noStroke(); - - // Ocean base - fill(18, 90, 165); - rect(0, horizonY, width, sandY - horizonY); - - // Ocean depth gradient (slightly lighter toward shore) - fill(40, 120, 190, 80); - rect(0, (horizonY + sandY) / 2, width, (sandY - horizonY) / 2); - - // 3 animated wave layers (far → near) - const waveLayers = [ - { t: 0.18, amp: 3, freq: 0.007, speed: 0.7, alpha: 60, wh: 3 }, - { t: 0.52, amp: 6, freq: 0.009, speed: 1.1, alpha: 100, wh: 5 }, - { t: 0.82, amp: 9, freq: 0.011, speed: 1.6, alpha: 145, wh: 8 }, - ]; - for (let lyr of waveLayers) { - let wy = horizonY + (sandY - horizonY) * lyr.t; - fill(255, 255, 255, lyr.alpha); - beginShape(); - vertex(0, wy + lyr.wh); - for (let x = 0; x <= width; x += 8) { - vertex(x, wy + sin(x * lyr.freq + waveTime * lyr.speed) * lyr.amp); - } - vertex(width, wy + lyr.wh); - endShape(CLOSE); + if (gameState !== 'intro' && gameState !== 'name') { + drawKakamoraSlots(); + updateFlyingKakamoraList(); + drawFlyingKakamoraList(); } - // Shore foam - fill(255, 255, 255, 90); - beginShape(); - vertex(0, sandY + 6); - for (let x = 0; x <= width; x += 6) { - vertex(x, sandY + sin(x * 0.013 + waveTime * 2.2) * 4); - } - vertex(width, sandY + 6); - endShape(CLOSE); - - // Sand - fill(210, 185, 135); - rect(0, sandY, width, height - sandY); - - // Wet sand strip at shore - fill(185, 158, 105, 200); - rect(0, sandY, width, 14); - - // Sand highlight - fill(235, 215, 165, 50); - rect(0, sandY + 10, width, 35); + if (gameState === 'intro') drawIntroQuest(); + if (gameState === 'departure') drawDepartureQuest(); } -// ====== TREES ====== -function drawTrees() { - imageMode(CENTER); - for (let i = 0; i < TREE_COUNT; i++) { - let stage = constrain(treeHits - i * 3, 0, 3); - let img; - if (stage === 0) img = treeImgs.full[i % treeImgs.full.length]; - else if (stage === 1) img = treeImgs.half[i % treeImgs.half.length]; - else if (stage === 2) img = treeImgs.one[0]; - else img = treeImgs.empty[i % treeImgs.empty.length]; - - let x = ((i + 0.5) / TREE_COUNT) * width; - let h = height * (0.30 + (i % 3) * 0.02); - let w = h * (img.width / img.height); - let groundY = height * 0.82; - let cy = groundY - h / 2; - - image(img, x, cy, w, h); - } -} // ====== COCONUT ====== function drawCoconut() { @@ -190,22 +214,50 @@ function drawCoconut() { image(coconutImgs[coconut.state], 0, 0, coconut.r * 2, coconut.r * 2); noTint(); pop(); - } else if (coconut.state === 0) { - // idle: p5play 애니메이션 현재 프레임을 수동 렌더링 - let frame = idleSprite && idleSprite.ani ? idleSprite.ani.src : null; - let img = frame || coconutImgs[0]; - image(img, coconut.x + coconut.shakeX, coconut.y, coconut.r * 2, coconut.r * 2); } else { - image(coconutImgs[coconut.state], coconut.x + coconut.shakeX, coconut.y, coconut.r * 2, coconut.r * 2); + let alpha = coconut.entering ? lerp(40, 255, min(coconut.enterT * 1.8, 1)) : 255; + tint(255, alpha); + push(); + translate(coconut.x + coconut.shakeX, coconut.y + coconut.shakeY); + rotate(coconut.baseAngle); + imageMode(CENTER); + image(coconutImgs[coconut.state], 0, 0, coconut.r * 2, coconut.r * 2); + pop(); + noTint(); } } +function updateEnter() { + if (!coconut.entering) return; + coconut.enterT += 0.038; + let ease = 1 - pow(1 - min(coconut.enterT, 1), 3); + coconut.x = lerp(coconut.enterFromX, width / 2, ease); + coconut.y = lerp(coconut.enterFromY, height * 0.42, ease); + if (coconut.enterT >= 1) { + coconut.entering = false; + coconut.x = width / 2; + coconut.y = height * 0.42; + } +} + +function startCoconutEntrance() { + coconut.entering = true; + coconut.enterT = 0; + coconut.enterFromX = width - 100; + coconut.enterFromY = 100; + coconut.x = width - 100; + coconut.y = 100; + coconut.baseAngle = random(TWO_PI); +} + function updateShake() { if (coconut.shakeTimer > 0) { - coconut.shakeX = sin(coconut.shakeTimer * 2.2) * coconut.shakeTimer * 0.55; + coconut.shakeX = sin(coconut.shakeTimer * 2.4) * coconut.shakeTimer * 0.7; + coconut.shakeY = cos(coconut.shakeTimer * 3.1) * coconut.shakeTimer * 0.4; coconut.shakeTimer--; } else { coconut.shakeX = 0; + coconut.shakeY = 0; } } @@ -214,27 +266,34 @@ function updateFlyAway() { coconut.x += coconut.flyVX; coconut.y += coconut.flyVY; - coconut.flyVY += 0.9; + coconut.flyVY += 0.55; coconut.flyRotation += coconut.flyRotSpeed; - coconut.flyAlpha -= 14; + coconut.flyAlpha -= 7; if (coconut.flyAlpha <= 0 || coconut.y > height + 100) { if (score < 10) launchKakamoraToSlot(score); score++; - treeHits++; - coconut.x = width / 2; - coconut.y = height * 0.42; coconut.state = 0; + coconut.slices = 0; coconut.flying = false; coconut.flyAlpha = 255; coconut.flyRotation = 0; coconut.shakeX = 0; + coconut.shakeY = 0; coconut.shakeTimer = 0; + coconutTimerEnd = millis() + COCONUT_TIME_MS; + + if (score >= 7 && gameState === 'playing') { + gameState = 'departure'; + } else { + startCoconutEntrance(); + } } } // ====== TRAIL INPUT ====== function mouseDragged() { + if (gameState !== 'playing') return; let pos = createVector(mouseX, mouseY); trail.push(pos); @@ -248,101 +307,206 @@ function mouseDragged() { velocities.push(0); } - // 날아가는 중엔 드래그 무시 if (dist(mouseX, mouseY, coconut.x, coconut.y) < coconut.r && !coconut.flying) { - spawnParticles(mouseX, mouseY, dx, dy); - coconut.shakeTimer = max(coconut.shakeTimer, 10); + spawnParticles(mouseX, mouseY); + spawnImgParticles(mouseX, mouseY, dx, dy); + coconut.shakeTimer = max(coconut.shakeTimer, 18); } - if (trail.length > 15) trail.shift(); - if (velocities.length > 15) velocities.shift(); + if (trail.length > 22) trail.shift(); + if (velocities.length > 22) velocities.shift(); } function mouseReleased() { - // 드래그가 코코넛을 지나쳤는지 확인 + if (gameState !== 'playing') { trail = []; velocities = []; return; } + let hits = trail.filter(p => dist(p.x, p.y, coconut.x, coconut.y) < coconut.r).length; if (hits >= 2 && !coconut.flying) { - coconut.state++; + sliceSound.play(); + coconut.slices++; + coconut.state = floor(coconut.slices / 2); flashAlpha = 25; - if (coconut.state >= 4) { - // 4번째 드래그 → 날아가기 시작 + if (coconut.slices >= 8) { + let power = floor(max(0, (coconutTimerEnd - millis()) / COCONUT_TIME_MS) * 100); + slotPowers[score] = power; coconut.flying = true; - coconut.flyVX = random([-1, 1]) * random(10, 16); - coconut.flyVY = random(-22, -16); - coconut.flyRotSpeed = random(-0.28, 0.28); + coconut.flyVX = random([-1, 1]) * random(3, 6); + coconut.flyVY = random(-6, -2); + coconut.flyRotSpeed = random() > 0.5 ? random(0.07, 0.13) : random(-0.13, -0.07); bgBlue = 255; } } + // linger the slash line briefly + lingerTrail = trail.slice(); + lingerVelocities = velocities.slice(); + slashLingerAlpha = 255; trail = []; velocities = []; } // ====== TRAIL DRAW ====== -function drawTrail() { +function drawSlashSegments(pts, vels, masterAlpha) { + if (pts.length < 2) return; noFill(); - for (let i = 1; i < trail.length; i++) { - let speed = velocities[i] || 0; - strokeWeight(map(speed, 0, 50, 1, 8)); - stroke(255, 200); - line(trail[i - 1].x, trail[i - 1].y, trail[i].x, trail[i].y); + for (let i = 1; i < pts.length; i++) { + let t = i / pts.length; // 0=tail, 1=head + let speed = vels[i] || 0; + let a = t * masterAlpha; + + // Outer glow + strokeWeight(map(speed, 0, 40, 6, 22)); + stroke(255, 240, 180, a * 0.18); + line(pts[i-1].x, pts[i-1].y, pts[i].x, pts[i].y); + + // Mid glow + strokeWeight(map(speed, 0, 40, 3, 12)); + stroke(255, 255, 220, a * 0.45); + line(pts[i-1].x, pts[i-1].y, pts[i].x, pts[i].y); + + // Sharp core + strokeWeight(map(speed, 0, 40, 1, 4)); + stroke(255, 255, 255, a); + line(pts[i-1].x, pts[i-1].y, pts[i].x, pts[i].y); + } + noStroke(); +} + +function drawTrail() { + drawSlashSegments(trail, velocities, 255); + + // Linger fade after release + if (slashLingerAlpha > 0) { + drawSlashSegments(lingerTrail, lingerVelocities, slashLingerAlpha); + slashLingerAlpha -= 28; + if (slashLingerAlpha <= 0) { lingerTrail = []; lingerVelocities = []; } } } -// ====== SCORE ====== -function drawScore() { - noStroke(); - textSize(22); - textAlign(LEFT, TOP); - fill(255, 255, 255, 220); - text(`coconut x${score}`, 20, 20); +// ====== SLOT LAYOUT (horizontal top-left) ====== +const S_MARGIN = 14; +const S_HDRH = 30; +const S_PAD = 12; +const S_GAP = 10; + +function getSlotSize() { + // fit 7 slots between left margin and timer area (right ~220px) + let available = width - S_MARGIN * 2 - S_PAD * 2 - 6 * S_GAP - 220; + return constrain(floor(available / 7), 60, 92); } -// ====== SLOT POSITION HELPER ====== function getSlotPos(i) { - const gap = 8; - const size = floor((width - 40 - gap * 9) / 10); - const startX = 20 + size / 2; + let sz = getSlotSize(); return { - x: startX + i * (size + gap), - y: height - size / 2 - 16, - size + x: S_MARGIN + S_PAD + sz / 2 + i * (sz + S_GAP), + y: S_MARGIN + S_HDRH + S_PAD + sz / 2, + size: sz }; } // ====== KAKAMORA SLOTS ====== function drawKakamoraSlots() { - for (let i = 0; i < 10; i++) { + let sz = getSlotSize(); + let panelW = S_PAD * 2 + 7 * sz + 6 * S_GAP; + let pwrH = 20; + let panelH = S_HDRH + S_PAD + sz + pwrH + S_PAD; + let px = S_MARGIN; + let py = S_MARGIN; + + // Panel background + fill(12, 16, 26, 210); + noStroke(); + rect(px, py, panelW, panelH, 2); + + // Header bar + fill(35, 42, 60, 255); + noStroke(); + rect(px, py, panelW, S_HDRH, 2, 2, 0, 0); + fill(255, 195, 55); + textAlign(LEFT, CENTER); + textFont(moanaFont); + textSize(13); + text('WARRIOR CREW', px + S_PAD, py + S_HDRH / 2); + // score counter right side + textAlign(RIGHT, CENTER); + fill(200, 210, 230); + textSize(12); + text(`${score} / 7`, px + panelW - S_PAD, py + S_HDRH / 2); + textFont('sans-serif'); + + for (let i = 0; i < 7; i++) { let { x, y, size } = getSlotPos(i); let filled = i < score; - if (filled) { + if (filled && slotChars[i] >= 0) { + let frames = charFrames[slotChars[i]]; + let frameIdx = floor((frameCount + slotOffsets[i]) / ANI_FPS) % frames.length; + let img = frames[frameIdx]; + let sc = min(size / img.width, size / img.height) * CHAR_SCALES[slotChars[i]]; drawingContext.save(); drawingContext.beginPath(); drawingContext.arc(x, y, size / 2, 0, Math.PI * 2); drawingContext.clip(); - imageMode(CORNER); - image(kakomoraImgs[i % 5], x - size / 2, y - size / 2, size, size); + imageMode(CENTER); + image(img, x, y, img.width * sc, img.height * sc); drawingContext.restore(); - noFill(); - stroke(255); - strokeWeight(2.5); - ellipse(x, y, size, size); - } else { - fill(50, 50, 50, 190); - stroke(110, 110, 110); - strokeWeight(1.5); - ellipse(x, y, size, size); + // Power display — threshold at 55: pwr >= 55 = full green + let pwr = slotPowers[i] !== undefined ? slotPowers[i] : 0; + let pwrT = min(pwr / 55, 1); + let pwrCol = lerpColor(color(220, 50, 50), color(50, 215, 85), pwrT); + // Glow ring (outer) — pulses slightly based on power + noFill(); + strokeWeight(7); + stroke(red(pwrCol), green(pwrCol), blue(pwrCol), 55 + pwrT * 50); + ellipse(x, y, size + 11, size + 11); + // Sharp ring (inner) + strokeWeight(2.5); + stroke(pwrCol); + ellipse(x, y, size + 2, size + 2); noStroke(); - fill(170, 170, 170); - textSize(size * 0.28); + + // Power badge below circle + let pillW = floor(size * 0.86); + let pillH = 23; + let pillX = x - pillW / 2; + let pillY = y + size / 2 + 6; + // shadow + fill(0, 0, 0, 150); + rect(pillX + 1, pillY + 1, pillW, pillH, 12); + // dark background + fill(8, 10, 20, 240); + rect(pillX, pillY, pillW, pillH, 12); + // progress fill — bar shows true pwr/100, color uses lowered threshold + fill(red(pwrCol), green(pwrCol), blue(pwrCol), 210); + let barW = max(0, (pillW - 4) * (pwr / 100)); + if (barW > 0) rect(pillX + 2, pillY + 2, barW, pillH - 4, 10); + // label: "PWR XX" + fill(255, 255, 255, 245); textAlign(CENTER, CENTER); - text('?', x, y); + textFont('Google Sans, Nunito, sans-serif'); + textStyle(BOLD); + textSize(12); + text(`PWR ${pwr}`, x, pillY + pillH / 2); + textStyle(NORMAL); + textFont('sans-serif'); + } else { + fill(22, 28, 42); + noStroke(); + ellipse(x, y, size, size); + stroke(50, 58, 78); + strokeWeight(1.5); + noFill(); + ellipse(x, y, size, size); + fill(55, 65, 88); + noStroke(); + textAlign(CENTER, CENTER); + textSize(13); + text(i + 1, x, y); } } } @@ -350,13 +514,18 @@ function drawKakamoraSlots() { // ====== FLYING KAKAMORA ====== function launchKakamoraToSlot(slotIdx) { let { x: tx, y: ty, size } = getSlotPos(slotIdx); + let charIdx = charOrder[charOrderIdx % 7]; + charOrderIdx++; + slotChars[slotIdx] = charIdx; + slotOffsets[slotIdx] = floor(random(1000)); flyingKakamoraList.push({ startX: width / 2, startY: height * 0.42, targetX: tx, targetY: ty, t: 0, - imgIdx: slotIdx % 5, + charIdx, + questShown: false, size }); } @@ -373,41 +542,38 @@ function drawFlyingKakamoraList() { let y = lerp(k.startY, k.targetY, ease) - sin(k.t * PI) * height * 0.3; let sz = lerp(k.size * 1.8, k.size, ease); + let frames = charFrames[k.charIdx]; + let frameIdx = floor(frameCount / ANI_FPS) % frames.length; + let img = frames[frameIdx]; + let sc = min(sz / img.width, sz / img.height) * CHAR_SCALES[k.charIdx]; drawingContext.save(); drawingContext.beginPath(); drawingContext.arc(x, y, sz / 2, 0, Math.PI * 2); drawingContext.clip(); - imageMode(CORNER); - image(kakomoraImgs[k.imgIdx], x - sz / 2, y - sz / 2, sz, sz); + imageMode(CENTER); + image(img, x, y, img.width * sc, img.height * sc); drawingContext.restore(); - - noFill(); - stroke(255, 200); - strokeWeight(2); - ellipse(x, y, sz, sz); } } // ====== PARTICLES ====== class Particle { - constructor(x, y, dx, dy) { + constructor(x, y) { this.pos = createVector(x, y); - // 드래그 반대 방향 + 랜덤 퍼짐 - let baseAngle = (dx !== 0 || dy !== 0) - ? atan2(dy, dx) + PI + random(-0.7, 0.7) - : random(TWO_PI); - let spd = random(1.5, 5); + // 전방향 랜덤 퍼짐 (드래그 방향 편향 없이 360도) + let baseAngle = random(TWO_PI); + let spd = random(2, 6); this.vel = createVector(cos(baseAngle) * spd, sin(baseAngle) * spd); this.life = 255; - this.r = random(3, 7); - this.isHusk = random() > 0.4; // 60% 껍질(갈색), 40% 과육(크림) + this.r = random(1.5, 3.5); + this.isHusk = random() > 0.4; } update() { this.pos.add(this.vel); - this.vel.y += 0.18; // 중력 - this.vel.mult(0.93); // 마찰 - this.life -= 7; + this.vel.y += 0.22; + this.vel.mult(0.91); + this.life -= 9; } draw() { @@ -418,8 +584,8 @@ class Particle { } } -function spawnParticles(x, y, dx, dy) { - for (let i = 0; i < 5; i++) particles.push(new Particle(x, y, dx, dy)); +function spawnParticles(x, y) { + for (let i = 0; i < 12; i++) particles.push(new Particle(x, y)); } function drawParticles() { @@ -438,3 +604,235 @@ function drawFlash() { flashAlpha -= 20; } } + +// ====== COCONUT TIMER ====== +function checkCoconutTimer() { + if (coconut.flying || coconutTimerEnd === 0) return; + if (millis() > coconutTimerEnd) { + coconut.state = 0; + coconut.slices = 0; + coconutTimerEnd = millis() + COCONUT_TIME_MS; + } +} + +function drawCoconutTimer() { + if (coconut.flying || coconutTimerEnd === 0) return; + let fraction = max(0, (coconutTimerEnd - millis()) / COCONUT_TIME_MS); + let secs = (fraction * (COCONUT_TIME_MS / 1000)).toFixed(1); + let c = lerpColor(color(255, 60, 60), color(80, 220, 100), fraction); + + // Top-right pill + let tx = width - 18; + let ty = 18; + let label = `TIME ${secs}s`; + textFont('Google Sans, Nunito, sans-serif'); + textSize(26); + let tw = textWidth(label) + 36; + let th = 54; + + fill(12, 16, 26, 220); + noStroke(); + rect(tx - tw, ty, tw, th, 2); + + // Progress bar + fill(30, 37, 54); + rect(tx - tw + 8, ty + th - 12, tw - 16, 6, 2); + fill(c); + rect(tx - tw + 8, ty + th - 12, (tw - 16) * fraction, 6, 2); + + fill(c); + textAlign(RIGHT, TOP); + text(label, tx - 10, ty + 10); + textFont('sans-serif'); + +} + +// ====== SHARED DIALOG HELPER ====== +function drawDialogBox(dx, dy, dw, dh, speakerName, lines, btnLabel) { + fill(0, 0, 0, 90); + noStroke(); + rect(0, 0, width, height); + + fill(28, 32, 45, 185); + noStroke(); + rect(dx, dy, dw, dh, 2); + + // Name banner — sharp corners + textFont(moanaFont); + textSize(20); + let bw = textWidth(speakerName) + 44; + let bh = 36; + fill(210, 105, 20); + stroke(255, 175, 70); + strokeWeight(2); + rect(dx + 22, dy - bh / 2, bw, bh, 2); + noStroke(); + fill(255); + textAlign(LEFT, CENTER); + text(speakerName, dx + 34, dy + 2); + + // Quest lines — system font for readability + textFont('sans-serif'); + textSize(14); + fill(210, 210, 215); + textAlign(LEFT, TOP); + for (let i = 0; i < lines.length; i++) { + text(`• ${lines[i]}`, dx + 28, dy + 46 + i * 34); + } + + // Button row — Moana font + let btnW = 180; + let btnH = 36; + let btnX = dx + dw / 2 - btnW / 2; + let btnY = dy + dh - btnH - 16; + fill(18, 80, 150); + noStroke(); + rect(btnX, btnY, btnW, btnH, 2); + stroke(90, 185, 255); + strokeWeight(1.5); + noFill(); + rect(btnX, btnY, btnW, btnH, 2); + noStroke(); + fill(210, 240, 255); + textFont(moanaFont); + textAlign(CENTER, CENTER); + textSize(18); + text(btnLabel, dx + dw / 2, btnY + btnH / 2); + textFont('sans-serif'); +} + +// ====== INTRO QUEST ====== +function drawIntroQuest() { + let dw = min(width * 0.54, 580); + let dh = 230; + let dx = (width - dw) / 2; + let dy = (height - dh) / 2; + drawDialogBox(dx, dy, dw, dh, 'Chief Tamatoa', [ + 'You have 5 seconds to crack each coconut and claim a warrior.', + 'Strike fast. The quicker the slash, the stronger they become.', + 'Recruit all 7 warriors before we head into battle.' + ], 'BEGIN'); +} + +// ====== DEPARTURE QUEST ====== +function drawDepartureQuest() { + let dw = min(width * 0.54, 580); + let dh = 190; + let dx = (width - dw) / 2; + let dy = height - dh - 28; + drawDialogBox(dx, dy, dw, dh, 'Chief Tamatoa', [ + 'Seven warriors stand ready. The crew is assembled.', + 'The enemy will not hold back. Neither will we.', + 'The tide is turning. Now we sail and fight.' + ], 'SET SAIL'); +} + +// ====== NAME ENTRY ====== +function drawNameEntry() { + fill(0, 0, 0, 130); + noStroke(); + rect(0, 0, width, height); + + let pw = min(width * 0.5, 440); + let ph = 240; + let px = (width - pw) / 2; + let py = (height - ph) / 2; + + fill(14, 20, 36, 225); + noStroke(); + rect(px, py, pw, ph, 2); + + // Header bar + fill(24, 35, 58); + rect(px, py, pw, 42, 2, 2, 0, 0); + textFont(moanaFont); + textAlign(CENTER, CENTER); + textSize(18); + fill(255, 215, 80); + text('KAKAMORA WARRIOR', width / 2, py + 21); + textFont('sans-serif'); + + // Prompt + fill(160, 185, 220); + textAlign(CENTER, CENTER); + textSize(13); + text('Enter your name to join the crew.', width / 2, py + 72); + text('Your score will be saved to the leaderboard.', width / 2, py + 90); +} + +function confirmName() { + let val = nameInput.value().trim(); + if (val.length === 0) return; + playerName = val; + nameInput.hide(); + nameConfirmBtn.hide(); + gameState = 'intro'; +} + + +// ====== IMAGE PARTICLES ====== +class ImgParticle { + constructor(x, y, dx, dy) { + this.pos = createVector(x, y); + let baseAngle = (dx !== 0 || dy !== 0) + ? atan2(dy, dx) + PI + random(-0.6, 0.6) + : random(TWO_PI); + let spd = random(1.5, 4.5); + this.vel = createVector(cos(baseAngle) * spd, sin(baseAngle) * spd); + this.life = 255; + this.size = random(20, 40); + this.img = random(effectImgs); + this.rot = random(TWO_PI); + this.rotSpeed = random(-0.18, 0.18); + } + + update() { + this.pos.add(this.vel); + this.vel.y += 0.14; + this.vel.mult(0.91); + this.life -= 14; + this.rot += this.rotSpeed; + } + + draw() { + push(); + translate(this.pos.x, this.pos.y); + rotate(this.rot); + tint(255, this.life); + imageMode(CENTER); + image(this.img, 0, 0, this.size, this.size); + noTint(); + pop(); + } +} + +function spawnImgParticles(x, y, dx, dy) { + for (let i = 0; i < 4; i++) imgParticles.push(new ImgParticle(x, y, dx, dy)); +} + +function updateImgParticles() { + for (let i = imgParticles.length - 1; i >= 0; i--) { + imgParticles[i].update(); + if (imgParticles[i].life <= 0) imgParticles.splice(i, 1); + } +} + +function drawImgParticles() { + for (let p of imgParticles) p.draw(); +} + +// ====== MOUSE HANDLER ====== +function mousePressed() { + if (gameState === 'name') return; + if (gameState === 'intro') { + gameState = 'playing'; + coconutTimerEnd = millis() + COCONUT_TIME_MS; + startCoconutEntrance(); + beachSound.play(); + return; + } + if (gameState === 'departure') { + // 출항 처리 (추후 배틀 씬으로 전환) + return; + } +}