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


<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tails 3D Platformer</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000;
            font-family: sans-serif;
        }
        #ui {
            position: absolute;
            top: 20px;
            left: 20px;
            color: #fff;
            font-size: 24px;
            font-weight: bold;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
            pointer-events: none;
            z-index: 10;
        }
        #instructions {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            color: #fff;
            background: rgba(0,0,0,0.6);
            padding: 10px 20px;
            border-radius: 8px;
            pointer-events: none;
            text-align: center;
            z-index: 10;
        }
        #loader-screen {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            background: #fe982a; /* Оранжевый цвет под стиль Тейлза */
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 28px;
            font-weight: bold;
            text-shadow: 1px 1px 5px rgba(0,0,0,0.3);
            z-index: 100;
            transition: opacity 0.5s ease;
        }
    </style>
    <!-- Библиотеки Three.js, OrbitControls и GLTFLoader -->
    <script src="https://cloudflare.com"></script>
    <script src="https://jsdelivr.net"></script>
    <script src="https://jsdelivr.net"></script>
</head>
<body>

    <div id="loader-screen">Загрузка Тейлза из сети...</div>
    <div id="ui">Кольца: <span id="score">0</span></div>
    <div id="instructions">WASD — Движение | ПРОБЕЛ — Прыжок | Мышь — Обзор камеры</div>

    <script>
        // --- Настройка сцены ---
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xa0c0ff);
        scene.fog = new THREE.FogExp2(0xa0c0ff, 0.015);

        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);

        // --- Освещение ---
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
        scene.add(ambientLight);

        const dirLight = new THREE.DirectionLight(0xffffff, 1.0);
        dirLight.position.set(20, 40, 20);
        dirLight.castShadow = true;
        dirLight.shadow.mapSize.width = 2000;
        dirLight.shadow.mapSize.height = 2000;
        dirLight.shadow.camera.near = 0.5;
        dirLight.shadow.camera.far = 100;
        const d = 40;
        dirLight.shadow.camera.left = -d;
        dirLight.shadow.camera.right = d;
        dirLight.shadow.camera.top = d;
        dirLight.shadow.camera.bottom = -d;
        scene.add(dirLight);

        // --- Контроллер камеры (Третье лицо) ---
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        controls.minDistance = 4;
        controls.maxDistance = 10;
        controls.enablePan = false;

        // --- Игровые переменные и физика ---
        let score = 0;
        const gravity = -35;
        const jumpForce = 13;
        const moveSpeed = 11;
        
        const player = {
            mesh: new THREE.Group(), 
            velocity: new THREE.Vector3(),
            isGrounded: false,
            radius: 0.6
        };
        scene.add(player.mesh);

        const platforms = [];
        const rings = [];

        // --- АВТОЗАГРУЗКА 3D-МОДЕЛИ ТЕЙЛЗА ИЗ GITHUB ---
        const loader = new THREE.GLTFLoader();
        // Используем прямую проверенную ссылку на GLTF модель Тейлза с открытого репозитория
        const modelUrl = 'https://githubusercontent.com';

        loader.load(modelUrl, function(gltf) {
            const model = gltf.scene;
            
            // Настройка размера и центрирования
            model.scale.set(0.6, 0.6, 0.6);
            model.position.y = -0.6; 
            
            // Поворачиваем модель лицом к движению
            model.rotation.y = Math.PI;

            // Включаем тени
            model.traverse(node => {
                if (node.isMesh) {
                    node.castShadow = true;
                    node.receiveShadow = true;
                    // Исправляем возможные проблемы с отображением текстур при импорте
                    if(node.material) node.material.side = THREE.DoubleSide; 
                }
            });

            player.mesh.add(model);
            player.mesh.position.set(0, 5, 0);

            // Убираем экран загрузки
            const loaderScreen = document.getElementById('loader-screen');
            loaderScreen.style.opacity = '0';
            setTimeout(() => loaderScreen.remove(), 500);

        }, undefined, function(error) {
            console.error('Ошибка загрузки:', error);
            document.getElementById('loader-screen').innerText = "Ошибка сети. Попробуйте обновить страницу.";
        });

        // --- Генерация уровня ---
        function createPlatform(x, y, z, w, h, d) {
            const geo = new THREE.BoxGeometry(w, h, d);
            const mat = new THREE.MeshStandardMaterial({ color: 0x5c4033, roughness: 0.9 }); 
            const topGeo = new THREE.BoxGeometry(w, 0.2, d);
            const topMat = new THREE.MeshStandardMaterial({ color: 0x3cb371, roughness: 0.6 });

            const mesh = new THREE.Mesh(geo, mat);
            mesh.position.set(x, y - h/2, z);
            mesh.receiveShadow = true;

            const topMesh = new THREE.Mesh(topGeo, topMat);
            topMesh.position.set(0, h/2 + 0.1, 0);
            topMesh.receiveShadow = true;
            mesh.add(topMesh);

            scene.add(mesh);
            platforms.push({ mesh: mesh, box: new THREE.Box3().setFromObject(mesh), topY: y });
        }

        function createRing(x, y, z) {
            const geo = new THREE.TorusGeometry(0.35, 0.08, 8, 24);
            const mat = new THREE.MeshStandardMaterial({ color: 0xFFD700, metalness: 0.9, roughness: 0.1, emissive: 0x332200 });
            const mesh = new THREE.Mesh(geo, mat);
            mesh.position.set(x, y, z);
            mesh.castShadow = true;
            scene.add(mesh);
            rings.push(mesh);
        }

        // Строим классическую зону Green Hill из островов
        createPlatform(0, 0, 0, 14, 10, 14);
        createRing(0, 1.2, 2);
        createRing(0, 1.2, 4);

        createPlatform(0, 2, -14, 10, 10, 10);
        createRing(0, 3.2, -14);

        createPlatform(12, 4, -24, 8, 12, 8);
        createRing(12, 5.2, -24);

        createPlatform(0, 6, -38, 12, 15, 12);
        createRing(0, 7.2, -38);
        createRing(2, 7.2, -38);
        createRing(-2, 7.2, -38);

        createPlatform(-14, 9, -38, 8, 18, 8);
        createRing(-14, 10.2, -38);

        // --- Обработка ввода ---
        const keys = { w: false, a: false, s: false, d: false, space: false };
        window.addEventListener('keydown', (e) => {
            if(e.code === 'KeyW') keys.w = true;
            if(e.code === 'KeyS') keys.s = true;
            if(e.code === 'KeyA') keys.a = true;
            if(e.code === 'KeyD') keys.d = true;
            if(e.code === 'Space') keys.space = true;
        });
        window.addEventListener('keyup', (e) => {
            if(e.code === 'KeyW') keys.w = false;
            if(e.code === 'KeyS') keys.s = false;
            if(e.code === 'KeyA') keys.a = false;
            if(e.code === 'KeyD') keys.d = false;
            if(e.code === 'Space') keys.space = false;
        });

        // --- Главный Цикл ---
        const clock = new THREE.Clock();

        function animate() {
            requestAnimationFrame(animate);
            const dt = Math.min(clock.getDelta(), 0.1); 

            // Направление бега от камеры
            const camForward = new THREE.Vector3();
            camera.getWorldDirection(camForward);
            camForward.y = 0;
            camForward.normalize();

            const camRight = new THREE.Vector3();
            camRight.crossVectors(camForward, camera.up).normalize().negate();

            const moveDirection = new THREE.Vector3();
            if (keys.w) moveDirection.add(camForward);
            if (keys.s) moveDirection.add(camForward.clone().negate());
            if (keys.a) moveDirection.add(camRight.clone().negate());
            if (keys.d) moveDirection.add(camRight);
            
            moveDirection.normalize();

            player.velocity.x = moveDirection.x * moveSpeed;
            player.velocity.z = moveDirection.z * moveSpeed;

            // Физика прыжка и падения
            player.velocity.y += gravity * dt;

            if (keys.space && player.isGrounded) {
                player.velocity.y = jumpForce;
                player.isGrounded = false;
            }

            player.mesh.position.addScaledVector(player.velocity, dt);

            // Плавный поворот и анимационный наклон при движении
            if (moveDirection.lengthSq() > 0) {
                const targetAngle = Math.atan2(moveDirection.x, moveDirection.z);
                let diff = targetAngle - player.mesh.rotation.y;