added tree, animated character
BIN
assets/.DS_Store
vendored
Normal file
BIN
assets/kakamora-characters/co_ani_1.png
Normal file
|
After Width: | Height: | Size: 450 KiB |
BIN
assets/kakamora-characters/co_ani_2.png
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
assets/kakamora-characters/co_ani_3.png
Normal file
|
After Width: | Height: | Size: 397 KiB |
BIN
assets/kakamora-characters/co_ani_4.png
Normal file
|
After Width: | Height: | Size: 386 KiB |
BIN
assets/kakamora-characters/co_ani_5.png
Normal file
|
After Width: | Height: | Size: 395 KiB |
BIN
assets/tree/tree_empty_1.png
Normal file
|
After Width: | Height: | Size: 772 KiB |
BIN
assets/tree/tree_empty_2.png
Normal file
|
After Width: | Height: | Size: 771 KiB |
BIN
assets/tree/tree_empty_3.png
Normal file
|
After Width: | Height: | Size: 689 KiB |
BIN
assets/tree/tree_full_1.png
Normal file
|
After Width: | Height: | Size: 701 KiB |
BIN
assets/tree/tree_full_2.png
Normal file
|
After Width: | Height: | Size: 674 KiB |
BIN
assets/tree/tree_half_1.png
Normal file
|
After Width: | Height: | Size: 704 KiB |
BIN
assets/tree/tree_half_2.png
Normal file
|
After Width: | Height: | Size: 704 KiB |
BIN
assets/tree/tree_one_1.png
Normal file
|
After Width: | Height: | Size: 713 KiB |
@@ -8,6 +8,7 @@
|
||||
|
||||
<body>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5play@3/p5play.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
<script src="src/Player.js"></script>
|
||||
|
||||
171
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);
|
||||
}
|
||||
}
|
||||
|
||||