量子纠缠空间游戏
来自 Wikiprompt,自由的提示词百科全书
量子纠缠空间游戏 A prompt to build a single-file Three.js game where players match 3D particle clusters by spatial orientation, with physics-based fusion effects.
提示词内容收藏
🌐
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quantum Entanglement</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background: #f5f7fa;
}
#info {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 40px;
padding: 12px 28px;
color: #1a2b3c;
font-weight: 400;
letter-spacing: 0.3px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.06);
border: 1px solid rgba(255, 255, 255, 0.9);
z-index: 10;
pointer-events: none;
text-align: center;
font-size: 0.95rem;
backdrop-filter: blur(8px);
white-space: nowrap;
}
#info span {
font-weight: 600;
color: #0f1c2b;
}
#status {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-radius: 60px;
padding: 10px 24px;
color: #2c3e50;
font-size: 0.9rem;
border: 1px solid rgba(255, 255, 255, 0.8);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
z-index: 10;
pointer-events: none;
letter-spacing: 0.2px;
backdrop-filter: blur(8px);
}
#status strong {
font-weight: 600;
color: #1a2b3c;
}
canvas {
display: block;
}
.instruction {
position: absolute;
bottom: 90px;
left: 50%;
transform: translateX(-50%);
color: #5a6b7b;
font-size: 0.8rem;
background: rgba(255,255,255,0.4);
padding: 6px 16px;
border-radius: 40px;
backdrop-filter: blur(4px);
border: 1px solid rgba(255,255,255,0.5);
pointer-events: none;
white-space: nowrap;
letter-spacing: 0.2px;
}
</style>
</head>
<body>
<div id="info">🌀 <span>QUANTUM ENTANGLEMENT</span> , unfold two identical clusters</div>
<div id="status">⚡ <strong>0</strong> / 2 matched</div>
<div class="instruction">drag to rotate · click to unfold</div>
<!-- three.js from CDN -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.128.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.128.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
// --- setup scene, camera, renderers ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf5f7fa); // pure white void with subtle tint
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(8, 6, 12);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = false;
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
// CSS2DRenderer for subtle labels (optional, but we use for clarity)
const labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.left = '0px';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
// --- controls (for rotation via drag) ---
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.autoRotate = false;
controls.enableZoom = true;
controls.rotateSpeed = 0.8;
controls.target.set(0, 0, 0);
controls.maxPolarAngle = Math.PI / 2;
// --- lighting for frosted glass / transmission effect ---
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(5, 10, 7);
scene.add(dirLight);
const backLight = new THREE.DirectionalLight(0xe0f0ff, 0.8);
backLight.position.set(-5, 0, -5);
scene.add(backLight);
// subtle fill
const fillLight = new THREE.PointLight(0xffffff, 0.3);
fillLight.position.set(0, 5, 5);
scene.add(fillLight);
// --- helper: create frosted glass material (transmission-like) ---
function createFrostedMaterial(color = 0xffffff, opacity = 0.35) {
return new THREE.MeshPhysicalMaterial({
color: color,
metalness: 0.1,
roughness: 0.25,
transmission: 0.9,
thickness: 0.5,
transparent: true,
opacity: opacity,
clearcoat: 0.1,
clearcoatRoughness: 0.4,
envMapIntensity: 1.0,
specularIntensity: 0.3,
side: THREE.DoubleSide
});
}
// --- data: define 6 clusters with unique geometric patterns (some identical) ---
// each cluster: geometry type, size, color, and a signature (orientation + topology)
// we will use quaternion orientation stored as euler for simplicity, but we compare using quaternion dot.
// For matching, we need identical topology AND orientation (within tolerance)
// We'll create 6 clusters: 3 pairs (identical topology+orientation)
// Each cluster has a "signature" = geometry type + rotation (euler) + scale (uniform)
// To make it interesting, we use different geometries: Icosahedron, Octahedron, TorusKnot, etc.
// But we need pairs: so we duplicate geometry+rotation+scale.
// Define cluster prototypes
const prototypes = [
{ geo: 'icosa', scale: 1.0, color: 0xc0e0ff, euler: [0.5, 0.2, 0.1] }, // pair A
{ geo: 'octa', scale: 0.9, color: 0xffd9b0, euler: [0.1, 0.8, 0.3] }, // pair B
{ geo: 'torus', scale: 0.8, color: 0xd0f0c0, euler: [0.3, 0.4, 0.9] } // pair C
];
// Create 6 clusters: two of each prototype
const clusters = [];
const clusterGroup = new THREE.Group();
scene.add(clusterGroup);
// positions arranged in a loose 3D grid
const positions = [
[-2.5, 1.2, -1.5],
[2.5, 1.0, 1.8],
[-2.0, -1.5, 2.2],
[2.2, -1.2, -2.0],
[0.5, 2.0, -2.5],
[-0.8, -2.0, -0.5]
];
// store cluster data for interaction
const clusterData = [];
// helper to create geometry based on type
function makeGeometry(type, scale) {
let geo;
switch(type) {
case 'icosa':
geo = new THREE.IcosahedronGeometry(1.0, 0);
break;
case 'octa':
geo = new THREE.OctahedronGeometry(1.0, 0);
break;
case 'torus':
geo = new THREE.TorusKnotGeometry(0.7, 0.25, 64, 8, 2, 3);
break;
default:
geo = new THREE.IcosahedronGeometry(1.0, 0);
}
return geo;
}
// create clusters
prototypes.forEach((proto, idx) => {
for (let copy = 0; copy < 2; copy++) {
const pos = positions[clusterData.length];
const geo = makeGeometry(proto.geo, proto.scale);
const mat = createFrostedMaterial(proto.color, 0.4);
const mesh = new THREE.Mesh(geo, mat);
mesh.scale.set(proto.scale, proto.scale, proto.scale);
mesh.position.set(pos[0], pos[1], pos[2]);
// set rotation from euler
const euler = new THREE.Euler(proto.euler[0], proto.euler[1], proto.euler[2], 'XYZ');
mesh.quaternion.setFromEuler(euler);
// store signature: we use quaternion + geometry type + scale as signature
const signature = {
geoType: proto.geo,
scale: proto.scale,
quat: mesh.quaternion.clone(),
color: proto.color
};
// add to scene
clusterGroup.add(mesh);
// store data
clusterData.push({
mesh: mesh,
signature: signature,
unfolded: false,
matched: false,
originalPos: mesh.position.clone(),
originalQuat: mesh.quaternion.clone(),
// for animation
velocity: new THREE.Vector3(),
isImploding: false,
implodeTarget: null
});
}
});
// add some floating particles for atmosphere (volumetric light trails effect)
const particleGeo = new THREE.BufferGeometry();
const particleCount = 400;
const particlePos = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount * 3; i += 3) {
particlePos[i] = (Math.random() - 0.5) * 20;
particlePos[i+1] = (Math.random() - 0.5) * 15;
particlePos[i+2] = (Math.random() - 0.5) * 20;
}
particleGeo.setAttribute('position', new THREE.BufferAttribute(particlePos, 3));
const particleMat = new THREE.PointsMaterial({
color: 0x88aaff,
size: 0.08,
transparent: true,
opacity: 0.25,
blending: THREE.AdditiveBlending
});
const particles = new THREE.Points(particleGeo, particleMat);
scene.add(particles);
// light trails (simple lines)
const trailMat = new THREE.LineBasicMaterial({ color: 0x88aaff, opacity: 0.15, transparent: true });
for (let i = 0; i < 12; i++) {
const points = [];
const start = new THREE.Vector3((Math.random()-0.5)*8, (Math.random()-0.5)*6, (Math.random()-0.5)*8);
const end = new THREE.Vector3((Math.random()-0.5)*8, (Math.random()-0.5)*6, (Math.random()-0.5)*8);
points.push(start, end);
const lineGeo = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(lineGeo, trailMat);
scene.add(line);
}
// --- interaction state ---
let selectedCluster = null; // currently unfolded cluster (first)
let matchCount = 0;
const statusEl = document.querySelector('#status strong');
// raycasting
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// click handler
renderer.domElement.addEventListener('click', (event) => {
// compute mouse pos
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const meshes = clusterData.filter(d => !d.matched).map(d => d.mesh);
const intersects = raycaster.intersectObjects(meshes);
if (intersects.length > 0) {
const hitMesh = intersects[0].object;
const data = clusterData.find(d => d.mesh === hitMesh);
if (!data || data.matched) return;
// toggle unfold: if already unfolded, ignore (or we can allow re-click to deselect? but spec says unfold)
if (!data.unfolded) {
// unfold: scale up slightly and change opacity? we'll animate scale
data.unfolded = true;
// animate scale to 1.3x
data.mesh.scale.multiplyScalar(1.3);
// change emissive? not needed
// if no selected cluster, set as first
if (!selectedCluster) {
selectedCluster = data;
} else {
// we have two unfolded clusters -> check match
const first = selectedCluster;
const second = data;
// compare signatures: geometry type, scale, and quaternion orientation (dot product)
const sig1 = first.signature;
const sig2 = second.signature;
// topology: geometry type and scale
const topologyMatch = (sig1.geoType === sig2.geoType) && (Math.abs(sig1.scale - sig2.scale) < 0.01);
// orientation: quaternion dot product absolute value close to 1 (within tolerance)
const quatDot = Math.abs(sig1.quat.dot(sig2.quat));
const orientationMatch = quatDot > 0.98; // allow small tolerance
if (topologyMatch && orientationMatch) {
// MATCH! trigger implosion effect
triggerImplosion(first, second);
// mark both as matched
first.matched = true;
second.matched = true;
matchCount += 2;
statusEl.textContent = matchCount;
// reset selected
selectedCluster = null;
// if all matched (6 clusters -> 3 pairs) -> show win
if (matchCount === 6) {
setTimeout(() => {
alert('✨ Entanglement achieved! All clusters fused.');
}, 300);
}
} else {
// mismatch: reset unfold (scale back) and clear selection
first.unfolded = false;
second.unfolded = false;
first.mesh.scale.multiplyScalar(1/1.3);
second.mesh.scale.multiplyScalar(1/1.3);
selectedCluster = null;
}
}
} else {
// if already unfolded, clicking again could deselect? but we keep simple: ignore
// optionally we could allow toggling off, but spec says unfold, so we keep.
}
}
});
// --- implosion effect: create particles and destroy meshes ---
function triggerImplosion(clusterA, clusterB) {
// create particle burst at midpoint
const mid = new THREE.Vector3().addVectors(clusterA.mesh.position, clusterB.mesh.position).multiplyScalar(0.5);
// create 60 particles
const particleCount = 80;
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const velocities = [];
for (let i = 0; i < particleCount; i++) {
// random direction
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
const r = 0.5 + Math.random() * 1.5;
const x = mid.x + r * Math.sin(phi) * Math.cos(theta);
const y = mid.y + r * Math.sin(phi) * Math.sin(theta);
const z = mid.z + r * Math.cos(phi);
positions[i*3] = x;
positions[i*3+1] = y;
positions[i*3+2] = z;
// color: white/blue
colors[i*3] = 0.8 + 0.2 * Math.random();
colors[i*3+1] = 0.9 + 0.1 * Math.random();
colors[i*3+2] = 1.0;
// velocity towards mid (implosion)
const dir = new THREE.Vector3(mid.x - x, mid.y - y, mid.z - z).normalize();
const speed = 0.08 + Math.random() * 0.1;
velocities.push(dir.multiplyScalar(speed));
}
const pGeo = new THREE.BufferGeometry();
pGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
pGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const pMat = new THREE.PointsMaterial({
size: 0.12,
vertexColors: true,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const particleSystem = new THREE.Points(pGeo, pMat);
scene.add(particleSystem);
// animate particles: move towards mid and shrink
const startPositions = positions.slice();
let frame = 0;
const maxFrames = 60;
function animateParticles() {
frame++;
const posAttr = particleSystem.geometry.attributes.position;
const posArray = posAttr.array;
for (let i = 0; i < particleCount; i++) {
// move towards mid
const idx = i*3;
const dx = mid.x - posArray[idx];
const dy = mid.y - posArray[idx+1];
const dz = mid.z - posArray[idx+2];
const dist = Math.sqrt(dx*dx + dy*dy + dz*dz);
if (dist > 0.01) {
const speed = 0.1;
posArray[idx] += dx * speed;
posArray[idx+1] += dy * speed;
posArray[idx+2] += dz * speed;
}
}
posAttr.needsUpdate = true;
// shrink size
pMat.size = Math.max(0.02, 0.12 * (1 - frame/maxFrames));
if (frame < maxFrames) {
requestAnimationFrame(animateParticles);
} else {
scene.remove(particleSystem);
pGeo.dispose();
pMat.dispose();
}
}
animateParticles();
// destroy meshes (hide them)
clusterA.mesh.visible = false;
clusterB.mesh.visible = false;
// also remove from clusterData? but we keep for reference
}
// --- animation loop ---
function animate() {
requestAnimationFrame(animate);
// subtle rotation of particles
particles.rotation.y += 0.0002;
// update controls
controls.update();
// render
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
animate();
// --- resize handler ---
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
});
// --- add some floating glow lines (volumetric light trails) ---
// (already added some lines, but we can add more dynamic ones)
// we'll add a few more lines with additive blending
for (let i = 0; i < 8; i++) {
const points = [];
const start = new THREE.Vector3((Math.random()-0.5)*12, (Math.random()-0.5)*8, (Math.random()-0.5)*12);
const end = new THREE.Vector3((Math.random()-0.5)*12, (Math.random()-0.5)*8, (Math.random()-0.5)*12);
points.push(start, end);
const lineGeo = new THREE.BufferGeometry().setFromPoints(points);
const lineMat = new THREE.LineBasicMaterial({ color: 0xccddff, opacity: 0.1, transparent: true });
const line = new THREE.Line(lineGeo, lineMat);
scene.add(line);
}
// small floating orbs for ambiance
const orbGeo = new THREE.SphereGeometry(0.08, 6, 6);
const orbMat = new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0x88aaff, emissiveIntensity: 0.3 });
for (let i = 0; i < 20; i++) {
const orb = new THREE.Mesh(orbGeo, orbMat);
orb.position.set((Math.random()-0.5)*14, (Math.random()-0.5)*10, (Math.random()-0.5)*14);
scene.add(orb);
}
// final touch: add a faint grid or void? keep pure white.
console.log('Quantum Entanglement ready.');
</script>
</body>
</html>
用法
此提示词专为 coding 设计。复制上方内容并粘贴到你常用的 AI 工具中。
为获得最佳效果,可将占位符(方括号或大写字母标示)替换为你的具体需求。
讨论
0 条评论