vsspace / index.html
vikassabbi's picture
Add 2 files
ff1f3ab verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Galactic Defender | Advanced Space Shooter</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000;
font-family: 'Orbitron', sans-serif;
touch-action: none;
}
canvas {
display: block;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
#score-display {
position: absolute;
top: 20px;
left: 20px;
color: #0ff;
font-size: 24px;
text-shadow: 0 0 10px #0ff;
}
#health-container {
position: absolute;
top: 20px;
right: 20px;
width: 200px;
height: 30px;
background: rgba(255,0,0,0.2);
border: 2px solid #f00;
border-radius: 15px;
overflow: hidden;
}
#health-bar {
height: 100%;
width: 100%;
background: linear-gradient(90deg, #f00, #ff0);
transition: width 0.3s;
}
#health-text {
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
line-height: 30px;
color: white;
font-size: 14px;
text-shadow: 0 0 5px #000;
}
#game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f00;
font-size: 48px;
text-align: center;
display: none;
text-shadow: 0 0 20px #f00;
}
#start-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
z-index: 100;
}
#start-button {
margin-top: 30px;
padding: 15px 40px;
background: linear-gradient(45deg, #0ff, #00f);
border: none;
border-radius: 50px;
color: white;
font-size: 24px;
cursor: pointer;
pointer-events: auto;
box-shadow: 0 0 20px #0ff;
transition: all 0.3s;
}
#start-button:hover {
transform: scale(1.1);
box-shadow: 0 0 30px #0ff;
}
.title {
font-size: 72px;
margin-bottom: 20px;
background: linear-gradient(45deg, #0ff, #00f);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
}
.instructions {
max-width: 600px;
text-align: center;
margin-bottom: 30px;
line-height: 1.6;
color: #aaa;
}
.powerup-indicator {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: #ff0;
font-size: 18px;
text-shadow: 0 0 10px #ff0;
display: none;
background: rgba(0,0,0,0.5);
padding: 10px 20px;
border-radius: 20px;
border: 1px solid #ff0;
pointer-events: none;
}
.controls {
position: absolute;
bottom: 20px;
width: 100%;
display: flex;
justify-content: space-between;
padding: 0 20px;
box-sizing: border-box;
pointer-events: none;
}
.control-btn {
width: 80px;
height: 80px;
background: rgba(255,255,255,0.2);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
pointer-events: auto;
touch-action: manipulation;
user-select: none;
}
#fire-btn {
background: rgba(255,0,0,0.3);
border: 2px solid #f00;
}
#joystick {
position: absolute;
bottom: 30px;
left: 30px;
width: 120px;
height: 120px;
pointer-events: none;
}
#joystick-base {
width: 100%;
height: 100%;
background: rgba(255,255,255,0.1);
border-radius: 50%;
position: relative;
}
#joystick-head {
width: 60px;
height: 60px;
background: rgba(0,255,255,0.3);
border-radius: 50%;
position: absolute;
top: 30px;
left: 30px;
transition: transform 0.1s;
border: 2px solid #0ff;
}
@font-face {
font-family: 'Orbitron';
src: url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
}
.explosion {
position: absolute;
width: 100px;
height: 100px;
background: radial-gradient(circle, rgba(255,100,0,0.8) 0%, rgba(255,0,0,0) 70%);
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
animation: explode 0.5s forwards;
}
@keyframes explode {
0% { transform: translate(-50%, -50%) scale(0); opacity: 1; }
100% { transform: translate(-50%, -50%) scale(3); opacity: 0; }
}
#mobile-controls {
display: none;
}
@media (max-width: 768px) {
#mobile-controls {
display: block;
}
.instructions {
max-width: 90%;
font-size: 14px;
}
.title {
font-size: 48px;
}
}
#debug-info {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-size: 12px;
background: rgba(0,0,0,0.5);
padding: 5px;
border-radius: 3px;
}
#desktop-controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: #aaa;
font-size: 14px;
text-align: center;
background: rgba(0,0,0,0.5);
padding: 10px 20px;
border-radius: 20px;
pointer-events: none;
}
.control-hint {
display: inline-block;
margin: 0 5px;
padding: 2px 8px;
background: rgba(255,255,255,0.1);
border-radius: 4px;
border: 1px solid #0ff;
}
</style>
</head>
<body>
<div id="ui">
<div id="score-display">SCORE: 0</div>
<div id="health-container">
<div id="health-bar"></div>
<div id="health-text">100%</div>
</div>
<div id="game-over">GAME OVER<br><span style="font-size: 24px;">Score: 0</span><br><button id="restart-button" style="margin-top: 20px; padding: 10px 20px; background: #f00; color: white; border: none; border-radius: 5px; cursor: pointer; pointer-events: auto;">PLAY AGAIN</button></div>
<div class="powerup-indicator" id="powerup-indicator">DOUBLE FIRE ACTIVE!</div>
<div id="desktop-controls">
<span class="control-hint">WASD</span> Move |
<span class="control-hint">Mouse</span> Aim |
<span class="control-hint">Space</span> Fire |
<span class="control-hint">Shift</span> Boost |
<span class="control-hint">Q/E</span> Roll
</div>
<div id="mobile-controls">
<div id="joystick">
<div id="joystick-base">
<div id="joystick-head"></div>
</div>
</div>
<div class="controls">
<div class="control-btn" id="fire-btn">FIRE</div>
</div>
</div>
<div id="debug-info"></div>
</div>
<div id="start-screen">
<h1 class="title">GALACTIC DEFENDER</h1>
<div class="instructions">
<p><strong>DESKTOP CONTROLS:</strong> WASD to move, Mouse to aim, SPACE to fire, SHIFT to boost, Q/E to roll</p>
<p><strong>MOBILE CONTROLS:</strong> Virtual joystick to move, FIRE button to shoot</p>
<p><strong>POWER-UPS:</strong> Green = Health, Yellow = Double Fire</p>
<p>Destroy alien ships before they reach you! Full 3D movement enabled.</p>
</div>
<button id="start-button">START MISSION</button>
</div>
<script>
// Game variables
let score = 0;
let health = 100;
let gameOver = false;
let doubleFireActive = false;
let doubleFireTimeout;
let explosions = [];
let isMobile = false;
let debugMode = false;
// Three.js variables
let scene, camera, renderer;
let player, playerSpeed = 0.8; // Increased base speed
let boostSpeed = 2.0; // Increased boost speed
let currentSpeed = playerSpeed;
let bullets = [];
let enemies = [];
let enemyBullets = [];
let powerups = [];
let lastEnemySpawnTime = 0;
let enemySpawnInterval = 2000; // ms
let lastEnemyShootTime = 0;
let enemyShootInterval = 1000; // ms
let lastPowerupSpawnTime = 0;
let powerupSpawnInterval = 10000; // ms
let clock = new THREE.Clock();
let starField;
let controls;
let playerDirection = new THREE.Vector3(0, 0, -1);
let playerQuaternion = new THREE.Quaternion();
let playerEuler = new THREE.Euler(0, 0, 0, 'YXZ');
let playerVelocity = new THREE.Vector3();
let playerRotationSpeed = 0.08; // Increased rotation speed
let playerRollSpeed = 0.05; // Increased roll speed
let loader = new THREE.GLTFLoader();
// Mobile controls
let joystickActive = false;
let joystickStartX = 0;
let joystickStartY = 0;
let joystickHead = null;
let joystickBase = null;
let movementX = 0;
let movementY = 0;
// Key states
const keys = {
ArrowUp: false,
ArrowDown: false,
ArrowLeft: false,
ArrowRight: false,
w: false,
a: false,
s: false,
d: false,
' ': false,
q: false,
e: false,
Shift: false
};
// Initialize the game
function init() {
// Check if mobile
isMobile = /Mobi|Android/i.test(navigator.userAgent);
// Create scene
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0.001);
// Create camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, -1);
// Create renderer
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);
// Add lights
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
scene.add(directionalLight);
// Create starfield background
createStarfield();
// Create player ship
createPlayer();
// Add event listeners
window.addEventListener('resize', onWindowResize);
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('click', () => {
if (document.pointerLockElement !== renderer.domElement) {
renderer.domElement.requestPointerLock();
}
});
// Mobile controls
if (isMobile) {
setupMobileControls();
}
// Start button event listener
document.getElementById('start-button').addEventListener('click', startGame);
document.getElementById('restart-button').addEventListener('click', restartGame);
document.getElementById('fire-btn').addEventListener('touchstart', () => {
keys[' '] = true;
// Auto-fire when holding the button
if (!gameOver) shoot();
});
document.getElementById('fire-btn').addEventListener('touchend', () => keys[' '] = false);
// Start animation loop
animate();
}
function setupMobileControls() {
joystickHead = document.getElementById('joystick-head');
joystickBase = document.getElementById('joystick-base');
joystickBase.addEventListener('touchstart', handleJoystickStart, { passive: false });
joystickBase.addEventListener('touchmove', handleJoystickMove, { passive: false });
joystickBase.addEventListener('touchend', handleJoystickEnd, { passive: false });
// Add touch event for the whole screen to control aiming
renderer.domElement.addEventListener('touchmove', handleTouchAim, { passive: false });
}
function handleTouchAim(e) {
if (gameOver || !joystickActive) return;
e.preventDefault();
const touch = e.touches[0];
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
// Calculate relative movement from center
const movementX = (touch.clientX - centerX) / 100;
const movementY = (touch.clientY - centerY) / 100;
// Update player direction based on touch position
playerEuler.y -= movementX * 0.002;
playerEuler.x -= movementY * 0.002;
// Limit vertical rotation to prevent flipping
playerEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, playerEuler.x));
playerQuaternion.setFromEuler(playerEuler);
playerDirection.set(0, 0, -1).applyQuaternion(playerQuaternion);
}
function handleJoystickStart(e) {
e.preventDefault();
joystickActive = true;
const rect = joystickBase.getBoundingClientRect();
joystickStartX = rect.left + rect.width / 2;
joystickStartY = rect.top + rect.height / 2;
handleJoystickMove(e);
}
function handleJoystickMove(e) {
if (!joystickActive) return;
e.preventDefault();
const touch = e.touches[0];
const touchX = touch.clientX;
const touchY = touch.clientY;
// Calculate distance from center
const dx = touchX - joystickStartX;
const dy = touchY - joystickStartY;
const distance = Math.sqrt(dx * dx + dy * dy);
const maxDistance = 60;
// Limit to joystick radius
const angle = Math.atan2(dy, dx);
const limitedDistance = Math.min(distance, maxDistance);
// Position joystick head
const posX = limitedDistance * Math.cos(angle);
const posY = limitedDistance * Math.sin(angle);
joystickHead.style.transform = `translate(${posX}px, ${posY}px)`;
// Calculate movement direction (invert Y for natural movement)
movementX = dx / maxDistance;
movementY = -dy / maxDistance; // Inverted Y axis
// Normalize if outside circle
if (distance > maxDistance) {
movementX = dx / distance;
movementY = -dy / distance; // Inverted Y axis
}
}
function handleJoystickEnd() {
joystickActive = false;
joystickHead.style.transform = 'translate(0, 0)';
movementX = 0;
movementY = 0;
}
function startGame() {
document.getElementById('start-screen').style.display = 'none';
gameOver = false;
score = 0;
health = 100;
updateUI();
// Request pointer lock for mouse controls
if (!isMobile) {
renderer.domElement.requestPointerLock();
}
}
function restartGame() {
// Clear all game objects
while (enemies.length > 0) {
scene.remove(enemies[0].mesh);
enemies.shift();
}
while (bullets.length > 0) {
scene.remove(bullets[0]);
bullets.shift();
}
while (enemyBullets.length > 0) {
scene.remove(enemyBullets[0]);
enemyBullets.shift();
}
while (powerups.length > 0) {
scene.remove(powerups[0].mesh);
powerups.shift();
}
// Reset player position and rotation
player.position.set(0, 0, 0);
player.rotation.set(0, 0, 0);
playerVelocity.set(0, 0, 0);
playerDirection.set(0, 0, -1);
// Reset game state
gameOver = false;
score = 0;
health = 100;
doubleFireActive = false;
document.getElementById('powerup-indicator').style.display = 'none';
document.getElementById('game-over').style.display = 'none';
updateUI();
// Request pointer lock again
if (!isMobile) {
renderer.domElement.requestPointerLock();
}
}
function createStarfield() {
const starGeometry = new THREE.BufferGeometry();
const starMaterial = new THREE.PointsMaterial({
color: 0xffffff,
size: 0.1,
transparent: true,
opacity: 0.8
});
const starVertices = [];
for (let i = 0; i < 10000; i++) {
const x = (Math.random() - 0.5) * 2000;
const y = (Math.random() - 0.5) * 2000;
const z = (Math.random() - 0.5) * 2000;
starVertices.push(x, y, z);
}
starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
starField = new THREE.Points(starGeometry, starMaterial);
scene.add(starField);
}
function createPlayer() {
// Create a more advanced player ship
const geometry = new THREE.BoxGeometry(2, 1, 3);
// Create ship body
const bodyGeometry = new THREE.BoxGeometry(1.5, 0.5, 2);
const bodyMaterial = new THREE.MeshPhongMaterial({
color: 0x00aaff,
emissive: 0x0044aa,
specular: 0xffffff,
shininess: 30,
flatShading: false
});
// Create wings
const wingGeometry = new THREE.BoxGeometry(0.5, 0.2, 1.5);
const wingMaterial = new THREE.MeshPhongMaterial({
color: 0x0066cc,
emissive: 0x003366,
specular: 0xffffff,
shininess: 30,
flatShading: false
});
// Create cockpit
const cockpitGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.5, 16);
cockpitGeometry.rotateX(Math.PI / 2);
const cockpitMaterial = new THREE.MeshPhongMaterial({
color: 0x00ffff,
emissive: 0x006666,
specular: 0xffffff,
shininess: 100,
transparent: true,
opacity: 0.7
});
// Create engine glow
const engineGlowGeometry = new THREE.CylinderGeometry(0.3, 0.6, 1, 8);
const engineGlowMaterial = new THREE.MeshBasicMaterial({
color: 0x00ffff,
transparent: true,
opacity: 0.7,
blending: THREE.AdditiveBlending
});
// Assemble the ship
const ship = new THREE.Group();
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.set(0, 0, 0);
ship.add(body);
const leftWing = new THREE.Mesh(wingGeometry, wingMaterial);
leftWing.position.set(-1.2, 0, 0);
ship.add(leftWing);
const rightWing = new THREE.Mesh(wingGeometry, wingMaterial);
rightWing.position.set(1.2, 0, 0);
ship.add(rightWing);
const cockpit = new THREE.Mesh(cockpitGeometry, cockpitMaterial);
cockpit.position.set(0, 0.3, 0.5);
ship.add(cockpit);
const engineGlow = new THREE.Mesh(engineGlowGeometry, engineGlowMaterial);
engineGlow.position.set(0, 0, -1.5);
ship.add(engineGlow);
ship.castShadow = true;
ship.receiveShadow = true;
player = ship;
scene.add(player);
}
function createEnemy() {
const type = Math.floor(Math.random() * 3); // 3 different enemy types
let geometry, material, scale = 1;
switch(type) {
case 0: // Organic alien ship
geometry = new THREE.SphereGeometry(1, 16, 16);
material = new THREE.MeshPhongMaterial({
color: 0xcc00ff,
emissive: 0x660099,
specular: 0xffffff,
shininess: 30,
flatShading: false
});
break;
case 1: // Mechanical alien ship
geometry = new THREE.BoxGeometry(1.5, 0.8, 1.5);
material = new THREE.MeshPhongMaterial({
color: 0xff6600,
emissive: 0x993300,
specular: 0xffffff,
shininess: 20,
flatShading: false
});
break;
case 2: // Advanced alien mothership
const mothership = new THREE.Group();
// Main body
const mainBody = new THREE.Mesh(
new THREE.SphereGeometry(1.2, 16, 16),
new THREE.MeshPhongMaterial({
color: 0x00ff00,
emissive: 0x006600,
specular: 0xffffff,
shininess: 50
})
);
// Rings
const ring1 = new THREE.Mesh(
new THREE.TorusGeometry(1.5, 0.2, 16, 32),
new THREE.MeshPhongMaterial({
color: 0x00cc00,
emissive: 0x004400,
specular: 0xffffff,
shininess: 30
})
);
ring1.rotation.x = Math.PI / 2;
const ring2 = new THREE.Mesh(
new THREE.TorusGeometry(1.8, 0.15, 16, 32),
new THREE.MeshPhongMaterial({
color: 0x009900,
emissive: 0x002200,
specular: 0xffffff,
shininess: 20
})
);
ring2.rotation.z = Math.PI / 2;
mothership.add(mainBody);
mothership.add(ring1);
mothership.add(ring2);
const enemyMesh = mothership;
scale = 0.8;
break;
}
const enemyMesh = type !== 2 ? new THREE.Mesh(geometry, material) : geometry;
enemyMesh.scale.set(scale, scale, scale);
enemyMesh.castShadow = true;
enemyMesh.receiveShadow = true;
// Position enemy randomly in 3D space around player
const angle = Math.random() * Math.PI * 2;
const radius = 30 + Math.random() * 50;
const height = (Math.random() - 0.5) * 40;
const x = Math.cos(angle) * radius;
const y = height;
const z = Math.sin(angle) * radius;
enemyMesh.position.set(x, y, z);
// Make enemy face player
enemyMesh.lookAt(player.position);
scene.add(enemyMesh);
enemies.push({
mesh: enemyMesh,
type: type,
health: type === 2 ? 5 : (type === 1 ? 3 : 2),
speed: 0.05 + Math.random() * 0.05,
lastShot: 0,
shootInterval: 1500 + Math.random() * 1000
});
}
function createBullet(position, direction, isEnemy = false) {
const geometry = new THREE.SphereGeometry(0.2, 8, 8);
const material = new THREE.MeshPhongMaterial({
color: isEnemy ? 0xff0000 : 0x00ffff,
emissive: isEnemy ? 0x990000 : 0x006666,
specular: 0xffffff,
shininess: 100
});
const bullet = new THREE.Mesh(geometry, material);
bullet.position.copy(position);
bullet.userData = {
direction: direction.clone().normalize(),
speed: isEnemy ? 0.3 : 0.5,
isEnemy: isEnemy
};
scene.add(bullet);
if (isEnemy) {
enemyBullets.push(bullet);
} else {
bullets.push(bullet);
}
return bullet;
}
function createPowerup(position) {
const type = Math.floor(Math.random() * 2); // 0 = health, 1 = double fire
const geometry = new THREE.IcosahedronGeometry(0.8);
const material = new THREE.MeshPhongMaterial({
color: type === 0 ? 0x00ff00 : 0xffff00,
emissive: type === 0 ? 0x006600 : 0x666600,
specular: 0xffffff,
shininess: 100,
transparent: true,
opacity: 0.9
});
const powerup = new THREE.Mesh(geometry, material);
powerup.position.copy(position);
powerup.userData = {
type: type,
speed: 0.05,
rotationSpeed: 0.02
};
scene.add(powerup);
powerups.push(powerup);
return powerup;
}
function createExplosion(position, size = 1) {
const explosion = document.createElement('div');
explosion.className = 'explosion';
explosion.style.left = `${position.x * (window.innerWidth / 2) + window.innerWidth / 2}px`;
explosion.style.top = `${-position.y * (window.innerHeight / 2) + window.innerHeight / 2}px`;
explosion.style.width = `${size * 50}px`;
explosion.style.height = `${size * 50}px`;
document.getElementById('ui').appendChild(explosion);
explosions.push({
element: explosion,
startTime: Date.now()
});
// Remove after animation completes
setTimeout(() => {
if (explosion.parentNode) {
explosion.parentNode.removeChild(explosion);
}
}, 500);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onKeyDown(event) {
if (keys.hasOwnProperty(event.key)) {
keys[event.key] = true;
event.preventDefault();
}
// Toggle debug mode
if (event.key === '`') {
debugMode = !debugMode;
document.getElementById('debug-info').style.display = debugMode ? 'block' : 'none';
}
// Auto-fire when space is held down
if (event.key === ' ' && !gameOver) {
shoot();
}
}
function onKeyUp(event) {
if (keys.hasOwnProperty(event.key)) {
keys[event.key] = false;
event.preventDefault();
}
}
function onMouseMove(event) {
if (document.pointerLockElement === renderer.domElement && !isMobile) {
const movementX = event.movementX || 0;
const movementY = event.movementY || 0;
// Update player direction based on mouse movement
playerEuler.y -= movementX * 0.002;
playerEuler.x -= movementY * 0.002;
// Limit vertical rotation to prevent flipping
playerEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, playerEuler.x));
playerQuaternion.setFromEuler(playerEuler);
playerDirection.set(0, 0, -1).applyQuaternion(playerQuaternion);
}
}
function shoot() {
if (gameOver) return;
const bulletPosition = player.position.clone();
bulletPosition.add(playerDirection.clone().multiplyScalar(-2)); // Shoot from front of ship
// Create main bullet
createBullet(bulletPosition, playerDirection.clone().negate());
// If double fire is active, create two additional bullets at angles
if (doubleFireActive) {
const angle = 0.2;
const leftDir = playerDirection.clone().negate();
leftDir.applyAxisAngle(new THREE.Vector3(0, 1, 0), angle);
createBullet(bulletPosition, leftDir);
const rightDir = playerDirection.clone().negate();
rightDir.applyAxisAngle(new THREE.Vector3(0, 1, 0), -angle);
createBullet(bulletPosition, rightDir);
}
}
function updatePlayer(delta) {
if (gameOver) return;
// Apply boost if shift is pressed
currentSpeed = keys.Shift ? boostSpeed : playerSpeed;
// Reset velocity
playerVelocity.set(0, 0, 0);
// Calculate movement based on key states or joystick
if (isMobile && joystickActive) {
// Mobile joystick controls
const moveX = movementX;
const moveY = movementY;
// Apply movement relative to camera view
const forward = new THREE.Vector3(0, 0, -1);
const right = new THREE.Vector3(1, 0, 0);
playerVelocity.add(right.multiplyScalar(moveX * currentSpeed * delta * 60));
playerVelocity.add(forward.multiplyScalar(moveY * currentSpeed * delta * 60));
} else {
// Keyboard controls (now more responsive)
if (keys.w || keys.ArrowUp) playerVelocity.z -= currentSpeed * delta * 60;
if (keys.s || keys.ArrowDown) playerVelocity.z += currentSpeed * delta * 60;
if (keys.a || keys.ArrowLeft) playerVelocity.x -= currentSpeed * delta * 60;
if (keys.d || keys.ArrowRight) playerVelocity.x += currentSpeed * delta * 60;
// Roll controls
if (keys.q) player.rotation.z += playerRollSpeed * delta * 60;
if (keys.e) player.rotation.z -= playerRollSpeed * delta * 60;
}
// Apply movement direction rotation
playerVelocity.applyQuaternion(playerQuaternion);
// Apply movement
player.position.add(playerVelocity);
// Update player rotation based on direction
player.quaternion.copy(playerQuaternion);
// Auto-fire when space is held down
if (keys[' ']) {
const now = Date.now();
if (now - (player.lastShot || 0) > 300) { // Shoot every 300ms
shoot();
player.lastShot = now;
}
}
// Update camera position to follow player
camera.position.copy(player.position);
camera.position.add(playerDirection.clone().multiplyScalar(10)); // 10 units behind player
camera.lookAt(player.position);
// Debug info
if (debugMode) {
document.getElementById('debug-info').innerHTML = `
Position: ${player.position.x.toFixed(1)}, ${player.position.y.toFixed(1)}, ${player.position.z.toFixed(1)}<br>
Rotation: ${player.rotation.x.toFixed(2)}, ${player.rotation.y.toFixed(2)}, ${player.rotation.z.toFixed(2)}<br>
Speed: ${currentSpeed.toFixed(1)}<br>
Enemies: ${enemies.length}
`;
}
}
function updateEnemies(delta) {
const now = Date.now();
// Spawn new enemies
if (now - lastEnemySpawnTime > enemySpawnInterval && enemies.length < 15) {
createEnemy();
lastEnemySpawnTime = now;
// Make game harder over time by decreasing spawn interval
enemySpawnInterval = Math.max(500, enemySpawnInterval - 10);
}
// Update existing enemies
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
// Move toward player
const direction = new THREE.Vector3().subVectors(player.position, enemy.mesh.position).normalize();
enemy.mesh.position.addScaledVector(direction, enemy.speed * delta * 60);
// Make enemy face player
enemy.mesh.lookAt(player.position);
// Enemy shooting
if (now - enemy.lastShot > enemy.shootInterval) {
const bulletPos = enemy.mesh.position.clone();
const bulletDir = new THREE.Vector3().subVectors(player.position, bulletPos);
createBullet(bulletPos, bulletDir, true);
enemy.lastShot = now;
}
// Check distance to player for collision
if (enemy.mesh.position.distanceTo(player.position) < 3) {
health -= 20;
scene.remove(enemy.mesh);
enemies.splice(i, 1);
createExplosion(enemy.mesh.position, 1.5);
updateUI();
if (health <= 0) {
gameOver = true;
document.getElementById('game-over').style.display = 'block';
document.getElementById('game-over').querySelector('span').textContent = `Score: ${score}`;
}
continue;
}
// Remove if health <= 0
if (enemy.health <= 0) {
score += enemy.type === 2 ? 50 : (enemy.type === 1 ? 30 : 20);
createExplosion(enemy.mesh.position, enemy.type === 2 ? 2 : 1);
updateUI();
scene.remove(enemy.mesh);
enemies.splice(i, 1);
}
}
}
function updateBullets(delta) {
// Player bullets
for (let i = bullets.length - 1; i >= 0; i--) {
const bullet = bullets[i];
bullet.position.addScaledVector(bullet.userData.direction, bullet.userData.speed * delta * 60);
// Check collision with enemies
for (let j = enemies.length - 1; j >= 0; j--) {
const enemy = enemies[j];
if (bullet.position.distanceTo(enemy.mesh.position) < 1.5) {
enemy.health--;
scene.remove(bullet);
bullets.splice(i, 1);
break;
}
}
// Remove if too far away
if (bullet.position.distanceTo(player.position) > 100) {
scene.remove(bullet);
bullets.splice(i, 1);
}
}
// Enemy bullets
for (let i = enemyBullets.length - 1; i >= 0; i--) {
const bullet = enemyBullets[i];
bullet.position.addScaledVector(bullet.userData.direction, bullet.userData.speed * delta * 60);
// Check collision with player
if (bullet.position.distanceTo(player.position) < 2) {
health -= 10;
scene.remove(bullet);
enemyBullets.splice(i, 1);
createExplosion(bullet.position, 0.5);
updateUI();
if (health <= 0) {
gameOver = true;
document.getElementById('game-over').style.display = 'block';
document.getElementById('game-over').querySelector('span').textContent = `Score: ${score}`;
}
continue;
}
// Remove if too far away
if (bullet.position.distanceTo(player.position) > 100) {
scene.remove(bullet);
enemyBullets.splice(i, 1);
}
}
}
function updatePowerups(delta) {
const now = Date.now();
// Spawn new powerups
if (now - lastPowerupSpawnTime > powerupSpawnInterval && powerups.length < 3) {
// Position powerup randomly in 3D space around player
const angle = Math.random() * Math.PI * 2;
const radius = 20 + Math.random() * 30;
const height = (Math.random() - 0.5) * 20;
const x = Math.cos(angle) * radius;
const y = height;
const z = Math.sin(angle) * radius;
createPowerup(new THREE.Vector3(x, y, z));
lastPowerupSpawnTime = now;
}
// Update existing powerups
for (let i = powerups.length - 1; i >= 0; i--) {
const powerup = powerups[i];
// Rotate
powerup.rotation.x += powerup.userData.rotationSpeed;
powerup.rotation.y += powerup.userData.rotationSpeed;
// Check collision with player
if (powerup.position.distanceTo(player.position) < 2) {
if (powerup.userData.type === 0) {
// Health powerup
health = Math.min(100, health + 20);
} else {
// Double fire powerup
doubleFireActive = true;
document.getElementById('powerup-indicator').style.display = 'block';
// Clear any existing timeout
if (doubleFireTimeout) {
clearTimeout(doubleFireTimeout);
}
// Set timeout to deactivate double fire
doubleFireTimeout = setTimeout(() => {
doubleFireActive = false;
document.getElementById('powerup-indicator').style.display = 'none';
}, 10000);
}
scene.remove(powerup);
powerups.splice(i, 1);
updateUI();
continue;
}
}
}
function updateUI() {
document.getElementById('score-display').textContent = `SCORE: ${score}`;
document.getElementById('health-bar').style.width = `${health}%`;
document.getElementById('health-text').textContent = `${health}%`;
}
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (!gameOver) {
updatePlayer(delta);
updateEnemies(delta);
updateBullets(delta);
updatePowerups(delta);
// Rotate starfield for parallax effect
if (starField) {
starField.rotation.z += 0.0005;
}
}
renderer.render(scene, camera);
}
// Start the game
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=vikassabbi/vsspace" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>