4D Кликер
by LuckyGlider65692 lines27.2 KB
<!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> по кубу • <kbd>Q</kbd> 4D-эффект • <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.