🎮ArcadeLab

4D Кликер

by LuckyGlider65
692 lines27.2 KB
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>4D Кликер</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            overflow: hidden;
            background: #0a0a1a;
            font-family: 'Segoe UI', Arial, sans-serif;
            user-select: none;
        }
        canvas {
            display: block;
        }
        
        #ui {
            position: absolute;
            top: 20px;
            left: 0;
            right: 0;
            display: flex;
            justify-content: center;
            gap: 30px;
            color: #fff;
            font-size: 20px;
            font-weight: bold;
            text-shadow: 0 0 20px rgba(78,205,196,0.3);
            pointer-events: none;
            z-index: 10;
            flex-wrap: wrap;
            padding: 0 15px;
        }
        #ui span {
            background: rgba(0,0,0,0.7);
            padding: 10px 24px;
            border-radius: 12px;
            border: 1px solid rgba(255,255,255,0.08);
            backdrop-filter: blur(10px);
            display: flex;
            align-items: center;
            gap: 8px;
        }
        #ui .score { color: #f7d44a; }
        #ui .click-power { color: #ff6b6b; }
        #ui .auto-power { color: #4ecdc4; }
        #ui .dimension { color: #a29bfe; }
        
        #shop {
            position: absolute;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 15px;
            z-index: 10;
            flex-wrap: wrap;
            justify-content: center;
            padding: 0 15px;
        }
        .shop-btn {
            padding: 12px 24px;
            background: rgba(0,0,0,0.8);
            color: #fff;
            border: 1px solid rgba(255,255,255,0.1);
            border-radius: 12px;
            font-size: 14px;
            font-weight: bold;
            cursor: pointer;
            transition: 0.3s;
            backdrop-filter: blur(10px);
            min-width: 140px;
            text-align: center;
            pointer-events: all;
        }
        .shop-btn:hover {
            transform: scale(1.05);
            border-color: rgba(255,255,255,0.3);
        }
        .shop-btn .cost {
            color: #f7d44a;
            font-size: 12px;
            display: block;
            margin-top: 4px;
        }
        .shop-btn .level {
            color: rgba(255,255,255,0.5);
            font-size: 11px;
            display: block;
        }
        .shop-btn.click-btn { border-color: #ff6b6b; }
        .shop-btn.click-btn:hover { box-shadow: 0 0 30px rgba(255,107,107,0.2); }
        .shop-btn.auto-btn { border-color: #4ecdc4; }
        .shop-btn.auto-btn:hover { box-shadow: 0 0 30px rgba(78,205,196,0.2); }
        .shop-btn:disabled {
            opacity: 0.4;
            cursor: not-allowed;
            transform: none !important;
        }
        
        #clickArea {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 300px;
            height: 300px;
            border-radius: 50%;
            cursor: pointer;
            z-index: 5;
            pointer-events: all;
            background: transparent;
            border: none;
        }
        
        #clickCounter {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #fff;
            font-size: 48px;
            font-weight: bold;
            text-shadow: 0 0 40px rgba(167,139,250,0.5);
            z-index: 6;
            pointer-events: none;
            text-align: center;
            line-height: 1.2;
        }
        #clickCounter small {
            display: block;
            font-size: 16px;
            color: rgba(255,255,255,0.3);
            font-weight: normal;
        }
        
        .floating-text {
            position: absolute;
            pointer-events: none;
            font-size: 28px;
            font-weight: bold;
            color: #f7d44a;
            text-shadow: 0 0 20px rgba(247,212,74,0.5);
            z-index: 20;
            animation: floatUp 1.2s ease-out forwards;
        }
        
        @keyframes floatUp {
            0% {
                opacity: 1;
                transform: translateY(0) scale(1);
            }
            100% {
                opacity: 0;
                transform: translateY(-120px) scale(0.5);
            }
        }
        
        #controls-hint {
            position: absolute;
            bottom: 100px;
            left: 50%;
            transform: translateX(-50%);
            color: rgba(255,255,255,0.2);
            font-size: 12px;
            z-index: 10;
            background: rgba(0,0,0,0.4);
            padding: 6px 16px;
            border-radius: 20px;
            border: 1px solid rgba(255,255,255,0.05);
            pointer-events: none;
            white-space: nowrap;
        }
        #controls-hint kbd {
            background: rgba(255,255,255,0.06);
            padding: 1px 8px;
            border-radius: 3px;
            border: 1px solid rgba(255,255,255,0.05);
        }
        
        @media (max-width: 768px) {
            #ui { font-size: 14px; gap: 10px; top: 10px; }
            #ui span { padding: 6px 14px; }
            #clickArea { width: 200px; height: 200px; }
            #clickCounter { font-size: 32px; }
            #clickCounter small { font-size: 12px; }
            .shop-btn { padding: 8px 16px; font-size: 12px; min-width: 100px; }
            #controls-hint { font-size: 10px; bottom: 85px; padding: 4px 12px; }
        }
    </style>
