Spaces:
Running
Running
| <html> | |
| <head> | |
| <title>Tank Battle</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| background: #333; | |
| font-family: Arial; | |
| } | |
| #gameCanvas { | |
| background-repeat: repeat; | |
| } | |
| #instructions { | |
| position: fixed; | |
| top: 10px; | |
| right: 10px; | |
| color: white; | |
| background: rgba(0,0,0,0.7); | |
| padding: 10px; | |
| border-radius: 5px; | |
| z-index: 1000; | |
| } | |
| #weaponInfo { | |
| position: fixed; | |
| top: 150px; | |
| right: 10px; | |
| color: white; | |
| background: rgba(0,0,0,0.7); | |
| padding: 10px; | |
| border-radius: 5px; | |
| z-index: 1000; | |
| font-size: 18px; | |
| } | |
| .button { | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| padding: 20px 40px; | |
| font-size: 24px; | |
| background: #4CAF50; | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| display: none; | |
| z-index: 1000; | |
| } | |
| #countdown { | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| font-size: 72px; | |
| color: white; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.5); | |
| z-index: 1000; | |
| display: none; | |
| } | |
| #winMessage { | |
| font-size: 72px; | |
| background: none; | |
| top: 30%; /* ํ๋ฉด์ ์์ชฝ์ผ๋ก ์ด๋ */ | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| z-index: 1001; /* ๋ค์ ๋ผ์ด๋ ๋ฒํผ๋ณด๋ค ์๋ก ์ค์ */ | |
| } | |
| #nextRound { | |
| top: 80%; /* ํ๋ฉด์ ์๋์ชฝ์ผ๋ก ์ด๋ */ | |
| z-index: 1000; /* "You Win"๋ณด๋ค ์๋๋ก ์ค์ */ | |
| } | |
| #nextRound { | |
| top: 80%; /* ์๋์ชฝ์ผ๋ก ์ด๋ */ | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="instructions"> | |
| Controls:<br> | |
| WASD - Move tank<br> | |
| Mouse - Aim<br> | |
| Space - Fire<br> | |
| C - Switch Weapon<br> | |
| R - Toggle Auto-fire | |
| </div> | |
| <div id="weaponInfo">Current Weapon: Cannon</div> | |
| <div id="countdown">3</div> | |
| <button id="nextRound" class="button">Next Round</button> | |
| <button id="restart" class="button">Restart Game</button> | |
| <canvas id="gameCanvas"></canvas> | |
| <div id="shop" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:rgba(0,0,0,0.9); padding:20px; border-radius:10px; color:white; z-index:1000;"> | |
| <h2>Tank Shop</h2> | |
| <div style="display:flex; gap:20px;"> | |
| <div id="tank1" style="text-align:center;"> | |
| <h3>PZ.IV</h3> | |
| <img src="player2.png" width="90" height="50"> | |
| <p>300 Gold</p> | |
| <p style="color: #4CAF50;">+50% HP</p> | |
| <button onclick="buyTank('player2.png', 300, 'tank1')">Buy</button> | |
| </div> | |
| <div id="tank2" style="text-align:center;"> | |
| <h3>TIGER</h3> | |
| <img src="player3.png" width="110" height="55"> | |
| <p>500 Gold</p> | |
| <p style="color: #4CAF50;">+100% HP</p> | |
| <p style="color: #ff6b6b;">-30% Speed</p> | |
| <button onclick="buyTank('player3.png', 500, 'tank2')">Buy</button> | |
| </div> | |
| <div id="bf109" style="text-align:center;"> | |
| <h3>BF-109</h3> | |
| <img src="bf109.png" width="100" height="100"> | |
| <p>1000 Gold</p> | |
| <p style="color: #4CAF50;">Air support from BF-109</p> | |
| <button onclick="buyBF109()">Buy</button> | |
| </div> | |
| <div id="ju87" style="text-align:center;"> | |
| <h3>JU-87</h3> | |
| <img src="ju87.png" width="100" height="100"> | |
| <p>1500 Gold</p> | |
| <p style="color: #4CAF50;">Get ju-87 air support</p> | |
| <button onclick="buyJU87()">Buy</button> | |
| </div> | |
| <div id="apcr" style="text-align:center;"> | |
| <h3>APCR</h3> | |
| <img src="apcr.png" width="80" height="20"> <!-- ์ฌ๊ธฐ๋ฅผ 80x20์ผ๋ก ์์ --> | |
| <p>1000 Gold</p> | |
| <p style="color: #4CAF50;">+100% Bullet Speed</p> | |
| <button onclick="buyAPCR()">Buy</button> | |
| </div> | |
| </div> | |
| </div> | |
| <button id="bossButton" class="button">Fight Boss!</button> | |
| <div id="winMessage" class="button" style="font-size: 72px; background: none;">You Win!</div> | |
| <script> | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const nextRoundBtn = document.getElementById('nextRound'); | |
| const restartBtn = document.getElementById('restart'); | |
| const weaponInfo = document.getElementById('weaponInfo'); | |
| const countdownEl = document.getElementById('countdown'); | |
| const bossButton = document.getElementById('bossButton'); | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| // Game state | |
| let currentRound = 1; | |
| let gameOver = false; | |
| let currentWeapon = 'cannon'; | |
| let enemies = []; | |
| let bullets = []; | |
| let items = []; | |
| let lastShot = 0; | |
| let isCountingDown = true; | |
| let countdownTime = 3; | |
| let autoFire = false; | |
| let gold = 0; | |
| let isBossStage = false; | |
| let effects = []; | |
| let hasAPCR = false; // APCR ๊ตฌ๋งค ์ฌ๋ถ | |
| let hasBF109 = false; // BF-109 ๊ตฌ๋งค ์ฌ๋ถ | |
| let hasJU87 = false; // JU-87 ๊ตฌ๋งค ์ฌ๋ถ | |
| let lastJU87Spawn = 0; // ๋ง์ง๋ง JU-87 ์์ฑ ์๊ฐ | |
| let supportUnits = []; // ์ง์ ์ ๋ ๋ฐฐ์ด | |
| let lastSupportSpawn = 0; // ๋ง์ง๋ง ์ง์ ์ ๋ ์์ฑ ์๊ฐ | |
| // Load assets | |
| const backgroundImg = new Image(); | |
| backgroundImg.src = 'city.png'; | |
| const playerImg = new Image(); | |
| playerImg.src = 'player.png'; | |
| const enemyImg = new Image(); | |
| enemyImg.src = 'enemy.png'; | |
| const bulletImg = new Image(); // APCR ์ด์ ์ด๋ฏธ์ง | |
| bulletImg.src = 'apcr2.png'; | |
| // Audio setup | |
| const cannonSound = new Audio('firemn.ogg'); | |
| const machinegunSound = new Audio('firemg.ogg'); | |
| const enemyFireSound = new Audio('fireenemy.ogg'); | |
| const bgm = new Audio('BGM2.ogg'); | |
| const countSound = new Audio('count.ogg'); | |
| const deathSound = new Audio('death.ogg'); | |
| bgm.loop = true; | |
| enemyFireSound.volume = 0.5; | |
| const weapons = { | |
| cannon: { | |
| fireRate: 1000, | |
| damage: 0.25, | |
| bulletSize: 5, | |
| sound: cannonSound | |
| }, | |
| machinegun: { | |
| fireRate: 200, | |
| damage: 0.05, | |
| bulletSize: 2, | |
| sound: machinegunSound | |
| } | |
| }; | |
| // Player setup | |
| const player = { | |
| x: canvas.width/2, | |
| y: canvas.height/2, | |
| speed: 5, | |
| angle: 0, | |
| width: 100, | |
| height: 45, | |
| health: 1000, | |
| maxHealth: 1000 | |
| }; | |
| function startCountdown() { | |
| isCountingDown = true; | |
| countdownTime = 3; | |
| countdownEl.style.display = 'block'; | |
| countdownEl.textContent = countdownTime; | |
| bgm.pause(); | |
| countSound.play(); | |
| const countInterval = setInterval(() => { | |
| countdownTime--; | |
| if(countdownTime <= 0) { | |
| clearInterval(countInterval); | |
| countdownEl.style.display = 'none'; | |
| isCountingDown = false; | |
| bgm.play(); | |
| } | |
| countdownEl.textContent = countdownTime > 0 ? countdownTime : 'GO!'; | |
| }, 1000); | |
| } | |
| class Effect { | |
| constructor(x, y, duration, type, angle = 0, parent = null) { | |
| this.x = x; | |
| this.y = y; | |
| this.startTime = Date.now(); | |
| this.duration = duration; | |
| this.type = type; | |
| this.angle = angle; | |
| this.parent = parent; // ๋ถ๋ชจ ์ ๋ (๋ฐ์ฌํ ์ ๋) | |
| this.offset = { x: Math.cos(angle) * 30, y: Math.sin(angle) * 30 }; // ๋ถ๋ชจ๋ก๋ถํฐ์ ์คํ์ | |
| this.img = new Image(); | |
| this.img.src = type === 'death' ? 'bang.png' : 'fire2.png'; | |
| } | |
| update() { | |
| if(this.parent && this.type === 'fire') { | |
| this.x = this.parent.x + this.offset.x; | |
| this.y = this.parent.y + this.offset.y; | |
| this.angle = this.parent.angle; | |
| } | |
| } | |
| isExpired() { | |
| return Date.now() - this.startTime > this.duration; | |
| } | |
| } | |
| class SupportUnit { | |
| constructor(yPosition) { | |
| this.x = 0; | |
| this.y = yPosition; | |
| this.speed = 5; | |
| this.lastShot = 0; | |
| this.width = 100; | |
| this.height = 100; | |
| this.angle = 0; // ํญ์ ์ค๋ฅธ์ชฝ์ ํฅํจ | |
| this.img = new Image(); | |
| this.img.src = 'bf109.png'; | |
| this.hasPlayedSound = false; // ์๋ฆฌ ์ฌ์ ์ฌ๋ถ ์ถ๊ฐ | |
| } | |
| update() { | |
| // ์ด๋ | |
| this.x += this.speed; | |
| // ๋ฐ์ฌ (1์ด์ 5๋ฐ) | |
| const now = Date.now(); | |
| if (now - this.lastShot > 200) { | |
| this.shoot(); | |
| this.lastShot = now; | |
| } | |
| return this.x < canvas.width; | |
| } | |
| shoot() { | |
| // ์นด์ดํธ๋ค์ด ์ค์ด ์๋ ๋๋ง ์๋ฆฌ ์ฌ์ | |
| if (!this.hasPlayedSound && !isCountingDown) { | |
| new Audio('bf109mg.ogg').play(); | |
| this.hasPlayedSound = true; | |
| } | |
| bullets.push({ | |
| x: this.x + Math.cos(this.angle) * 30, | |
| y: this.y + Math.sin(this.angle) * 30, | |
| angle: this.angle, | |
| speed: 10, | |
| isEnemy: false, | |
| damage: weapons.machinegun.damage, | |
| size: weapons.machinegun.bulletSize | |
| }); | |
| } | |
| } | |
| class JU87 { | |
| constructor() { | |
| this.x = canvas.width; | |
| this.y = 50; | |
| this.speed = 5; | |
| this.width = 100; | |
| this.height = 100; | |
| this.angle = Math.PI; | |
| this.img = new Image(); | |
| this.img.src = 'ju87.png'; | |
| this.target = null; | |
| this.lastShot = 0; | |
| this.spawnTime = Date.now(); | |
| this.hasPlayedSound = false; | |
| this.hasPlayedMGSound = false; | |
| this.isReturning = false; | |
| this.circleAngle = 0; | |
| this.returningToCenter = false; // ์ค์์ผ๋ก ๋์๊ฐ๋ ์ํ ์ถ๊ฐ | |
| } | |
| selectTarget() { | |
| return enemies.length > 0 ? | |
| enemies[Math.floor(Math.random() * enemies.length)] : null; | |
| } | |
| shoot() { | |
| // ์นด์ดํธ๋ค์ด ์ค์ด ์๋ ๋๋ง ์๋ฆฌ ์ฌ์ | |
| if (!this.hasPlayedMGSound && !isCountingDown) { | |
| const mgSound = new Audio('ju87mg.ogg'); | |
| mgSound.volume = 1.0; | |
| mgSound.currentTime = 0; | |
| mgSound.play().catch(error => console.error('Audio play failed:', error)); | |
| this.hasPlayedMGSound = true; | |
| } | |
| // 100x100 ํฝ์ ๊ธฐ์ค์ผ๋ก ๋ ๊ฐ ์์น ์ขํ ์ค์ | |
| [[20, 50], [80, 50]].forEach(([x, y]) => { | |
| const offsetX = x - 50; | |
| const offsetY = y - 50; | |
| const rotatedX = this.x + (Math.cos(this.angle) * offsetX - Math.sin(this.angle) * offsetY); | |
| const rotatedY = this.y + (Math.sin(this.angle) * offsetX + Math.cos(this.angle) * offsetY); | |
| bullets.push({ | |
| x: rotatedX, | |
| y: rotatedY, | |
| angle: this.angle, | |
| speed: 10, | |
| isEnemy: false, | |
| damage: weapons.machinegun.damage * 2, | |
| size: weapons.machinegun.bulletSize | |
| }); | |
| }); | |
| } | |
| update() { | |
| if (!this.hasPlayedSound) { | |
| const sirenSound = new Audio('ju87siren.ogg'); | |
| sirenSound.volume = 1.0; | |
| sirenSound.play().catch(error => console.error('Audio play failed:', error)); | |
| this.hasPlayedSound = true; | |
| } | |
| const timeSinceSpawn = Date.now() - this.spawnTime; | |
| const centerX = canvas.width / 2; | |
| const centerY = canvas.height / 2; | |
| if (timeSinceSpawn > 5000) { | |
| if (!this.isReturning) { | |
| this.isReturning = true; | |
| this.target = null; | |
| this.angle = Math.atan2(centerY - this.y, centerX - this.x); | |
| } else { | |
| this.angle = Math.PI; | |
| this.x -= this.speed; | |
| return this.x > 0; | |
| } | |
| } else if (this.returningToCenter) { | |
| const distToCenter = Math.hypot(this.x - centerX, this.y - centerY); | |
| if (distToCenter > 50) { | |
| this.angle = Math.atan2(centerY - this.y, centerX - this.x); | |
| } else { | |
| this.returningToCenter = false; | |
| this.target = this.selectTarget(); | |
| } | |
| } else if (this.target) { | |
| const distToTarget = Math.hypot(this.x - this.target.x, this.y - this.target.y); | |
| if (!enemies.includes(this.target) || distToTarget < 30) { | |
| this.returningToCenter = true; | |
| this.target = null; | |
| } else { | |
| this.angle = Math.atan2(this.target.y - this.y, this.target.x - this.x); | |
| } | |
| } else { | |
| this.target = this.selectTarget(); | |
| if (!this.target) { | |
| this.circleAngle += 0.02; | |
| const radius = Math.min(canvas.width, canvas.height) / 4; | |
| const targetX = centerX + Math.cos(this.circleAngle) * radius; | |
| const targetY = centerY + Math.sin(this.circleAngle) * radius; | |
| this.angle = Math.atan2(targetY - this.y, targetX - this.x); | |
| } | |
| } | |
| this.x += Math.cos(this.angle) * this.speed; | |
| this.y += Math.sin(this.angle) * this.speed; | |
| if (!this.returningToCenter && !this.isReturning && this.target && | |
| Date.now() - this.lastShot > 200) { | |
| this.shoot(); | |
| this.lastShot = Date.now(); | |
| } | |
| return true; | |
| } | |
| } | |
| class Enemy { | |
| constructor(isBoss = false) { | |
| this.x = Math.random() * canvas.width; | |
| this.y = Math.random() * canvas.height; | |
| this.health = isBoss ? 15000 : 1000; | |
| this.maxHealth = this.health; | |
| this.speed = isBoss ? 1 : 2; | |
| this.lastShot = 0; | |
| this.shootInterval = isBoss ? 1000 : 1000; | |
| this.angle = 0; | |
| this.width = 100; | |
| this.height = 45; | |
| this.moveTimer = 0; | |
| this.moveInterval = Math.random() * 2000 + 1000; | |
| this.moveAngle = Math.random() * Math.PI * 2; | |
| this.isBoss = isBoss; | |
| if (isBoss) { | |
| this.enemyImg = new Image(); | |
| this.enemyImg.src = 'boss.png'; | |
| } else if (currentRound >= 7) { | |
| this.enemyImg = new Image(); | |
| this.enemyImg.src = 'enemy3.png'; | |
| } else if (currentRound >= 4) { | |
| this.enemyImg = new Image(); | |
| this.enemyImg.src = 'enemy2.png'; | |
| } | |
| } | |
| update() { | |
| if(isCountingDown) return; | |
| const now = Date.now(); | |
| if (now - this.moveTimer > this.moveInterval) { | |
| this.moveAngle = Math.random() * Math.PI * 2; | |
| this.moveTimer = now; | |
| } | |
| this.x += Math.cos(this.moveAngle) * this.speed; | |
| this.y += Math.sin(this.moveAngle) * this.speed; | |
| this.x = Math.max(this.width/2, Math.min(canvas.width - this.width/2, this.x)); | |
| this.y = Math.max(this.height/2, Math.min(canvas.height - this.height/2, this.y)); | |
| this.angle = Math.atan2(player.y - this.y, player.x - this.x); | |
| if (now - this.lastShot > this.shootInterval && !isCountingDown) { | |
| this.shoot(); | |
| this.lastShot = now; | |
| } | |
| } | |
| shoot() { | |
| const sound = this.isBoss ? new Audio('firemn.ogg') : enemyFireSound.cloneNode(); | |
| sound.play(); | |
| // ๋ฐ์ฌ ์ดํํธ ์ถ๊ฐ | |
| effects.push(new Effect( | |
| this.x + Math.cos(this.angle) * 30, | |
| this.y + Math.sin(this.angle) * 30, | |
| 500, | |
| 'fire', | |
| this.angle, | |
| this // ์์ ์ ๋ถ๋ชจ๋ก ์ ๋ฌ | |
| )); | |
| bullets.push({ | |
| x: this.x + Math.cos(this.angle) * 30, | |
| y: this.y + Math.sin(this.angle) * 30, | |
| angle: this.angle, | |
| speed: this.isBoss ? 10 : 5, | |
| isEnemy: true, | |
| size: this.isBoss ? 5 : 3, | |
| damage: this.isBoss ? 300 : 150 | |
| }); | |
| } | |
| } | |
| function showShop() { | |
| document.getElementById('shop').style.display = 'block'; | |
| } | |
| // ํ๋ ์ด์ด์ ๊ธฐ๋ณธ ์ํ๋ฅผ ์ ์ฅ | |
| const defaultPlayerStats = { | |
| maxHealth: 1000, | |
| speed: 5, | |
| width: 100, | |
| height: 45 | |
| }; | |
| function buyTank(tankImg, cost, tankId) { | |
| if (gold >= cost) { | |
| gold -= cost; | |
| playerImg.src = tankImg; | |
| document.getElementById(tankId).style.display = 'none'; | |
| document.getElementById('shop').style.display = 'none'; | |
| if (tankId === 'tank1') { // PZ.IV | |
| player.maxHealth = 1500; | |
| player.speed = defaultPlayerStats.speed; // ๊ธฐ๋ณธ ์ด๋์๋๋ก ๋ณต๊ตฌ | |
| player.width = 90; // PZ.IV์ ํฌ๊ธฐ ์ค์ | |
| player.height = 50; // PZ.IV์ ํฌ๊ธฐ ์ค์ | |
| } | |
| else if (tankId === 'tank2') { // TIGER | |
| player.maxHealth = 2000; | |
| player.speed = defaultPlayerStats.speed * 0.7; | |
| player.width = 100; // TIGER๋ ๊ธฐ๋ณธ ํฌ๊ธฐ ์ ์ง | |
| player.height = 45; // TIGER๋ ๊ธฐ๋ณธ ํฌ๊ธฐ ์ ์ง | |
| } | |
| player.health = player.maxHealth; | |
| } | |
| } | |
| function buyAPCR() { | |
| if (gold >= 1000 && !hasAPCR) { | |
| gold -= 1000; | |
| hasAPCR = true; | |
| document.getElementById('apcr').style.display = 'none'; | |
| document.getElementById('shop').style.display = 'none'; | |
| } | |
| } | |
| function buyBF109() { | |
| if (gold >= 1000 && !hasBF109) { | |
| gold -= 1000; | |
| hasBF109 = true; | |
| document.getElementById('bf109').style.display = 'none'; | |
| document.getElementById('shop').style.display = 'none'; | |
| } | |
| } | |
| function buyJU87() { | |
| if (gold >= 1500 && !hasJU87) { | |
| gold -= 1500; | |
| hasJU87 = true; | |
| document.getElementById('ju87').style.display = 'none'; | |
| document.getElementById('shop').style.display = 'none'; | |
| lastJU87Spawn = Date.now(); // ๊ตฌ๋งค ์ฆ์ ์คํฐ ํ์ด๋จธ ์ด๊ธฐํ | |
| } | |
| } | |
| function initRound() { | |
| enemies = []; | |
| for(let i = 0; i < 1 * currentRound; i++) { | |
| enemies.push(new Enemy()); | |
| } | |
| player.health = player.maxHealth; | |
| bullets = []; | |
| items = []; | |
| supportUnits = []; | |
| lastSupportSpawn = 0; | |
| // ์นด์ดํธ๋ค์ด ์์ | |
| startCountdown(); | |
| // ์นด์ดํธ๋ค์ด์ด ๋๋๋ฉด JU87 ์คํฐ | |
| setTimeout(() => { | |
| if (hasJU87) { | |
| supportUnits.push(new JU87()); | |
| lastJU87Spawn = Date.now(); | |
| } | |
| }, 3000); // 3์ด ํ์ ์คํฐ | |
| } | |
| function startBossStage() { | |
| isBossStage = true; | |
| enemies = []; | |
| enemies.push(new Enemy(true)); | |
| player.health = player.maxHealth; | |
| bullets = []; | |
| items = []; | |
| document.getElementById('bossButton').style.display = 'none'; | |
| bgm.src = 'BGM.ogg'; // ๋ณด์ค์ BGM์ผ๋ก ๋ณ๊ฒฝ | |
| startCountdown(); | |
| } | |
| canvas.addEventListener('mousemove', (e) => { | |
| player.angle = Math.atan2(e.clientY - player.y, e.clientX - player.x); | |
| }); | |
| const keys = {}; | |
| document.addEventListener('keydown', e => { | |
| keys[e.key] = true; | |
| if(e.key.toLowerCase() === 'c') { | |
| currentWeapon = currentWeapon === 'cannon' ? 'machinegun' : 'cannon'; | |
| weaponInfo.textContent = `Current Weapon: ${currentWeapon.charAt(0).toUpperCase() + currentWeapon.slice(1)}`; | |
| } else if(e.key.toLowerCase() === 'r') { | |
| autoFire = !autoFire; | |
| } | |
| }); | |
| document.addEventListener('keyup', e => keys[e.key] = false); | |
| function fireBullet() { | |
| if(isCountingDown) return; | |
| const weapon = weapons[currentWeapon]; | |
| const now = Date.now(); | |
| if ((keys[' '] || autoFire) && now - lastShot > weapon.fireRate) { | |
| weapon.sound.cloneNode().play(); | |
| effects.push(new Effect( | |
| player.x + Math.cos(player.angle) * 30, | |
| player.y + Math.sin(player.angle) * 30, | |
| 500, | |
| 'fire', | |
| player.angle, | |
| player | |
| )); | |
| bullets.push({ | |
| x: player.x + Math.cos(player.angle) * 30, | |
| y: player.y + Math.sin(player.angle) * 30, | |
| angle: player.angle, | |
| speed: hasAPCR ? 20 : 10, // APCR ์ ์ฉ ์ 100% ์ฆ๊ฐ | |
| isEnemy: false, | |
| damage: weapon.damage, | |
| size: weapon.bulletSize, | |
| isAPCR: hasAPCR | |
| }); | |
| lastShot = now; | |
| } | |
| } | |
| function updateGame() { | |
| if(gameOver) return; | |
| if(!isCountingDown) { | |
| // ํ๋ ์ด์ด ์์ง์ | |
| if(keys['w']) player.y -= player.speed; | |
| if(keys['s']) player.y += player.speed; | |
| if(keys['a']) player.x -= player.speed; | |
| if(keys['d']) player.x += player.speed; | |
| player.x = Math.max(player.width/2, Math.min(canvas.width - player.width/2, player.x)); | |
| player.y = Math.max(player.height/2, Math.min(canvas.height - player.height/2, player.y)); | |
| fireBullet(); | |
| } | |
| // BF109 ๊ด๋ จ ์ฝ๋ | |
| if (hasBF109 && !isCountingDown) { | |
| const now = Date.now(); | |
| if (now - lastSupportSpawn > 10000) { // 10์ด๋ง๋ค | |
| supportUnits.push( | |
| new SupportUnit(canvas.height * 0.2), | |
| new SupportUnit(canvas.height * 0.5), | |
| new SupportUnit(canvas.height * 0.8) | |
| ); | |
| lastSupportSpawn = now; | |
| } | |
| } | |
| // JU87 ๊ด๋ จ ์ฝ๋ ์ถ๊ฐ | |
| if (hasJU87 && !isCountingDown) { | |
| const now = Date.now(); | |
| if (now - lastJU87Spawn > 15000) { // 15์ด๋ง๋ค | |
| supportUnits.push(new JU87()); | |
| lastJU87Spawn = now; | |
| } | |
| } | |
| // ๋ชจ๋ ์ง์ ์ ๋ ์ ๋ฐ์ดํธ (BF109์ JU87 ๋ชจ๋ ์ฒ๋ฆฌ) | |
| supportUnits = supportUnits.filter(unit => unit.update()); | |
| enemies.forEach(enemy => enemy.update()); | |
| if(!isCountingDown) { | |
| bullets = bullets.filter(bullet => { | |
| bullet.x += Math.cos(bullet.angle) * bullet.speed; | |
| bullet.y += Math.sin(bullet.angle) * bullet.speed; | |
| if(!bullet.isEnemy) { | |
| enemies = enemies.filter(enemy => { | |
| const dist = Math.hypot(bullet.x - enemy.x, bullet.y - enemy.y); | |
| if(dist < 30) { | |
| let damage = currentWeapon === 'cannon' ? 250 : 50; // ๊ณ ์ ๋ฐ๋ฏธ์ง๋ก ๋ณ๊ฒฝ | |
| enemy.health -= damage; | |
| if(enemy.health <= 0) { | |
| spawnHealthItem(enemy.x, enemy.y); | |
| gold += 100; | |
| // ์ฃฝ์ ์ดํํธ์ ์ฌ์ด๋ ์ถ๊ฐ | |
| effects.push(new Effect(enemy.x, enemy.y, 1000, 'death')); | |
| deathSound.cloneNode().play(); | |
| return false; | |
| } | |
| if(player.health <= 0) { | |
| gameOver = true; | |
| restartBtn.style.display = 'block'; | |
| effects.push(new Effect(player.x, player.y, 1000, 'death')); | |
| deathSound.cloneNode().play(); | |
| } | |
| return true; | |
| } | |
| return true; | |
| }); | |
| } else { | |
| const dist = Math.hypot(bullet.x - player.x, bullet.y - player.y); | |
| if(dist < 30) { | |
| player.health -= bullet.damage || 100; | |
| if(player.health <= 0) { | |
| gameOver = true; | |
| restartBtn.style.display = 'block'; | |
| } | |
| return false; | |
| } | |
| } | |
| return bullet.x >= 0 && bullet.x <= canvas.width && | |
| bullet.y >= 0 && bullet.y <= canvas.height; | |
| }); | |
| items = items.filter(item => { | |
| const dist = Math.hypot(item.x - player.x, item.y - player.y); | |
| if(dist < 30) { | |
| player.health = Math.min(player.health + 200, player.maxHealth); | |
| return false; | |
| } | |
| return true; | |
| }); | |
| if(enemies.length === 0) { | |
| if (!isBossStage) { | |
| if(currentRound < 10) { | |
| nextRoundBtn.style.display = 'block'; | |
| showShop(); | |
| } else { | |
| document.getElementById('bossButton').style.display = 'block'; | |
| } | |
| } else { | |
| gameOver = true; | |
| document.getElementById('winMessage').style.display = 'block'; | |
| restartBtn.style.display = 'block'; | |
| bgm.pause(); // ํ์ฌ BGM ์ ์ง | |
| const victorySound = new Audio('victory.ogg'); // ์น๋ฆฌ ์ฌ์ด๋ ์์ฑ | |
| victorySound.play(); // ์น๋ฆฌ ์ฌ์ด๋ ์ฌ์ | |
| } | |
| } | |
| } | |
| enemies.forEach(enemy => enemy.update()); | |
| } | |
| function spawnHealthItem(x, y) { | |
| items.push({x, y}); | |
| } | |
| function drawHealthBar(x, y, health, maxHealth, width, height, color) { | |
| ctx.fillStyle = '#333'; | |
| ctx.fillRect(x - width/2, y - height/2, width, height); | |
| ctx.fillStyle = color; | |
| ctx.fillRect(x - width/2, y - height/2, width * (health/maxHealth), height); | |
| } | |
| function drawGame() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| const pattern = ctx.createPattern(backgroundImg, 'repeat'); | |
| ctx.fillStyle = pattern; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // ํ๋ ์ด์ด ๊ทธ๋ฆฌ๊ธฐ | |
| ctx.save(); | |
| ctx.translate(player.x, player.y); | |
| ctx.rotate(player.angle); | |
| ctx.drawImage(playerImg, -player.width/2, -player.height/2, player.width, player.height); | |
| ctx.restore(); | |
| // ์ฒด๋ ฅ๋ฐ | |
| drawHealthBar(canvas.width/2, 30, player.health, player.maxHealth, 200, 20, 'green'); | |
| // ์ ๊ทธ๋ฆฌ๊ธฐ | |
| enemies.forEach(enemy => { | |
| ctx.save(); | |
| ctx.translate(enemy.x, enemy.y); | |
| ctx.rotate(enemy.angle); | |
| const img = enemy.isBoss ? enemy.enemyImg : (enemy.enemyImg || enemyImg); | |
| ctx.drawImage(img, -enemy.width/2, -enemy.height/2, enemy.width, enemy.height); | |
| ctx.restore(); | |
| drawHealthBar(enemy.x, enemy.y - 40, enemy.health, enemy.maxHealth, 60, 5, 'red'); | |
| }); | |
| supportUnits.forEach(unit => { | |
| ctx.save(); | |
| ctx.translate(unit.x, unit.y); | |
| ctx.rotate(unit.angle); | |
| ctx.drawImage(unit.img, -unit.width/2, -unit.height/2, unit.width, unit.height); | |
| ctx.restore(); | |
| }); | |
| // ์ด์ ๊ทธ๋ฆฌ๊ธฐ | |
| bullets.forEach(bullet => { | |
| if (bullet.isEnemy || !bullet.isAPCR) { | |
| ctx.beginPath(); | |
| ctx.fillStyle = bullet.isEnemy ? 'red' : 'blue'; | |
| ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } else { | |
| ctx.save(); | |
| ctx.translate(bullet.x, bullet.y); | |
| ctx.rotate(bullet.angle); | |
| // ๊ธฐ๊ด์ด์ผ ๋ ํฌ๊ธฐ 50% ๊ฐ์ | |
| const width = currentWeapon === 'machinegun' ? 10 : 20; // ๊ธฐ๊ด์ด์ผ ๋ 10, ์บ๋ ผ์ผ ๋ 20 | |
| const height = currentWeapon === 'machinegun' ? 5 : 10; // ๊ธฐ๊ด์ด์ผ ๋ 5, ์บ๋ ผ์ผ ๋ 10 | |
| ctx.drawImage(bulletImg, -width/2, -height/2, width, height); | |
| ctx.restore(); | |
| } | |
| }); | |
| // ์์ดํ ๊ทธ๋ฆฌ๊ธฐ | |
| items.forEach(item => { | |
| ctx.beginPath(); | |
| ctx.fillStyle = 'green'; | |
| ctx.arc(item.x, item.y, 10, 0, Math.PI * 2); | |
| ctx.fill(); | |
| }); | |
| // UI ๊ทธ๋ฆฌ๊ธฐ | |
| ctx.fillStyle = 'white'; | |
| ctx.font = '24px Arial'; | |
| ctx.fillText(`Round ${currentRound}/10`, 10, 30); | |
| ctx.fillText(`Gold: ${gold}`, 10, 60); | |
| // ์ดํํธ ๊ทธ๋ฆฌ๊ธฐ | |
| effects = effects.filter(effect => !effect.isExpired()); | |
| effects.forEach(effect => { | |
| effect.update(); // ์ดํํธ ์์น ์ ๋ฐ์ดํธ | |
| ctx.save(); | |
| ctx.translate(effect.x, effect.y); | |
| if(effect.type === 'fire') ctx.rotate(effect.angle); | |
| // bang.png๋ 1.5๋ฐฐ ํฌ๊ฒ | |
| const size = effect.type === 'death' ? 75 : 42; // death๋ 75px (1.5๋ฐฐ), fire๋ 42px | |
| ctx.drawImage(effect.img, -size/2, -size/2, size, size); | |
| ctx.restore(); | |
| }); | |
| if(isCountingDown) { | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| } | |
| } | |
| // drawGame ํจ์ ๋ฐ์ผ๋ก ์ด๋ | |
| function gameLoop() { | |
| updateGame(); | |
| drawGame(); | |
| requestAnimationFrame(gameLoop); | |
| } | |
| nextRoundBtn.addEventListener('click', () => { | |
| currentRound++; | |
| nextRoundBtn.style.display = 'none'; | |
| document.getElementById('shop').style.display = 'none'; | |
| initRound(); | |
| }); | |
| bossButton.addEventListener('click', startBossStage); | |
| restartBtn.addEventListener('click', () => { | |
| currentRound = 1; | |
| gameOver = false; | |
| isBossStage = false; | |
| gold = 0; | |
| hasAPCR = false; // APCR ์ด๊ธฐํ | |
| hasBF109 = false; // BF109 ์ด๊ธฐํ | |
| hasJU87 = false; | |
| supportUnits = []; // ์ง์ ์ ๋ ๋ฐฐ์ด ์ด๊ธฐํ | |
| restartBtn.style.display = 'none'; | |
| document.getElementById('winMessage').style.display = 'none'; | |
| document.getElementById('tank1').style.display = 'block'; | |
| document.getElementById('tank2').style.display = 'block'; | |
| document.getElementById('apcr').style.display = 'block'; | |
| document.getElementById('bf109').style.display = 'block'; // BF109 ์์ ์์ดํ ๋ค์ ํ์ | |
| playerImg.src = 'player.png'; | |
| bgm.src = 'BGM2.ogg'; | |
| bgm.play(); | |
| initRound(); | |
| }); | |
| Promise.all([ | |
| new Promise(resolve => backgroundImg.onload = resolve), | |
| new Promise(resolve => playerImg.onload = resolve), | |
| new Promise(resolve => enemyImg.onload = resolve) | |
| ]).then(() => { | |
| initRound(); | |
| gameLoop(); | |
| bgm.play(); | |
| }); | |
| window.addEventListener('resize', () => { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| }); | |
| </script> | |
| </body> | |
| </html> |