diff --git a/imageAsset.js b/imageAsset.js index f4f0da2..9660034 100644 --- a/imageAsset.js +++ b/imageAsset.js @@ -1,57 +1,68 @@ -let animations = {}; +let spriteSheets = {}; +let P1imgs = {}, P2imgs = {}, itemimgs = {}; let backgroundManager; +let gravity = 0.8; function preloadAssets() { - // 배경 이미지는 기존 방식 그대로 - const bg = loadImage("assets/Mario-Background.png"); + // 1) 스프라이트 시트 로드 + spriteSheets.backgrounds = loadImage("assets/Mario-Background.png"); + spriteSheets.characters = loadImage("assets/Mario-Character+Item.png"); + spriteSheets.specialweapon = loadImage("assets/Mario-Enemy.png"); + spriteSheets.tileset = loadImage("assets/Mario-Tileset.png"); +} + +function sliceAssets() { + // 2) 배경 슬라이스 및 Background 인스턴스 생성 + const bgsrc = spriteSheets.backgrounds; const w = 512, h = 512; - const dayImg = createImage(w,h); - const nightImg = createImage(w,h); - dayImg.copy(bg, 514,1565, w,h, 0,0, w,h); - nightImg.copy(bg, 514,5721, w,h, 0,0, w,h); - backgroundManager = new Background(dayImg, nightImg); + const bgDay = createImage(w, h); + const bgNight = createImage(w, h); + bgDay.copy(bgsrc, 514, 1565, w, h, 0, 0, w, h); + bgNight.copy(bgsrc, 514, 5721, w, h, 0, 0, w, h); + backgroundManager = new Background(bgDay, bgNight); - // Mario 애니메이션 로드 - animations.marioIdle = loadAnimation( - "assets/Mario-Character+Item.png", - 1, 98, 32, 32, 1 - ); - animations.marioWalk = loadAnimation( - "assets/Mario-Character+Item.png", - 75, 98, 32,32, 3 - ); - animations.marioJump = loadAnimation( - "assets/Mario-Character+Item.png", - 215, 98, 32,32, 1 - ); - animations.marioShoot = loadAnimation( - "assets/Mario-Character+Item.png", - 627, 98, 32,32, 1 - ); + // 3) 크로마키 함수 + function applyChromaKey(img, keyColor = {r:147, g:187, b:236}) { + img.loadPixels(); + for (let i = 0; i < img.pixels.length; i += 4) { + if (img.pixels[i] === keyColor.r && img.pixels[i+1] === keyColor.g && img.pixels[i+2] === keyColor.b) { + img.pixels[i+3] = 0; + } + } + img.updatePixels(); + } - // Luigi 애니메이션 로드 - animations.luigiIdle = loadAnimation( - "assets/Mario-Character+Item.png", - 1, 629, 32,32, 1 - ); - animations.luigiWalk = loadAnimation( - "assets/Mario-Character+Item.png", - 75, 629, 32,32, 3 - ); - animations.luigiJump = loadAnimation( - "assets/Mario-Character+Item.png", - 215,629, 32,32, 1 - ); - animations.luigiShoot = loadAnimation( - "assets/Mario-Character+Item.png", - 627,629, 32,32, 1 - ); + // 4) 캐릭터 프레임 슬라이스 (Mario) + const src = spriteSheets.characters; + const cw = 32, ch = 32; + const mi = createImage(cw,ch); mi.copy(src, 1, 98, cw, ch, 0,0, cw,ch); + const mw1= createImage(cw,ch); mw1.copy(src,75, 98, cw,ch,0,0, cw,ch); + const mw2= createImage(cw,ch); mw2.copy(src,108,98, cw,ch,0,0, cw,ch); + const mw3= createImage(cw,ch); mw3.copy(src,141,98, cw,ch,0,0, cw,ch); + const mj = createImage(cw,ch); mj.copy(src,215,98, cw,ch,0,0, cw,ch); + const ma = createImage(cw,ch); ma.copy(src,627,98, cw,ch,0,0, cw,ch); + [mi,mw1,mw2,mw3,mj,ma].forEach(img=>applyChromaKey(img)); + P1imgs = { idle:[mi], walk:[mw1,mw2,mw3], jump:[mj], shoot:[ma] }; - // 아이템 애니메이션 (단프레임) - animations.mushroom = loadAnimation("assets/Mario-Character+Item.png", 1, 2126, 16, 16, 1); - animations.poison = loadAnimation("assets/Mario-Character+Item.png", 1, 2143, 16, 16, 1); - animations.giant = loadAnimation("assets/Mario-Character+Item.png", 35,2143,32,32, 1); - animations.fire = loadAnimation("assets/Mario-Character+Item.png", 101,2177, 8,8, 1); - animations.bomb = loadAnimation("assets/Mario-Character+Item.png", 194,2143,16,16,1); - animations.bigmissile = loadAnimation("assets/Mario-Enemy.png", 127,356,64,64,1); + // 5) 캐릭터 프레임 슬라이스 (Luigi) + const li = createImage(cw,ch); li.copy(src,1,629,cw,ch,0,0,cw,ch); + const lw1= createImage(cw,ch); lw1.copy(src,75,629,cw,ch,0,0,cw,ch); + const lw2= createImage(cw,ch); lw2.copy(src,108,629,cw,ch,0,0,cw,ch); + const lw3= createImage(cw,ch); lw3.copy(src,141,629,cw,ch,0,0,cw,ch); + const lj = createImage(cw,ch); lj.copy(src,215,629,cw,ch,0,0,cw,ch); + const la = createImage(cw,ch); la.copy(src,627,629,cw,ch,0,0,cw,ch); + [li,lw1,lw2,lw3,lj,la].forEach(img=>applyChromaKey(img)); + P2imgs = { idle:[li], walk:[lw1,lw2,lw3], jump:[lj], shoot:[la] }; + + // 6) 아이템 및 특수 투사체 프레임 + const ss = 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] }; } \ No newline at end of file diff --git a/sketch.js b/sketch.js index 693bb98..a684c2d 100644 --- a/sketch.js +++ b/sketch.js @@ -1,22 +1,45 @@ -let player; -let gravity = 0.8; - -let projectiles = []; -let specialProjectiles = []; -let bombs = []; - -let groundY = 500; -let keys = {}; -let powerUps = []; -let deathZoneX; -let deathZoneY = 600; +let player1, player2; +let projectiles = [], specialProjectiles = [], bombs = []; +let groundY = 500, deathZoneY = 600; let controlsP1, controlsP2; function preload() { - //sprite download preloadAssets(); } +function setup() { + createCanvas(800, 1600); + sliceAssets(); + + controlsP1 = { left:'ArrowLeft', right:'ArrowRight', jump:'ArrowUp', attack:'[', bomb:']' }; + controlsP2 = { left:'a', right:'d', jump:'w', attack:'t', bomb:'y' }; + + player1 = new Player(100, 100, P1imgs, controlsP1); + player2 = new Player(200, 100, P2imgs, controlsP2); +} + +function draw() { + backgroundManager.draw(); + rect(0, groundY, width, 100); + + player1.update(); player1.draw(); + player2.update(); player2.draw(); + + handleProjectiles(); + handleBombs(); + handleSpecialProjectiles(); +} + +function keyPressed() { + if (key === ' ') backgroundManager.modeChange(); + player1.handleKeyPressed(key); + player2.handleKeyPressed(key); +} +function keyReleased() { + player1.handleKeyReleased(key); + player2.handleKeyReleased(key); +} + class Background { constructor(dayImg, nightImg) { this.bgDay = dayImg; @@ -51,69 +74,49 @@ class Background { } } } - class Player { - constructor(x,y, imgSet, controls){ - this.x = x; - this.y = y; - this.vx = 0; - this.vy = 0; - this.width = 32; - this.height = 32; - + constructor(x, y, imgSet, controls) { + this.x = x; this.y = y; + this.vx = 0; this.vy = 0; + this.knockbackVX = 0; + this.width = 32; this.height = 32; this.onGround = false; this.jumpCount = 0; this.deathCount = 10; this.imgSet = imgSet; - - this.controls = controls;//custom keyset - this.keys = {};// key pressed 상태 추적적 - + this.controls = controls; + this.keys = {}; this.bombHoldStartTime = null; - this.skill = { - resist: false, - fastFire: false, - isGiant: false, - itemCount: 0 - } + this.skill = { resist:false, fastFire:false, isGiant:false, itemCount:0 }; this.facing = "right"; this.state = "idle"; this.frame = 0; } update() { - // moving - console.log(this.vx); - if (this.keys[this.controls.left]) { - this.vx = -5; - this.facing = "left"; - } - else if (this.keys[this.controls.right]) { - this.vx = 5; - this.facing = "right"; - } - else { - this.vx = 0; - } - - //ground judge + 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"; } + + this.vx = inputVX + this.knockbackVX; this.vy += gravity; - this.x += this.vx; - this.y += this.vy; + 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 { + this.state = "idle"; + } else { this.onGround = false; this.state = "jump"; } - //낙사 + this.knockbackVX *= 0.9; + if (abs(this.knockbackVX) < 0.1) this.knockbackVX = 0; + if (this.y > deathZoneY) { this.deathCount--; this.state = "dead"; @@ -121,71 +124,54 @@ class Player { } } - respawn(){ - this.x = 100; - this.y = 100; - this.vx = 0; - this.vy = 0; + respawn() { + this.x = 100; this.y = 100; + this.vx = this.vy = this.knockbackVX = 0; } jump() { - if(this.jumpCount < 2) { + if (this.jumpCount < 2) { this.vy = -15; this.jumpCount++; } } - //3 attack logic shoot() { - console.log("shoot"); - const spawnX = this.facing === 'right' ? this.x + this.width : this.x; - const spawnY = this.y + this.height / 2; + const spawnX = this.facing === 'right' ? this.x + this.width : this.x - 16; + const spawnY = this.y + this.height/2; const dir = this.facing === 'right' ? 20 : -20; - const proj = new Projectile(spawnX, spawnY, dir); - projectiles.push(proj); + projectiles.push(new Projectile(spawnX, spawnY, dir)); } dropBomb() { - console.log("bomb"); - const bombX = this.x + this.width / 2; - const bombY = this.y; - bombs.push(new Bomb(bombX, bombY)); + const bx = this.x + this.width/2; + const by = this.y + this.height; + bombs.push(new Bomb(bx, by)); } fireBigMissile() { - console.log("받아라 비장의무기~~"); - const dir = this.facing === "right" ? 5 : -5; - specialProjectiles.push(new BigMissile(this.x, this.y, dir)); - this.skill.itemCount = this.skill.itemCount - 3; + const dir = this.facing === 'right' ? 5 : -5; + const mW = 64 * 2, mH = 64 * 2; + const spawnX = this.facing === 'right' + ? this.x + this.width + : this.x - mW; + const spawnY = this.y + this.height - mH; + specialProjectiles.push(new BigMissile(spawnX, spawnY, dir)); } - //end.. handleKeyPressed(k) { this.keys[k] = true; - if (k === this.controls.jump) this.jump(); - if (k === this.controls.attack) this.shoot(); - if (k === this.controls.bomb) { - if (this.skill.itemCount >= 3 && this.bombHoldStartTime === null) { - this.bombHoldStartTime = millis(); - } - else { - this.dropBomb(); - } - } + if (k === this.controls.jump) this.jump(); + if (k === this.controls.attack)this.shoot(); + if (k === this.controls.bomb) this.bombHoldStartTime = millis(); } handleKeyReleased(k) { this.keys[k] = false; - if (k === this.controls.bomb && this.bombHoldStartTime !== null) { - const heldDuration = millis() - this.bombHoldStartTime; - - if (heldDuration >= 2000 && this.skill.itemCount >= 3) { - this.fireBigMissile(); // 대형 미사일 발사 - this.skill.itemCount = this.skill.itemCount - 3; // 카운트 초기화 - } - - // 초기화 (2초 미만이어도 시간 초기화) + const held = millis() - this.bombHoldStartTime; + if (held >= 1000 ) this.fireBigMissile(); + else this.dropBomb(); this.bombHoldStartTime = null; } } @@ -196,24 +182,21 @@ class Player { if (this.facing === 'left') { translate(this.x + this.width, this.y); scale(-1,1); - image(img, 0, 0, this.width, this.height); + image(img, 0,0, this.width, this.height); } else { image(img, this.x, this.y, this.width, this.height); } pop(); } - } + + class Projectile { - constructor(x, y, vx, width = 8, height = 8) { - this.x = x; - this.y = y; - this.vx = vx; - this.width = width; - this.height = height; - this.spawnTime = millis(); - this.lifetime = 10000; + constructor(x, y, vx, w=8, h=8) { + this.x = x; this.y = y; + this.vx = vx; this.width = w; this.height = h; + this.spawnTime = millis(); this.lifetime = 10000; this.shouldDestroy = false; } @@ -221,29 +204,26 @@ class Projectile { this.x += this.vx; for (const t of targets) { - if(this.hits(t)) { - const knockback = this.vx; - t.vx += knockback; + if (this.hits(t)) { + t.knockbackVX += this.vx * 0.5; this.shouldDestroy = true; } } + if (millis() - this.spawnTime > this.lifetime) { this.shouldDestroy = true; } } draw() { - fill(255, 100, 0); - rect(this.x, this.y, this.width, this.height); + image(itemimgs.fire[0], this.x, this.y, this.width * 2, this.height * 2); } destroy() { return this.shouldDestroy; } - // 충돌 체크 메서드 hits(target) { - console.log("shoot hit!"); return ( this.x < target.x + target.width && this.x + this.width > target.x && @@ -255,80 +235,151 @@ class Projectile { class Bomb { - constructor(x,y) { + constructor(x, y) { this.x = x; this.y = y; - this.width = 16; + this.width = 16; this.height = 16; - this.timer = 100; - this.shouldDestroy = false; + + this.timer = 60; // 폭발 대기 프레임 수 + this.explodeTimer = 15; // 폭발 시각화 지속 프레임 수 + this.exploded = false; // 폭발 상태 플래그 + this.shouldRemove = false; // 완전 제거 플래그 + + this.radius = 100; // 넉백 및 시각화 반경(이 값을 키워 범위 확대) } update() { - this.timer --; - if(this.timer <=0){ - this.shouldDestroy = true; + if (!this.exploded) { + // 타이머가 0 이 되면 폭발 시작 + this.timer--; + if (this.timer <= 0) { + this.exploded = true; + this.explode(); // 넉백 한 번 적용 + } + } else { + // 폭발 시각화가 끝나면 제거 + this.explodeTimer--; + if (this.explodeTimer <= 0) { + this.shouldRemove = true; + } } } explode() { - console.log("exploded"); - + // 폭발 반경 내 모든 플레이어에게 넉백 적용 + [player1, player2].forEach(p => { + const cx = this.x + this.width/2; + const cy = this.y + this.height/2; + const px = p.x + p.width/2; + const py = p.y + p.height/2; + const d = dist(cx, cy, px, py); + + if (d < this.radius) { + const angle = atan2(py - cy, px - cx); + const force = map(d, 0, this.radius, 20, 5); + p.knockbackVX += cos(angle) * force * 2; + p.vy += sin(angle) * force * 2; + } + }); } draw() { - fill(255,0,0); - rect(this.x, this.y, this.width, this.height); + 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 + ); + noStroke(); + } } destroy() { - return this.shouldDestroy; + return this.shouldRemove; } } - class BigMissile { - constructor(x,y,vx) { - this.x = x; - this.y = y; - this.vx = vx; - this.width = 64; - this.height = 64 - + constructor(x, y, vx) { this.spawnTime = millis(); this.lifetime = 20000; this.shouldDestroy = false; + + 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 knockback = this.vx * 0.1; - t.vx += knockback; + // 강력한 넉백 + 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; } } + if (millis() - this.spawnTime > this.lifetime) { this.shouldDestroy = true; } } + draw() { - fill(255, 0, 255); - rect(this.x, this.y, this.width, this.height); + const img = itemimgs.bigmissile[0]; + push(); + if (this.vx > 0) { + // 왼쪽 발사시 이미지 뒤집기 + translate(this.x + this.width * 2, this.y); + scale(-1, 1); + image(img, 0, 0, this.width * 2, this.height * 2); + } + else { + image(img, this.x, this.y, this.width * 2, this.height * 2); + } + pop(); } + + hits(target) { + // 미사일 자체 크기로 충돌 영역 계산 (2배 확대된 폭) + const w = this.width * 2; + const h = this.height * 2; + return ( + this.x < target.x + target.width && + this.x + w > target.x && + this.y < target.y + target.height && + this.y + h > target.y + ); + } + destroy() { return this.shouldDestroy; } - explode() { - console.log("explode"); - } - } - class Item { - } - - - function handleProjectiles() { for (let i = projectiles.length - 1; i >= 0; i--) { projectiles[i].update([player1, player2]); @@ -339,15 +390,13 @@ function handleProjectiles() { } } } - function handleBombs() { for (let i = bombs.length - 1; i >= 0; i--) { - bombs[i]; - bombs[i].update([player1, player2]); - bombs[i].draw(); - if (bombs[i].destroy()) { - bombs[i].explode(); - bombs.splice(i, 1); // 제거 + const b = bombs[i]; + b.update(); + b.draw(); + if (b.destroy()) { + bombs.splice(i, 1); } } } @@ -359,55 +408,4 @@ function handleSpecialProjectiles() { specialProjectiles.splice(i, 1); } } -} - -let backgroundManager; -function setup() { - createCanvas(800, 1600); - sliceImages(); - const bgsrc = spriteSheets.backgrounds; - const w = 512, h = 512; - const bgDay = createImage(w, h); - const bgNight = createImage(w, h); - bgDay.copy(bgsrc, 514, 1565, w, h, 0, 0, w, h); - bgNight.copy(bgsrc, 514, 5721, w, h, 0, 0, w, h); - backgroundManager = new Background(bgDay, bgNight); - - - player1 = new Player(0, 0, P1imgs, controlsP1); - player2 = new Player(0, 0, P2imgs, controlsP2); - - -} - - -function draw() { - - backgroundManager.draw(); - rect(0, groundY, width, 100); - - player1.update(); - player1.draw(); - player2.update(); - player2.draw(); - - handleProjectiles(); - handleBombs(); - handleSpecialProjectiles(); - - -} - - -function keyPressed(){ - if(key === ' '){//spacebar(임시) - backgroundManager.modeChange(); - } - player1.handleKeyPressed(key); - player2.handleKeyPressed(key); -} -function keyReleased() { - player1.handleKeyReleased(key); - player2.handleKeyReleased(key); -} - +} \ No newline at end of file