diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..cd077a4 Binary files /dev/null and b/.DS_Store differ diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 0000000..bd9b06f Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/kakamora-characters/co_ani_1.png b/assets/kakamora-characters/co_ani_1.png new file mode 100644 index 0000000..1fd4eaf Binary files /dev/null and b/assets/kakamora-characters/co_ani_1.png differ diff --git a/assets/kakamora-characters/co_ani_2.png b/assets/kakamora-characters/co_ani_2.png new file mode 100644 index 0000000..0b5d9b9 Binary files /dev/null and b/assets/kakamora-characters/co_ani_2.png differ diff --git a/assets/kakamora-characters/co_ani_3.png b/assets/kakamora-characters/co_ani_3.png new file mode 100644 index 0000000..be628fd Binary files /dev/null and b/assets/kakamora-characters/co_ani_3.png differ diff --git a/assets/kakamora-characters/co_ani_4.png b/assets/kakamora-characters/co_ani_4.png new file mode 100644 index 0000000..5a58bc4 Binary files /dev/null and b/assets/kakamora-characters/co_ani_4.png differ diff --git a/assets/kakamora-characters/co_ani_5.png b/assets/kakamora-characters/co_ani_5.png new file mode 100644 index 0000000..1a70c02 Binary files /dev/null and b/assets/kakamora-characters/co_ani_5.png differ diff --git a/assets/tree/tree_empty_1.png b/assets/tree/tree_empty_1.png new file mode 100644 index 0000000..5cc4704 Binary files /dev/null and b/assets/tree/tree_empty_1.png differ diff --git a/assets/tree/tree_empty_2.png b/assets/tree/tree_empty_2.png new file mode 100644 index 0000000..426d13b Binary files /dev/null and b/assets/tree/tree_empty_2.png differ diff --git a/assets/tree/tree_empty_3.png b/assets/tree/tree_empty_3.png new file mode 100644 index 0000000..b667345 Binary files /dev/null and b/assets/tree/tree_empty_3.png differ diff --git a/assets/tree/tree_full_1.png b/assets/tree/tree_full_1.png new file mode 100644 index 0000000..3000db1 Binary files /dev/null and b/assets/tree/tree_full_1.png differ diff --git a/assets/tree/tree_full_2.png b/assets/tree/tree_full_2.png new file mode 100644 index 0000000..1b05668 Binary files /dev/null and b/assets/tree/tree_full_2.png differ diff --git a/assets/tree/tree_half_1.png b/assets/tree/tree_half_1.png new file mode 100644 index 0000000..c308149 Binary files /dev/null and b/assets/tree/tree_half_1.png differ diff --git a/assets/tree/tree_half_2.png b/assets/tree/tree_half_2.png new file mode 100644 index 0000000..6b610be Binary files /dev/null and b/assets/tree/tree_half_2.png differ diff --git a/assets/tree/tree_one_1.png b/assets/tree/tree_one_1.png new file mode 100644 index 0000000..cbcfed7 Binary files /dev/null and b/assets/tree/tree_one_1.png differ diff --git a/index.html b/index.html index 7433eff..c303a62 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,7 @@
+ diff --git a/sketch.js b/sketch.js index 052e022..f183326 100644 --- a/sketch.js +++ b/sketch.js @@ -1,18 +1,24 @@ -// ====== GLOBALS ====== let trail = []; let velocities = []; let particles = []; let flashAlpha = 0; let score = 0; -let bgBlue = 0; // 0=dark, 255=blue, fades out +let bgBlue = 0; + +let treeImgs = {}; +let treeHits = 0; +const TREE_COUNT = 8; +let waveTime = 0; + +let idleSprite; +let idleAni; let coconut = { - x: 0, y: 0, // setup()에서 초기화 + x: 0, y: 0, r: 0, state: 0, shakeX: 0, shakeTimer: 0, - // fly-away flying: false, flyVX: 0, flyVY: 0, flyAlpha: 255, @@ -33,12 +39,36 @@ function preload() { 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'), + ]; + + idleAni = loadAni('assets/kakamora-characters/co_ani_1.png', 5); } // ====== SETUP ====== function setup() { createCanvas(windowWidth, windowHeight); 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; } function windowResized() { @@ -54,9 +84,11 @@ function resetCoconutHome() { // ====== DRAW ====== function draw() { - background(lerpColor(color(30), color(25, 90, 200), bgBlue / 255)); + background(lerpColor(color(30, 50, 80), color(25, 90, 200), bgBlue / 255)); if (bgBlue > 0) bgBlue = max(0, bgBlue - 4); + drawBackground(); + drawTrees(); updateShake(); updateFlyAway(); drawCoconut(); @@ -70,6 +102,83 @@ function draw() { 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); + } + + // 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); +} + +// ====== 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() { imageMode(CENTER); @@ -81,6 +190,11 @@ 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); } @@ -98,22 +212,23 @@ function updateShake() { function updateFlyAway() { if (!coconut.flying) return; - coconut.x += coconut.flyVX; - coconut.y += coconut.flyVY; - coconut.flyVY += 0.9; + coconut.x += coconut.flyVX; + coconut.y += coconut.flyVY; + coconut.flyVY += 0.9; coconut.flyRotation += coconut.flyRotSpeed; - coconut.flyAlpha -= 14; + coconut.flyAlpha -= 14; if (coconut.flyAlpha <= 0 || coconut.y > height + 100) { if (score < 10) launchKakamoraToSlot(score); score++; - coconut.x = width / 2; - coconut.y = height * 0.42; - coconut.state = 0; - coconut.flying = false; - coconut.flyAlpha = 255; + treeHits++; + coconut.x = width / 2; + coconut.y = height * 0.42; + coconut.state = 0; + coconut.flying = false; + coconut.flyAlpha = 255; coconut.flyRotation = 0; - coconut.shakeX = 0; + coconut.shakeX = 0; coconut.shakeTimer = 0; } } @@ -139,7 +254,7 @@ function mouseDragged() { coconut.shakeTimer = max(coconut.shakeTimer, 10); } - if (trail.length > 15) trail.shift(); + if (trail.length > 15) trail.shift(); if (velocities.length > 15) velocities.shift(); } @@ -152,9 +267,9 @@ function mouseReleased() { flashAlpha = 25; if (coconut.state >= 4) { // 4번째 드래그 → 날아가기 시작 - coconut.flying = true; - coconut.flyVX = random([-1, 1]) * random(10, 16); - coconut.flyVY = random(-22, -16); + coconut.flying = true; + coconut.flyVX = random([-1, 1]) * random(10, 16); + coconut.flyVY = random(-22, -16); coconut.flyRotSpeed = random(-0.28, 0.28); bgBlue = 255; } @@ -171,7 +286,7 @@ function drawTrail() { 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); + line(trail[i - 1].x, trail[i - 1].y, trail[i].x, trail[i].y); } } @@ -188,7 +303,7 @@ function drawScore() { // ====== SLOT POSITION HELPER ====== function getSlotPos(i) { - const gap = 8; + const gap = 8; const size = floor((width - 40 - gap * 9) / 10); const startX = 20 + size / 2; return { @@ -254,9 +369,9 @@ function updateFlyingKakamoraList() { function drawFlyingKakamoraList() { for (let k of flyingKakamoraList) { let ease = 1 - pow(1 - min(k.t, 1), 2); - let x = lerp(k.startX, k.targetX, ease); - 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 x = lerp(k.startX, k.targetX, ease); + 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); drawingContext.save(); drawingContext.beginPath(); @@ -284,21 +399,21 @@ class Particle { let spd = random(1.5, 5); this.vel = createVector(cos(baseAngle) * spd, sin(baseAngle) * spd); this.life = 255; - this.r = random(3, 7); + this.r = random(3, 7); this.isHusk = random() > 0.4; // 60% 껍질(갈색), 40% 과육(크림) } update() { this.pos.add(this.vel); - this.vel.y += 0.18; // 중력 + this.vel.y += 0.18; // 중력 this.vel.mult(0.93); // 마찰 - this.life -= 7; + this.life -= 7; } draw() { noStroke(); if (this.isHusk) fill(158, 108, 52, this.life); - else fill(242, 224, 188, this.life); + else fill(242, 224, 188, this.life); ellipse(this.pos.x, this.pos.y, this.r, this.r * 0.55); } }