Загрузка данных


<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8"><title>Sonic Adventure 2: Green Hill Mini</title>
    <style>
        body { margin: 0; overflow: hidden; background: #4a90e2; font-family: monospace; }
        #hud { position: absolute; top: 15px; left: 15px; color: #fff; font-size: 22px; font-weight: bold; text-shadow: 2px 2px #000; pointer-events: none; }
        .y { color: #f1c40f; }
    </style>
    <script src="https://cloudflare.com"></script>
</head>
<body>
    <div id="hud"><span class="y">RINGS:</span> <span id="r">000</span> | <span class="y">SCORE:</span> <span id="s">0000</span></div>
    <script>
        // --- ИНИЦИАЛИЗАЦИЯ СЦЕНЫ ---
        const scene = new THREE.Scene();
        scene.fog = new THREE.Fog(0x4a90e2, 20, 70);
        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        document.body.appendChild(renderer.domElement);

        scene.add(new THREE.AmbientLight(0xffffff, 0.7));
        const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
        dirLight.position.set(10, 20, 10);
        dirLight.castShadow = true;
        scene.add(dirLight);

        // --- СОЗДАНИЕ МОДЕЛИ СОНИКА (СТИЛЬ SA2 С SOAP SHOES) ---
        const sonic = new THREE.Group();
        const blueM = new THREE.MeshStandardMaterial({ color: 0x002fa7, roughness: 0.4 }); // Более глубокий синий SA2
        const peachM = new THREE.MeshStandardMaterial({ color: 0xffcc99, roughness: 0.6 });
        const whiteM = new THREE.MeshStandardMaterial({ color: 0xffffff });
        const redM = new THREE.MeshStandardMaterial({ color: 0xcc1111 });
        const darkM = new THREE.MeshStandardMaterial({ color: 0x222222 }); // Для деталей Soap Shoes

        // Стройное тело и голова (Модерн пропорции SA2)
        const body = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 0.3, 1.4, 8), blueM);
        body.position.y = 0.5;
        const belly = new THREE.Mesh(new THREE.SphereGeometry(0.38, 8, 8), peachM);
        belly.position.set(0, 0.5, 0.15); belly.scale.set(1, 1.2, 0.6);
        
        const head = new THREE.Mesh(new THREE.SphereGeometry(0.7, 8, 8), blueM);
        head.position.set(0, 1.5, 0.1);
        const muzzle = new THREE.Mesh(new THREE.ConeGeometry(0.35, 0.7, 8), peachM);
        muzzle.rotation.x = Math.PI / 1.8; muzzle.position.set(0, 1.4, 0.7);
        sonic.add(body, belly, head, muzzle);

        // Длинные, загнутые назад иглы (Специфика SA2 арт-стиля)
        for (let i = 0; i < 3; i++) {
            const spike = new THREE.Mesh(new THREE.ConeGeometry(0.28, 1.5, 5), blueM);
            spike.rotation.x = -Math.PI / 2.3;
            spike.position.set(0, 1.7 - i * 0.35, -0.4 - i * 0.15);
            sonic.add(spike);
        }

        // Длинные тонкие руки и ноги
        const armL = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 1, 4), peachM); armL.position.set(-0.6, 0.6, 0.1);
        const armR = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 1, 4), peachM); armR.position.set(0.6, 0.6, 0.1);
        const gloveL = new THREE.Mesh(new THREE.SphereGeometry(0.22, 6, 6), whiteM); gloveL.position.set(-0.6, 0.1, 0.1);
        const gloveR = new THREE.Mesh(new THREE.SphereGeometry(0.22, 6, 6), whiteM); gloveR.position.set(0.6, 0.1, 0.1);
        
        const legL = new THREE.Group();
        const legR = new THREE.Group();
        const limbL = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 1, 4), blueM); limbL.position.y = -0.5;
        const limbR = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 1, 4), blueM); limbR.position.y = -0.5;

        // Легендарные кроссовки Soap Shoes (сложная low-poly форма с подошвой для гринда)
        const shoeBoxL = new THREE.Mesh(new THREE.BoxGeometry(0.45, 0.3, 1.1), redM); shoeBoxL.position.set(0, -1.1, 0.2);
        const soleL = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.1, 1.15), darkM); soleL.position.set(0, -1.25, 0.2); // Гринд-вставка
        const stripeL = new THREE.Mesh(new THREE.BoxGeometry(0.47, 0.15, 0.3), whiteM); stripeL.position.set(0, -0.96, 0.2);
        legL.add(limbL, shoeBoxL, soleL, stripeL); legL.position.set(-0.4, 0, 0);

        const shoeBoxR = new THREE.Mesh(new THREE.BoxGeometry(0.45, 0.3, 1.1), redM); shoeBoxR.position.set(0, -1.1, 0.2);
        const soleR = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.1, 1.15), darkM); soleR.position.set(0, -1.25, 0.2);
        const stripeR = new THREE.Mesh(new THREE.BoxGeometry(0.47, 0.15, 0.3), whiteM); stripeR.position.set(0, -0.96, 0.2);
        legR.add(limbR, shoeBoxR, soleR, stripeR); legR.position.set(0.4, 0, 0);

        sonic.add(armL, armR, gloveL, gloveR, legL, legR);
        sonic.position.set(0, 1.3, 0);
        sonic.traverse(child => { if(child.isMesh) child.castShadow = true; });
        scene.add(sonic);

        // --- ОКРУЖЕНИЕ GREEN HILL ZONE ---
        const canvas = document.createElement('canvas'); canvas.width = 128; canvas.height = 128;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = '#e67e22'; ctx.fillRect(0,0,128,128);
        ctx.fillStyle = '#d35400';
        for(let x=0; x<4; x++) for(let y=0; y<4; y++) if((x+y)%2===0) ctx.fillRect(x*32, y*32, 32, 32);
        const groundTex = new THREE.CanvasTexture(canvas);
        groundTex.wrapS = groundTex.wrapT = THREE.RepeatWrapping;
        groundTex.repeat.set(2, 20);

        const track = new THREE.Mesh(new THREE.BoxGeometry(16, 1, 400), new THREE.MeshStandardMaterial({ map: groundTex, roughness: 0.9 }));
        track.position.set(0, -0.5, -150); track.receiveShadow = true;
        scene.add(track);

        const rings = [];
        const ringGeo = new THREE.TorusGeometry(0.5, 0.1, 6, 16);
        const ringMat = new THREE.MeshStandardMaterial({ color: 0xf1c40f, metalness: 0.9, roughness: 0.1 });
        
        const hillGeo = new THREE.ConeGeometry(8, 12, 5);
        const hillMat = new THREE.MeshStandardMaterial({ color: 0x2ecc71, roughness: 0.9 });

        for (let i = 0; i < 25; i++) {
            const ring = new THREE.Mesh(ringGeo, ringMat);
            ring.position.set((Math.random() - 0.5) * 8, 1.2, -30 - i * 14);
            scene.add(ring); rings.push(ring);

            const hillL = new THREE.Mesh(hillGeo, hillMat); hillL.position.set(-16 - Math.random()*10, 5, -i * 16);
            const hillR = new THREE.Mesh(hillGeo, hillMat); hillR.position.set(16 + Math.random()*10, 5, -i * 16);
            scene.add(hillL, hillR);
        }

        // --- ЛОГИКА И УПРАВЛЕНИЕ ---
        let score = 0, ringsCount = 0, velX = 0, velY = 0, isJumping = false;
        const keys = { a: false, d: false, space: false };

        window.addEventListener('keydown', (e) => {
            if(e.code === 'KeyA' || e.code === 'ArrowLeft') keys.a = true;
            if(e.code === 'KeyD' || e.code === 'ArrowRight') keys.d = true;
            if(e.code === 'Space') keys.space = true;
        });
        window.addEventListener('keyup', (e) => {
            if(e.code === 'KeyA' || e.code === 'ArrowLeft') keys.a = false;
            if(e.code === 'KeyD' || e.code === 'ArrowRight') keys.d = false;
            if(e.code === 'Space') keys.space = false;
        });

        // --- ИГРОВОЙ ЦИКЛ ---
        function animate() {
            requestAnimationFrame(animate);

            sonic.position.z -= 0.45; // Чуть быстрее, в стиле SA2
            if (sonic.position.z < -350) {
                sonic.position.z = 0;
                rings.forEach((r, idx) => { r.position.z = -30 - idx * 14; r.visible = true; });
            }

            if (keys.a && sonic.position.x > -7) velX -= 0.05;
            if (keys.d && sonic.position.x < 7) velX += 0.05;
            velX *= 0.85;
            sonic.position.x += velX;

            // Наклон корпуса при поворотах (динамика из SA2)
            sonic.rotation.y = -velX * 2;
            sonic.rotation.z = -velX * 1.5;

            if (keys.space && !isJumping) { velY = 0.26; isJumping = true; }
            if (isJumping) {
                velY -= 0.012;
                sonic.position.y += velY;
                if (sonic.position.y <= 1.3) { sonic.position.y = 1.3; isJumping = false; }
                // Сворачивание в шар при прыжке
                head.position.y = 0.8; 
                sonic.rotation.x -= 0.2;
            } else {
                head.position.y = 1.5;
                sonic.rotation.x = 0;
                
                // Размашистая SA2-анимация бега для ног и рук
                const t = Date.now() * 0.012;
                legL.position.z = Math.sin(t) * 0.6; legL.position.y = Math.max(0, Math.cos(t) * 0.3);
                legR.position.z = -Math.sin(t) * 0.6; legR.position.y = Math.max(0, -Math.cos(t) * 0.3);
                
                armL.rotation.x = -Math.sin(t) * 0.8; armR.rotation.x = Math.sin(t) * 0.8;
                gloveL.position.z = Math.cos(t) * 0.4 + 0.1; gloveR.position.z = -Math.cos(t) * 0.4 + 0.1;
            }

            rings.forEach(ring => {
                ring.rotation.y += 0.05;
                if (ring.visible && sonic.position.distanceTo(ring.position) < 1.8) {
                    ring.visible = false;
                    ringsCount++; score += 100;
                    document.getElementById('r').innerText = String(ringsCount).padStart(3, '0');
                    document.getElementById('s').innerText = String(score).padStart(4, '0');
                }
            });

            camera.position.set(sonic.position.x, sonic.position.y + 3.5, sonic.position.z + 8.5);
            camera.lookAt(sonic.position.x, sonic.position.y + 1, sonic.position.z - 5);

            renderer.render(scene, camera);
        }

        window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();