works better
This commit is contained in:
517
index.html
517
index.html
@ -67,6 +67,74 @@
|
||||
color: #ff4444 !important;
|
||||
text-shadow: 0 0 10px rgba(255, 68, 68, 0.5) !important;
|
||||
}
|
||||
|
||||
.game-over-screen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.game-over-content {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.game-over-content h1 {
|
||||
font-size: 4rem;
|
||||
color: #ff4444;
|
||||
text-shadow: 0 0 20px rgba(255, 68, 68, 0.8);
|
||||
margin-bottom: 30px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.score-display {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.score-display div {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.score-display span {
|
||||
color: #00ff88;
|
||||
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.restart-btn {
|
||||
background: linear-gradient(135deg, #ff4444 0%, #cc0000 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 15px 30px;
|
||||
font-size: 1.2rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: bold;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
box-shadow: 0 4px 15px rgba(255, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.restart-btn:hover {
|
||||
background: linear-gradient(135deg, #ff6666 0%, #ff0000 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(255, 68, 68, 0.5);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -80,6 +148,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="gameOverScreen" class="game-over-screen" style="display: none;">
|
||||
<div class="game-over-content">
|
||||
<h1>GAME OVER</h1>
|
||||
<div class="score-display">
|
||||
<div>Final Score: <span id="finalScore">0</span></div>
|
||||
<div>High Score: <span id="highScore">0</span></div>
|
||||
</div>
|
||||
<button id="restartButton" class="restart-btn">RESTART GAME</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
@ -107,7 +186,13 @@
|
||||
camera: { x: 0, y: 0 },
|
||||
tileSize: 32,
|
||||
mapWidth: 50,
|
||||
mapHeight: 40
|
||||
mapHeight: 40,
|
||||
pathfindingCache: new Map(),
|
||||
maxCacheSize: 100,
|
||||
pathfindingQueue: [],
|
||||
maxPathfindingPerFrame: 2,
|
||||
gameOver: false,
|
||||
highScore: localStorage.getItem('zombieHighScore') || 0
|
||||
};
|
||||
|
||||
// Sprite loader
|
||||
@ -267,8 +352,8 @@
|
||||
for (let y = 0; y < game.mapHeight; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < game.mapWidth; x++) {
|
||||
// Border walls
|
||||
if (x === 0 || x === game.mapWidth - 1 || y === 0 || y === game.mapHeight - 1) {
|
||||
// No border walls - zombies can enter from all sides
|
||||
if (false) {
|
||||
row.push(TILE_TYPES.WALL);
|
||||
}
|
||||
// Buildings in clusters
|
||||
@ -335,6 +420,161 @@
|
||||
return game.map[tileY][tileX];
|
||||
}
|
||||
|
||||
// Pathfinding system
|
||||
class PathNode {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.g = 0; // Cost from start
|
||||
this.h = 0; // Heuristic cost to goal
|
||||
this.f = 0; // Total cost (g + h)
|
||||
this.parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
function manhattanDistance(a, b) {
|
||||
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
||||
}
|
||||
|
||||
function getNeighbors(node) {
|
||||
const neighbors = [];
|
||||
const directions = [
|
||||
{x: 0, y: -1}, {x: 1, y: 0}, {x: 0, y: 1}, {x: -1, y: 0}, // Cardinal
|
||||
{x: -1, y: -1}, {x: 1, y: -1}, {x: 1, y: 1}, {x: -1, y: 1} // Diagonal
|
||||
];
|
||||
|
||||
for (let dir of directions) {
|
||||
const newX = node.x + dir.x;
|
||||
const newY = node.y + dir.y;
|
||||
|
||||
// Check bounds
|
||||
if (newX >= 0 && newX < game.mapWidth && newY >= 0 && newY < game.mapHeight) {
|
||||
// Check if tile is walkable
|
||||
if (isWalkable(game.map[newY][newX])) {
|
||||
// For diagonal movement, also check adjacent tiles to prevent corner cutting
|
||||
if (dir.x !== 0 && dir.y !== 0) {
|
||||
if (isWalkable(game.map[node.y][newX]) && isWalkable(game.map[newY][node.x])) {
|
||||
neighbors.push(new PathNode(newX, newY));
|
||||
}
|
||||
} else {
|
||||
neighbors.push(new PathNode(newX, newY));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
function findPath(startX, startY, goalX, goalY) {
|
||||
// Convert world coordinates to tile coordinates
|
||||
const startTileX = Math.floor(startX / game.tileSize);
|
||||
const startTileY = Math.floor(startY / game.tileSize);
|
||||
const goalTileX = Math.floor(goalX / game.tileSize);
|
||||
const goalTileY = Math.floor(goalY / game.tileSize);
|
||||
|
||||
// Check cache first (quantize coordinates for better cache hits)
|
||||
const quantizedStartX = Math.floor(startTileX / 2) * 2;
|
||||
const quantizedStartY = Math.floor(startTileY / 2) * 2;
|
||||
const quantizedGoalX = Math.floor(goalTileX / 2) * 2;
|
||||
const quantizedGoalY = Math.floor(goalTileY / 2) * 2;
|
||||
const cacheKey = `${quantizedStartX},${quantizedStartY}-${quantizedGoalX},${quantizedGoalY}`;
|
||||
|
||||
if (game.pathfindingCache.has(cacheKey)) {
|
||||
return game.pathfindingCache.get(cacheKey);
|
||||
}
|
||||
|
||||
// Check if start and goal are valid
|
||||
if (!isWalkable(game.map[startTileY]?.[startTileX]) ||
|
||||
!isWalkable(game.map[goalTileY]?.[goalTileX])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const openSet = [];
|
||||
const closedSet = new Set();
|
||||
const startNode = new PathNode(startTileX, startTileY);
|
||||
const goalNode = new PathNode(goalTileX, goalTileY);
|
||||
|
||||
startNode.h = manhattanDistance(startNode, goalNode);
|
||||
startNode.f = startNode.h;
|
||||
openSet.push(startNode);
|
||||
|
||||
while (openSet.length > 0) {
|
||||
// Find node with lowest f cost
|
||||
let currentIndex = 0;
|
||||
for (let i = 1; i < openSet.length; i++) {
|
||||
if (openSet[i].f < openSet[currentIndex].f) {
|
||||
currentIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
const current = openSet.splice(currentIndex, 1)[0];
|
||||
const currentKey = `${current.x},${current.y}`;
|
||||
closedSet.add(currentKey);
|
||||
|
||||
// Check if we reached the goal
|
||||
if (current.x === goalNode.x && current.y === goalNode.y) {
|
||||
const path = [];
|
||||
let node = current;
|
||||
while (node) {
|
||||
path.unshift({
|
||||
x: node.x * game.tileSize + game.tileSize / 2,
|
||||
y: node.y * game.tileSize + game.tileSize / 2
|
||||
});
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
if (game.pathfindingCache.size >= game.maxCacheSize) {
|
||||
// Remove oldest entry
|
||||
const firstKey = game.pathfindingCache.keys().next().value;
|
||||
game.pathfindingCache.delete(firstKey);
|
||||
}
|
||||
game.pathfindingCache.set(cacheKey, path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
const neighbors = getNeighbors(current);
|
||||
for (let neighbor of neighbors) {
|
||||
const neighborKey = `${neighbor.x},${neighbor.y}`;
|
||||
|
||||
if (closedSet.has(neighborKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate movement cost (diagonal costs more)
|
||||
const isDiagonal = Math.abs(neighbor.x - current.x) + Math.abs(neighbor.y - current.y) === 2;
|
||||
const movementCost = isDiagonal ? 1.4 : 1.0;
|
||||
const tentativeG = current.g + movementCost;
|
||||
|
||||
let existingNode = openSet.find(node => node.x === neighbor.x && node.y === neighbor.y);
|
||||
|
||||
if (!existingNode) {
|
||||
neighbor.g = tentativeG;
|
||||
neighbor.h = manhattanDistance(neighbor, goalNode);
|
||||
neighbor.f = neighbor.g + neighbor.h;
|
||||
neighbor.parent = current;
|
||||
openSet.push(neighbor);
|
||||
} else if (tentativeG < existingNode.g) {
|
||||
existingNode.g = tentativeG;
|
||||
existingNode.f = existingNode.g + existingNode.h;
|
||||
existingNode.parent = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache empty result
|
||||
const emptyPath = [];
|
||||
if (game.pathfindingCache.size >= game.maxCacheSize) {
|
||||
const firstKey = game.pathfindingCache.keys().next().value;
|
||||
game.pathfindingCache.delete(firstKey);
|
||||
}
|
||||
game.pathfindingCache.set(cacheKey, emptyPath);
|
||||
|
||||
return emptyPath; // No path found
|
||||
}
|
||||
|
||||
class Player {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
@ -379,17 +619,15 @@
|
||||
|
||||
// Shooting
|
||||
const now = Date.now();
|
||||
if (this.burstCount > 0 && now - this.burstTimer > this.weapon.fireRate) {
|
||||
// Handle burst firing (remaining shots after initial click)
|
||||
if (this.burstCount > 1 && now - this.burstTimer >= this.weapon.fireRate) {
|
||||
this.shoot();
|
||||
this.burstCount--;
|
||||
this.burstTimer = now;
|
||||
} else if ((game.mouse.down && this.weapon.autoFire) || this.burstCount > 0) {
|
||||
if (now - this.lastShot > this.weapon.fireRate) {
|
||||
if (this.burstCount === 0) {
|
||||
this.burstCount = this.weapon.burstCount;
|
||||
this.burstTimer = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle automatic weapons (hold to fire)
|
||||
else if (game.mouse.down && this.weapon.autoFire && now - this.lastShot >= this.weapon.fireRate) {
|
||||
this.shoot();
|
||||
}
|
||||
|
||||
// Check weapon pickups
|
||||
@ -450,28 +688,113 @@
|
||||
this.hp = 50 + Math.random() * 50;
|
||||
this.maxHp = this.hp;
|
||||
this.sprite = ['zombie.png', 'zombie2.png', 'zombie3.png', 'zombie4.png'][Math.floor(Math.random() * 4)];
|
||||
this.path = [];
|
||||
this.pathIndex = 0;
|
||||
this.lastPathUpdate = 0;
|
||||
this.pathUpdateInterval = 1000 + Math.random() * 500; // Update path every 1-1.5 seconds
|
||||
this.lastPlayerX = null;
|
||||
this.lastPlayerY = null;
|
||||
}
|
||||
|
||||
update() {
|
||||
// Move towards player with collision detection
|
||||
const dx = game.player.x - this.x;
|
||||
const dy = game.player.y - this.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
const now = Date.now();
|
||||
const distanceToPlayer = Math.sqrt(
|
||||
Math.pow(game.player.x - this.x, 2) + Math.pow(game.player.y - this.y, 2)
|
||||
);
|
||||
|
||||
if (distance > 0) {
|
||||
let newX = this.x + (dx / distance) * this.speed;
|
||||
let newY = this.y + (dy / distance) * this.speed;
|
||||
// If very close to player, use direct movement for responsiveness
|
||||
if (distanceToPlayer < 64) {
|
||||
const dx = game.player.x - this.x;
|
||||
const dy = game.player.y - this.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Check collision for X movement
|
||||
if (isWalkable(getTileAt(newX - this.width/2, this.y)) &&
|
||||
isWalkable(getTileAt(newX + this.width/2, this.y))) {
|
||||
this.x = newX;
|
||||
if (distance > 0) {
|
||||
let newX = this.x + (dx / distance) * this.speed;
|
||||
let newY = this.y + (dy / distance) * this.speed;
|
||||
|
||||
// Check collision for X movement
|
||||
if (isWalkable(getTileAt(newX - this.width/2, this.y)) &&
|
||||
isWalkable(getTileAt(newX + this.width/2, this.y))) {
|
||||
this.x = newX;
|
||||
}
|
||||
|
||||
// Check collision for Y movement
|
||||
if (isWalkable(getTileAt(this.x, newY - this.height/2)) &&
|
||||
isWalkable(getTileAt(this.x, newY + this.height/2))) {
|
||||
this.y = newY;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Update path periodically or if no path exists
|
||||
if (now - this.lastPathUpdate > this.pathUpdateInterval || this.path.length === 0) {
|
||||
// Only recalculate if player has moved significantly or path is old
|
||||
const playerMoved = this.lastPlayerX && this.lastPlayerY &&
|
||||
(Math.abs(game.player.x - this.lastPlayerX) > 64 ||
|
||||
Math.abs(game.player.y - this.lastPlayerY) > 64);
|
||||
|
||||
if (!this.lastPlayerX || playerMoved || this.path.length === 0 ||
|
||||
now - this.lastPathUpdate > this.pathUpdateInterval) {
|
||||
this.path = findPath(this.x, this.y, game.player.x, game.player.y);
|
||||
this.pathIndex = 0;
|
||||
this.lastPathUpdate = now;
|
||||
this.lastPlayerX = game.player.x;
|
||||
this.lastPlayerY = game.player.y;
|
||||
}
|
||||
}
|
||||
|
||||
// Follow the path
|
||||
if (this.path.length > 0 && this.pathIndex < this.path.length) {
|
||||
const target = this.path[this.pathIndex];
|
||||
const dx = target.x - this.x;
|
||||
const dy = target.y - this.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// If we're close enough to the current waypoint, move to the next one
|
||||
if (distance < 16) {
|
||||
this.pathIndex++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check collision for Y movement
|
||||
if (isWalkable(getTileAt(this.x, newY - this.height/2)) &&
|
||||
isWalkable(getTileAt(this.x, newY + this.height/2))) {
|
||||
this.y = newY;
|
||||
// Move towards current waypoint
|
||||
if (distance > 0) {
|
||||
let newX = this.x + (dx / distance) * this.speed;
|
||||
let newY = this.y + (dy / distance) * this.speed;
|
||||
|
||||
// Check collision for X movement
|
||||
if (isWalkable(getTileAt(newX - this.width/2, this.y)) &&
|
||||
isWalkable(getTileAt(newX + this.width/2, this.y))) {
|
||||
this.x = newX;
|
||||
}
|
||||
|
||||
// Check collision for Y movement
|
||||
if (isWalkable(getTileAt(this.x, newY - this.height/2)) &&
|
||||
isWalkable(getTileAt(this.x, newY + this.height/2))) {
|
||||
this.y = newY;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback to direct movement if no path found
|
||||
const dx = game.player.x - this.x;
|
||||
const dy = game.player.y - this.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance > 0) {
|
||||
let newX = this.x + (dx / distance) * this.speed * 0.5; // Slower when no path
|
||||
let newY = this.y + (dy / distance) * this.speed * 0.5;
|
||||
|
||||
// Check collision for X movement
|
||||
if (isWalkable(getTileAt(newX - this.width/2, this.y)) &&
|
||||
isWalkable(getTileAt(newX + this.width/2, this.y))) {
|
||||
this.x = newX;
|
||||
}
|
||||
|
||||
// Check collision for Y movement
|
||||
if (isWalkable(getTileAt(this.x, newY - this.height/2)) &&
|
||||
isWalkable(getTileAt(this.x, newY + this.height/2))) {
|
||||
this.y = newY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -596,28 +919,30 @@
|
||||
}
|
||||
|
||||
function spawnZombie() {
|
||||
const side = Math.floor(Math.random() * 4);
|
||||
let x, y;
|
||||
const mapPixelWidth = game.mapWidth * game.tileSize;
|
||||
const mapPixelHeight = game.mapHeight * game.tileSize;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 50;
|
||||
|
||||
switch(side) {
|
||||
case 0: // Top
|
||||
x = Math.random() * mapPixelWidth;
|
||||
y = -32;
|
||||
break;
|
||||
case 1: // Right
|
||||
x = mapPixelWidth + 32;
|
||||
y = Math.random() * mapPixelHeight;
|
||||
break;
|
||||
case 2: // Bottom
|
||||
x = Math.random() * mapPixelWidth;
|
||||
y = mapPixelHeight + 32;
|
||||
break;
|
||||
case 3: // Left
|
||||
x = -32;
|
||||
y = Math.random() * mapPixelHeight;
|
||||
break;
|
||||
// Keep trying to find a walkable spawn position
|
||||
do {
|
||||
x = Math.random() * (game.mapWidth * game.tileSize);
|
||||
y = Math.random() * (game.mapHeight * game.tileSize);
|
||||
attempts++;
|
||||
} while (!isWalkable(getTileAt(x, y)) && attempts < maxAttempts);
|
||||
|
||||
// If we couldn't find a walkable tile, spawn on a road or floor tile
|
||||
if (attempts >= maxAttempts) {
|
||||
// Find any walkable tile
|
||||
for (let ty = 0; ty < game.mapHeight; ty++) {
|
||||
for (let tx = 0; tx < game.mapWidth; tx++) {
|
||||
if (isWalkable(game.map[ty][tx])) {
|
||||
x = tx * game.tileSize + game.tileSize / 2;
|
||||
y = ty * game.tileSize + game.tileSize / 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isWalkable(getTileAt(x, y))) break;
|
||||
}
|
||||
}
|
||||
|
||||
game.zombies.push(new Zombie(x, y));
|
||||
@ -627,6 +952,7 @@
|
||||
// Bullet-zombie collisions
|
||||
for (let i = game.bullets.length - 1; i >= 0; i--) {
|
||||
const bullet = game.bullets[i];
|
||||
let bulletHit = false;
|
||||
|
||||
for (let j = game.zombies.length - 1; j >= 0; j--) {
|
||||
const zombie = game.zombies[j];
|
||||
@ -650,10 +976,14 @@
|
||||
|
||||
game.zombies.splice(j, 1);
|
||||
}
|
||||
game.bullets.splice(i, 1);
|
||||
bulletHit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bulletHit) {
|
||||
game.bullets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Player-zombie collisions
|
||||
@ -672,15 +1002,8 @@
|
||||
updateUI();
|
||||
|
||||
if (game.player.health <= 0) {
|
||||
// Game over - restart
|
||||
game.player.health = game.player.maxHealth;
|
||||
game.player.x = canvas.width / 2;
|
||||
game.player.y = canvas.height / 2;
|
||||
game.zombies = [];
|
||||
game.bullets = [];
|
||||
game.weaponDrops = [];
|
||||
game.score = 0;
|
||||
updateUI();
|
||||
// Game over
|
||||
showGameOver();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -695,6 +1018,49 @@
|
||||
document.getElementById('score').textContent = game.score;
|
||||
}
|
||||
|
||||
function showGameOver() {
|
||||
game.gameOver = true;
|
||||
|
||||
// Update high score
|
||||
if (game.score > game.highScore) {
|
||||
game.highScore = game.score;
|
||||
localStorage.setItem('zombieHighScore', game.highScore);
|
||||
}
|
||||
|
||||
// Update game over screen
|
||||
document.getElementById('finalScore').textContent = game.score;
|
||||
document.getElementById('highScore').textContent = game.highScore;
|
||||
document.getElementById('gameOverScreen').style.display = 'flex';
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
// Hide game over screen
|
||||
document.getElementById('gameOverScreen').style.display = 'none';
|
||||
|
||||
// Reset game state
|
||||
game.gameOver = false;
|
||||
game.score = 0;
|
||||
game.zombies = [];
|
||||
game.bullets = [];
|
||||
game.weaponDrops = [];
|
||||
game.pathfindingCache.clear();
|
||||
|
||||
// Reset player
|
||||
const centerX = Math.floor(game.mapWidth / 2) * game.tileSize;
|
||||
const centerY = Math.floor(game.mapHeight / 2) * game.tileSize;
|
||||
game.player.health = game.player.maxHealth;
|
||||
game.player.x = centerX;
|
||||
game.player.y = centerY;
|
||||
game.player.weapon = { ...weaponTypes.pistol };
|
||||
|
||||
// Spawn initial zombies
|
||||
for (let i = 0; i < 5; i++) {
|
||||
spawnZombie();
|
||||
}
|
||||
|
||||
updateUI();
|
||||
}
|
||||
|
||||
function drawMap() {
|
||||
// Pre-rendered tile canvases for performance
|
||||
if (!game.tileCanvases) {
|
||||
@ -794,18 +1160,21 @@
|
||||
// Draw map
|
||||
drawMap();
|
||||
|
||||
// Update
|
||||
game.player.update();
|
||||
|
||||
game.zombies.forEach(zombie => zombie.update());
|
||||
|
||||
game.bullets = game.bullets.filter(bullet => bullet.update());
|
||||
|
||||
checkCollisions();
|
||||
|
||||
// Spawn zombies (reduced spawn rate)
|
||||
if (Math.random() < 0.01 + game.score * 0.00005) {
|
||||
spawnZombie();
|
||||
// Only update game if not game over
|
||||
if (!game.gameOver) {
|
||||
// Update
|
||||
game.player.update();
|
||||
|
||||
game.zombies.forEach(zombie => zombie.update());
|
||||
|
||||
game.bullets = game.bullets.filter(bullet => bullet.update());
|
||||
|
||||
checkCollisions();
|
||||
|
||||
// Spawn zombies (reduced spawn rate)
|
||||
if (Math.random() < 0.01 + game.score * 0.00005) {
|
||||
spawnZombie();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw
|
||||
@ -852,9 +1221,12 @@
|
||||
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
game.mouse.down = true;
|
||||
if (!game.player.weapon.autoFire) {
|
||||
game.player.burstCount = game.player.weapon.burstCount;
|
||||
game.player.burstTimer = Date.now();
|
||||
const now = Date.now();
|
||||
// Always shoot on click for non-auto weapons if fire rate allows
|
||||
if (!game.player.weapon.autoFire && now - game.player.lastShot >= game.player.weapon.fireRate) {
|
||||
game.player.shoot(); // Shoot immediately on click
|
||||
game.player.burstCount = game.player.weapon.burstCount - 1; // Set remaining burst shots
|
||||
game.player.burstTimer = now;
|
||||
}
|
||||
});
|
||||
|
||||
@ -862,6 +1234,9 @@
|
||||
game.mouse.down = false;
|
||||
});
|
||||
|
||||
// Restart button event listener
|
||||
document.getElementById('restartButton').addEventListener('click', restartGame);
|
||||
|
||||
// Start loading sprites
|
||||
loadSprites();
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user