3D Theater Shooter
body { margin: 0; overflow: hidden; }
canvas { display: block; width: 100%; height: 100vh; }
#crosshair {
position: absolute;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
border: 2px solid white;
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
#hud {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: Arial, sans-serif;
font-size: 20px;
text-shadow: 1px 1px 2px black;
}
#gameOver {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
text-align: center;
font-family: Arial, sans-serif;
border-radius: 10px;
}
#gameOver button {
margin-top: 10px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
#achievements {
position: absolute;
top: 10px;
right: 10px;
color: gold;
font-family: Arial, sans-serif;
font-size: 16px;
text-shadow: 1px 1px 2px black;
}
#startScreen {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
text-align: center;
font-family: Arial, sans-serif;
border-radius: 10px;
}
#startScreen button {
margin-top: 10px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
#errorMessage {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 0, 0, 0.8);
color: white;
padding: 20px;
text-align: center;
font-family: Arial, sans-serif;
border-radius: 10px;
}
Очки: 0
Здоровье: 100
Враги: 5
Волна: 1
Время: 0с
Игра окончена
Итоговые очки: 0
Достигнутая волна: 1
Перезапустить
3D Шутер в Театре
Управление: WASD - движение, мышь - прицел, клик - стрельба
Цель: уничтожайте призрачных актеров, собирайте аптечки
Начать игру
Ошибка
Не удалось загрузить игру. Проверьте подключение или поддержку браузера.
// Scene setup
let scene, camera, renderer, controls;
try {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Add fog for atmosphere
scene.fog = new THREE.Fog(0x333333, 10, 50);
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffa500, 0.5);
directionalLight.position.set(0, 10, 10);
scene.add(directionalLight);
// Theater floor (stage)
const floorGeometry = new THREE.PlaneGeometry(50, 50);
const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x4a2f1a });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
// Theater walls
const wallGeometry = new THREE.BoxGeometry(50, 10, 0.2);
const wallMaterial = new THREE.MeshStandardMaterial({ color: 0x8b0000 });
const backWall = new THREE.Mesh(wallGeometry, wallMaterial);
backWall.position.set(0, 5, -25);
scene.add(backWall);
const leftWall = new THREE.Mesh(wallGeometry, wallMaterial);
leftWall.rotation.y = Math.PI / 2;
leftWall.position.set(-25, 5, 0);
scene.add(leftWall);
const rightWall = new THREE.Mesh(wallGeometry, wallMaterial);
rightWall.rotation.y = -Math.PI / 2;
rightWall.position.set(25, 5, 0);
scene.add(rightWall);
// Stage curtains
const curtainGeometry = new THREE.PlaneGeometry(20, 10);
const curtainMaterial = new THREE.MeshStandardMaterial({ color: 0xFF0000 });
const leftCurtain = new THREE.Mesh(curtainGeometry, curtainMaterial);
leftCurtain.position.set(-10, 5, -24.8);
scene.add(leftCurtain);
const rightCurtain = new THREE.Mesh(curtainGeometry, curtainMaterial);
rightCurtain.position.set(10, 5, -24.8);
scene.add(rightCurtain);
// Theater seats (simplified as boxes)
const seatGeometry = new THREE.BoxGeometry(1, 1, 1);
const seatMaterial = new THREE.MeshStandardMaterial({ color: 0x2f4f4f });
for (let i = -15; i <= 15; i += 3) {
for (let j = 5; j <= 20; j += 3) {
const seat = new THREE.Mesh(seatGeometry, seatMaterial);
seat.position.set(i, 0.5, j);
scene.add(seat);
}
}
// Enemy models (simple cubes as ghostly actors)
const enemyGeometry = new THREE.BoxGeometry(1, 2, 1);
const enemyMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, transparent: true, opacity: 0.7 });
const enemies = [];
// Health pickup (theater-themed medical chest)
const pickupGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const pickupMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const pickups = [];
// Player setup
camera.position.y = 1.6; // Eye height
camera.position.z = 10;
controls = new THREE.PointerLockControls(camera, document.body);
scene.add(controls.getObject());
// Gamification variables
let score = 0;
let health = 100;
let wave = 1;
let gameOver = false;
let waveStartTime = Date.now();
let noDamageEnemies = 0;
let achievements = [];
const scoreElement = document.getElementById('score');
const healthElement = document.getElementById('health');
const enemiesElement = document.getElementById('enemies');
const waveElement = document.getElementById('wave');
const waveTimeElement = document.getElementById('waveTime');
const achievementsElement = document.getElementById('achievements');
const gameOverElement = document.getElementById('gameOver');
const finalScoreElement = document.getElementById('finalScore');
const finalWaveElement = document.getElementById('finalWave');
const startScreen = document.getElementById('startScreen');
const errorMessage = document.getElementById('errorMessage');
// Sound effects
let shootSound, hitSound, pickupSound, applauseSound;
try {
shootSound = new Audio('https://www.soundjay.com/buttons/sounds/button-09.mp3');
hitSound = new Audio('https://www.soundjay.com/human/sounds/hurt-01.mp3');
pickupSound = new Audio('https://www.soundjay.com/buttons/sounds/button-16.mp3');
applauseSound = new Audio('https://www.soundjay.com/crowd/sounds/applause-01.mp3');
} catch (e) {
console.warn('Звуки не загружены:', e);
}
// Spawn enemies for a wave
function spawnWave(waveNumber) {
const enemyCount = 5 + waveNumber * 2;
for (let i = 0; i < enemyCount; i++) {
const enemy = new THREE.Mesh(enemyGeometry, enemyMaterial);
enemy.position.set(
Math.random() * 40 - 20,
1,
Math.random() * 40 - 20
);
scene.add(enemy);
enemies.push(enemy);
}
enemiesElement.textContent = enemies.length;
waveElement.textContent = waveNumber;
waveStartTime = Date.now();
}
// Spawn health pickup
function spawnPickup() {
const pickup = new THREE.Mesh(pickupGeometry, pickupMaterial);
pickup.position.set(
Math.random() * 40 - 20,
0.5,
Math.random() * 40 - 20
);
scene.add(pickup);
pickups.push(pickup);
}
// Initial wave
spawnWave(wave);
// Movement
const moveSpeed = 0.1;
const velocity = new THREE.Vector3();
let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
document.addEventListener('keydown', (event) => {
if (gameOver) return;
switch (event.code) {
case 'KeyW': moveForward = true; break;
case 'KeyS': moveBackward = true; break;
case 'KeyA': moveLeft = true; break;
case 'KeyD': moveRight = true; break;
}
});
document.addEventListener('keyup', (event) => {
switch (event.code) {
case 'KeyW': moveForward = false; break;
case 'KeyS': moveBackward = false; break;
case 'KeyA': moveLeft = false; break;
case 'KeyD': moveRight = false; break;
}
});
// Shooting mechanics
document.addEventListener('click', () => {
if (gameOver || startScreen.style.display !== 'none') return;
if (shootSound) shootSound.play();
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
const intersects = raycaster.intersectObjects(enemies);
if (intersects.length > 0) {
const enemy = intersects[0].object;
scene.remove(enemy);
enemies.splice(enemies.indexOf(enemy), 1);
score += 100;
noDamageEnemies++;
scoreElement.textContent = score;
enemiesElement.textContent = enemies.length;
checkAchievements();
if (enemies.length === 0) {
wave++;
spawnWave(wave);
spawnPickup();
}
}
});
// Start game
function startGame() {
if (document.pointerLockElement || document.mozPointerLockElement) {
startScreen.style.display = 'none';
} else {
controls.lock();
}
}
controls.addEventListener('lock', () => {
startScreen.style.display = 'none';
});
controls.addEventListener('unlock', () => {
if (!gameOver) startScreen.style.display = 'block';
});
// Enemy attack logic
function updateEnemies() {
enemies.forEach(enemy => {
const direction = new THREE.Vector3();
direction.subVectors(camera.position, enemy.position).normalize();
enemy.position.add(direction.multiplyScalar(0.02 + wave * 0.005));
const distance = camera.position.distanceTo(enemy.position);
if (distance < 2) {
health -= 0.5;
noDamageEnemies = 0;
if (hitSound) hitSound.play();
healthElement.textContent = Math.max(0, Math.floor(health));
if (health <= 0) {
endGame(false);
}
}
});
}
// Pickup collection
function checkPickups() {
pickups.forEach((pickup, index) => {
const distance = camera.position.distanceTo(pickup.position);
if (distance < 1) {
health = Math.min(100, health + 25);
healthElement.textContent = Math.floor(health);
scene.remove(pickup);
pickups.splice(index, 1);
if (pickupSound) pickupSound.play();
}
});
}
// Achievements
function checkAchievements() {
if (noDamageEnemies >= 10 && !achievements.includes('Flawless')) {
achievements.push('Flawless');
achievementsElement.innerHTML += '<div>Достижение: Безупречно (10 врагов без урона)</div>';
if (applauseSound) applauseSound.play();
}
const waveTime = (Date.now() - waveStartTime) / 1000;
if (waveTime < 30 && !achievements.includes('Speedy')) {
achievements.push('Speedy');
achievementsElement.innerHTML += '<div>Достижение: Быстрый (Волна за 30 секунд)</div>';
if (applauseSound) applauseSound.play();
}
}
// Game over logic
function endGame(won) {
gameOver = true;
controls.unlock();
gameOverElement.style.display = 'block';
finalScoreElement.textContent = score;
finalWaveElement.textContent = wave;
gameOverElement.querySelector('h1').textContent = won ? 'Победа!' : 'Игра окончена';
}
// Restart game
function restartGame() {
score = 0;
health = 100;
wave = 1;
gameOver = false;
noDamageEnemies = 0;
achievements = [];
scoreElement.textContent = score;
healthElement.textContent = health;
waveElement.textContent = wave;
waveTimeElement.textContent = '0';
achievementsElement.innerHTML = '';
gameOverElement.style.display = 'none';
enemies.forEach(enemy => scene.remove(enemy));
enemies.length = 0;
pickups.forEach(pickup => scene.remove(pickup));
pickups.length = 0;
spawnWave(wave);
camera.position.set(0, 1.6, 10);
startScreen.style.display = 'block';
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
if (gameOver || startScreen.style.display !== 'none') return;
// Movement
velocity.x = 0;
velocity.z = 0;
if (moveForward) velocity.z -= moveSpeed;
if (moveBackward) velocity.z += moveSpeed;
if (moveLeft) velocity.x -= moveSpeed;
if (moveRight) velocity.x += moveSpeed;
controls.moveRight(velocity.x);
controls.moveForward(velocity.z);
// Animate enemies (oscillation + attack)
enemies.forEach(enemy => {
enemy.position.y = 1 + Math.sin(Date.now() * 0.001) * 0.5;
});
updateEnemies();
checkPickups();
// Update wave timer
waveTimeElement.textContent = Math.floor((Date.now() - waveStartTime) / 1000);
renderer.render(scene, camera);
}
animate();
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
} catch (e) {
console.error('Ошибка инициализации игры:', e);
errorMessage.style.display = 'block';
}
</script>