</head>
<body>
    <div id="ui">
        <span>🌀 <span class="dimension" id="dimDisplay">4D</span></span>
        <span>💎 <span class="score" id="scoreDisplay">0</span></span>
        <span>👆 <span class="click-power" id="clickPowerDisplay">1</span></span>
        <span>⚡ <span class="auto-power" id="autoPowerDisplay">0</span>/с</span>
    </div>
    
    <div id="clickArea"></div>
    <div id="clickCounter">
        0
        <small>кликни по гиперкубу</small>
    </div>
    
    <div id="shop">
        <button class="shop-btn click-btn" id="clickUpgrade">
            💪 Улучшить клик
            <span class="level">Ур. <span id="clickLevel">1</span></span>
            <span class="cost">💎 <span id="clickCost">10</span></span>
        </button>
        <button class="shop-btn auto-btn" id="autoUpgrade">
            🤖 Авто-кликер
            <span class="level">Ур. <span id="autoLevel">0</span></span>
            <span class="cost">💎 <span id="autoCost">50</span></span>
        </button>
        <button class="shop-btn" id="resetBtn" style="border-color:#e94560; min-width:80px;">
            🔄 Сброс
        </button>
    </div>
    
    <div id="controls-hint">
        <kbd>Клик</kbd> по кубу &bull; <kbd>Q</kbd> 4D-эффект &bull; <kbd>R</kbd> сброс
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        (function(){
            // ---------- ИГРОВЫЕ ДАННЫЕ ----------
            let score = 0;
            let clickPower = 1;
            let autoPower = 0;
            let clickLevel = 1;
            let autoLevel = 0;
            
            let clickCost = 10;
            let autoCost = 50;
            
            let fourDEffect = true;
            let time = 0;
            let totalClicks = 0;
            
            // DOM элементы
            const scoreDisplay = document.getElementById('scoreDisplay');
            const clickPowerDisplay = document.getElementById('clickPowerDisplay');
            const autoPowerDisplay = document.getElementById('autoPowerDisplay');
            const clickCounter = document.getElementById('clickCounter');
            const dimDisplay = document.getElementById('dimDisplay');
            const clickLevelDisplay = document.getElementById('clickLevel');
            const autoLevelDisplay = document.getElementById('autoLevel');
            const clickCostDisplay = document.getElementById('clickCost');
            const autoCostDisplay = document.getElementById('autoCost');
            const clickUpgradeBtn = document.getElementById('clickUpgrade');
            const autoUpgradeBtn = document.getElementById('autoUpgrade');
            const resetBtn = document.getElementById('resetBtn');
            const clickArea = document.getElementById('clickArea');

            // ---------- THREE.JS СЦЕНА ----------
            const scene = new THREE.Scene();
            scene.background = new THREE.Color(0x0a0a1a);
            
            const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 30);
            camera.position.set(0, 1.5, 5);
            camera.lookAt(0, 0, 0);

            const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            document.body.prepend(renderer.domElement);

            // ---------- СВЕТ ----------
            const ambientLight = new THREE.AmbientLight(0x222244, 0.6);
            scene.add(ambientLight);

            const mainLight = new THREE.DirectionalLight(0xffffff, 1.0);
            mainLight.position.set(5, 8, 5);
            mainLight.castShadow = true;
            scene.add(mainLight);

            const fillLight = new THREE.DirectionalLight(0x4ecdc4, 0.3);
            fillLight.position.set(-3, 2, -3);
            scene.add(fillLight);

            const rimLight = new THREE.DirectionalLight(0xa29bfe, 0.4);
            rimLight.position.set(0, -2, -4);
            scene.add(rimLight);

            // ---------- ГИПЕРКУБ (ТЕССЕРАКТ) ----------
            function createTesseract() {
                const group = new THREE.Group();
                
                // Вершины 4D куба
                const verts4D = [];
                for (let i = 0; i < 16; i++) {
                    const v = [];
                    for (let j = 0; j < 4; j++) {
                        v.push((i >> j) & 1 ? 1 : -1);
                    }
                    verts4D.push(v);
                }

                // Рёбра
                const edges = [];
                for (let i = 0; i < 16; i++) {
                    for (let j = i+1; j < 16; j++) {
                        let diff = 0;
                        for (let k = 0; k < 4; k++) {
                            if (verts4D[i][k] !== verts4D[j][k]) diff++;
                        }
                        if (diff === 1) edges.push([i, j]);
                    }
                }

                // Линии
                const lineMat = new THREE.LineBasicMaterial({
                    color: 0x4ecdc4,
                    transparent: true,
                    opacity: 0.4
                });
                const positions = [];
                for (const edge of edges) {
                    const v1 = verts4D[edge[0]];
                    const v2 = verts4D[edge[1]];
                    const p1 = project4D(v1[0], v1[1], v1[2], v1[3]);
                    const p2 = project4D(v2[0], v2[1], v2[2], v2[3]);
                    positions.push(p1.x, p1.y, p1.z);
                    positions.push(p2.x, p2.y, p2.z);
                }
                const geo = new THREE.BufferGeometry();
                geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
                const lines = new THREE.LineSegments(geo, lineMat);
                group.add(lines);

                // Точки
                const dotMat = new THREE.PointsMaterial({
                    color: 0xa29bfe,
                    size: 0.08,
                    transparent: true,
                    opacity: 0.8
                });
                const dotPositions = [];
                for (const v of verts4D) {
                    const p = project4D(v[0], v[1], v[2], v[3]);
                    dotPositions.push(p.x, p.y, p.z);
                }
                const dotGeo = new THREE.BufferGeometry();
                dotGeo.setAttribute('position', new THREE.Float32BufferAttribute(dotPositions, 3));
                const dots = new THREE.Points(dotGeo, dotMat);
                group.add(dots);

                // Внутренняя сфера (ядро)
                const coreGeo = new THREE.SphereGeometry(0.6, 32, 32);
                const coreMat = new THREE.MeshStandardMaterial({
                    color: 0x6c5ce7,
                    emissive: 0x6c5ce7,
                    emissiveIntensity: 0.3,
                    metalness: 0.4,
                    roughness: 0.2,
                    transparent: true,
                    opacity: 0.6
                });
                const core = new THREE.Mesh(coreGeo, coreMat);
                core.castShadow = true;
                group.add(core);

                // Внешняя оболочка (парящие частицы)
                const shellParticles = 200;
                const shellGeo = new THREE.BufferGeometry();
                const shellPos = new Float32Array(shellParticles * 3);
                for (let i = 0; i < shellParticles; i++) {
                    const theta = Math.random() * Math.PI * 2;
                    const phi = Math.acos(2 * Math.random() - 1);
                    const r = 0.8 + Math.random() * 0.6;
                    shellPos[i*3] = r * Math.sin(phi) * Math.cos(theta);
                    shellPos[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
                    shellPos[i*3+2] = r * Math.cos(phi);
                }
                shellGeo.setAttribute('position', new THREE.BufferAttribute(shellPos, 3));
                const shellMat = new THREE.PointsMaterial({
                    color: 0xa29bfe,
                    size: 0.03,
                    transparent: true,
                    opacity: 0.5,
                    blending: THREE.AdditiveBlending
                });
                const shell = new THREE.Points(shellGeo, shellMat);
                group.add(shell);

                return group;
            }

            function project4D(x, y, z, w) {
                const angle = time * 0.3;
                const cosA = Math.cos(angle);
                const sinA = Math.sin(angle);
                
                const x1 = x * cosA - w * sinA;
                const w1 = x * sinA + w * cosA;
                const y1 = y * cosA - w1 * sinA * 0.5;
                const w2 = y * sinA * 0.5 + w1 * cosA;
                
                const scale = 1.0 / (1.0 + w2 * 0.15);
                return {
                    x: x1 * scale * 1.5,
                    y: y1 * scale * 1.5,
                    z: z * scale * 1.5
                };
            }

            const tesseract = createTesseract();
            tesseract.position.set(0, 0, 0);
            scene.add(tesseract);

            // ---------- ЧАСТИЦЫ ----------
            const particleCount = 300;
            const particleGeo = new THREE.BufferGeometry();
            const particlePos = new Float32Array(particleCount * 3);
            const particleColors = new Float32Array(particleCount * 3);
            const particleSizes = new Float32Array(particleCount);
            
            for (let i = 0; i < particleCount; i++) {
                const theta = Math.random() * Math.PI * 2;
                const phi = Math.acos(2 * Math.random() - 1);
                const r = 3 + Math.random() * 5;
                particlePos[i*3] = r * Math.sin(phi) * Math.cos(theta);
                particlePos[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
                particlePos[i*3+2] = r * Math.cos(phi);
                const c = new THREE.Color().setHSL(0.65 + Math.random() * 0.2, 0.7, 0.5 + Math.random() * 0.3);
                particleColors[i*3] = c.r;
                particleColors[i*3+1] = c.g;
                particleColors[i*3+2] = c.b;
                particleSizes[i] = 0.02 + Math.random() * 0.04;
            }
            
            particleGeo.setAttribute('position', new THREE.BufferAttribute(particlePos, 3));
            particleGeo.setAttribute('color', new THREE.BufferAttribute(particleColors, 3));
            particleGeo.setAttribute('size', new THREE.BufferAttribute(particleSizes, 1));
            
            const particleMat = new THREE.PointsMaterial({
                size: 0.05,
                vertexColors: true,
                transparent: true,
                opacity: 0.4,
                blending: THREE.AdditiveBlending,
                depthWrite: false
            });
            const particles = new THREE.Points(particleGeo, particleMat);
            scene.add(particles);

            // ---------- ОБНОВЛЕНИЕ UI ----------
            function updateUI() {
                scoreDisplay.textContent = Math.floor(score);
                clickPowerDisplay.textContent = clickPower;
                autoPowerDisplay.textContent = autoPower;
                clickCounter.innerHTML = Math.floor(score) + '<small>кликни по гиперкубу</small>';
                clickLevelDisplay.textContent = clickLevel;
                autoLevelDisplay.textContent = autoLevel;
                clickCostDisplay.textContent = clickCost;
                autoCostDisplay.textContent = autoCost;
                
                clickUpgradeBtn.disabled = score < clickCost;
                autoUpgradeBtn.disabled = score < autoCost;
            }

            // ---------- ФЛОАТИНГ ТЕКСТ ----------
            function showFloatingText(x, y, text, color = '#f7d44a') {
                const el = document.createElement('div');
                el.className = 'floating-text';
                el.textContent = text;
                el.style.left = (x - 20) + 'px';
                el.style.top = (y - 20) + 'px';
                el.style.color = color;
                document.body.appendChild(el);
                setTimeout(() => el.remove(), 1200);
            }

            // ---------- КЛИК ----------
            function handleClick(e) {
                if (score === 0 && totalClicks === 0) {
                    // Первый клик — небольшое обучение
                }
                
                const addScore = clickPower;
                score += addScore;
                totalClicks++;
                
                // Эффект пульсации гиперкуба
                tesseract.scale.setScalar(1 + 0.05 * Math.sin(time * 20));
                
                // Показываем всплывающий текст
                const rect = clickArea.getBoundingClientRect();
                const cx = rect.left + rect.width / 2;
                const cy = rect.top + rect.height / 2;
                const offsetX = (Math.random() - 0.5) * 80;
                const offsetY = (Math.random() - 0.5) * 80;
                showFloatingText(cx + offsetX, cy + offsetY, '+' + addScore);
                
                // Иногда показываем дополнительный эффект
                if (Math.random() < 0.1) {
                    showFloatingText(cx + (Math.random() - 0.5) * 150, cy + (Math.random() - 0.5) * 150, '💎', '#a29bfe');
                }
                
                updateUI();
            }

            // ---------- АВТО-КЛИК ----------
            let autoInterval = null;
            
            function startAutoClicker() {
                if (autoInterval) clearInterval(autoInterval);
                if (autoPower > 0) {
                    autoInterval = setInterval(() => {
                        if (autoPower > 0) {
                            score += autoPower;
                            updateUI();
                        }
                    }, 1000);
                }
            }

            // ---------- УЛУЧШЕНИЯ ----------
            function buyClickUpgrade() {
                if (score < clickCost) return;
                score -= clickCost;
                clickLevel++;
                clickPower = Math.floor(clickPower * 1.5) + 1;
                clickCost = Math.floor(clickCost * 1.8);
                updateUI();
                
                // Визуальный эффект
                tesseract.scale.setScalar(1.2);
                setTimeout(() => tesseract.scale.setScalar(1), 300);
            }

            function buyAutoUpgrade() {
                if (score < autoCost) return;
                score -= autoCost;
                autoLevel++;
                autoPower = Math.floor(autoPower * 1.5) + 1;
                autoCost = Math.floor(autoCost * 1.7);
                startAutoClicker();
                updateUI();
                
                // Визуальный эффект
                const rect = autoUpgradeBtn.getBoundingClientRect();
                showFloatingText(rect.left + rect.width/2, rect.top - 10, '⚡', '#4ecdc4');
            }

            // ---------- СБРОС ----------
            function resetGame() {
                if (score < 100 && totalClicks < 10) {
                    // Если почти ничего не накоплено, сбрасываем без подтверждения
                } else {
                    if (!confirm('Сбросить прогресс? Вы потеряете все очки и улучшения.')) return;
                }
                
                score = 0;
                clickPower = 1;
                autoPower = 0;
                clickLevel = 1;
                autoLevel = 0;
                clickCost = 10;
                autoCost = 50;
                totalClicks = 0;
                if (autoInterval) clearInterval(autoInterval);
                updateUI();
                tesseract.scale.setScalar(1);
            }

            // ---------- ПЕРЕКЛЮЧЕНИЕ 4D ----------
            function toggle4D() {
                fourDEffect = !fourDEffect;
                dimDisplay.textContent = fourDEffect ? '4D' : '3D';
                dimDisplay.style.color = fourDEffect ? '#a29bfe' : '#4ecdc4';
            }

            // ---------- ОБНОВЛЕНИЕ СЦЕНЫ ----------
            function updateScene() {
                time += 0.01;
                
                if (fourDEffect) {
                    tesseract.rotation.x = time * 0.08;
                    tesseract.rotation.y = time * 0.12;
                    tesseract.rotation.z = time * 0.04;
                    // Пульсация ядра
                    const core = tesseract.children[2];
                    if (core && core.material) {
                        core.material.emissiveIntensity = 0.3 + 0.2 * Math.sin(time * 2);
                    }
                }
                
                particles.rotation.y += 0.0003;
                particles.rotation.x += 0.0001;
                
                // Камера слегка покачивается
                camera.position.x = Math.sin(time * 0.05) * 0.3;
                camera.position.y = 1.5 + Math.sin(time * 0.07) * 0.2;
                camera.lookAt(0, 0, 0);
            }

            // ---------- АНИМАЦИЯ ----------
            function animate() {
                requestAnimationFrame(animate);
                updateScene();
                renderer.render(scene, camera);
            }

            // ---------- СОБЫТИЯ ----------
            // Клик по области
            clickArea.addEventListener('click', handleClick);
            clickArea.addEventListener('touchstart', (e) => {
                e.preventDefault();
                const touch = e.touches[0];
                const rect = clickArea.getBoundingClientRect();
                const x = touch.clientX;
                const y = touch.clientY;
                if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
                    handleClick(e);
                }
            });

            // Кнопки улучшений
            clickUpgradeBtn.addEventListener('click', buyClickUpgrade);
            autoUpgradeBtn.addEventListener('click', buyAutoUpgrade);
            resetBtn.addEventListener('click', resetGame);

            // Клавиши
            document.addEventListener('keydown', (e) => {
                if (e.key === 'q' || e.key === 'Q') {
                    toggle4D();
                    e.preventDefault();
                }
                if (e.key === 'r' || e.key === 'R') {
                    resetGame();
                    e.preventDefault();
                }
                if (e.key === ' ' || e.key === 'Space') {
                    e.preventDefault();
                    handleClick(e);
                }
                if (e.key === '1') {
                    buyClickUpgrade();
                    e.preventDefault();
                }
                if (e.key === '2') {
                    buyAutoUpgrade();
                    e.preventDefault();
                }
            });

            // Ресайз
            window.addEventListener('resize', () => {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            });

            // ---------- СТАРТ ----------
            updateUI();
            animate();
            
            console.log('🌀 4D Кликер загружен!');
            console.log('Клик по кубу — зарабатывай очки!');
            console.log('Управление:');
            console.log('  Клик/Пробел — клик');
            console.log('  Q — переключение 4D/3D');
            console.log('  R — сброс');
            console.log('  1 — улучшить клик');
            console.log('  2 — купить авто-кликер');

            // ---------- АДАПТИВНОСТЬ КЛИК-ОБЛАСТИ ----------
            function updateClickAreaSize() {
                const isMobile = window.innerWidth < 768;
                const size = isMobile ? '200px' : '300px';
                clickArea.style.width = size;
                clickArea.style.height = size;
            }
            updateClickAreaSize();
            window.addEventListener('resize', updateClickAreaSize);

        })();
    </script>
</body>
</html>

Game Source: 4D Кликер

Creator: LuckyGlider65

Libraries: none

Complexity: complex (692 lines, 27.2 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 4d-luckyglider65" to link back to the original. Then publish at arcadelab.ai/publish.