diff --git a/imageAsset.js b/imageAsset.js index 9660034..97cbd98 100644 --- a/imageAsset.js +++ b/imageAsset.js @@ -1,5 +1,5 @@ let spriteSheets = {}; -let P1imgs = {}, P2imgs = {}, itemimgs = {}; +let P1imgs = {}, P2imgs = {}, itemimgs = {}, tileimgs = {}; let backgroundManager; let gravity = 0.8; @@ -55,14 +55,55 @@ function sliceAssets() { P2imgs = { idle:[li], walk:[lw1,lw2,lw3], jump:[lj], shoot:[la] }; // 6) 아이템 및 특수 투사체 프레임 - const ss = spriteSheets.specialweapon; + const spsrc = spriteSheets.specialweapon; const ow=16, oh=16; const mush = createImage(ow,oh); mush.copy(src,1,2126,ow,oh,0,0,ow,oh); const poison= createImage(ow,oh); poison.copy(src,1,2143,ow,oh,0,0,ow,oh); const giant = createImage(2*ow,2*oh); giant.copy(src,35,2143,2*ow,2*oh,0,0,2*ow,2*oh); const fire = createImage(ow/2,oh/2); fire.copy(src,101,2177,ow/2,oh/2,0,0,ow/2,oh/2); const bomb = createImage(ow,oh); bomb.copy(src,194,2143,ow,oh,0,0,ow,oh); - const bm = createImage(4*ow,4*oh); bm.copy(ss,127,356,4*ow,4*oh,0,0,4*ow,4*oh); - [mush,poison,giant,fire,bomb,bm].forEach(img=>applyChromaKey(img)); - itemimgs = { mush:[mush], poison:[poison], giant:[giant], fire:[fire], bomb:[bomb], bigmissile:[bm] }; + const bm = createImage(4*ow,4*oh); bm.copy(spsrc,127,356,4*ow,4*oh,0,0,4*ow,4*oh); + const beffect = createImage(1.5*ow, 1.5*oh); beffect.copy(spsrc, 604, 413, 1.5*ow, 1.5*oh, 0, 0, 1.5*ow, 1.5*oh); + [mush,poison,giant,fire,bomb,bm, beffect].forEach(img=>applyChromaKey(img)); + function applyColorFilter(img, delta) { + // img.pixels 에 접근해 기존 색상을 유지하며 R 증가, G 감소 + img.loadPixels(); + for (let i = 0; i < img.pixels.length; i += 4) { + const alpha = img.pixels[i+3]; + if (alpha > 0) { + let r = img.pixels[i]; + let g = img.pixels[i+1]; + let b = img.pixels[i+2]; + r = constrain(r + delta.r, 0, 255); + g = constrain(g - delta.g, 0, 255); + b = constrain(b - delta.b, 0, 255) + img.pixels[i] = r; + img.pixels[i+1] = g; + img.pixels[i+2] = b; + } + } + img.updatePixels(); + } + // 기존 bomb 이미지 복제 후 크로마키, 색상 필터 적용 + const bombWarn = bomb.get(); + applyColorFilter(bombWarn, {r: 150, g: 100, b:200} ); + + itemimgs = { + mush:[mush], poison:[poison], giant:[giant], + fire:[fire], bomb:[bomb], bigmissile:[bm], bomb_warning:[bombWarn], explosion:[beffect] + }; + + const tilesrc = spriteSheets.tileset; + const bb = createImage(ow, oh); bb.copy(tilesrc, 18, 23, ow, oh, 0, 0, ow, oh); + const qb = createImage(ow, oh); qb.copy(tilesrc, 35, 23, ow, oh, 0, 0, ow, oh); + const gb = createImage(ow, oh); gb.copy(tilesrc, 154, 142, ow, oh, 0, 0, ow, oh); + [bb, qb, gb].forEach(img => applyChromaKey(img)); + tileimgs = { + breakableblock: [bb], + qustionblock: [qb], + groundblock: [gb] + }; + + + } \ No newline at end of file diff --git a/sketch.js b/sketch.js index a684c2d..d7fe325 100644 --- a/sketch.js +++ b/sketch.js @@ -8,7 +8,7 @@ function preload() { } function setup() { - createCanvas(800, 1600); + createCanvas(800, 600); sliceAssets(); controlsP1 = { left:'ArrowLeft', right:'ArrowRight', jump:'ArrowUp', attack:'[', bomb:']' }; @@ -74,6 +74,25 @@ class Background { } } } + +class Tile { + constructor(x,y,type) { + this.x=x; this.y=y; this.type=type; + this.img=tileimgs[type][0]; + this.width=32; this.height=32; + } + draw() { + image(this.img,this.x,this.y,this.width,this.height); + } + // Full AABB for breakable & question blocks + collides(x,y,w,h) { + return ( + x < this.x+this.width && x+w > this.x && + y < this.y+this.height && y+h > this.y + ); + } +} + class Player { constructor(x, y, imgSet, controls) { this.x = x; this.y = y; @@ -91,9 +110,19 @@ class Player { this.facing = "right"; this.state = "idle"; this.frame = 0; + this.attackTimer = 0; } update() { + // decrement attack timer + if (this.attackTimer > 0) { + this.attackTimer--; + if (this.attackTimer === 0) { + // return to appropriate post-attack state + this.state = this.onGround ? 'idle' : 'jump'; + } + } + let inputVX = 0; if (this.keys[this.controls.left]) { inputVX = -5; this.facing = "left"; } else if (this.keys[this.controls.right]){ inputVX = 5; this.facing = "right"; } @@ -103,15 +132,17 @@ class Player { this.x += this.vx; this.y += this.vy; + if (this.y + this.height >= groundY) { this.y = groundY - this.height; this.vy = 0; this.onGround = true; this.jumpCount = 0; - this.state = "idle"; - } else { + if (this.attackTimer === 0) this.state = 'idle'; + } + else { this.onGround = false; - this.state = "jump"; + if (this.attackTimer === 0) this.state = 'jump'; } this.knockbackVX *= 0.9; @@ -131,7 +162,7 @@ class Player { jump() { if (this.jumpCount < 2) { - this.vy = -15; + this.vy = -12; this.jumpCount++; } } @@ -141,12 +172,19 @@ class Player { const spawnY = this.y + this.height/2; const dir = this.facing === 'right' ? 20 : -20; projectiles.push(new Projectile(spawnX, spawnY, dir)); + this.state = 'shoot'; + this.attackTimer = 10; + this.frame = 0; } dropBomb() { - const bx = this.x + this.width/2; - const by = this.y + this.height; - bombs.push(new Bomb(bx, by)); + const bx = this.facing === 'right' ? this.x + this.width : this.x - 32; + const by = this.y; + const v_bomb = this.facing === 'right' ? 5 : -5; + bombs.push(new Bomb(bx, by, v_bomb)); + this.state = 'shoot'; + this.attackTimer = 10; + this.frame = 0; } fireBigMissile() { @@ -155,8 +193,11 @@ class Player { const spawnX = this.facing === 'right' ? this.x + this.width : this.x - mW; - const spawnY = this.y + this.height - mH; + const spawnY = this.y - this.height; specialProjectiles.push(new BigMissile(spawnX, spawnY, dir)); + this.state = 'shoot'; + this.attackTimer = 15; + this.frame = 0; } handleKeyPressed(k) { @@ -235,15 +276,17 @@ class Projectile { class Bomb { - constructor(x, y) { + constructor(x, y, vx) { this.x = x; this.y = y; - this.width = 16; - this.height = 16; + this.vx = vx; this.vy = 0; + this.width = 32; + this.height = 32; - this.timer = 60; // 폭발 대기 프레임 수 + this.timer = 120; // 폭발 대기 프레임 수 this.explodeTimer = 15; // 폭발 시각화 지속 프레임 수 this.exploded = false; // 폭발 상태 플래그 + this.warning = false; this.shouldRemove = false; // 완전 제거 플래그 this.radius = 100; // 넉백 및 시각화 반경(이 값을 키워 범위 확대) @@ -253,11 +296,24 @@ class Bomb { if (!this.exploded) { // 타이머가 0 이 되면 폭발 시작 this.timer--; + this.vy += gravity; + this.x += this.vx; + this.y += this.vy; + // 바닥 튕김 + if (this.y + this.height >= groundY) { + this.y = groundY - this.height; + this.vy = -0.5 * this.vy; + this.vx = 0.7 * this.vx; + } + if(this.timer <= 60) { + this.warning = true; + } if (this.timer <= 0) { this.exploded = true; this.explode(); // 넉백 한 번 적용 } - } else { + } + else { // 폭발 시각화가 끝나면 제거 this.explodeTimer--; if (this.explodeTimer <= 0) { @@ -283,22 +339,27 @@ class Bomb { } }); } + draw() { if (!this.exploded) { - image(itemimgs.bomb[0], this.x, this.y, this.width * 2, this.height * 2); - } else { - // 폭발 중일 땐 커다란 반투명 원으로 시각화 - noFill(); - stroke(255, 150, 0); - strokeWeight(4); - ellipse( - this.x + this.width/2, - this.y + this.height/2, - this.radius * 2, - this.radius * 2 + if(this.warning){ + image(itemimgs.bomb_warning[0], this.x, this.y, this.width, this.height); + } + else { + image(itemimgs.bomb[0], this.x, this.y, this.width, this.height); + } + + } + else { + const img = itemimgs.explosion[0]; + image( + img, + this.x - this.width, + this.y - this.height, + 3*this.width, + 3*this.height ); - noStroke(); } } @@ -315,33 +376,40 @@ class BigMissile { this.width = 64; this.height = 64; this.vx = vx; - // 하단 정렬: 플레이어 바닥 높이에 맞추기 위해 y에서 2배 높이만큼 올림 this.x = x; this.y = y - this.height; } update(targets) { + // 미사일 이동 this.x += this.vx; for (const t of targets) { - if (this.hits(t)) { - // 강력한 넉백 - const force = this.vx * 2; - t.knockbackVX += force; - // 겹침 방지: 대상 위치 조정 - const w = this.width * 2; - if (this.vx > 0) { - // 미사일이 오른쪽으로 이동 중이면 대상은 미사일 우측으로 밀어냄 - t.x = this.x + w; - } else { - // 왼쪽 이동 중이면 대상은 미사일 좌측으로 - t.x = this.x - t.width; - } - // 미사일은 충돌로 사라지지 않음 - break; + // AABB 충돌 체크 + const w = this.width * 2; + const h = this.height * 2; + const overlapX = this.x < t.x + t.width && this.x + w > t.x; + const overlapY = this.y < t.y + t.height && this.y + h > t.y; + if (!overlapX || !overlapY) continue; + + // 플레이어가 미사일 위에 서 있는지 검사 + const playerBottom = t.y + t.height; + const missileTop = this.y; + // 아래로 충돌 시(떨어져서 올라탄 경우) + if (t.vy > 0 && playerBottom <= missileTop + h * 0.1) { + // 지면 위에 착지 처리 + t.y = missileTop - t.height; + t.vy = 0; } + else { + // 측면 충돌 시 겹침 방지용 강제 이동 + if (this.vx > 0) t.x = this.x + w; + else t.x = this.x - t.width; + } + break; } + // 수명 검사 if (millis() - this.spawnTime > this.lifetime) { this.shouldDestroy = true; }