tankwar / index.html
cutechicken's picture
Update index.html
03f0d26 verified
raw
history blame
45.4 kB
<!DOCTYPE html>
<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;
}
#nextRound {
top: 80% !important;
}
#restart {
top: 80% !important;
}
#winMessage {
top: 30% !important;
font-size: 72px;
background: none;
}
#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;
}
#titleScreen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('city2.png') no-repeat center center;
background-size: cover;
z-index: 2000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#titleScreen h1 {
font-size: 72px;
color: white;
text-shadow: 2px 2px 5px black;
margin-bottom: 50px;
}
.stageButton {
padding: 15px 30px;
font-size: 24px;
background: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 10px;
}
.stageButton:disabled {
background: #666;
cursor: not-allowed;
}
#shop {
position: fixed;
top: 30% !important;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.9);
padding: 20px;
border-radius: 10px;
color: white;
z-index: 1000;
display: none;
}
</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="titleScreen">
<h1>TANK WAR</h1>
<div id="stageSelect">
<button class="stageButton" onclick="startStage(1)">Stage 1</button>
<button class="stageButton" onclick="startStage(2)">Stage 2</button>
<button class="stageButton" disabled>Stage 3</button>
<button class="stageButton" disabled>Stage 4</button>
</div>
</div>
<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">
<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 currentStage = 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;
let hasBF109 = false;
let hasJU87 = false;
let lastJU87Spawn = 0;
let supportUnits = [];
let allyUnits = [];
let lastSupportSpawn = 0;
let gameStarted = false;
//경고 시스템 계승
let warningLines = [];
let spitfires = [];
let lastSpitfireSpawn = 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();
bulletImg.src = 'apcr2.png';
// Audio setup
const cannonSound = new Audio('firemn.ogg');
const machinegunSound = new Audio('firemg.ogg');
const enemyFireSound = new Audio('fireenemy.ogg');
let bgm = new Audio('title.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
}
};
const player = {
x: canvas.width/2,
y: canvas.height/2,
speed: 5,
angle: 0,
width: 100,
height: 45,
health: 1000,
maxHealth: 1000
};
function startStage(stageNumber) {
console.log("Starting stage:", stageNumber);
const titleScreen = document.getElementById('titleScreen');
titleScreen.style.display = 'none';
document.getElementById('instructions').style.display = 'block';
document.getElementById('weaponInfo').style.display = 'block';
document.getElementById('gameCanvas').style.display = 'block';
// 기존 BGM 정지
bgm.pause();
bgm.currentTime = 0;
if (stageNumber === 1) {
backgroundImg.src = 'city.png';
bgm = new Audio('BGM2.ogg');
} else if (stageNumber === 2) {
backgroundImg.src = 'city2.png';
bgm = new Audio('BGM3.ogg');
enemyImg.src = 'enemyuk1.png';
}
// 게임 상태 초기화 추가
currentRound = 1;
currentStage = stageNumber;
gameOver = false;
gold = 0;
hasAPCR = false;
hasBF109 = false;
hasJU87 = false;
allyUnits = [];
supportUnits = [];
bullets = [];
items = [];
effects = [];
bgm.loop = true;
bgm.play().catch(err => console.error("Error playing game music:", err));
gameStarted = true;
initRound();
gameLoop();
}
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);
}
function initRound() {
console.log(`Initializing round ${currentRound}`);
// 버튼 상태 초기화
nextRoundBtn.style.display = 'none';
document.getElementById('bossButton').style.display = 'none';
document.getElementById('shop').style.display = 'none';
document.getElementById('winMessage').style.display = 'none';
// 적 생성
enemies = [];
// 2스테이지에서는 3명부터 시작해서 1명씩 증가
const enemyCount = currentStage === 2 ? currentRound + 2 : currentRound;
for(let i = 0; i < enemyCount; i++) {
let x, y;
const edge = Math.floor(Math.random() * 4);
switch(edge) {
case 0: x = Math.random() * canvas.width; y = 0; break;
case 1: x = canvas.width; y = Math.random() * canvas.height; break;
case 2: x = Math.random() * canvas.width; y = canvas.height; break;
case 3: x = 0; y = Math.random() * canvas.height; break;
}
const enemy = new Enemy();
enemy.x = x;
enemy.y = y;
enemies.push(enemy);
}
// 게임 상태 초기화
player.health = player.maxHealth;
bullets = [];
items = [];
supportUnits = [];
lastSupportSpawn = 0;
// 2스테이지에서 3호전차 지원 유닛 추가
if (currentStage === 2 && allyUnits.length < 2) {
allyUnits.push(new PanzerIII());
}
console.log(`Round ${currentRound} initialized with ${enemies.length} enemies`);
// 카운트다운 시작
startCountdown();
// JU87 스폰 설정
if (hasJU87) {
setTimeout(() => {
supportUnits.push(new JU87());
lastJU87Spawn = Date.now();
}, 3000);
}
}
function checkRoundClear() {
if(enemies.length === 0) {
console.log(`Checking round clear: Current round ${currentRound}, Boss stage: ${isBossStage}`);
if (!isBossStage) {
if(currentRound < 10) {
console.log('Normal round clear - showing next round button and shop');
nextRoundBtn.style.display = 'block';
document.getElementById('bossButton').style.display = 'none';
showShop();
} else {
console.log('Final round clear - showing boss button');
nextRoundBtn.style.display = 'none';
document.getElementById('bossButton').style.display = 'block';
document.getElementById('shop').style.display = 'none';
}
} else {
console.log('Boss clear - showing victory message');
gameOver = true;
document.getElementById('winMessage').style.display = 'block';
document.getElementById('bossButton').style.display = 'none';
nextRoundBtn.style.display = 'none';
document.getElementById('shop').style.display = 'none';
restartBtn.style.display = 'block';
bgm.pause();
const victorySound = new Audio('victory.ogg');
victorySound.play();
}
}
}
function showShop() {
document.getElementById('shop').style.display = 'block';
}
const defaultPlayerStats = {
maxHealth: 1000,
speed: 5,
width: 100,
height: 45
};
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() {
if (enemies.length === 0) return null;
let nearestEnemy = null;
let minDist = Infinity;
enemies.forEach(enemy => {
// 스핏파이어는 무시
if (enemy instanceof Spitfire) return;
const dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
if (dist < minDist) {
minDist = dist;
nearestEnemy = enemy;
}
});
return nearestEnemy;
}
checkCollision() {
if (!this.target) return false;
const dist = Math.hypot(this.target.x - this.x, this.target.y - this.y);
return dist < (this.width + this.target.width) / 2;
}
shoot() {
if (!this.hasPlayedMGSound && !isCountingDown) {
const mgSound = new Audio('ju87mg.ogg');
mgSound.volume = 1.0;
mgSound.play();
this.hasPlayedMGSound = true;
}
[[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
});
});
}
moveToCenter() {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
this.angle = Math.atan2(centerY - this.y, centerX - this.x);
const dist = Math.hypot(centerX - this.x, centerY - this.y);
if (dist > 10) {
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed;
return false;
}
return true;
}
update() {
if (!this.hasPlayedSound) {
const sirenSound = new Audio('ju87siren.ogg');
sirenSound.volume = 1.0;
sirenSound.play();
this.hasPlayedSound = true;
}
const timeSinceSpawn = Date.now() - this.spawnTime;
// 시간 초과시에만 귀환
if (timeSinceSpawn > 5000) {
if (!this.isReturning) {
this.isReturning = true;
this.target = null;
this.returningToCenter = true;
}
}
// 충돌 시 중앙으로 이동 후 재공격
if (this.checkCollision()) {
this.returningToCenter = true;
this.target = null;
}
if (this.isReturning) {
if (this.returningToCenter) {
if (this.moveToCenter()) {
this.returningToCenter = false;
}
} else {
this.angle = Math.PI;
this.x -= this.speed;
return this.x > 0;
}
} else {
if (!this.target || !enemies.includes(this.target)) {
this.target = this.selectTarget();
if (!this.target) {
this.moveToCenter();
}
}
if (this.target) {
this.angle = Math.atan2(this.target.y - this.y, this.target.x - this.x);
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed;
if (Date.now() - this.lastShot > 200) {
this.shoot();
this.lastShot = Date.now();
}
}
}
// 화면 경계 체크
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));
return true;
}
}
//2스테이지 스핏파이어
class Spitfire {
constructor(yPosition) {
this.x = canvas.width;
this.y = yPosition;
this.speed = 5;
this.width = 100;
this.height = 100;
this.lastShot = 0;
this.img = new Image();
this.img.src = 'spitfire.png';
}
shoot() {
const mgSound = new Audio('firemg.ogg');
mgSound.volume = 0.5;
mgSound.play();
bullets.push({
x: this.x,
y: this.y,
angle: Math.PI,
speed: 10,
isEnemy: true,
damage: 100,
size: 2,
isSpitfireBullet: true
});
}
update() {
this.x -= this.speed;
const now = Date.now();
if (now - this.lastShot > 200) { // 1초에 5발
this.shoot();
this.lastShot = now;
}
return this.x > 0;
}
}
//스핏파이어 경고시스템
class WarningLine {
constructor(y) {
this.y = y;
this.startTime = Date.now();
this.duration = 2000; // 2초
}
draw(ctx) {
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.lineWidth = 5; // 선 두께를 5로 증가
ctx.setLineDash([10, 20]); // 점선 패턴도 더 크게 조정
ctx.moveTo(0, this.y);
ctx.lineTo(canvas.width, this.y);
ctx.stroke();
ctx.setLineDash([]);
ctx.lineWidth = 1; // 다른 그리기에 영향을 주지 않도록 리셋
}//문제생기면 }+ 수정
isExpired() {
return Date.now() - this.startTime > this.duration;
}
}
function spawnSpitfires() {
if (currentStage === 2 && !isCountingDown) {
const now = Date.now();
if (now - lastSpitfireSpawn > 15000) { // 15초마다
const positions = [
canvas.height * 0.2,
canvas.height * 0.5,
canvas.height * 0.8
];
// 경고선 생성
positions.forEach(y => {
warningLines.push(new WarningLine(y));
});
// 2초 후 스핏파이어 생성
setTimeout(() => {
positions.forEach(y => {
spitfires.push(new Spitfire(y));
});
}, 2000);
lastSpitfireSpawn = now;
}
}
}
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;
this.mgSound = null;
}
update() {
this.x += this.speed;
if (isCountingDown) {
if (this.mgSound) {
this.mgSound.pause();
this.mgSound.currentTime = 0;
}
this.hasPlayedSound = false;
}
const now = Date.now();
if (now - this.lastShot > 200 && !isCountingDown) {
this.shoot();
this.lastShot = now;
}
return this.x < canvas.width;
}
shoot() {
if (!this.hasPlayedSound) {
const firstSound = new Audio('bf109mg.ogg');
firstSound.volume = 0.7;
firstSound.play();
this.hasPlayedSound = true;
}
if (!isCountingDown) {
const shootSound = new Audio('bf109mgse.ogg');
shootSound.volume = 0.3;
shootSound.play();
}
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 PanzerIII {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.speed = 2;
this.health = 500;
this.maxHealth = 500;
this.angle = 0;
this.width = 70;
this.height = 40;
this.lastShot = 0;
this.shootInterval = 2000;
this.img = new Image();
this.img.src = 'team.png';
this.isDead = false;
}
shoot() {
enemyFireSound.cloneNode().play();
bullets.push({
x: this.x + Math.cos(this.angle) * 30,
y: this.y + Math.sin(this.angle) * 30,
angle: this.angle,
speed: 5,
isEnemy: false,
damage: 50,
size: 3,
color: 'blue'
});
effects.push(new Effect(
this.x + Math.cos(this.angle) * 30,
this.y + Math.sin(this.angle) * 30,
500,
'fire',
this.angle,
this
));
}
update() {
if(isCountingDown || this.isDead) return;
// 가장 가까운 적 찾기
let nearestEnemy = null;
let minDist = Infinity;
enemies.forEach(enemy => {
const dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
if(dist < minDist) {
minDist = dist;
nearestEnemy = enemy;
}
});
// 체력 체크 및 사망 처리
if(this.health <= 0 && !this.isDead) {
this.die();
return;
}
// 적을 향해 이동 및 발사
if(nearestEnemy) {
this.angle = Math.atan2(nearestEnemy.y - this.y, nearestEnemy.x - this.x);
// 일정 거리 유지
if(minDist > 300) {
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed;
}
const now = Date.now();
if(now - this.lastShot > this.shootInterval) {
this.shoot();
this.lastShot = now;
}
}
// 화면 경계 체크
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));
}
die() {
this.isDead = true;
// 폭발 이펙트 추가
effects.push(new Effect(
this.x,
this.y,
1000,
'death'
));
// 폭발 사운드 재생
const deathSound = new Audio('death.ogg');
deathSound.volume = 1.0;
deathSound.play();
}
}
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') {
player.maxHealth = 1500;
player.speed = defaultPlayerStats.speed;
player.width = 90;
player.height = 50;
} else if (tankId === 'tank2') {
player.maxHealth = 2000;
player.speed = defaultPlayerStats.speed * 0.7;
player.width = 100;
player.height = 45;
}
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 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;
}
}
// 스핏파이어 스폰 및 업데이트 로직 추가
spawnSpitfires();
// 스핏파이어 업데이트
spitfires = spitfires.filter(spitfire => spitfire.update());
// 경고선 업데이트
warningLines = warningLines.filter(line => !line.isExpired());
// 지원 유닛 업데이트
supportUnits = supportUnits.filter(unit => unit.update());
// 아군 3호전차 업데이트
allyUnits.forEach(unit => unit.update());
allyUnits = allyUnits.filter(unit => unit.health > 0);
// 적 업데이트
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;
}
return true;
}
return true;
});
} else {
// 상점이 열려있지 않을 때만 플레이어 데미지 처리
if (!document.getElementById('shop').style.display ||
document.getElementById('shop').style.display === 'none') {
const distToPlayer = Math.hypot(bullet.x - player.x, bullet.y - player.y);
if(distToPlayer < 30) {
player.health -= bullet.damage || 100;
if(player.health <= 0) {
gameOver = true;
restartBtn.style.display = 'block';
effects.push(new Effect(player.x, player.y, 1000, 'death'));
deathSound.cloneNode().play();
}
return false;
}
// 아군 3호전차 피격 체크
for(let ally of allyUnits) {
const distToAlly = Math.hypot(bullet.x - ally.x, bullet.y - ally.y);
if(distToAlly < 30) {
ally.health -= bullet.damage || 100;
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;
});
// 아군 전차와 적 전차 충돌 체크
allyUnits.forEach(ally => {
enemies.forEach(enemy => {
const dist = Math.hypot(ally.x - enemy.x, ally.y - enemy.y);
if (dist < (ally.width + enemy.width) / 2) {
// 충돌 시 서로 밀어내기
const angle = Math.atan2(ally.y - enemy.y, ally.x - enemy.x);
const pushDistance = ((ally.width + enemy.width) / 2 - dist) / 2;
ally.x += Math.cos(angle) * pushDistance;
ally.y += Math.sin(angle) * pushDistance;
enemy.x -= Math.cos(angle) * pushDistance;
enemy.y -= Math.sin(angle) * pushDistance;
}
});
});
// 라운드 클리어 체크
checkRoundClear();
}
}
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 ? (currentStage === 2 ? 'enemyukboss.png' : 'boss.png') : enemy.enemyImg;
// 이 부분을 아래와 같이 수정
const img = enemy.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');
});
// 아군 3호전차 그리기
allyUnits.forEach(ally => {
ctx.save();
ctx.translate(ally.x, ally.y);
ctx.rotate(ally.angle);
ctx.drawImage(ally.img, -ally.width/2, -ally.height/2, ally.width, ally.height);
ctx.restore();
drawHealthBar(ally.x, ally.y - 40, ally.health, ally.maxHealth, 60, 5, 'blue');
});
// 경고선 그리기
warningLines.forEach(line => line.draw(ctx));
// 스핏파이어 그리기
spitfires.forEach(spitfire => {
ctx.save();
ctx.translate(spitfire.x, spitfire.y);
ctx.rotate(Math.PI); // 왼쪽으로 비행하므로 180도 회전
ctx.drawImage(spitfire.img, -spitfire.width/2, -spitfire.height/2, spitfire.width, spitfire.height);
ctx.restore();
});
// 지원 유닛 그리기
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.color || (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);
const width = currentWeapon === 'machinegun' ? 10 : 20;
const height = currentWeapon === 'machinegun' ? 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(`Stage ${currentStage} - 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);
const size = effect.type === 'death' ? 75 : 42;
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);
}
}
//bf109 히트 시스템
function updateBF109Damage() {
supportUnits = supportUnits.filter(unit => {
if (unit instanceof SupportUnit) { // BF109인 경우
let hitCount = 0;
bullets = bullets.filter(bullet => {
if (bullet.isSpitfireBullet) {
const dist = Math.hypot(bullet.x - unit.x, bullet.y - unit.y);
if (dist < 30) {
hitCount++;
return false;
}
}
return true;
});
if (hitCount >= 5) {
effects.push(new Effect(unit.x, unit.y, 1000, 'death'));
deathSound.cloneNode().play();
return false;
}
}
return true;
});
}
function gameLoop() {
if (!gameOver && gameStarted) {
updateGame();
drawGame();
requestAnimationFrame(gameLoop);
}
}
class Enemy {
constructor(isBoss = false) {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.health = currentStage === 2 ? (isBoss ? 15000 : 1500) : (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 (currentStage === 2) {
if (isBoss) {
this.enemyImg = new Image();
this.enemyImg.src = 'enemyukboss.png';
} else if (currentRound >= 7) {
this.enemyImg = new Image();
this.enemyImg.src = 'enemyuk3.png';
} else if (currentRound >= 4) {
this.enemyImg = new Image();
this.enemyImg.src = 'enemyuk2.png';
} else {
this.enemyImg = new Image();
this.enemyImg.src = 'enemyuk1.png';
}
} else {
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';
} else {
this.enemyImg = new Image();
this.enemyImg.src = 'enemy.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 startBossStage() {
isBossStage = true;
enemies = [];
enemies.push(new Enemy(true));
player.health = player.maxHealth;
bullets = [];
items = [];
allyUnits = []; // 3호전차 초기화
if (currentStage === 2 && allyUnits.length < 2) {
allyUnits.push(new PanzerIII());
}
document.getElementById('bossButton').style.display = 'none';
bgm.src = 'BGM.ogg';
bgm.loop = true;
bgm.play();
startCountdown();
}
// 이벤트 리스너
document.addEventListener('DOMContentLoaded', () => {
const titleScreen = document.getElementById('titleScreen');
const instructions = document.getElementById('instructions');
const weaponInfo = document.getElementById('weaponInfo');
const gameCanvas = document.getElementById('gameCanvas');
instructions.style.display = 'none';
weaponInfo.style.display = 'none';
gameCanvas.style.display = 'none';
bgm.play().catch(err => console.error("Error playing title music:", err));
// 다음 라운드 버튼 클릭 이벤트
nextRoundBtn.addEventListener('click', () => {
currentRound++;
nextRoundBtn.style.display = 'none';
document.getElementById('shop').style.display = 'none';
initRound();
});
// 재시작 버튼 클릭 이벤트
restartBtn.addEventListener('click', () => {
location.reload();
});
// 보스 버튼 클릭 이벤트
document.getElementById('bossButton').addEventListener('click', () => {
startBossStage();
});
});
// 키보드 및 마우스 이벤트
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);
canvas.addEventListener('mousemove', (e) => {
player.angle = Math.atan2(e.clientY - player.y, e.clientX - player.x);
});
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
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 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,
isEnemy: false,
damage: weapon.damage,
size: weapon.bulletSize,
isAPCR: hasAPCR
});
lastShot = now;
}
}
// Effect 클래스
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;
}
}
</script>
</body>
</html>