From 9ec95b775c1eea52e351a128e26649af173c6416 Mon Sep 17 00:00:00 2001 From: joowon Date: Sun, 11 May 2025 02:48:57 +0900 Subject: [PATCH] final --- README.md | 344 ++++++++++++++++++++++++++++++++++++++- Report_image/bombadd.png | Bin 0 -> 2761 bytes Report_image/giant.png | Bin 0 -> 2483 bytes Report_image/missile.png | Bin 0 -> 17416 bytes Report_image/mush.png | Bin 0 -> 2084 bytes Report_image/poison.png | Bin 0 -> 2588 bytes imageAsset.js | 8 - sketch.js | 225 +++++++------------------ 8 files changed, 401 insertions(+), 176 deletions(-) create mode 100644 Report_image/bombadd.png create mode 100644 Report_image/giant.png create mode 100644 Report_image/missile.png create mode 100644 Report_image/mush.png create mode 100644 Report_image/poison.png diff --git a/README.md b/README.md index 9c5d289..9d27c7e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,344 @@ -# ๐Ÿš€ p5.js-templates -p5.js templates for ID311 Software Prototyping. +# 1. Author info +- name : Joowon Kim +- ID : 20200150 +- Email : taeng31@kaist.ac.kr -This is pangame of nintendo chracters. +# 2. URL of the git repository +- https://github.com/joowonkime/Mario-fan-game -all sprites came from https://www.spriters-resource.com/nintendo_switch/K.html +# 3. Youtube Video Link -and I cut the sprite in https://www.piskelapp.com/p/create/sprite +# 4. Explanation of Game +This game is a 2-player game where you push your opponent out of the map with various attacks and the last player alive wins. +Player 1 is Mario and can move with the arrow keys, throw a fireball with the [ key, and throw a bomb with the ] key. +Player 2 is Luigi and can move with the wasd key, throw a fireball with the t key, and throw a bomb with the y key. +A player's death count can only be reduced by being pushed out of the map. Each player can use various attacks to push the opponent out of the map. +- A player can shoot a fireball and push the opponent out if it hits the opponent in a straight line. +- A player can throw a bomb to send the opponent flying farther and destroy some blocks. The longer you charge the bomb, the farther it flies. The player starts with 5 bombs. +- A player can shoot a missile by charging the bomb key for more than 1 second. The huge missile slowly moves forward and pushes the player it touches all the way. This missile can also be climbed on top. However, this missile is filled by one every time you eat three items. + +![huge missile!](./Report_image/missile.png) + +Also, for the variety of the game, items are dropped at random locations at regular intervals. Each item has the following types. + +![mush](./Report_image/mush.png) +- **mush**: The player who gets this will have their fireball abilities strengthened for a certain period of time, knocking back opponents twice as far. + +![poison](./Report_image/poison.png) +- **poison**: The player who gets this will be poisoned and stuck in place. While stuck, they will not be knocked back at all and cannot move. However, they can be pushed back by missiles. + +![giant](./Report_image/giant.png) +- **giant**: The player who gets this will double in size. The larger player's fireball size will be doubled, and they will only be pushed back by half of the opponent's knockback attacks. + +![bombadd](./Report_image/bombadd.png) +- **bombadd**: 5 bombs are added. + +The player starts with a total of 5 death counts, and the game is to survive by pushing away the opponents using the above methods. + +# 5. Code Explanation +This game is based on p5.js. + +The entire code can be broken down into the following major components: + +Downloading image and music assets + +Creating the background and map + +Implementing the Player class + +Attack object system + +Item interactions + +## ๐ŸŽจ 1. Image Sprites +All sprite assets came from: +๐Ÿ‘‰ https://www.spriters-resource.com/nintendo_switch/K.html + +The downloaded Mario sprite sheets are handled in imageAsset.js via the following steps: + +preloadAssets: Loads the sprite sheet using loadImage. + +sliceAssets: Extracts specific regions (using pixel coordinates and sizes) using createImage. Includes sprites for player states, items, and objects. + +applyChromakey: Removes the shared background color from the sprites to make them transparent using a chroma key approach. + +## ๐ŸŽต 1-1. Music Assets +All music assets came from: +๐Ÿ‘‰ https://downloads.khinsider.com/game-soundtracks/album/super-mario-bros + +All sound effects and background music are handled through soundAsset.js. + +## ๐ŸŒ„ 2. Map and Background +The map and background are generated in sketch.js during setup() using assets from ImageAsset.js. + +Background Class +Repeats a background image across the screen. + +Pressing the spacebar changes the background theme (aesthetic only). + +Map Structure +Generated from a predefined mapLayout. + +Uses two block types: groundBlock and breakableBlock. + +Managed by the Tile class. + +Tile Class +Represents a 32ร—32 tile placed using constructor(x, y, type). + +collides() detects collision with the player. + +Tile.type is checked in bomb.explode() to decide if a block can break. + +If a block breaks, BreakEffect triggers a short animation. + +## ๐Ÿ“ฆ 2-1. Global Object Arrays +Repeatedly created objects are managed via global arrays: + + +let tiles = [], decoTiles = []; + +let projectiles = [], specialProjectiles = [], bombs = []; + +let breakEffects = []; + +let items = []; + +And each global variable has a function that manages it. +#### handleProjectiles() +#### handleBombs() +#### handleSpecialProjectiles() +-> These three functions commonly track the interaction between each object and player through update([player1, player2]), and check destroy() to remove the object when it needs to be removed through splice(i, 1). +#### breakManager() +-> When a block explodes due to a bomb, the breakeffect is stored in the breakeffects array, and after this animation is over, it removes the breakeffect from the breakEffects array. +#### randomSpawnItem() +-> Randomly spawns and drops a random type of item at a random x-coordinate every 10~14 seconds. + +## ๐Ÿง 3. Player Class +The Player class manages all player behavior and interactions. Below is a breakdown of its core structure and methods. + +### Constructor +constructor(x, y, imgSet, controls) +Initializes player with the following: + +x, y: **Position** + +vx, vy: **Movement velocities** + +knockbackVX: **Knockback velocity** + +width, height: **Size** + +onGround: **Boolean to check ground contact** + +jumpCount: **Counts number of jumps** + +imgSet: **Assigned sprite set** + +controls, keys: Input key mapping & tracking + +bombHoldStartTime, chargeTime, maxCharge: Charge system for bombs + +facing, state, frame: **For sprite control and animation** + +_animTimer, _animInterval: **Timing helper for walking animation** + +attackTimer: **Prevents rapid re-attacks** + +dropping, dropRowY, currentTileY : **Drop-through tile logic** + +itemCount, bombCount, bigMissileCount: **Player resources** + +fireTimer, poisonTimer, giantTimer: **Status effect durations** + +deathCount: **Lives remaining** + +### update() +Called every frame to update player state: + +Updates status effect timers (fireTimer, poisonTimer, giantTimer). + +Decreases attackTimer. When it reaches 0, switches animation state to 'idle' or 'jump'. + +If the bomb key is held, updates chargeTime up to maxCharge. + +Responds to left/right movement inputs and adjusts facing direction. + +Applies gravity and updates vertical position. + +Checks collision with tiles to determine whether the player is on the ground. + +Uses landed flag to distinguish whether the player just landed this frame (vs onGround which tracks ongoing contact). + +Implements drop-through logic: pressing down allows the player to fall through tiles. Uses dropRowY and currentTileY to prevent re-dropping the same tile. + +Applies friction to knockbackVX. + +If the player falls below deathZoneY, calls respawn(). + +### applyItem() +Called when the player picks up an item. + +Increments itemCount and applies the corresponding status effect by resetting its timer. + +### respawn() +Plays a death sound. + +deathcount-- + +If the player has lives remaining, respawns them at the top of the screen. + +If no lives remain, ends the game(gameOver flag = true) and declares the other player as the winner. + +### jump() +Increments jumpCount and applies upward velocity. + +### shoot() +Fires a fireball when the fire key is pressed: + +Adds a new Projectile to projectiles + +Triggers attack animation + +Applies slight knockback to the player + +### dropBomb() +Launches a bomb when triggered: + +Adds a new Bomb to bombs + +Plays attack animation + +Bombโ€™s velocity is based on chargeTime + +### fireBigMissile() +Fires a large missile: + +Adds a BigMissile to specialProjectiles + +Plays attack animation + +Applies stronger knockback to the player + +### handleKeyPressed() and handleKeyReleased() +Handles jump and fire inside handleKeyPressed(). + +Bomb behavior is split: + +handleKeyPressed() sets bombHoldStartTime + +handleKeyReleased() checks chargeTime and determines whether to throw a bomb or fire a missile. + +### draw() +Draws the charge gauge above the playerโ€™s head. + +Flips sprite based on facing. + +Adds visual poison effect when poisonTimer is active. + +Changes sprite frame based on state and frame to animate character behavior. + +# ๐Ÿ’ฅ 4. Attack Object +This game features three types of attack-related classes: Projectile, Bomb, and BigMissile. + +๐Ÿš€ Projectile Class +#### constructor() +Determines the size based on two flags: + +enchanted: whether the firing player had consumed a "mushroom" item. + +isGiant: whether the player had consumed a "giant" item. + +Spawns the projectile at a given (x, y) position. + +Sets spawnTime and a lifeTime of 10 seconds. + +#### update() +The projectile moves forward in a straight line. + +If it hits a player, the shouldDestroy flag is set to true to allow removal. + +#### draw() +Renders the fire sprite on screen. + +#### destroy() +Returns the shouldDestroy flag to signal if the projectile should be removed. + +#### hits() +Returns true if the projectile intersects with a player. + +๐Ÿ’ฃ Bomb Class +#### constructor(x, y, vx, vy) +Initializes the bomb with position and velocity: + +this.x = x; this.y = y; + +this.vx = vx; this.vy = vy; + +this.width = 32; this.height = 32; + +this.timer = 120; + +this.explodeTimer = 15; + +this.exploded = false; : **Flag to check if bomb has exploded** + +this.warning = false; : **Flag to show warning before explosion** + +this.shouldRemove = false; + +this.radius = 100; + +this.stuck = false; : **Flag to check if bomb is stuck on a tile** + +this.stuckY = null; + + +#### update() +Updates the bomb's position while it bounces (with gravity and dampening: this.vy *= -0.5; this.vx *= 0.7). + +After 1 second: warning = true + +After 2 seconds: exploded = true, and explode() is triggered. + +#### explode() +Applies knockback to all players within the blast radius, scaled based on their distance and item status. + +Breakable blocks within the radius are destroyed with a break animation via breakEffects.push(). + +#### draw() +Displays the bomb with different sprites depending on state: normal, warning, or exploding. + +#### destroy() +Returns the shouldDestroy flag to indicate if the bomb should be removed. + +๐Ÿ›ฐ๏ธ BigMissile Class +#### constructor() +Creates a missile object at a given location with a lifeTime of 20 seconds. + +#### update() +If a player collides horizontally with the missile, they are pushed along the missile's direction. + +If a player collides vertically, they can stand on top of the missile like a platform. + +Collision detection is handled by hits(). + +#### hits() +Returns true if the missile intersects with a player. + +#### destroy() +Returns the shouldDestroy flag to indicate if the missile should be removed. + +# ๐ŸŽ 5. Items and Miscellaneous +### Item Class: +Functions similarly to other object classes. Items are spawned mid-air via randomSpawnItem() and fall due to gravity until they land on a tile. +Collision with the player is checked using the hits() method. +When picked up, the item's toRemove flag is set to true. + +### drawUI(): +Renders a UI panel on the bottom-left and bottom-right corners of the screen, showing each player's current status and resources. + +### drawVictoryScreen(): +When gameOver is true, a victory screen is displayed in the center of the canvas showing the winnerโ€™s sprite along with the text "YOU WIN!". diff --git a/Report_image/bombadd.png b/Report_image/bombadd.png new file mode 100644 index 0000000000000000000000000000000000000000..781bcc02e403829a8afa0ba71233fb9da968179a GIT binary patch literal 2761 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 SF1elmq>Yt5w(6uu^uGZI(E5-7 literal 0 HcmV?d00001 diff --git a/Report_image/giant.png b/Report_image/giant.png new file mode 100644 index 0000000000000000000000000000000000000000..5fd7b6fbd40ab62238c500369330052ad18f6c2a GIT binary patch literal 2483 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 AJOBUy literal 0 HcmV?d00001 diff --git a/Report_image/missile.png b/Report_image/missile.png new file mode 100644 index 0000000000000000000000000000000000000000..86cd84c04f7f3033581c5931f3ec191362618025 GIT binary patch literal 17416 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 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]; @@ -90,7 +83,6 @@ function sliceAssets() { } img.updatePixels(); } - // ๊ธฐ์กด bomb ์ด๋ฏธ์ง€ ๋ณต์ œ ํ›„ ํฌ๋กœ๋งˆํ‚ค, ์ƒ‰์ƒ ํ•„ํ„ฐ ์ ์šฉ const bombWarn = bomb.get(); applyColorFilter(bombWarn, {r: 150, g: 100, b:200} ); diff --git a/sketch.js b/sketch.js index c9d8159..136e1b8 100644 --- a/sketch.js +++ b/sketch.js @@ -16,11 +16,9 @@ const groundY = [100, 200, 300, 400, 500, 550]; //352, 384, 416, //448, 480, 512, 544, 576, 608, 640, 672, 704, 736, // const mapLayout = [ - // row 0 (y=100): 64~192 bb, 576~704 bb [ { x1: 224, x2: 544, type: 'breakableblock' }, ], - // row 1 (y=200): 128~224 bb, 256~320 gb1, 352~416 null, 448~512 gb1, 544~640 bb [ { x1: 128, x2: 224, type: 'breakableblock' }, { x1: 256, x2: 320, type: 'groundblock1' }, @@ -28,15 +26,12 @@ const mapLayout = [ { x1: 448, x2: 512, type: 'groundblock1' }, { x1: 544, x2: 640, type: 'breakableblock' }, ], - // row 2 (y=300): 64~704 gb1 [ { x1: 64, x2: 704, type: 'groundblock1' }, ], - // row 3 (y=400): 256~512 gb1 [ { x1: 256, x2: 512, type: 'groundblock1' }, ], - // row 4 (y=500): 128~224 gb1, 256~288 bb, 320~448 null, 480~512 bb, 544~640 gb1 [ { x1: 128, x2: 224, type: 'groundblock1' }, { x1: 256, x2: 288, type: 'breakableblock' }, @@ -44,7 +39,6 @@ const mapLayout = [ { x1: 480, x2: 512, type: 'breakableblock' }, { x1: 544, x2: 640, type: 'groundblock1' }, ], - // row 5 (y=550): 32~736 bb [ { x1: 32, x2: 736, type: 'breakableblock' }, ], @@ -64,13 +58,13 @@ function setup() { controlsP1 = { left:'ArrowLeft', right:'ArrowRight', jump:'ArrowUp', attack:'[', bomb:']', down:'ArrowDown' }; controlsP2 = { left:'a', right:'d', jump:'w', attack:'t', bomb:'y', down:'s' }; - player1 = new Player(100, 100, P1imgs, controlsP1); - player2 = new Player(200, 100, P2imgs, controlsP2); + player1 = new Player(150, 300, P1imgs, controlsP1); + player2 = new Player(650, 300, P2imgs, controlsP2); for (let row = 0; row < groundY.length; row++) { const y = groundY[row] - TILE_SIZE; for (let seg of mapLayout[row]) { - if (!seg.type) continue; // โ† add this back + if (!seg.type) continue; for (let x = seg.x1; x <= seg.x2; x += TILE_SIZE) { tiles.push(new Tile(x, y, seg.type)); } @@ -80,7 +74,6 @@ function setup() { function draw() { if (gameOver) { - // ์Šน๋ฆฌ์Œ ํ•œ ๋ฒˆ๋งŒ ์žฌ์ƒ bgm.bgmGround.stop(); if (!victoryPlayed) { effectSound.victory.play(); @@ -93,8 +86,10 @@ function draw() { decoTiles.forEach(t => t.draw()); tiles.forEach(t => t.draw()); - player1.update(); player1.draw(); - player2.update(); player2.draw(); + player1.update(); + player1.draw(); + player2.update(); + player2.draw(); randomSpawnItem(); @@ -152,7 +147,7 @@ class BreakEffect { decoimgs.breakeffect3[0] ]; this.currentFrame = 0; - this.frameTimer = 8; // ๊ฐ ํ”„๋ ˆ์ž„์„ ๋ช‡ ํ‹ฑ ๋™์•ˆ ๋ณด์—ฌ์ค„์ง€ + this.frameTimer = 8; this.active = true; } update() { @@ -226,41 +221,38 @@ class Player { this._animTimer = 0; this._animInterval = 6; this.attackTimer = 0; - this.dropping = false; // drop-through state - this.dropRowY = null; // current tile to drop through - this.currentTileY = null; // current tile Y position for drop-through + this.dropping = false; + this.dropRowY = null; + this.currentTileY = null; this.itemCount = 0; - this.bombCount = 10; + this.bombCount = 5; this.bigMissileCount = 0; - // ํšจ๊ณผ ํƒ€์ด๋จธ๋“ค (frame ๋‹จ์œ„) this.fireTimer = 0; this.poisonTimer = 0; this.giantTimer = 0; - this.deathCount = 5; // ๋‚จ์€ ๋ชฉ์ˆจ - this.invulnerable = false; // ๋ฌด์  ํ”Œ๋ž˜๊ทธ - this.invTimer = 0; // ๋ฌด์  ๋‚จ์€ ํ”„๋ ˆ์ž„ + this.deathCount = 5; } applyItem(type) { this.itemCount++; - if (this.itemCount % 2 === 0) { + if (this.itemCount % 3 === 0) { this.bigMissileCount++; } switch(type) { case 'mush': effectSound.getItem.play(); - this.fireTimer = 8 * 60; // 8์ดˆ๊ฐ„ (60fps ๊ฐ€์ •) + this.fireTimer = 8 * 60; break; case 'poison': effectSound.getItem.play(); - this.poisonTimer = 3 * 60; // 3์ดˆ๊ฐ„ + this.poisonTimer = 3 * 60; break; case 'giant': effectSound.getItem.play(); - this.giantTimer = 5 * 60; // 5์ดˆ๊ฐ„ + this.giantTimer = 5 * 60; break; case 'bombadd': effectSound.getItem.play(); @@ -270,79 +262,64 @@ class Player { } update() { - // (1) poison ํšจ๊ณผ: ์ด๋™ ๋ถˆ๊ฐ€ if (this.poisonTimer > 0) { this.poisonTimer--; - // ์ด๋™ ๋ฒกํ„ฐ ๊ฐ•์ œ 0 this.vx = this.vy = 0; return; } - // (2) giant ํšจ๊ณผ: ํฌ๊ธฐ ๋ฐ ๊ณต๊ฒฉ ํฌ๊ธฐ ์กฐ์ • if (this.giantTimer > 0) { this.giantTimer--; this.width = 64; this.height = 64; } else { - // ๊ธฐ๋ณธ ํฌ๊ธฐ๋กœ ๋ณต์› this.width = 32; this.height = 32; } - // (3) mush ํšจ๊ณผ: fire ์ธ์ฑˆํŠธ if (this.fireTimer > 0) { this.fireTimer--; } - // Bomb charging + if (this.keys[this.controls.bomb] && this.bombHoldStartTime !== null) { this.chargeTime = min(millis() - this.bombHoldStartTime, this.maxCharge); } - // Attack cooldown + if (this.attackTimer > 0) { this.attackTimer--; if (this.attackTimer === 0) this.state = this.onGround ? 'idle' : 'jump'; } - // Horizontal movement 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.x += this.vx; - // Gravity ์ ์šฉ this.vy += gravity; let nextY = this.y + this.vy; - // ์œ„๋กœ ์˜ฌ๋ผ๊ฐˆ ๋•Œ ๋“œ๋กญ ์ทจ์†Œ if (this.vy < 0 && this.dropRowY !== null) { this.dropRowY = null; this.dropping = false; } let landed = false; - // ํ•œ ๋ฐฉํ–ฅ ํ”Œ๋žซํผ ์ถฉ๋Œ ์ฒ˜๋ฆฌ (์•„๋ž˜๋กœ ๋–จ์–ด์งˆ ๋•Œ๋งŒ) + if (this.vy > 0) { for (let tile of tiles) { - // ๋“œ๋กญ ์ค‘์ผ ๋•Œ, ์ง€์ •๋œ ํ–‰์€ ์ถฉ๋Œ ๋ฌด์‹œ if (this.dropping && tile.y === this.dropRowY) continue; - if ( - this.x + this.width > tile.x && - this.x < tile.x + tile.width && - this.y + this.height <= tile.y && - nextY + this.height >= tile.y - ) { - // ์ฐฉ์ง€ + if ( this.x + this.width > tile.x &&this.x < tile.x + tile.width && + this.y + this.height <= tile.y && nextY + this.height >= tile.y){ + landed = true; this.y = tile.y - this.height; this.vy = 0; this.onGround = true; this.jumpCount = 0; - // ๋‚˜์ค‘์— DROP ํ‚ค๋กœ ํ†ต๊ณผ์‹œํ‚ฌ ํ–‰ ๊ธฐ๋ก this.currentTileY = tile.y; - // ์ฐฉ์ง€ํ•˜๋ฉด ๋“œ๋กญ ์ƒํƒœ ์ดˆ๊ธฐํ™” this.dropping = false; this.dropRowY = null; break; @@ -368,42 +345,35 @@ class Player { if (this.state === 'walk') { this._animTimer++; - } else { + } + else { this._animTimer = 0; } - // DOWN ํ‚ค๋กœ ํ˜„์žฌ ํ”Œ๋žซํผ ํ–‰ ํ†ต๊ณผ - if ( - this.keys[this.controls.down] && - this.onGround && - this.currentTileY !== null - ) { + if (this.keys[this.controls.down] && this.onGround && this.currentTileY !== null) { this.dropping = true; this.dropRowY = this.currentTileY; this.onGround = false; this.currentTileY = null; this.jumpCount = 1; } - // Knockback decay + this.knockbackVX *= 0.9; if (abs(this.knockbackVX) < 0.1) this.knockbackVX = 0; - // Respawn if fallen off map if (this.y > deathZoneY) this.respawn(); } respawn() { - // ๋ชฉ์ˆจ ํ•˜๋‚˜ ๊ฐ์†Œ effectSound.dead.play(); this.deathCount--; if (this.deathCount > 0) { - // ๋งต ๊ฐ€์šด๋ฐ ์ƒ๋‹จ์œผ๋กœ ๋ฆฌ์…‹ const spawnHeight = 1000; this.x = width/2 - this.width/2; this.y = - spawnHeight; this.vx = this.vy = this.knockbackVX = 0; - } else { - // ๋ชฉ์ˆจ ๋ชจ๋‘ ์†Œ์ง„ โ†’ ๊ฒŒ์ž„ ์˜ค๋ฒ„ + } + else { gameOver = true; winner = (this === player1 ? player2 : player1); } @@ -488,17 +458,13 @@ class Player { draw() { if (this.poisonTimer > 0) { - // R=255, G=B=150 ์ •๋„: ์—ฐํ•œ ๋ถ‰์€๋น›์œผ๋กœ tint(200, 100, 255); } else { noTint(); } - // charge gauge ๊ทธ๋ฆฌ๊ธฐ if (this.chargeTime > 0) { - // ์ตœ๋Œ€ ๋„ˆ๋น„๋ฅผ this.width(32px)๋กœ ๋งคํ•‘ const w = map(this.chargeTime, 0, this.maxCharge, 0, this.width); - // ์ถฉ์ „ ์ „: ๋…ธ๋ž‘, ์ถฉ์ „ ์™„๋ฃŒ ์‹œ: ๋นจ๊ฐ• if (this.chargeTime < this.maxCharge) { fill(255, 255, 0); } else { @@ -509,11 +475,10 @@ class Player { } if (this.state === 'walk') { - const seq = this.imgSet.walk; // [walk1, walk2, walk3] + const seq = this.imgSet.walk; this.frame = Math.floor(this._animTimer / this._animInterval) % seq.length; } else { - // walk ์™ธ ์ƒํƒœ๋Š” 0๋ฒˆ ํ”„๋ ˆ์ž„ ๊ณ ์ • (idle ์ด๋‚˜ shoot ๋“ฑ) this.frame = 0; } const img = this.imgSet[this.state][this.frame]; @@ -530,8 +495,6 @@ class Player { } } - - class Projectile { constructor(x, y, vx, enchanted = false, isgiant = false) { this.x = x; @@ -540,11 +503,10 @@ class Projectile { this.enchanted = enchanted; this.isgiant = isgiant; - // ํฌ๊ธฐ ์„ธํŒ… if (enchanted) { this.width = 16; this.height = 16; - this.knockbackFactor = 1.0; // vx * knockbackFactor ๊ณ„์‚ฐ โ†’ ์‚ฌ์‹ค์ƒ 2๋ฐฐ + this.knockbackFactor = 1.0; this.sprite = itemimgs.fire_enchant[0]; } else if(isgiant) { @@ -567,25 +529,20 @@ class Projectile { } update(targets) { - // ์ด๋™ this.x += this.vx; - // ์ถฉ๋Œ & ๋„‰๋ฐฑ for (const t of targets) { if (!this.shouldDestroy && this.hits(t)) { if (t.giantTimer > 0 || t.poisonTimer > 0) { - // giant ์ƒํƒœ๋ฉด ๋„‰๋ฐฑ ์—†์ด ๊ทธ๋ƒฅ ์‚ญ์ œ this.shouldDestroy = true; } else { - // giant ์ƒํƒœ ์•„๋‹ˆ๋ฉด ๋„‰๋ฐฑ t.knockbackVX += this.vx * this.knockbackFactor; this.shouldDestroy = true; } } } - // ์ˆ˜๋ช… ๊ฒ€์‚ฌ if (millis() - this.spawnTime > this.lifetime) { this.shouldDestroy = true; } @@ -627,36 +584,32 @@ class Bomb { this.shouldRemove = false; this.radius = 100; - this.stuck = false; // ์ฐฉ์ง€ ํ”Œ๋ž˜๊ทธ - this.stuckY = null; // ๋ฉˆ์ถ˜ ํ”Œ๋žซํผ์˜ y ์ขŒํ‘œ + this.stuck = false; + this.stuckY = null; } update() { if (this.stuck) { - // ๋ฐ‘์— ํƒ€์ผ์ด ๋‚จ์•„ ์žˆ๋Š”์ง€ ์ฒดํฌ - const underY = this.y + this.height; - let hasTile = false; - for (let tile of tiles) { - if ( - tile.y === underY && - this.x + this.width > tile.x && - this.x < tile.x + tile.width - ) { - hasTile = true; - break; + const underY = this.y + this.height; + let hasTile = false; + for (let tile of tiles) { + if ( + tile.y === underY && + this.x + this.width > tile.x && + this.x < tile.x + tile.width + ) { + hasTile = true; + break; + } + } + if (!hasTile) { + this.stuck = false; + } + else { + this.vy = 0; + this.y = underY - this.height; } } - if (!hasTile) { - // ํƒ€์ผ์ด ์‚ฌ๋ผ์กŒ์œผ๋ฉด ๋‹ค์‹œ ๋–จ์–ด์ง€๋„๋ก - this.stuck = false; - } - else { - // ์—ฌ์ „ํžˆ ํƒ€์ผ ์œ„๋ฉด ์†๋„ 0, ์œ„์น˜ ๊ณ ์ •๋งŒ - this.vy = 0; - this.y = underY - this.height; - } - } - // 1) ํญ๋ฐœ ํƒ€์ด๋จธ if (!this.exploded) { this.timer--; if (this.timer <= 60) this.warning = true; @@ -667,48 +620,37 @@ class Bomb { } } else { - // ํญ๋ฐœ ์• ๋‹ˆ๋ฉ”์ด์…˜s effectSound.bomb.play(); this.explodeTimer--; if (this.explodeTimer <= 0) this.shouldRemove = true; return; } - // 2) ์ด๋ฏธ ์ฐฉ์ง€(stuck) ์ƒํƒœ๋ฉด ์œ„์น˜๋งŒ ๊ณ ์ • if (this.stuck) { this.vy = 0; - // ๋ฉˆ์ถ˜ ํ–‰์˜ y๊ฐ’ ๊ธฐ์ค€์œผ๋กœ ์œ„์น˜ ๊ณ ์ • this.y = this.stuckY - this.height; return; } - // 3) ์ค‘๋ ฅยท์ด๋™ this.vy += gravity; this.x += this.vx; this.y += this.vy; const landThreshold = 1; - // 4) ํƒ€์ผ ๋ฐ”์šด์Šค ์ฒ˜๋ฆฌ for (let tile of tiles) { - if ( - this.y + this.height >= tile.y && - this.y + this.height - this.vy < tile.y && - this.x + this.width > tile.x && - this.x < tile.x + tile.width + if (this.y + this.height >= tile.y && this.y + this.height - this.vy < tile.y && + this.x + this.width > tile.x && this.x < tile.x + tile.width ) { - // ํƒ€์ผ ๊ผญ๋Œ€๊ธฐ๋กœ ์œ„์น˜ ๊ณ ์ • this.y = tile.y - this.height; - // ๋ฐ˜์‚ฌ ๊ฐ์‡  this.vy *= -0.5; this.vx *= 0.7; - // ์†๋„๊ฐ€ ์ž‘์•„์ง€๋ฉด ์ง„์งœ ์ฐฉ์ง€ if (Math.abs(this.vy) < landThreshold) { this.vy = 0; this.stuck = true; - this.stuckY = tile.y; // y์ขŒํ‘œ๋งŒ ์ €์žฅ + this.stuckY = tile.y; } break; } @@ -716,7 +658,6 @@ class Bomb { } explode() { - // ํญ๋ฐœ ๋ฐ˜๊ฒฝ ๋‚ด ๋ชจ๋“  ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ๋„‰๋ฐฑ ์ ์šฉ [player1, player2].forEach(p => { const cx = this.x + this.width/2; const cy = this.y + this.height/2; @@ -751,10 +692,8 @@ class Bomb { const tx = t.x + TILE_SIZE/2; const ty = t.y + TILE_SIZE/2; if (dist(cx, cy, tx, ty) < this.radius) { - // ํƒ€์ผ ์ œ๊ฑฐ tiles.splice(i, 1); effectSound.breakBlock.play(); - // ํŒŒ๊ดด ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ถ”๊ฐ€ breakEffects.push(new BreakEffect(t.x, t.y)); } } @@ -795,31 +734,25 @@ class BigMissile { } update(targets) { - // ๋ฏธ์‚ฌ์ผ ์ด๋™ + this.x += this.vx; for (const t of targets) { - // 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; @@ -828,7 +761,6 @@ class BigMissile { break; } - // ์ˆ˜๋ช… ๊ฒ€์‚ฌ if (millis() - this.spawnTime > this.lifetime) { this.shouldDestroy = true; } @@ -838,7 +770,6 @@ class BigMissile { 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); @@ -850,7 +781,6 @@ class BigMissile { } hits(target) { - // ๋ฏธ์‚ฌ์ผ ์ž์ฒด ํฌ๊ธฐ๋กœ ์ถฉ๋Œ ์˜์—ญ ๊ณ„์‚ฐ (2๋ฐฐ ํ™•๋Œ€๋œ ํญ) const w = this.width * 2; const h = this.height * 2; return ( @@ -875,10 +805,9 @@ class Item { this.width = TILE_SIZE; this.height = TILE_SIZE; this.toRemove = false; - this.stuck = false; // ์ฐฉ์ง€ ํ”Œ๋ž˜๊ทธ + this.stuck = false; } - /** ๊ธฐ์กด hit ๊ธฐ๋Šฅ: target ์˜ค๋ธŒ์ ํŠธ์™€์˜ ๊ฒน์นจ(์ถฉ๋Œ) ํ™•์ธ */ hits(target) { return ( this.x < target.x + target.width && @@ -888,9 +817,7 @@ class Item { ); } - /** ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ํƒ€์ผ ์œ„ ์ฐฉ์ง€ ํŒ์ •๋งŒ ๋‹ด๋‹น */ landOnTiles() { - // ์•„๋ž˜๋กœ ๋–จ์–ด์งˆ ๋•Œ๋งŒ ๊ฒ€์‚ฌ if (this.vy <= 0) return; const nextY = this.y + this.vy; @@ -898,27 +825,22 @@ class Item { const prevBot = this.y + this.height; const currBot = nextY + this.height; - // โ€œ์œ„โ†’์•„๋ž˜ ๊ถค์  ๊ต์ฐจโ€ + ๊ฐ€๋กœ ๋ฒ”์œ„ ๊ฒน์นจ if (prevBot <= tile.y && currBot >= tile.y && this.x + this.width > tile.x && this.x < tile.x + tile.width ) { - // ๋”ฑ ํƒ€์ผ ์œ„์— ์ฐฉ์ง€ this.y = tile.y - this.height; this.vy = 0; this.stuck = true; return; } } - - // ์ฐฉ์ง€ ๋ชปํ–ˆ์œผ๋ฉด ์‹ค์ œ y ๊ฐฑ์‹  this.y = nextY; } update() { if (this.stuck) { - // ๋ฐ‘์— ํƒ€์ผ์ด ๋‚จ์•„ ์žˆ๋Š”์ง€ ์ฒดํฌ const underY = this.y + this.height; let hasTile = false; for (let tile of tiles) { @@ -932,29 +854,24 @@ class Item { } } if (!hasTile) { - // ํƒ€์ผ์ด ์‚ฌ๋ผ์กŒ์œผ๋ฉด ๋‹ค์‹œ ๋–จ์–ด์ง€๋„๋ก this.stuck = false; - } else { - // ์—ฌ์ „ํžˆ ํƒ€์ผ ์œ„๋ฉด ์†๋„ 0, ์œ„์น˜ ๊ณ ์ •๋งŒ + } + else { this.vy = 0; this.y = underY - this.height; } } - - // 1) ํญ๋ฐœ ๊ฐ™์€ ํŠน๋ณ„ ๋กœ์ง์ด ์—†์œผ๋ฏ€๋กœ ๋ฐ”๋กœ - // ์ฐฉ์ง€ ์ „์ด๋ผ๋ฉด ์ค‘๋ ฅ+์ฐฉ์ง€ ํŒ์ • + if (!this.stuck) { this.vy += 0.5 * gravity; this.landOnTiles(); } - // 2) ํ™”๋ฉด ์•„๋ž˜๋กœ ๋ฒ—์–ด๋‚˜๋ฉด ์ œ๊ฑฐ if (this.y > height) { this.toRemove = true; return; } - // 3) ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ ํŒ์ • (hits ๋ฉ”์„œ๋“œ ์žฌ์‚ฌ์šฉ) for (let p of [player1, player2]) { if (!this.toRemove && this.hits(p)) { p.applyItem(this.type); @@ -1001,7 +918,6 @@ function handleSpecialProjectiles() { } } function breakManager() { - // ๋’ค์—์„œ๋ถ€ํ„ฐ ์ˆœํšŒํ•˜๋ฉฐ ์—…๋ฐ์ดํŠธยท๋ Œ๋”ยท์ œ๊ฑฐ ์ฒ˜๋ฆฌ for (let i = breakEffects.length - 1; i >= 0; i--) { const e = breakEffects[i]; e.update(); @@ -1012,21 +928,16 @@ function breakManager() { } } function randomSpawnItem() { - // 1) ๋žœ๋ค ํƒ€์ด๋ฐ์— ์ƒˆ๋กœ์šด ์•„์ดํ…œ ์Šคํฐ if (frameCount >= nextItemFrame) { - // ํƒ€์ž… ์„ ํƒ const types = ['mush','poison','giant','bombadd']; const type = random(types); - // X ์œ„์น˜๋Š” ํ™”๋ฉด ๊ฐ€๋กœ ๋ฒ”์œ„ ์•ˆ์—์„œ ๋žœ๋ค const x = random(0, width - TILE_SIZE); items.push(new Item(type, x)); - // ๋‹ค์Œ ์Šคํฐ ํƒ€์ด๋ฐ: 3~8์ดˆ ๋’ค - nextItemFrame = frameCount + floor(random(3, 8) * 60); + nextItemFrame = frameCount + floor(random(10,15) * 60); } - // 2) ๊ธฐ์กด ์•„์ดํ…œ๋“ค ์—…๋ฐ์ดํŠธ โ†’ ๋“œ๋กœ์šฐ โ†’ ํ•„์š” ์‹œ ์ œ๊ฑฐ for (let i = items.length - 1; i >= 0; i--) { const it = items[i]; it.update(); @@ -1042,32 +953,23 @@ function drawUI() { const boxH = 60; const pad = 10; - // ๊ณตํ†ต ์Šคํƒ€์ผ textSize(12); textAlign(LEFT, TOP); - // โ”€โ”€โ”€โ”€โ”€โ”€ Player 1 UI (์ขŒํ•˜๋‹จ) โ”€โ”€โ”€โ”€โ”€โ”€ push(); - // ๋ฐ˜ํˆฌ๋ช… ๊ฒ€์ • ๋ฐฐ๊ฒฝ fill(0, 150); noStroke(); rect(pad, height - boxH - pad, boxW, boxH, 4); - // ํฐ์ƒ‰ ํ…์ŠคํŠธ fill(255); - // Ammo (big missiles) text(`Missle: ${player1.bigMissileCount}`, pad+8, height - boxH - pad + 8); - // Lives (deathCount) text(`Lives: ${player1.deathCount}`, pad+8, height - boxH - pad + 24); - // Bombs text(`Bombs: ${player1.bombCount}`, pad+8, height - boxH - pad + 40); pop(); - // โ”€โ”€โ”€โ”€โ”€โ”€ Player 2 UI (์šฐํ•˜๋‹จ) โ”€โ”€โ”€โ”€โ”€โ”€ push(); fill(0, 150); noStroke(); - // ์šฐ์ธก ๋์— ๋ถ™์ด๋ ค๋ฉด width - boxW - pad rect(width - boxW - pad, height - boxH - pad, boxW, boxH, 4); fill(255); @@ -1077,19 +979,16 @@ function drawUI() { pop(); } function drawVictoryScreen() { - // ๋ฐ˜ํˆฌ๋ช… ๊ฒ€์ •์œผ๋กœ ์ „์ฒด ์–ด๋‘ก๊ฒŒ fill(0, 180); rect(0, 0, width, height); - // ๊ฐ€์šด๋ฐ โ€œYOU WINโ€ ํ…์ŠคํŠธ textAlign(CENTER, CENTER); textSize(64); - fill(255, 215, 0); // ๊ณจ๋“œ ์ƒ‰ + fill(255, 215, 0); text('YOU WIN!', width/2, height/2 - 80); - // ์Šน๋ฆฌํ•œ ํ”Œ๋ ˆ์ด์–ด ์–ผ๊ตด ๋˜๋Š” ์Šคํ”„๋ผ์ดํŠธ ํฌ๊ฒŒ ๋ณด์—ฌ ์ฃผ๊ธฐ const iconSize = 128; - const img = winner.imgSet.idle[0]; // ๋˜๋Š” walk[0] ๋“ฑ ์›ํ•˜๋Š” ํ”„๋ ˆ์ž„ + const img = winner.imgSet.idle[0]; image( img, width/2 - iconSize/2,