ejectionface / index.html
GuangyuanSD's picture
Update index.html
22b5849 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>射他一脸!Ejection FACE!</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body {
margin: 0;
padding: 0;
overflow: hidden;
font-family: 'Press Start 2P', cursive;
background-color: #000;
color: white;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
#starfield {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
.star {
position: absolute;
width: 2px;
height: 2px;
background-color: white;
border-radius: 50%;
}
.target {
position: absolute;
width: 250px;
height: 250px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
transition: transform 0.1s ease-out;
z-index: 2;
}
.hit-effect {
position: absolute;
width: 100%;
height: 100%;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
opacity: 0;
transition: opacity 0.3s;
z-index: 3;
}
.hit-effect.active {
opacity: 1;
}
#custom-cursor {
position: absolute;
width: 40px;
height: 40px;
background-size: contain;
background-repeat: no-repeat;
pointer-events: none;
z-index: 999;
transform-origin: center center;
transform: rotate(0deg);
}
#score-display {
position: absolute;
top: 20px;
left: 0;
right: 0;
text-align: center;
font-size: 24px;
color: #ff0;
text-shadow: 0 0 10px #ff0, 0 0 20px #ff0;
z-index: 10;
opacity: 0;
transition: opacity 0.3s;
}
#score-display.active {
opacity: 1;
}
#hud {
position: absolute;
bottom: 20px;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
z-index: 10;
}
.weapon {
width: 60px;
height: 60px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
border: 2px solid #555;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s;
position: relative;
background-color: rgba(0, 0, 0, 0.7);
}
.weapon:hover {
transform: scale(1.1);
border-color: #ff0;
}
.weapon.locked {
filter: grayscale(100%);
opacity: 0.5;
}
.weapon.selected {
border-color: #ff0;
box-shadow: 0 0 10px #ff0;
}
.weapon-price {
position: absolute;
top: -15px;
right: -10px;
background-color: #000;
color: #ff0;
padding: 2px 5px;
border-radius: 10px;
font-size: 10px;
border: 1px solid #ff0;
}
.projectile {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
z-index: 5;
transform-origin: center;
}
#hit-sound, #shoot-sound, #bg-music {
display: none;
}
.hit-marker {
position: absolute;
width: 30px;
height: 30px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23ff0000"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>');
background-size: contain;
opacity: 0;
animation: hitMarker 1s forwards;
z-index: 4;
}
.hit-flash {
animation: flash 0.3s;
}
@keyframes flash {
0% { opacity: 1; }
50% { opacity: 0.3; }
100% { opacity: 1; }
}
/* 添加在CSS部分 */
@keyframes newTargetFlash {
0% { transform: scale(0.5); opacity: 0; }
50% { transform: scale(1.2); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
.new-target-flash {
animation: newTargetFlash 0.5s forwards;
}
.screen-flash {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 0, 0, 0.5);
pointer-events: none;
opacity: 0;
z-index: 1000;
}
.screen-flash.active {
animation: screenFlash 0.5s;
}
@keyframes screenFlash {
0% { opacity: 0.7; }
100% { opacity: 0; }
}
@keyframes hitMarker {
0% { transform: scale(0.5); opacity: 0; }
50% { transform: scale(1.5); opacity: 1; }
100% { transform: scale(1); opacity: 0; }
}
.ejection-text {
position: absolute;
font-size: 48px;
color: #ff0;
text-shadow: 0 0 10px #ff0, 0 0 20px #ff0;
animation: ejectionText 1.5s forwards;
z-index: 10;
opacity: 0;
}
@keyframes ejectionText {
0% { transform: scale(0.5); opacity: 0; }
20% { transform: scale(1.2); opacity: 1; }
40% { transform: scale(0.9); }
60% { transform: scale(1.1); }
80% { transform: scale(0.95); }
100% { transform: scale(1); opacity: 0; }
}
#health-bar {
position: absolute;
top: 20px;
left: 20px;
display: flex;
gap: 5px;
z-index: 10;
}
.health-point {
width: 20px;
height: 20px;
background-color: #ff0000;
border: 2px solid #fff;
border-radius: 50%;
}
.health-point.lost {
background-color: #333;
}
#game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
}
#game-over-text {
font-size: 72px;
color: #ff0000;
text-shadow: 0 0 20px #ff0000;
margin-bottom: 40px;
text-align: center;
}
#retry-button {
padding: 15px 30px;
font-size: 24px;
background-color: #ff0;
color: #000;
border: none;
border-radius: 10px;
cursor: pointer;
font-family: 'Press Start 2P', cursive;
transition: all 0.2s;
}
#retry-button:hover {
transform: scale(1.1);
box-shadow: 0 0 20px #ff0;
}
.dancing {
animation: dance 0.5s infinite alternate;
}
@keyframes dance {
0% { transform: rotate(-10deg); }
100% { transform: rotate(10deg); }
}
#center-dot {
position: absolute;
width: 10px;
height: 10px;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
</style>
</head>
<body>
<div id="game-container">
<!-- 其他元素... -->
<div id="screen-flash" class="screen-flash"></div>
<div id="starfield"></div>
<div id="targets-container"></div>
<div id="projectiles-container"></div>
<div id="hit-markers-container"></div>
<div id="score-display">射他一脸!Ejection FACE!</div>
<div id="health-bar"></div>
<div id="hud">
<div class="score-box">Hits: <span id="hit-count">0</span> | Score: <span id="score">0</span></div>
<div class="weapon selected" data-weapon="basic" data-price="0">
<div class="weapon-price">FREE</div>
</div>
<div class="weapon locked" data-weapon="rapid" data-price="50">
<div class="weapon-price">50</div>
</div>
<div class="weapon locked" data-weapon="double" data-price="300">
<div class="weapon-price">300</div>
</div>
<div class="weapon locked" data-weapon="shotgun" data-price="800">
<div class="weapon-price">800</div>
</div>
<div class="weapon locked" data-weapon="bouncy" data-price="1600">
<div class="weapon-price">1600</div>
</div>
</div>
<div id="custom-cursor"></div>
<div id="center-dot"></div>
<div id="game-over">
<div id="game-over-text">Ejection FAILED!<br>Loooser!</div>
<button id="retry-button">RETRY</button>
</div>
</div>
<audio id="hit-sound" preload="auto">
<source src="https://assets.mixkit.co/sfx/preview/mixkit-positive-interface-beep-221.mp3" type="audio/mpeg">
</audio>
<audio id="shoot-sound" preload="auto">
<source src="https://assets.mixkit.co/sfx/preview/mixkit-short-laser-gun-shot-1670.mp3" type="audio/mpeg">
</audio>
<audio id="bg-music" loop>
<source src="https://assets.mixkit.co/music/preview/mixkit-game-show-suspense-waiting-668.mp3" type="audio/mpeg">
</audio>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game state
const state = {
score: 0,
hits: 0,
health: 10,
targets: [],
projectiles: [],
stars: [],
currentWeapon: 'basic',
weapons: {
basic: { price: 0, fireRate: 500, projectileCount: 1, spread: 0, bouncy: false },
rapid: { price: 50, fireRate: 250, projectileCount: 1, spread: 0, bouncy: false },
double: { price: 300, fireRate: 300, projectileCount: 2, spread: 10, bouncy: false },
shotgun: { price: 800, fireRate: 500, projectileCount: 5, spread: 20, bouncy: false },
bouncy: { price: 1600, fireRate: 100, projectileCount: 18, spread: 360, bouncy: true }
},
lastShot: 0,
colors: ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'],
currentColorIndex: 0,
targetSpeed: 2,
targetCount: 1,
gameOver: false,
knockbackDistance: -30 // Added knockback distance
};
// DOM elements
const gameContainer = document.getElementById('game-container');
const starfield = document.getElementById('starfield');
const targetsContainer = document.getElementById('targets-container');
const projectilesContainer = document.getElementById('projectiles-container');
const hitMarkersContainer = document.getElementById('hit-markers-container');
const scoreDisplay = document.getElementById('score-display');
const hitCountElement = document.getElementById('hit-count');
const scoreElement = document.getElementById('score');
const customCursor = document.getElementById('custom-cursor');
const hitSound = document.getElementById('hit-sound');
const shootSound = document.getElementById('shoot-sound');
const bgMusic = document.getElementById('bg-music');
const weaponElements = document.querySelectorAll('.weapon');
const healthBar = document.getElementById('health-bar');
const gameOverScreen = document.getElementById('game-over');
const gameOverText = document.getElementById('game-over-text');
const retryButton = document.getElementById('retry-button');
// Initialize game
initHealthBar();
initStarfield();
createTargets(state.targetCount);
setupCursor();
setupWeapons();
playMusic();
// Event listeners
gameContainer.addEventListener('mousemove', handleMouseMove);
gameContainer.addEventListener('click', handleShoot);
retryButton.addEventListener('click', resetGame);
// Game loop
function gameLoop() {
if (!state.gameOver) {
updateTargets();
updateProjectiles();
checkCollisions();
checkCenterCollision();
requestAnimationFrame(gameLoop);
}
}
gameLoop();
// Initialize health bar
function initHealthBar() {
healthBar.innerHTML = '';
for (let i = 0; i < 10; i++) {
const healthPoint = document.createElement('div');
healthPoint.className = 'health-point';
healthBar.appendChild(healthPoint);
}
}
// Update health display
function updateHealth() {
const healthPoints = document.querySelectorAll('.health-point');
healthPoints.forEach((point, index) => {
if (index < state.health) {
point.classList.remove('lost');
} else {
point.classList.add('lost');
}
});
}
// Initialize starfield
function initStarfield() {
const starCount = Math.floor(window.innerWidth * window.innerHeight / 1000);
for (let i = 0; i < starCount; i++) {
createStar();
}
}
function createStar() {
const star = document.createElement('div');
star.className = 'star';
const x = Math.random() * window.innerWidth;
const y = Math.random() * window.innerHeight;
const size = Math.random() * 3;
const opacity = Math.random();
star.style.left = `${x}px`;
star.style.top = `${y}px`;
star.style.width = `${size}px`;
star.style.height = `${size}px`;
star.style.opacity = opacity;
starfield.appendChild(star);
// Animate star (parallax effect)
const speed = 0.5 + Math.random() * 2;
function animateStar() {
const currentX = parseFloat(star.style.left);
const currentY = parseFloat(star.style.top);
// Move star outward from center
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
let dx = currentX - centerX;
let dy = currentY - centerY;
// Normalize direction vector
const distance = Math.sqrt(dx * dx + dy * dy);
dx /= distance;
dy /= distance;
// Move star
star.style.left = `${currentX + dx * speed}px`;
star.style.top = `${currentY + dy * speed}px`;
// Reset star if it goes out of bounds
if (parseFloat(star.style.left) < 0 || parseFloat(star.style.left) > window.innerWidth ||
parseFloat(star.style.top) < 0 || parseFloat(star.style.top) > window.innerHeight) {
star.style.left = `${Math.random() * window.innerWidth}px`;
star.style.top = `${Math.random() * window.innerHeight}px`;
}
requestAnimationFrame(animateStar);
}
animateStar();
}
// Create targets
function createTargets(count) {
// Flash screen when adding new targets
const screenFlash = document.createElement('div');
screenFlash.className = 'screen-flash';
screenFlash.style.backgroundColor = state.colors[state.currentColorIndex];
document.body.appendChild(screenFlash);
setTimeout(() => {
screenFlash.remove();
}, 300);
targetsContainer.innerHTML = '';
state.targets = [];
for (let i = 0; i < count; i++) {
createTarget(i);
}
}
function createTarget(index) {
const target = document.createElement('div');
target.className = 'target';
// Use huggingface emoji as target
target.style.backgroundImage = 'url("https://huggingface.co/front/assets/huggingface_logo-noborder.svg")';
// Position randomly but ensure it's fully visible and not near center
const maxX = window.innerWidth - 250;
const maxY = window.innerHeight - 250;
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const minDistanceFromCenter = 300; // Minimum distance from center
let x, y;
let attempts = 0;
const maxAttempts = 100;
do {
x = Math.max(0, Math.min(maxX, Math.random() * maxX));
y = Math.max(0, Math.min(maxY, Math.random() * maxY));
attempts++;
// Check distance from center
const distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
// If we've tried too many times, just place it anywhere
if (attempts >= maxAttempts) break;
} while (Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)) < minDistanceFromCenter);
target.style.left = `${x}px`;
target.style.top = `${y}px`;
// Add hit effect
const hitEffect = document.createElement('div');
hitEffect.className = 'hit-effect';
hitEffect.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><circle cx=\'50\' cy=\'50\' r=\'40\' fill=\'' + state.colors[state.currentColorIndex] + '\' opacity=\'0.7\'/></svg>")';
target.appendChild(hitEffect);
targetsContainer.appendChild(target);
// Store target data
state.targets.push({
element: target,
hitEffect: hitEffect,
x: x,
y: y,
vx: (Math.random() - 0.5) * state.targetSpeed,
vy: (Math.random() - 0.5) * state.targetSpeed,
index: index
});
}
// Update targets position
function updateTargets() {
state.targets.forEach(target => {
// Update position
target.x += target.vx;
target.y += target.vy;
// Bounce off edges
if (target.x <= 0 || target.x >= window.innerWidth - 250) {
target.vx = -target.vx * (0.9 + Math.random() * 0.2);
}
if (target.y <= 0 || target.y >= window.innerHeight - 250) {
target.vy = -target.vy * (0.9 + Math.random() * 0.2);
}
// Random direction changes
if (Math.random() < 0.02) {
target.vx += (Math.random() - 0.5) * 0.5;
target.vy += (Math.random() - 0.5) * 0.5;
// Limit speed
const speed = Math.sqrt(target.vx * target.vx + target.vy * target.vy);
const maxSpeed = state.targetSpeed * 1.5;
if (speed > maxSpeed) {
target.vx = (target.vx / speed) * maxSpeed;
target.vy = (target.vy / speed) * maxSpeed;
}
}
// Apply position
target.element.style.left = `${target.x}px`;
target.element.style.top = `${target.y}px`;
});
}
// Setup custom cursor
function setupCursor() {
customCursor.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[state.currentColorIndex] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/></svg>")';
customCursor.style.display = 'block';
}
function handleMouseMove(e) {
customCursor.style.left = `${e.clientX - 20}px`;
customCursor.style.top = `${e.clientY - 20}px`;
// Rotate cursor slightly for visual feedback
const rotation = Math.sin(Date.now() / 200) * 5;
customCursor.style.transform = `rotate(${rotation}deg)`;
}
// Handle shooting
function handleShoot(e) {
if (state.gameOver) return;
const now = Date.now();
const weapon = state.weapons[state.currentWeapon];
if (now - state.lastShot < weapon.fireRate) return;
state.lastShot = now;
// Play shoot sound
shootSound.currentTime = 0;
shootSound.play();
// Create projectiles based on weapon type
if (state.currentWeapon === 'bouncy') {
// Special handling for bouncy weapon - 360 degree spread
for (let i = 0; i < weapon.projectileCount; i++) {
const angle = (i / weapon.projectileCount) * Math.PI * 2;
createBouncyProjectile(angle);
}
} else {
// Normal weapons
for (let i = 0; i < weapon.projectileCount; i++) {
createProjectile(e.clientX, e.clientY, i);
}
}
// Animate cursor
customCursor.style.transform = 'scale(0.8)';
setTimeout(() => {
customCursor.style.transform = 'scale(1)';
}, 100);
}
function createBouncyProjectile(angle) {
const projectile = document.createElement('div');
projectile.className = 'projectile';
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
// Set projectile color
projectile.style.backgroundColor = state.colors[state.currentColorIndex];
// Set initial position at center
projectile.style.left = `${centerX}px`;
projectile.style.top = `${centerY}px`;
projectilesContainer.appendChild(projectile);
// Calculate direction based on angle
const dx = Math.cos(angle);
const dy = Math.sin(angle);
// Store projectile data with higher speed for bouncy weapon
const projectileData = {
element: projectile,
x: centerX,
y: centerY,
vx: dx * 15, // Faster speed for bouncy weapon
vy: dy * 15, // Faster speed for bouncy weapon
bounces: 0,
maxBounces: 3
};
state.projectiles.push(projectileData);
}
function createProjectile(mouseX, mouseY, index) {
const projectile = document.createElement('div');
projectile.className = 'projectile';
// Calculate direction from center to mouse
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
let dx = mouseX - centerX;
let dy = mouseY - centerY;
// Normalize direction vector
const distance = Math.sqrt(dx * dx + dy * dy);
dx /= distance;
dy /= distance;
// Apply weapon spread
const weapon = state.weapons[state.currentWeapon];
const spreadAngle = (index - (weapon.projectileCount - 1) / 2) * weapon.spread * (Math.PI / 180);
// Rotate direction by spread angle
const cos = Math.cos(spreadAngle);
const sin = Math.sin(spreadAngle);
const tx = dx * cos - dy * sin;
const ty = dx * sin + dy * cos;
dx = tx;
dy = ty;
// Set projectile color
projectile.style.backgroundColor = state.colors[state.currentColorIndex];
// Set initial position at center
projectile.style.left = `${centerX}px`;
projectile.style.top = `${centerY}px`;
projectilesContainer.appendChild(projectile);
// Store projectile data
const projectileData = {
element: projectile,
x: centerX,
y: centerY,
vx: dx * 10,
vy: dy * 10,
bounces: 0,
maxBounces: weapon.bouncy ? 3 : 0
};
state.projectiles.push(projectileData);
}
// Update projectiles position
function updateProjectiles() {
state.projectiles.forEach((projectile, index) => {
// Update position
projectile.x += projectile.vx;
projectile.y += projectile.vy;
// Bounce off edges if bouncy
if (projectile.maxBounces > 0) {
if (projectile.x <= 0 || projectile.x >= window.innerWidth) {
projectile.vx = -projectile.vx;
projectile.bounces++;
projectile.x = Math.max(0, Math.min(window.innerWidth, projectile.x));
}
if (projectile.y <= 0 || projectile.y >= window.innerHeight) {
projectile.vy = -projectile.vy;
projectile.bounces++;
projectile.y = Math.max(0, Math.min(window.innerHeight, projectile.y));
}
}
// Apply position
projectile.element.style.left = `${projectile.x}px`;
projectile.element.style.top = `${projectile.y}px`;
// Remove if out of bounds or max bounces reached
if (projectile.x < 0 || projectile.x > window.innerWidth ||
projectile.y < 0 || projectile.y > window.innerHeight ||
projectile.bounces > projectile.maxBounces) {
projectile.element.remove();
state.projectiles.splice(index, 1);
}
});
}
// Check for collisions with targets
function checkCollisions() {
state.projectiles.forEach((projectile, pIndex) => {
state.targets.forEach(target => {
// Simple circle-rectangle collision detection
const targetRect = {
x: target.x,
y: target.y,
width: 250,
height: 250
};
// Find closest point on rectangle to circle
const closestX = Math.max(targetRect.x, Math.min(projectile.x, targetRect.x + targetRect.width));
const closestY = Math.max(targetRect.y, Math.min(projectile.y, targetRect.y + targetRect.height));
// Calculate distance
const distanceX = projectile.x - closestX;
const distanceY = projectile.y - closestY;
const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
if (distance < 10) { // Projectile radius
// Hit!
handleHit(target, projectile);
// Remove projectile
projectile.element.remove();
state.projectiles.splice(pIndex, 1);
}
});
});
}
// Check for collisions with center
function checkCenterCollision() {
if (state.gameOver) return;
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const centerRadius = 1;
state.targets.forEach(target => {
// Check if any part of target is within center radius
const targetCenterX = target.x + 125;
const targetCenterY = target.y + 125;
const distance = Math.sqrt(
Math.pow(targetCenterX - centerX, 2) +
Math.pow(targetCenterY - centerY, 2)
);
if (distance < centerRadius + 125) {
handleCenterHit();
}
});
}
// 修改handleCenterHit函数
function handleHit(target, projectile) {
// Play hit sound
hitSound.currentTime = 0;
hitSound.play();
// 添加击中闪烁效果
target.element.classList.add('hit-flash');
setTimeout(() => {
target.element.classList.remove('hit-flash');
}, 300);
// Show hit effect
target.hitEffect.classList.add('active');
setTimeout(() => {
target.hitEffect.classList.remove('active');
}, 500);
// Create hit marker
const hitMarker = document.createElement('div');
hitMarker.className = 'hit-marker';
hitMarker.style.left = `${projectile.x - 15}px`;
hitMarker.style.top = `${projectile.y - 15}px`;
hitMarkersContainer.appendChild(hitMarker);
setTimeout(() => {
hitMarker.remove();
}, 1000);
// Update score
state.hits++;
state.score += 10;
hitCountElement.textContent = state.hits;
scoreElement.textContent = state.score;
// Show "Ejection FACE!" text
const ejectionText = document.createElement('div');
ejectionText.className = 'ejection-text';
ejectionText.textContent = 'Ejection FACE!';
ejectionText.style.left = `${window.innerWidth / 2 - 150}px`;
ejectionText.style.top = `${window.innerHeight / 2 - 50}px`;
gameContainer.appendChild(ejectionText);
setTimeout(() => {
ejectionText.remove();
}, 1500);
// Change target direction on hit with increased knockback
const knockbackDirectionX = (projectile.x - (target.x + 125)) / 125;
const knockbackDirectionY = (projectile.y - (target.y + 125)) / 125;
// Apply knockback with increasing distance based on hits
const knockbackMultiplier = state.knockbackDistance + (state.hits * 0.5);
target.vx = knockbackDirectionX * knockbackMultiplier;
target.vy = knockbackDirectionY * knockbackMultiplier;
// Increase difficulty every 10 hits
if (state.hits % 10 === 0) {
state.targetSpeed += 0.2;
// Make targets change direction more often
state.targets.forEach(t => {
t.vx *= 1.1;
t.vy *= 1.1;
});
}
// Add new target every 10 hits
if (state.hits % 10 === 0) {
state.targetCount++;
state.currentColorIndex = (state.currentColorIndex + 1) % state.colors.length;
setupCursor();
createTargets(state.targetCount);
}
// Unlock weapons if enough score
weaponElements.forEach(weapon => {
const weaponType = weapon.dataset.weapon;
const price = parseInt(weapon.dataset.price);
if (state.score >= price) {
weapon.classList.remove('locked');
}
});
}
// 修改handleCenterHit函数,添加全屏红色闪烁
function handleCenterHit() {
if (state.health > 0) {
state.health--;
updateHealth();
// 全屏红色闪烁
const screenFlash = document.getElementById('screen-flash');
screenFlash.classList.add('active');
setTimeout(() => {
screenFlash.classList.remove('active');
}, 500);
// 将所有目标弹出到画面边缘
state.targets.forEach(target => {
// 计算从中心到目标的向量
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const targetCenterX = target.x + 125;
const targetCenterY = target.y + 125;
let dx = targetCenterX - centerX;
let dy = targetCenterY - centerY;
// 标准化向量并放大(弹出效果)
const distance = Math.sqrt(dx * dx + dy * dy);
dx /= distance;
dy /= distance;
// 设置很大的速度向边缘移动
target.vx = dx * 20;
target.vy = dy * 20;
// 添加闪烁效果
target.element.classList.add('hit-flash');
setTimeout(() => {
target.element.classList.remove('hit-flash');
}, 300);
});
if (state.health <= 0) {
// 延迟游戏结束,让玩家看到所有目标被弹出的效果
setTimeout(() => {
gameOver();
}, 500);
}
}
}
// Game over
function gameOver() {
state.gameOver = true;
// Make targets dance
state.targets.forEach(target => {
target.element.classList.add('dancing');
});
// Show game over screen
gameOverScreen.style.display = 'flex';
}
// Reset game
function resetGame() {
// Reset game state
state.score = 0;
state.hits = 0;
state.health = 10;
state.targets = [];
state.projectiles = [];
state.currentWeapon = 'basic';
state.currentColorIndex = 0;
state.targetSpeed = 2;
state.targetCount = 1;
state.gameOver = false;
state.knockbackDistance = -30;
// Reset UI
hitCountElement.textContent = '0';
scoreElement.textContent = '0';
gameOverScreen.style.display = 'none';
// Clear containers
targetsContainer.innerHTML = '';
projectilesContainer.innerHTML = '';
hitMarkersContainer.innerHTML = '';
// Reset weapons
weaponElements.forEach(weapon => {
if (weapon.dataset.weapon !== 'basic') {
weapon.classList.add('locked');
}
weapon.classList.remove('selected');
});
document.querySelector('.weapon[data-weapon="basic"]').classList.add('selected');
// Reset health
initHealthBar();
// Start new game
createTargets(state.targetCount);
gameLoop();
}
// Setup weapon selection
function setupWeapons() {
weaponElements.forEach(weapon => {
const weaponType = weapon.dataset.weapon;
// Set weapon icons
switch (weaponType) {
case 'basic':
weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[0] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/></svg>")';
break;
case 'rapid':
weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[1] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/><circle cx=\'50\' cy=\'50\' r=\'15\' fill=\'none\' stroke=\'' + state.colors[1] + '\' stroke-width=\'3\'/></svg>")';
break;
case 'double':
weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[2] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/><path d=\'M30 30 L70 70 M30 70 L70 30\' stroke=\'' + state.colors[2] + '\' stroke-width=\'3\'/></svg>")';
break;
case 'shotgun':
weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[3] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/><path d=\'M50 30 L30 50 M50 30 L70 50 M50 70 L30 50 M50 70 L70 50\' stroke=\'' + state.colors[3] + '\' stroke-width=\'3\'/></svg>")';
break;
case 'bouncy':
weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[4] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/><path d=\'M20 20 Q50 30 80 20 Q85 50 80 80 Q50 70 20 80 Q15 50 20 20\' fill=\'none\' stroke=\'' + state.colors[4] + '\' stroke-width=\'3\'/></svg>")';
break;
}
weapon.addEventListener('click', () => {
if (weapon.classList.contains('locked')) return;
// Select weapon
weaponElements.forEach(w => w.classList.remove('selected'));
weapon.classList.add('selected');
state.currentWeapon = weaponType;
setupCursor();
});
});
}
// Play background music
function playMusic() {
bgMusic.volume = 0.3;
bgMusic.play();
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=GuangyuanSD/ejectionface" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>