Загрузить файлы в «3d Runner»
This commit is contained in:
106
3d Runner/SPEC.md
Normal file
106
3d Runner/SPEC.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# 2.5D Browser Game Specification
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
- **Project Name**: Neon Runner 2.5D
|
||||||
|
- **Type**: Browser-based 2.5D endless runner/collector game
|
||||||
|
- **Core Functionality**: Player controls a character running on a 3D track, collecting coins while avoiding obstacles
|
||||||
|
- **Target Users**: Casual gamers looking for quick, engaging browser gameplay
|
||||||
|
|
||||||
|
## Technical Approach
|
||||||
|
- **Rendering**: Three.js for 3D graphics with fixed isometric-style camera angle
|
||||||
|
- **Perspective**: 2.5D - 3D graphics but gameplay confined to 2D plane (left/right movement)
|
||||||
|
- **Camera**: Fixed angle (~45°) looking down at the track, creating depth illusion
|
||||||
|
|
||||||
|
## Visual & Rendering Specification
|
||||||
|
|
||||||
|
### Scene Setup
|
||||||
|
- **Camera**: PerspectiveCamera at 45° angle, positioned above and behind player
|
||||||
|
- **Camera Position**: (0, 8, 12) looking at (0, 0, -5)
|
||||||
|
- **Lighting**:
|
||||||
|
- Ambient light: soft purple (#6644aa) at intensity 0.4
|
||||||
|
- Directional light: cyan (#00ffff) at intensity 0.8, position (5, 10, 5)
|
||||||
|
- Point light: magenta (#ff00ff) following player for glow effect
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
- **Background**: Dark gradient (deep purple to black)
|
||||||
|
- **Fog**: Linear fog, color #1a0a2e, near 20, far 80
|
||||||
|
- **Ground**: Infinite scrolling neon grid track
|
||||||
|
|
||||||
|
### Materials & Effects
|
||||||
|
- **Track**: Emissive grid material with cyan glow lines
|
||||||
|
- **Player**: Glowing cube/sphere with magenta emissive material + pulsing animation
|
||||||
|
- **Obstacles**: Red/orange glowing geometric shapes (cubes, pyramids)
|
||||||
|
- **Coins**: Yellow/gold rotating torus with golden emissive glow
|
||||||
|
- **Post-processing**: Bloom effect for neon glow aesthetic
|
||||||
|
|
||||||
|
### Color Palette
|
||||||
|
- Primary: Cyan (#00ffff)
|
||||||
|
- Secondary: Magenta (#ff00ff)
|
||||||
|
- Accent: Gold (#ffd700)
|
||||||
|
- Danger: Red (#ff3333)
|
||||||
|
- Background: Deep Purple (#1a0a2e)
|
||||||
|
|
||||||
|
## Game Mechanics Specification
|
||||||
|
|
||||||
|
### Player Controls
|
||||||
|
- **Left/Right Movement**: A/D or Arrow Keys or Mouse click (left/right side of screen)
|
||||||
|
- **Movement Range**: -3 to +3 units on X-axis
|
||||||
|
- **Movement Speed**: Smooth interpolation, 0.15 units per frame
|
||||||
|
|
||||||
|
### Gameplay Elements
|
||||||
|
- **Track Width**: 7 units total (-3.5 to +3.5)
|
||||||
|
- **Player Position**: Fixed Z at 0, moves only on X-axis
|
||||||
|
- **Speed**: Starts at 0.3 units/frame, increases 0.001 per frame (max 0.8)
|
||||||
|
|
||||||
|
### Obstacles
|
||||||
|
- **Types**:
|
||||||
|
- Low barrier (must dodge)
|
||||||
|
- Tall pillar (full lane blocker)
|
||||||
|
- **Spawn Rate**: Every 30-50 frames, random lane
|
||||||
|
- **Colors**: Red/orange emissive, slight pulsing
|
||||||
|
|
||||||
|
### Coins
|
||||||
|
- **Shape**: Torus (ring)
|
||||||
|
- **Spawn Rate**: Every 20-40 frames, random lane
|
||||||
|
- **Rotation**: Continuous Y-axis rotation
|
||||||
|
- **Collection**: Player proximity triggers collection (+10 points)
|
||||||
|
|
||||||
|
### Scoring
|
||||||
|
- **Distance Score**: +1 point per 10 frames survived
|
||||||
|
- **Coin Bonus**: +10 points per coin collected
|
||||||
|
- **High Score**: Stored in localStorage
|
||||||
|
|
||||||
|
### Game States
|
||||||
|
- **Start Screen**: "Click to Start" overlay
|
||||||
|
- **Playing**: Active gameplay
|
||||||
|
- **Game Over**: Show score, "Click to Restart" overlay
|
||||||
|
|
||||||
|
## UI Specification
|
||||||
|
|
||||||
|
### HUD Elements
|
||||||
|
- **Score Display**: Top-left, large neon font
|
||||||
|
- **High Score**: Top-right, smaller font
|
||||||
|
- **Style**: Cyberpunk/retro-futuristic with text-shadow glow
|
||||||
|
|
||||||
|
### Overlays
|
||||||
|
- **Start Screen**: Semi-transparent dark overlay with game title and instructions
|
||||||
|
- **Game Over**: Score display, high score, restart prompt
|
||||||
|
|
||||||
|
## Audio (Optional Enhancement)
|
||||||
|
- No audio required for MVP
|
||||||
|
|
||||||
|
## Performance Targets
|
||||||
|
- **Target FPS**: 60fps
|
||||||
|
- **Object Pooling**: Reuse obstacles and coins to prevent garbage collection
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
1. Game loads and displays 3D scene with neon aesthetic
|
||||||
|
2. Player can move left/right using keyboard or mouse
|
||||||
|
3. Obstacles spawn and scroll toward player
|
||||||
|
4. Coins spawn and can be collected for points
|
||||||
|
5. Collision with obstacle ends game
|
||||||
|
6. Score displays and updates in real-time
|
||||||
|
7. Game can be restarted after game over
|
||||||
|
8. High score persists between sessions
|
||||||
|
9. Smooth 60fps performance
|
||||||
|
10. Responsive to window resize
|
||||||
253
3d Runner/background.js
Normal file
253
3d Runner/background.js
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
/**
|
||||||
|
* Enhanced background - Space with floating geometric city silhouette shapes and
|
||||||
|
*/
|
||||||
|
class BackgroundShapes {
|
||||||
|
constructor(scene) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.shapes = [];
|
||||||
|
this.stars = [];
|
||||||
|
this.cityLights = [];
|
||||||
|
this.frameCount = 0;
|
||||||
|
|
||||||
|
this.createStars();
|
||||||
|
this.createCityscape();
|
||||||
|
this.createShapes();
|
||||||
|
}
|
||||||
|
|
||||||
|
createStars() {
|
||||||
|
// Starfield
|
||||||
|
const starGeometry = new THREE.BufferGeometry();
|
||||||
|
const starCount = 500;
|
||||||
|
const positions = new Float32Array(starCount * 3);
|
||||||
|
const colors = new Float32Array(starCount * 3);
|
||||||
|
const sizes = new Float32Array(starCount);
|
||||||
|
|
||||||
|
for (let i = 0; i < starCount; i++) {
|
||||||
|
positions[i * 3] = (Math.random() - 0.5) * 100;
|
||||||
|
positions[i * 3 + 1] = Math.random() * 40 + 5;
|
||||||
|
positions[i * 3 + 2] = -40 - Math.random() * 60;
|
||||||
|
|
||||||
|
// Random star colors (white, cyan, magenta, yellow)
|
||||||
|
const colorChoice = Math.random();
|
||||||
|
if (colorChoice < 0.7) {
|
||||||
|
colors[i * 3] = 1; colors[i * 3 + 1] = 1; colors[i * 3 + 2] = 1;
|
||||||
|
} else if (colorChoice < 0.85) {
|
||||||
|
colors[i * 3] = 0; colors[i * 3 + 1] = 1; colors[i * 3 + 2] = 1;
|
||||||
|
} else {
|
||||||
|
colors[i * 3] = 1; colors[i * 3 + 1] = 0; colors[i * 3 + 2] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes[i] = Math.random() * 2 + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||||
|
starGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
||||||
|
starGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
|
||||||
|
|
||||||
|
const starMaterial = new THREE.PointsMaterial({
|
||||||
|
size: 0.3,
|
||||||
|
vertexColors: true,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.8,
|
||||||
|
sizeAttenuation: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.starField = new THREE.Points(starGeometry, starMaterial);
|
||||||
|
scene.add(this.starField);
|
||||||
|
|
||||||
|
// Shooting stars
|
||||||
|
this.shootingStars = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
createCityscape() {
|
||||||
|
// Create neon city silhouette in background
|
||||||
|
const buildingCount = 30;
|
||||||
|
const buildingMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x0a0515,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.9
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < buildingCount; i++) {
|
||||||
|
const width = 1 + Math.random() * 2;
|
||||||
|
const height = 3 + Math.random() * 12;
|
||||||
|
const depth = 0.5;
|
||||||
|
|
||||||
|
const geometry = new THREE.BoxGeometry(width, height, depth);
|
||||||
|
const building = new THREE.Mesh(geometry, buildingMaterial);
|
||||||
|
|
||||||
|
building.position.set(
|
||||||
|
-25 + i * 1.8 + Math.random() * 0.5,
|
||||||
|
height / 2 - 1,
|
||||||
|
-45 - Math.random() * 10
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add window lights
|
||||||
|
const windowRows = Math.floor(height / 1.5);
|
||||||
|
const windowCols = Math.floor(width / 0.8);
|
||||||
|
|
||||||
|
for (let row = 0; row < windowRows; row++) {
|
||||||
|
for (let col = 0; col < windowCols; col++) {
|
||||||
|
if (Math.random() > 0.4) {
|
||||||
|
const windowGeom = new THREE.PlaneGeometry(0.3, 0.4);
|
||||||
|
const windowColor = Math.random() > 0.5 ? 0x00ffff : 0xff00ff;
|
||||||
|
const windowMat = new THREE.MeshBasicMaterial({
|
||||||
|
color: windowColor,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.3 + Math.random() * 0.4
|
||||||
|
});
|
||||||
|
const windowMesh = new THREE.Mesh(windowGeom, windowMat);
|
||||||
|
|
||||||
|
windowMesh.position.set(
|
||||||
|
-width/2 + 0.4 + col * 0.7,
|
||||||
|
-height/2 + 1 + row * 1.2,
|
||||||
|
depth/2 + 0.01
|
||||||
|
);
|
||||||
|
building.add(windowMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cityLights.push(building);
|
||||||
|
scene.add(building);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ground reflection plane
|
||||||
|
const reflectionGeom = new THREE.PlaneGeometry(60, 100);
|
||||||
|
const reflectionMat = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x00ffff,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.05,
|
||||||
|
side: THREE.DoubleSide
|
||||||
|
});
|
||||||
|
const reflection = new THREE.Mesh(reflectionGeom, reflectionMat);
|
||||||
|
reflection.rotation.x = -Math.PI / 2;
|
||||||
|
reflection.position.y = -0.48;
|
||||||
|
reflection.position.z = -20;
|
||||||
|
scene.add(reflection);
|
||||||
|
}
|
||||||
|
|
||||||
|
createShapes() {
|
||||||
|
// Floating neon shapes
|
||||||
|
const colors = [0x00ffff, 0xff00ff, 0x6644aa, 0xffd700];
|
||||||
|
const types = ['cube', 'octahedron', 'tetrahedron', 'torus'];
|
||||||
|
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
let geometry;
|
||||||
|
const type = types[Math.floor(Math.random() * types.length)];
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case 'cube':
|
||||||
|
geometry = new THREE.BoxGeometry(0.8, 0.8, 0.8);
|
||||||
|
break;
|
||||||
|
case 'octahedron':
|
||||||
|
geometry = new THREE.OctahedronGeometry(0.6);
|
||||||
|
break;
|
||||||
|
case 'tetrahedron':
|
||||||
|
geometry = new THREE.TetrahedronGeometry(0.6);
|
||||||
|
break;
|
||||||
|
case 'torus':
|
||||||
|
geometry = new THREE.TorusGeometry(0.4, 0.15, 8, 16);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const material = new THREE.MeshBasicMaterial({
|
||||||
|
color: colors[Math.floor(Math.random() * colors.length)],
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.2,
|
||||||
|
wireframe: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
|
||||||
|
mesh.position.set(
|
||||||
|
(Math.random() - 0.5) * 30,
|
||||||
|
Math.random() * 12 + 3,
|
||||||
|
-25 - Math.random() * 35
|
||||||
|
);
|
||||||
|
|
||||||
|
mesh.userData.rotationSpeed = {
|
||||||
|
x: (Math.random() - 0.5) * 0.02,
|
||||||
|
y: (Math.random() - 0.5) * 0.02,
|
||||||
|
z: (Math.random() - 0.5) * 0.02
|
||||||
|
};
|
||||||
|
|
||||||
|
mesh.userData.floatSpeed = 0.003 + Math.random() * 0.008;
|
||||||
|
mesh.userData.baseY = mesh.position.y;
|
||||||
|
mesh.userData.floatAmount = 1 + Math.random() * 2;
|
||||||
|
|
||||||
|
this.shapes.push(mesh);
|
||||||
|
scene.add(mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(frameCount) {
|
||||||
|
this.frameCount = frameCount;
|
||||||
|
|
||||||
|
// Animate starfield
|
||||||
|
if (this.starField) {
|
||||||
|
this.starField.rotation.y = frameCount * 0.0001;
|
||||||
|
this.starField.position.z = Math.sin(frameCount * 0.001) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate shapes
|
||||||
|
this.shapes.forEach(mesh => {
|
||||||
|
mesh.rotation.x += mesh.userData.rotationSpeed.x;
|
||||||
|
mesh.rotation.y += mesh.userData.rotationSpeed.y;
|
||||||
|
mesh.rotation.z += mesh.userData.rotationSpeed.z;
|
||||||
|
|
||||||
|
mesh.position.y = mesh.userData.baseY +
|
||||||
|
Math.sin(frameCount * mesh.userData.floatSpeed) * mesh.userData.floatAmount;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Twinkle city lights
|
||||||
|
this.cityLights.forEach((building, idx) => {
|
||||||
|
building.children.forEach((window, wIdx) => {
|
||||||
|
if (window.material) {
|
||||||
|
const twinkle = Math.sin(frameCount * 0.05 + idx * 0.5 + wIdx * 0.1);
|
||||||
|
window.material.opacity = 0.3 + twinkle * 0.2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Random shooting star
|
||||||
|
if (frameCount % 200 === 0 && Math.random() > 0.5) {
|
||||||
|
this.createShootingStar();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update shooting stars
|
||||||
|
for (let i = this.shootingStars.length - 1; i >= 0; i--) {
|
||||||
|
const ss = this.shootingStars[i];
|
||||||
|
ss.position.add(ss.userData.velocity);
|
||||||
|
ss.material.opacity -= 0.02;
|
||||||
|
|
||||||
|
if (ss.material.opacity <= 0) {
|
||||||
|
this.scene.remove(ss);
|
||||||
|
this.shootingStars.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createShootingStar() {
|
||||||
|
const geometry = new THREE.BufferGeometry().setFromPoints([
|
||||||
|
new THREE.Vector3(0, 0, 0),
|
||||||
|
new THREE.Vector3(0, -2, 1)
|
||||||
|
]);
|
||||||
|
const material = new THREE.LineBasicMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const shootingStar = new THREE.Line(geometry, material);
|
||||||
|
shootingStar.position.set(
|
||||||
|
Math.random() * 40 - 20,
|
||||||
|
25 + Math.random() * 10,
|
||||||
|
-30 - Math.random() * 20
|
||||||
|
);
|
||||||
|
shootingStar.userData.velocity = new THREE.Vector3(-0.3, -0.5, 0.5);
|
||||||
|
|
||||||
|
this.shootingStars.push(shootingStar);
|
||||||
|
this.scene.add(shootingStar);
|
||||||
|
}
|
||||||
|
}
|
||||||
113
3d Runner/coin.js
Normal file
113
3d Runner/coin.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Coin class - manages collectible coins with enhanced visuals
|
||||||
|
*/
|
||||||
|
class Coin {
|
||||||
|
constructor(scene, lane) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.mesh = null;
|
||||||
|
this.glowMesh = null;
|
||||||
|
|
||||||
|
this.createMesh(lane);
|
||||||
|
}
|
||||||
|
|
||||||
|
createMesh(lane) {
|
||||||
|
// Create procedural texture for coin
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 128;
|
||||||
|
canvas.height = 64;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// Gold gradient background
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, 128, 64);
|
||||||
|
gradient.addColorStop(0, '#ffd700');
|
||||||
|
gradient.addColorStop(0.3, '#ffec8b');
|
||||||
|
gradient.addColorStop(0.5, '#ffd700');
|
||||||
|
gradient.addColorStop(0.7, '#ffec8b');
|
||||||
|
gradient.addColorStop(1, '#daa520');
|
||||||
|
|
||||||
|
ctx.fillStyle = gradient;
|
||||||
|
ctx.fillRect(0, 0, 128, 64);
|
||||||
|
|
||||||
|
// Inner ring
|
||||||
|
ctx.strokeStyle = '#b8860b';
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(64, 32, 45, 20, 0, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Star symbol in center
|
||||||
|
ctx.fillStyle = '#b8860b';
|
||||||
|
ctx.beginPath();
|
||||||
|
const cx = 64, cy = 32;
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const angle = (i * 4 * Math.PI / 5) - Math.PI / 2;
|
||||||
|
const x = cx + Math.cos(angle) * 15;
|
||||||
|
const y = cy + Math.sin(angle) * 15;
|
||||||
|
if (i === 0) ctx.moveTo(x, y);
|
||||||
|
else ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
|
|
||||||
|
const geometry = new THREE.CylinderGeometry(0.5, 0.5, 0.15, 24);
|
||||||
|
const material = new THREE.MeshStandardMaterial({
|
||||||
|
map: texture,
|
||||||
|
color: 0xffd700,
|
||||||
|
emissive: 0xffd700,
|
||||||
|
emissiveIntensity: 0.6,
|
||||||
|
metalness: 0.9,
|
||||||
|
roughness: 0.1
|
||||||
|
});
|
||||||
|
|
||||||
|
const LANE_WIDTH = 3.5;
|
||||||
|
|
||||||
|
this.mesh = new THREE.Mesh(geometry, material);
|
||||||
|
this.mesh.position.set(lane * LANE_WIDTH, 1, -60);
|
||||||
|
this.mesh.rotation.x = Math.PI / 2;
|
||||||
|
|
||||||
|
// Outer glow ring
|
||||||
|
const glowGeometry = new THREE.TorusGeometry(0.6, 0.12, 8, 32);
|
||||||
|
const glowMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xffd700,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.4
|
||||||
|
});
|
||||||
|
this.glowMesh = new THREE.Mesh(glowGeometry, glowMaterial);
|
||||||
|
this.glowMesh.rotation.x = Math.PI / 2;
|
||||||
|
this.mesh.add(this.glowMesh);
|
||||||
|
|
||||||
|
// Add point light for glow
|
||||||
|
const coinLight = new THREE.PointLight(0xffd700, 0.4, 3);
|
||||||
|
this.mesh.add(coinLight);
|
||||||
|
|
||||||
|
this.scene.add(this.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(gameSpeed) {
|
||||||
|
this.mesh.position.z += gameSpeed;
|
||||||
|
|
||||||
|
// Rotate coin
|
||||||
|
this.mesh.rotation.z += 0.05;
|
||||||
|
this.glowMesh.rotation.z -= 0.03;
|
||||||
|
|
||||||
|
// Bobbing motion
|
||||||
|
this.mesh.position.y = 1 + Math.sin(Date.now() * 0.005) * 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPastCamera() {
|
||||||
|
return this.mesh.position.z > 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCollection(playerPosition) {
|
||||||
|
const dx = Math.abs(this.mesh.position.x - playerPosition.x);
|
||||||
|
const dz = Math.abs(this.mesh.position.z - playerPosition.z);
|
||||||
|
|
||||||
|
return dx < 1 && dz < 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.scene.remove(this.mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
3d Runner/index.html
Normal file
41
3d Runner/index.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
|
<title>Neon Runner 2.5D</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="gameCanvas"></canvas>
|
||||||
|
|
||||||
|
<div id="ui">
|
||||||
|
<div id="score">0</div>
|
||||||
|
<div id="highScore">HIGH: 0</div>
|
||||||
|
|
||||||
|
<div id="startScreen" class="overlay">
|
||||||
|
<h1>NEON RUNNER</h1>
|
||||||
|
<p>2.5D ENDLESS RUNNER</p>
|
||||||
|
<button class="start-btn" onclick="startGame()">START</button>
|
||||||
|
<div class="controls-hint">A/D or ←/→ or SWIPE to move</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="gameOverScreen" class="overlay hidden">
|
||||||
|
<h2>GAME OVER</h2>
|
||||||
|
<div class="final-score">SCORE: <span id="finalScore">0</span></div>
|
||||||
|
<div class="final-high">HIGH SCORE: <span id="finalHigh">0</span></div>
|
||||||
|
<button class="start-btn" onclick="restartGame()">RETRY</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||||
|
<script src="js/player.js"></script>
|
||||||
|
<script src="js/obstacle.js"></script>
|
||||||
|
<script src="js/coin.js"></script>
|
||||||
|
<script src="js/particles.js"></script>
|
||||||
|
<script src="js/trail.js"></script>
|
||||||
|
<script src="js/speedlines.js"></script>
|
||||||
|
<script src="js/background.js"></script>
|
||||||
|
<script src="js/game.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
237
3d Runner/style.css
Normal file
237
3d Runner/style.css
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap');
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #1a0a2e;
|
||||||
|
font-family: 'Orbitron', monospace;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameCanvas {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ui {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
#score {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 30px;
|
||||||
|
color: #00ffff;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff, 0 0 40px #00ffff;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
animation: glow-pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow-pulse {
|
||||||
|
0%, 100% { text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff, 0 0 40px #00ffff; }
|
||||||
|
50% { text-shadow: 0 0 15px #00ffff, 0 0 30px #00ffff, 0 0 60px #00ffff, 0 0 80px #00ffff; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#highScore {
|
||||||
|
position: absolute;
|
||||||
|
top: 25px;
|
||||||
|
right: 30px;
|
||||||
|
color: #ff00ff;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-shadow: 0 0 8px #ff00ff, 0 0 16px #ff00ff;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(26, 10, 46, 0.92);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: opacity 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#startScreen h1 {
|
||||||
|
font-size: clamp(36px, 10vw, 72px);
|
||||||
|
font-weight: 900;
|
||||||
|
color: #00ffff;
|
||||||
|
text-shadow: 0 0 20px #00ffff, 0 0 40px #00ffff, 0 0 80px #00ffff;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
animation: title-glow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes title-glow {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1);
|
||||||
|
text-shadow: 0 0 20px #00ffff, 0 0 40px #00ffff, 0 0 80px #00ffff;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.03);
|
||||||
|
text-shadow: 0 0 30px #00ffff, 0 0 60px #00ffff, 0 0 100px #00ffff, 0 0 120px #00ffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#startScreen p {
|
||||||
|
font-size: clamp(14px, 3vw, 20px);
|
||||||
|
color: #ff00ff;
|
||||||
|
text-shadow: 0 0 10px #ff00ff;
|
||||||
|
margin-bottom: 35px;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-btn {
|
||||||
|
padding: 18px 50px;
|
||||||
|
font-size: clamp(18px, 4vw, 26px);
|
||||||
|
font-family: 'Orbitron', monospace;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a0a2e;
|
||||||
|
background: linear-gradient(135deg, #00ffff, #ff00ff);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 0 20px #00ffff, 0 0 40px #ff00ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
background: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
transparent,
|
||||||
|
rgba(255, 255, 255, 0.3),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
transform: rotate(45deg) translateX(-100%);
|
||||||
|
transition: transform 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-btn:hover {
|
||||||
|
transform: scale(1.08);
|
||||||
|
box-shadow: 0 0 30px #00ffff, 0 0 60px #ff00ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-btn:hover::before {
|
||||||
|
transform: rotate(45deg) translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameOverScreen h2 {
|
||||||
|
font-size: clamp(32px, 8vw, 56px);
|
||||||
|
font-weight: 900;
|
||||||
|
color: #ff3333;
|
||||||
|
text-shadow: 0 0 20px #ff3333, 0 0 40px #ff3333;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
animation: shake 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
20% { transform: translateX(-10px); }
|
||||||
|
40% { transform: translateX(10px); }
|
||||||
|
60% { transform: translateX(-10px); }
|
||||||
|
80% { transform: translateX(10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameOverScreen .final-score {
|
||||||
|
font-size: clamp(24px, 6vw, 40px);
|
||||||
|
color: #00ffff;
|
||||||
|
text-shadow: 0 0 15px #00ffff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameOverScreen .final-high {
|
||||||
|
font-size: clamp(16px, 3vw, 24px);
|
||||||
|
color: #ffd700;
|
||||||
|
text-shadow: 0 0 10px #ffd700;
|
||||||
|
margin-bottom: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-hint {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
color: #888;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile optimizations */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
#score {
|
||||||
|
top: 15px;
|
||||||
|
left: 20px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#highScore {
|
||||||
|
top: 18px;
|
||||||
|
right: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scanline effect overlay */
|
||||||
|
#ui::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent 2px,
|
||||||
|
rgba(0, 0, 0, 0.03) 2px,
|
||||||
|
rgba(0, 0, 0, 0.03) 4px
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user