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>
Made on
Tilda