<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>3D Космический сборщик кристаллов</title>
<style>
body { margin: 0; overflow: hidden; font-family: 'Arial', sans-serif; }
#score {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0,0,0,0.75);
color: gold;
padding: 12px 24px;
border-radius: 40px;
font-size: 28px;
font-weight: bold;
font-family: monospace;
backdrop-filter: blur(5px);
z-index: 10;
pointer-events: none;
letter-spacing: 2px;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
}
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0,0,0,0.6);
color: #ccc;
padding: 10px 18px;
border-radius: 20px;
font-size: 14px;
font-family: monospace;
backdrop-filter: blur(4px);
z-index: 10;
pointer-events: none;
}
#win-message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.9);
color: gold;
padding: 30px 50px;
border-radius: 30px;
text-align: center;
font-size: 32px;
font-weight: bold;
backdrop-filter: blur(10px);
z-index: 20;
display: none;
border: 2px solid gold;
white-space: nowrap;
font-family: monospace;
}
#restart {
margin-top: 20px;
background: #4CAF50;
border: none;
padding: 12px 30px;
font-size: 20px;
border-radius: 50px;
cursor: pointer;
font-weight: bold;
color: white;
transition: 0.2s;
}
#restart:hover { background: #45a049; transform: scale(1.05); }
@media
(max-width: 600px) {
#score { font-size: 18px; padding: 8px 16px; top: 10px; right: 10px; }
#controls { font-size: 10px; bottom: 10px; left: 10px; }
#win-message { font-size: 20px; padding: 20px 30px; white-space: normal; width: 80%; }
}
</style>
</head>
<body>
<div id="score">💎 0 / 24</div>
<div id="controls">🎮 WASD или стрелки — двигать корабль | Собирай кристаллы</div>
<div id="win-message">
✨ ПОБЕДА! ✨<br>
<span id="final-score"></span><br>
<button id="restart">🔄 Играть снова</button>
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
// --- СЦЕНА, КАМЕРА, РЕНДЕР ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x050b2a);
scene.fog = new THREE.FogExp2(0x050b2a, 0.008);
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 6, 12);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// --- ОСВЕЩЕНИЕ ---
// Амбиент
const ambient = new THREE.AmbientLight(0x404080);
scene.add(ambient);
// Основной направленный свет
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(5, 12, 8);
dirLight.castShadow = true;
dirLight.receiveShadow = true;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
dirLight.shadow.camera.near = 0.5;
dirLight.shadow.camera.far = 25;
dirLight.shadow.camera.left = -10;
dirLight.shadow.camera.right = 10;
dirLight.shadow.camera.top = 10;
dirLight.shadow.camera.bottom = -10;
scene.add(dirLight);
// Заполняющий снизу
const fillLight = new THREE.PointLight(0x88aaff, 0.4);
fillLight.position.set(0, -3, 0);
scene.add(fillLight);
// Динамический свет на игроке
const playerGlow = new THREE.PointLight(0xffaa66, 0.9, 15);
playerGlow.castShadow = false;
scene.add(playerGlow);
// --- ЗВЁЗДЫ ---
const starGeo = new THREE.BufferGeometry();
const starCount = 2000;
const starPos = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
starPos[i*3] = (Math.random() - 0.5) * 400;
starPos[i*3+1] = (Math.random() - 0.5) * 150;
starPos[i*3+2] = (Math.random() - 0.5) * 150 - 70;
}
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
const stars = new THREE.Points(starGeo, new THREE.PointsMaterial({ color: 0xffffff, size: 0.2, transparent: true, opacity: 0.7 }));
scene.add(stars);
// Декоративная сетка пола
const grid = new THREE.GridHelper(40, 20, 0x88aaff, 0x3366aa);
grid.position.y = -2.8;
grid.material.transparent = true;
grid.material.opacity = 0.4;
scene.add(grid);
// Красивый светящийся круг под игроком
const ringFloor = new THREE.Mesh(
new THREE.RingGeometry(1.2, 1.8, 32),
new THREE.MeshStandardMaterial({ color: 0x44aaff, emissive: 0x2266aa, transparent: true, opacity: 0.5, side: THREE.DoubleSide })
);
ringFloor.rotation.x = -Math.PI / 2;
ringFloor.position.y = -2.7;
scene.add(ringFloor);
// --- МОДЕЛЬ ИГРОКА (улучшенный корабль) ---
const ship = new THREE.Group();
// Корпус - обтекаемый
const bodyGeo = new THREE.CylinderGeometry(0.65, 0.85, 1.3, 24);
const bodyMat = new THREE.MeshStandardMaterial({ color: 0x3a86ff, metalness: 0.85, roughness: 0.25, emissive: 0x001a44 });
const bodyMesh = new THREE.Mesh(bodyGeo, bodyMat);
bodyMesh.castShadow = true;
bodyMesh.receiveShadow = true;
bodyMesh.position.y = 0;
ship.add(bodyMesh);
// Нос (конус)
const noseGeo = new THREE.ConeGeometry(0.65, 1.0, 24);
const noseMat = new THREE.MeshStandardMaterial({ color: 0xffaa55, metalness: 0.9, emissive: 0x442200 });
const noseMesh = new THREE.Mesh(noseGeo, noseMat);
noseMesh.position.z = 0.85;
noseMesh.position.y = 0.1;
noseMesh.castShadow = true;
ship.add(noseMesh);
// Крылья
const wingGeo = new THREE.BoxGeometry(1.5, 0.12, 0.6);
const wingMat = new THREE.MeshStandardMaterial({ color: 0x2a6eff, metalness: 0.7 });
const leftWing = new THREE.Mesh(wingGeo, wingMat);
leftWing.position.set(-0.9, -0.15, 0.2);
leftWing.castShadow = true;
const rightWing = new THREE.Mesh(wingGeo, wingMat);
rightWing.position.set(0.9, -0.15, 0.2);
rightWing.castShadow = true;
ship.add(leftWing);
ship.add(rightWing);
// Хвостовые стабилизаторы
const tailGeo = new THREE.BoxGeometry(0.4, 0.5, 0.3);
const tailMat = new THREE.MeshStandardMaterial({ color: 0xff8844 });
const tail = new THREE.Mesh(tailGeo, tailMat);
tail.position.set(0, 0.3, -0.65);
tail.castShadow = true;
ship.add(tail);
// Свечение вокруг корабля (эффект двигателя)
const engineGlow = new THREE.PointLight(0xff6600, 0.8, 8);
engineGlow.position.set(0, -0.1, -0.9);
ship.add(engineGlow);
// Партиклы из двигателя (маленькие сферы)
const engineExhaust = new THREE.Mesh(
new THREE.SphereGeometry(0.2, 8, 8),
new THREE.MeshStandardMaterial({ color: 0xff8844, emissive: 0xff4400, transparent: true, opacity: 0.7 })
);
engineExhaust.position.set(0, -0.1, -1.0);
ship.add(engineExhaust);
ship.position.y = -1.6;
scene.add(ship);
let shipPos = { x: 0, z: 0 };
const speed = 6.2;
const keys = { w: false, s: false, a: false, d: false, ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false };
// --- КРИСТАЛЛЫ (улучшенные модели) ---
class Crystal {
constructor(x, z) {
this.group = new THREE.Group();
// Основной кристалл - октаэдр с блеском
const geom = new THREE.OctahedronGeometry(0.65);
const material = new THREE.MeshStandardMaterial({ color: 0xff44cc, metalness: 0.9, roughness: 0.2, emissive: 0x551133 });
this.core = new THREE.Mesh(geom, material);
this.core.castShadow = true;
this.group.add(this.core);
// Внутреннее свечение
const innerGlow = new THREE.Mesh(
new THREE.SphereGeometry(0.45, 16, 16),
new THREE.MeshBasicMaterial({ color: 0xff88dd, transparent: true, opacity: 0.5 })
);
this.group.add(innerGlow);
// Вращающиеся кольца
const ringGeo = new THREE.TorusGeometry(0.85, 0.06, 24, 60);
const ringMat = new THREE.MeshStandardMaterial({ color: 0xffaa77, emissive: 0x663300 });
this.ring = new THREE.Mesh(ringGeo, ringMat);
this.ring.rotation.x = Math.PI / 2;
this.group.add(this.ring);
// Второе кольцо под углом
const ring2Geo = new THREE.TorusGeometry(0.75, 0.05, 24, 60);
const ring2Mat = new THREE.MeshStandardMaterial({ color: 0xff88aa, emissive: 0x441133 });
this.ring2 = new THREE.Mesh(ring2Geo, ring2Mat);
this.ring2.rotation.z = Math.PI / 3;
this.group.add(this.ring2);
this.group.position.set(x, -1.8, z);
scene.add(this.group);
this.spinSpeed = 0.015 + Math.random() * 0.02;
this.floatOffset = Math.random() * Math.PI * 2;
}
update(time) {
this.group.rotation.y += this.spinSpeed;
this.group.rotation.x = Math.sin(time * 2 + this.floatOffset) * 0.2;
this.ring.rotation.z += 0.03;
this.ring2.rotation.x += 0.02;
this.ring2.rotation.y += 0.01;
}
}
let crystals = [];
let score = 0;
const totalCrystals = 24;
const scoreDiv = document.getElementById('score');
const winDiv = document.getElementById('win-message');
const finalSpan = document.getElementById('final-score');
function spawnCrystals() {
crystals.forEach(c => scene.remove(c.group));
crystals = [];
for (let i = 0; i < totalCrystals; i++) {
let x, z, ok;
do {
x = (Math.random() - 0.5) * 26;
z = (Math.random() - 0.5) * 22;
ok = Math.abs(x) < 2.8 && Math.abs(z) < 2.8 ? false : true;
} while (!ok);
crystals.push(new Crystal(x, z));
}
}
function updateScoreUI() {
scoreDiv.innerHTML = `💎 ${score} / ${totalCrystals}`;
if (score === totalCrystals) {
finalSpan.innerHTML = `Ты собрал все ${totalCrystals} кристаллов! ✨`;
winDiv.style.display = 'block';
}
}
// Эффект сбора (вспышка + частицы)
function createCollectEffect(pos) {
const particleCount = 30;
const particles = [];
for (let i = 0; i < particleCount; i++) {
const particle = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 6, 6),
new THREE.MeshStandardMaterial({ color: 0xffaa66, emissive: 0xff6600 })
);
particle.position.copy(pos);
particle.userData = {
vel: new THREE.Vector3(
(Math.random() - 0.5) * 3,
Math.random() * 2.5,
(Math.random() - 0.5) * 3
),
life: 1.0
};
scene.add(particle);
particles.push(particle);
}
function animateParts() {
let alive = false;
particles.forEach(p => {
if (p.userData.life > 0) {
alive = true;
p.userData.life -= 0.05;
p.material.opacity = p.userData.life;
p.material.transparent = true;
p.position.x += p.userData.vel.x * 0.12;
p.position.y += p.userData.vel.y * 0.12;
p.position.z += p.userData.vel.z * 0.12;
p.userData.vel.y -= 0.1;
p.scale.setScalar(p.userData.life);
} else {
scene.remove(p);
}
});
if (alive) requestAnimationFrame(animateParts);
}
animateParts();
}
// Проверка столкновений
function checkCollisions() {
const shipWorld = ship.position;
for (let i = 0; i < crystals.length; i++) {
const c = crystals[i];
const dx = shipWorld.x - c.group.position.x;
const dz = shipWorld.z - c.group.position.z;
const dist = Math.sqrt(dx*dx + dz*dz);
if (dist < 1.2) {
createCollectEffect(c.group.position);
scene.remove(c.group);
crystals.splice(i,1);
score++;
updateScoreUI();
i--;
}
}
}
// --- УПРАВЛЕНИЕ (ИСПРАВЛЕНО!) ---
window.addEventListener('keydown', (e) => {
const key = e.key;
if (keys.hasOwnProperty(key)) {
keys[key] = true;
e.preventDefault();
}
});
window.addEventListener('keyup', (e) => {
const key = e.key;
if (keys.hasOwnProperty(key)) keys[key] = false;
});
let lastTime = performance.now();
function updateMovement(delta) {
let moveX = 0, moveZ = 0;
if (keys.w || keys.ArrowUp) moveZ -= 1;
if (keys.s || keys.ArrowDown) moveZ += 1;
if (keys.a || keys.ArrowLeft) moveX -= 1;
if (keys.d || keys.ArrowRight) moveX += 1;
if (moveX !== 0 || moveZ !== 0) {
const len = Math.hypot(moveX, moveZ);
moveX /= len;
moveZ /= len;
}
let newX = ship.position.x + moveX * speed * delta;
let newZ = ship.position.z + moveZ * speed * delta;
// Границы арены
newX = Math.min(14, Math.max(-14, newX));
newZ = Math.min(12, Math.max(-12, newZ));
ship.position.x = newX;
ship.position.z = newZ;
// Поворот корабля
if (moveX !== 0 || moveZ !== 0) {
const angle = Math.atan2(moveX, moveZ);
ship.rotation.y = angle;
}
// Наклоны и динамика
ship.rotation.z = moveX * 0.35;
ship.rotation.x = -moveZ * 0.25;
// Эффект двигателя (пульсация)
const intensity = (Math.abs(moveX) + Math.abs(moveZ)) * 0.8 + 0.3;
engineGlow.intensity = 0.6 + intensity * 0.8;
// Световой шар следует за игроком
playerGlow.position.set(ship.position.x, -1, ship.position.z);
// Камера плавно следует
const targetCam = {
x: ship.position.x * 0.4,
y: 6 + Math.abs(ship.position.x) * 0.08,
z: ship.position.z + 7
};
camera.position.x += (targetCam.x - camera.position.x) * 0.08;
camera.position.y += (targetCam.y - camera.position.y) * 0.08;
camera.position.z += (targetCam.z - camera.position.z) * 0.08;
camera.lookAt(ship.position);
// Кольцо под игроком
ringFloor.position.x = ship.position.x;
ringFloor.position.z = ship.position.z;
}
// --- АНИМАЦИИ ---
let time = 0;
function animateStarsAndEffects() {
time += 0.016;
stars.rotation.y += 0.0008;
stars.rotation.x += 0.0004;
// Мерцание выхлопа
const exhaustMesh = ship.children.find(c => c.geometry && c.position.z === -1.0);
if (exhaustMesh) {
exhaustMesh.scale.setScalar(0.8 + Math.sin(Date.now() * 0.02) * 0.3);
}
}
// --- ПЕРЕЗАПУСК ---
document.getElementById('restart').addEventListener('click', () => {
// очистить старые кристаллы
crystals.forEach(c => scene.remove(c.group));
crystals = [];
score = 0;
ship.position.set(0, -1.6, 0);
camera.position.set(0, 6, 12);
ship.rotation.set(0,0,0);
spawnCrystals();
updateScoreUI();
winDiv.style.display = 'none';
});
// --- ЗАПУСК ---
spawnCrystals();
updateScoreUI();
function gameLoop() {
const now = performance.now();
let delta = Math.min(0.033, (now - lastTime) / 1000);
lastTime = now;
updateMovement(delta);
checkCollisions();
crystals.forEach(c => c.update(time));
animateStarsAndEffects();
renderer.render(scene, camera);
requestAnimationFrame(gameLoop);
}
gameLoop();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
console.log('✅ Игра запущена! Управление WASD / стрелки работает');
</script>
</body>
</html>










0 comments