Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Pro Penalty Shootout</title> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
font-family: 'Arial', sans-serif; | |
background: #000; | |
} | |
#container { position: relative; } | |
#ui { | |
position: absolute; | |
top: 20px; | |
width: 100%; | |
text-align: center; | |
color: white; | |
font-size: 24px; | |
text-shadow: 2px 2px 4px rgba(0,0,0,0.8); | |
} | |
#message { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
color: white; | |
font-size: 72px; | |
font-weight: bold; | |
text-shadow: 0 0 20px #000; | |
opacity: 0; | |
transition: opacity 0.3s; | |
pointer-events: none; | |
} | |
#controls { | |
position: absolute; | |
bottom: 20px; | |
left: 20px; | |
color: white; | |
background: rgba(0,0,0,0.7); | |
padding: 15px; | |
border-radius: 10px; | |
font-size: 18px; | |
line-height: 1.6; | |
} | |
#start-screen { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0,0,0,0.9); | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
color: white; | |
z-index: 100; | |
} | |
#start-screen h1 { | |
font-size: 48px; | |
color: #FFD700; | |
margin-bottom: 30px; | |
text-shadow: 0 0 10px #FFD700; | |
} | |
button { | |
padding: 15px 30px; | |
font-size: 18px; | |
background: #4CAF50; | |
color: white; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
margin: 10px; | |
transition: all 0.3s; | |
} | |
button:hover { | |
background: #45a049; | |
transform: scale(1.05); | |
} | |
#power-bar { | |
position: absolute; | |
bottom: 100px; | |
left: 50%; | |
transform: translateX(-50%); | |
width: 200px; | |
height: 20px; | |
background: rgba(255,255,255,0.2); | |
border-radius: 10px; | |
overflow: hidden; | |
display: none; | |
} | |
#power-level { | |
height: 100%; | |
width: 0%; | |
background: linear-gradient(to right, #4CAF50, #FFD700, #FF5722); | |
} | |
</style> | |
</head> | |
<body> | |
<div id="container"> | |
<div id="ui"> | |
<span id="goals">0</span> / <span id="shots">0</span> | |
</div> | |
<div id="message"></div> | |
<div id="power-bar"><div id="power-level"></div></div> | |
<div id="controls"> | |
<strong>CONTROLS:</strong><br> | |
SPACE - Power up shot<br> | |
LEFT/RIGHT - Aim<br> | |
Release SPACE - Shoot | |
</div> | |
<div id="start-screen"> | |
<h1>PRO PENALTY SHOOTOUT</h1> | |
<button id="start-btn">START GAME</button> | |
</div> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script> | |
<script> | |
// Setup | |
const scene = new THREE.Scene(); | |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); | |
camera.position.set(0, 4, 12); | |
camera.lookAt(0, 1, 0); | |
const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.shadowMap.enabled = true; | |
document.getElementById('container').appendChild(renderer.domElement); | |
// Lighting | |
const ambientLight = new THREE.AmbientLight(0x404040); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
directionalLight.position.set(5, 10, 7); | |
directionalLight.castShadow = true; | |
scene.add(directionalLight); | |
// Field | |
const field = new THREE.Mesh( | |
new THREE.PlaneGeometry(40, 60), | |
new THREE.MeshStandardMaterial({ color: 0x3CB371 }) | |
); | |
field.rotation.x = -Math.PI/2; | |
field.receiveShadow = true; | |
scene.add(field); | |
// Goal (more visible) | |
const goalWidth = 7.32; | |
const goalHeight = 2.44; | |
const postMaterial = new THREE.MeshStandardMaterial({ | |
color: 0xffffff, | |
metalness: 0.5 | |
}); | |
// Posts | |
const leftPost = new THREE.Mesh( | |
new THREE.BoxGeometry(0.5, goalHeight, 0.5), | |
postMaterial | |
); | |
leftPost.position.set(-goalWidth/2, goalHeight/2, -30); | |
leftPost.castShadow = true; | |
scene.add(leftPost); | |
const rightPost = new THREE.Mesh( | |
new THREE.BoxGeometry(0.5, goalHeight, 0.5), | |
postMaterial | |
); | |
rightPost.position.set(goalWidth/2, goalHeight/2, -30); | |
rightPost.castShadow = true; | |
scene.add(rightPost); | |
const crossbar = new THREE.Mesh( | |
new THREE.BoxGeometry(goalWidth, 0.5, 0.5), | |
postMaterial | |
); | |
crossbar.position.set(0, goalHeight, -30); | |
crossbar.castShadow = true; | |
scene.add(crossbar); | |
// Net | |
const net = new THREE.Mesh( | |
new THREE.BoxGeometry(goalWidth, goalHeight, 2), | |
new THREE.MeshBasicMaterial({ | |
wireframe: true, | |
color: 0xffffff, | |
transparent: true, | |
opacity: 0.7 | |
}) | |
); | |
net.position.set(0, goalHeight/2, -31); | |
scene.add(net); | |
// Ball | |
const ballGeometry = new THREE.SphereGeometry(0.4, 32, 32); | |
const ballMaterial = new THREE.MeshStandardMaterial({ | |
color: 0xffffff, | |
roughness: 0.2, | |
metalness: 0.5 | |
}); | |
const ball = new THREE.Mesh(ballGeometry, ballMaterial); | |
ball.position.set(0, 0.4, 0); | |
ball.castShadow = true; | |
scene.add(ball); | |
// Goalkeeper (more visible) | |
function createGoalkeeper() { | |
const group = new THREE.Group(); | |
// Blue jersey | |
const body = new THREE.Mesh( | |
new THREE.BoxGeometry(1, 1.8, 0.4), | |
new THREE.MeshStandardMaterial({ color: 0x1E90FF }) | |
); | |
body.position.y = 0.9; | |
group.add(body); | |
// Yellow head | |
const head = new THREE.Mesh( | |
new THREE.SphereGeometry(0.3), | |
new THREE.MeshStandardMaterial({ color: 0xFFD700 }) | |
); | |
head.position.y = 1.6; | |
group.add(head); | |
// Arms (in save position) | |
const leftArm = new THREE.Mesh( | |
new THREE.BoxGeometry(0.2, 0.8, 0.2), | |
body.material | |
); | |
leftArm.position.set(-0.7, 1.3, 0); | |
leftArm.rotation.z = Math.PI/3; | |
group.add(leftArm); | |
const rightArm = new THREE.Mesh( | |
new THREE.BoxGeometry(0.2, 0.8, 0.2), | |
body.material | |
); | |
rightArm.position.set(0.7, 1.3, 0); | |
rightArm.rotation.z = -Math.PI/3; | |
group.add(rightArm); | |
group.position.z = -29; | |
return group; | |
} | |
const goalkeeper = createGoalkeeper(); | |
scene.add(goalkeeper); | |
// Game state | |
let shots = 0, goals = 0; | |
const maxShots = 5; | |
let power = 0; | |
let isPowering = false; | |
let isShooting = false; | |
let aimDirection = 0; | |
const clock = new THREE.Clock(); | |
// Controls | |
const keys = { | |
Space: false, | |
ArrowLeft: false, | |
ArrowRight: false | |
}; | |
window.addEventListener('keydown', (e) => { | |
if (e.code in keys) { | |
keys[e.code] = true; | |
if (e.code === 'Space' && !isPowering && !isShooting && shots < maxShots) { | |
isPowering = true; | |
document.getElementById('power-bar').style.display = 'block'; | |
} | |
} | |
}); | |
window.addEventListener('keyup', (e) => { | |
if (e.code in keys) { | |
keys[e.code] = false; | |
if (e.code === 'Space' && isPowering && !isShooting) { | |
shootBall(); | |
} | |
} | |
}); | |
// Start game | |
document.getElementById('start-btn').addEventListener('click', () => { | |
document.getElementById('start-screen').style.display = 'none'; | |
}); | |
// Power meter | |
function updatePower() { | |
if (isPowering) { | |
power = Math.min(power + 2, 100); | |
document.getElementById('power-level').style.width = `${power}%`; | |
} | |
} | |
// Shooting | |
function shootBall() { | |
isPowering = false; | |
isShooting = true; | |
shots++; | |
document.getElementById('shots').textContent = shots; | |
document.getElementById('power-bar').style.display = 'none'; | |
// Determine target based on aim | |
const targetX = aimDirection * 4; | |
const targetY = goalHeight/2; | |
const speed = 0.5 + (power/100) * 1.5; // Faster ball movement | |
let lastTime = performance.now(); | |
function animateBall() { | |
const now = performance.now(); | |
const delta = (now - lastTime) / 16; // Normalize to ~60fps | |
lastTime = now; | |
// Move ball forward and adjust curve | |
ball.position.z -= speed * delta; | |
ball.position.x += (targetX - ball.position.x) * 0.03 * delta; | |
ball.position.y = 0.4 + Math.sin(ball.position.z * 0.2) * 3 * (power/100); | |
// Collision with goalkeeper | |
const keeperX = goalkeeper.position.x; | |
if (Math.abs(ball.position.x - keeperX) < 1.2 && ball.position.z < -27) { | |
endShot(false); | |
return; | |
} | |
// Goal check | |
if (ball.position.z <= -30) { | |
const isGoal = Math.abs(ball.position.x) < goalWidth/2 && ball.position.y < goalHeight; | |
if (isGoal) { | |
goals++; | |
document.getElementById('goals').textContent = goals; | |
showMessage("GOAL!"); | |
} else { | |
showMessage("MISSED!"); | |
} | |
endShot(isGoal); | |
return; | |
} | |
requestAnimationFrame(animateBall); | |
} | |
animateBall(); | |
power = 0; | |
} | |
function endShot(scored) { | |
isShooting = false; | |
setTimeout(() => { | |
ball.position.set(0, 0.4, 0); | |
if (shots >= maxShots) { | |
setTimeout(() => { | |
showMessage(`FINAL SCORE: ${goals}/${maxShots}`); | |
}, 500); | |
} | |
}, 1000); | |
} | |
function showMessage(text) { | |
const message = document.getElementById('message'); | |
message.textContent = text; | |
message.style.opacity = 1; | |
setTimeout(() => message.style.opacity = 0, 1500); | |
} | |
// Animation loop | |
function animate() { | |
requestAnimationFrame(animate); | |
// Goalkeeper movement | |
const time = clock.getElapsedTime(); | |
goalkeeper.position.x = Math.sin(time * 1.5) * 3; | |
// Handle aim input | |
if (isPowering) { | |
if (keys.ArrowLeft) aimDirection = Math.max(aimDirection - 0.05, -1); | |
if (keys.ArrowRight) aimDirection = Math.min(aimDirection + 0.05, 1); | |
updatePower(); | |
} | |
renderer.render(scene, camera); | |
} | |
// Window resize | |
window.addEventListener('resize', () => { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}); | |
animate(); | |
</script> | |
</body> | |
</html> |