|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>Advanced A-Frame 3D Space Shooter Game</title> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/aframe/1.2.0/aframe.min.js"></script> |
|
</head> |
|
<body> |
|
<a-scene fog="type: linear; color: #87CEEB; near: 0; far: 50"> |
|
<a-assets> |
|
<a-asset-item id="model1" src="1.obj"></a-asset-item> |
|
<a-asset-item id="model2" src="2.obj"></a-asset-item> |
|
<a-asset-item id="model3" src="3.obj"></a-asset-item> |
|
<a-asset-item id="model4" src="4.obj"></a-asset-item> |
|
<a-asset-item id="model5" src="5.obj"></a-asset-item> |
|
<a-asset-item id="model6" src="6.obj"></a-asset-item> |
|
<a-asset-item id="model7" src="7.obj"></a-asset-item> |
|
<a-asset-item id="model8" src="8.obj"></a-asset-item> |
|
<a-asset-item id="model9" src="9.obj"></a-asset-item> |
|
<a-asset-item id="model10" src="10.obj"></a-asset-item> |
|
</a-assets> |
|
<a-sky color="#87CEEB"></a-sky> |
|
<a-entity id="player" position="0 1.6 0"> |
|
<a-entity camera look-controls wasd-controls> |
|
<a-entity id="cursor" cursor="fuse: false;" position="0 0 -1" |
|
geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03" |
|
material="color: white; shader: flat" |
|
raycaster="objects: .enemy, .spawner"></a-entity> |
|
</a-entity> |
|
</a-entity> |
|
<a-entity id="scoreBar" position="-2 3 -5"> |
|
<a-box width="2" height="0.2" depth="0.1" color="#444444"></a-box> |
|
<a-box id="scoreIndicator" width="2" height="0.2" depth="0.1" color="#FFA500" scale="0 1 1"></a-box> |
|
<a-text value="Score" position="0 -0.3 0" align="center" color="white"></a-text> |
|
</a-entity> |
|
<a-entity id="healthBar" position="2 3 -5"> |
|
<a-box width="2" height="0.2" depth="0.1" color="#444444"></a-box> |
|
<a-box id="healthIndicator" width="2" height="0.2" depth="0.1" color="#00FF00" scale="1 1 1"></a-box> |
|
<a-text value="Health" position="0 -0.3 0" align="center" color="white"></a-text> |
|
</a-entity> |
|
<a-entity id="spawnCounter" position="0 3 -5"> |
|
<a-text value="Spawn Points: 0" align="center" color="white"></a-text> |
|
</a-entity> |
|
</a-scene> |
|
|
|
<script> |
|
(function() { |
|
const scene = document.querySelector('a-scene'); |
|
const player = document.querySelector('#player'); |
|
const camera = document.querySelector('[camera]'); |
|
const cursor = document.querySelector('#cursor'); |
|
const scoreIndicator = document.querySelector('#scoreIndicator'); |
|
const healthIndicator = document.querySelector('#healthIndicator'); |
|
const spawnCounter = document.querySelector('#spawnCounter'); |
|
let score = 0, health = 10000, enemies = new Set(), spawners = new Set(), explosionCount = 0; |
|
const maxScore = 1000; |
|
|
|
function updateDisplays() { |
|
scoreIndicator.setAttribute('scale', `${Math.min(score / maxScore, 1)} 1 1`); |
|
healthIndicator.setAttribute('scale', `${Math.max(health / 10000, 0)} 1 1`); |
|
spawnCounter.setAttribute('text', `value: Spawn Points: ${spawners.size}`); |
|
} |
|
|
|
function createEntity(type, attributes) { |
|
const entity = document.createElement('a-entity'); |
|
Object.entries(attributes).forEach(([key, value]) => entity.setAttribute(key, value)); |
|
scene.appendChild(entity); |
|
return entity; |
|
} |
|
|
|
function generateSpawnerPosition() { |
|
const angle = Math.random() * Math.PI * 2; |
|
const distance = 10 + Math.random() * 10; |
|
return new THREE.Vector3( |
|
Math.cos(angle) * distance, |
|
1.6, |
|
Math.sin(angle) * distance - 5 |
|
); |
|
} |
|
|
|
function createSpawner(position = null) { |
|
const modelNumber = Math.floor(Math.random() * 10) + 1; |
|
const spawner = createEntity('spawner', { |
|
class: 'spawner', |
|
'obj-model': `obj: #model${modelNumber}`, |
|
material: {color: '#00FF00', metalness: 0.7, roughness: 0.3}, |
|
position: position || generateSpawnerPosition() |
|
}); |
|
spawner.health = 100; |
|
spawners.add(spawner); |
|
updateDisplays(); |
|
} |
|
|
|
function createEnemy(spawnPosition) { |
|
if (enemies.size >= 30) return; |
|
const enemy = createEntity('enemy', { |
|
class: 'enemy', |
|
geometry: {primitive: 'sphere', radius: 0.5}, |
|
material: {color: '#FF0000', metalness: 0.7, roughness: 0.3, opacity: 0.5, transparent: true}, |
|
position: new THREE.Vector3( |
|
spawnPosition.x + (Math.random() - 0.5) * 2, |
|
1.6, |
|
spawnPosition.z + (Math.random() - 0.5) * 2 |
|
) |
|
}); |
|
enemy.appendChild(createEntity('particles', { |
|
'particle-system': {preset: 'dust', particleCount: 100, color: '#FF0000, #FF5500', size: 0.1, dur: 1000, accelerationValue: '0 -9.8 0'} |
|
})); |
|
enemy.creationTime = Date.now(); |
|
enemies.add(enemy); |
|
updateDisplays(); |
|
} |
|
|
|
function fireLaser(source, color, direction, isPlayer = false) { |
|
const sourcePosition = source.object3D.position.clone(); |
|
sourcePosition.y = 1.6; |
|
|
|
const laserLength = 100; |
|
const endPosition = sourcePosition.clone().add(direction.multiplyScalar(laserLength)); |
|
|
|
const laser = createEntity('laser', { |
|
class: 'laser', |
|
geometry: { |
|
primitive: 'cylinder', |
|
radius: isPlayer ? 0.2 : 0.1, |
|
height: laserLength |
|
}, |
|
material: { |
|
color: color, |
|
opacity: isPlayer ? 0.5 : 1, |
|
transparent: isPlayer, |
|
shader: 'flat' |
|
}, |
|
position: sourcePosition.clone().add(direction.multiplyScalar(laserLength / 2)) |
|
}); |
|
|
|
laser.object3D.lookAt(endPosition); |
|
laser.object3D.rotateX(Math.PI / 2); |
|
|
|
laser.setAttribute('animation', { |
|
property: 'scale', |
|
to: '1 0.01 1', |
|
dur: 1000, |
|
easing: 'linear' |
|
}); |
|
|
|
setTimeout(() => { |
|
scene.removeChild(laser); |
|
}, 1000); |
|
|
|
|
|
const targets = document.querySelectorAll('.enemy, .spawner'); |
|
targets.forEach(target => { |
|
const targetPos = target.object3D.position; |
|
if (isPointNearLine(sourcePosition, endPosition, targetPos, 0.5)) { |
|
handleHit(target, targetPos); |
|
} |
|
}); |
|
} |
|
|
|
function isPointNearLine(lineStart, lineEnd, point, threshold) { |
|
const lineVec = new THREE.Vector3().subVectors(lineEnd, lineStart); |
|
const pointVec = new THREE.Vector3().subVectors(point, lineStart); |
|
const lineLengthSq = lineVec.lengthSq(); |
|
const dotProduct = pointVec.dot(lineVec); |
|
const t = Math.max(0, Math.min(1, dotProduct / lineLengthSq)); |
|
const projection = lineStart.clone().add(lineVec.multiplyScalar(t)); |
|
return point.distanceTo(projection) < threshold; |
|
} |
|
|
|
function handleHit(target, position) { |
|
if (target.classList.contains('enemy')) { |
|
createExplosion(position); |
|
scene.removeChild(target); |
|
enemies.delete(target); |
|
score += 10; |
|
health = Math.min(10000, health + 20); |
|
} else if (target.classList.contains('spawner')) { |
|
target.health -= 20; |
|
createExplosion(position); |
|
if (target.health <= 0) { |
|
scene.removeChild(target); |
|
spawners.delete(target); |
|
score += 50; |
|
createSpawner(generateSpawnerPosition()); |
|
} |
|
} |
|
updateDisplays(); |
|
} |
|
|
|
function createExplosion(position) { |
|
const explosion = createEntity('explosion', {position: position}); |
|
for (let i = 0; i < 20; i++) { |
|
const particle = createEntity('particle', { |
|
geometry: {primitive: 'sphere', radius: 0.1}, |
|
material: {color: '#FFA500'}, |
|
position: `${(Math.random() - 0.5) * 2} ${(Math.random() - 0.5) * 2} ${(Math.random() - 0.5) * 2}` |
|
}); |
|
particle.setAttribute('animation', {property: 'position', to: `${(Math.random() - 0.5) * 2} ${(Math.random() - 0.5) * 2} ${(Math.random() - 0.5) * 2}`, dur: 1000, easing: 'easeOutQuad'}); |
|
particle.setAttribute('animation__opacity', {property: 'material.opacity', from: 1, to: 0, dur: 1000, easing: 'easeOutQuad'}); |
|
explosion.appendChild(particle); |
|
} |
|
setTimeout(() => scene.removeChild(explosion), 1000); |
|
explosionCount++; |
|
updateDisplays(); |
|
} |
|
|
|
function shootFromCamera() { |
|
const direction = new THREE.Vector3(0, 0, -1); |
|
direction.applyQuaternion(camera.object3D.quaternion); |
|
fireLaser(camera, '#FFFF00', direction, true); |
|
} |
|
|
|
function updateGame() { |
|
const now = Date.now(); |
|
const cameraPosition = camera.object3D.position; |
|
|
|
enemies.forEach(enemy => { |
|
if (now - enemy.creationTime > 20000) { |
|
scene.removeChild(enemy); |
|
enemies.delete(enemy); |
|
} else { |
|
const direction = new THREE.Vector3().subVectors(cameraPosition, enemy.object3D.position).normalize(); |
|
enemy.object3D.position.add(direction.multiplyScalar(0.05)); |
|
enemy.object3D.position.y = 1.6; |
|
if (enemy.object3D.position.distanceTo(cameraPosition) < 2) { |
|
createExplosion(enemy.object3D.position); |
|
scene.removeChild(enemy); |
|
enemies.delete(enemy); |
|
health = Math.max(0, health - 50); |
|
} |
|
if (Math.random() < 0.01) { |
|
const enemyDirection = new THREE.Vector3().subVectors(cameraPosition, enemy.object3D.position).normalize(); |
|
fireLaser(enemy, '#000000', enemyDirection); |
|
} |
|
} |
|
}); |
|
|
|
spawners.forEach(spawner => { |
|
const direction = new THREE.Vector3().subVectors(cameraPosition, spawner.object3D.position).normalize(); |
|
spawner.object3D.position.add(direction.multiplyScalar(0.02)); |
|
spawner.object3D.position.y = 1.6; |
|
spawner.object3D.lookAt(cameraPosition); |
|
if (spawner.object3D.position.distanceTo(cameraPosition) < 3) { |
|
createExplosion(spawner.object3D.position); |
|
scene.removeChild(spawner); |
|
spawners.delete(spawner); |
|
health = Math.max(0, health - 100); |
|
} |
|
if (Math.random() < 0.005 && enemies.size < 30) { |
|
createEnemy(spawner.object3D.position); |
|
} |
|
}); |
|
|
|
while (spawners.size < 5) createSpawner(); |
|
updateDisplays(); |
|
} |
|
|
|
document.addEventListener('keydown', (event) => { if (event.code === 'Space') shootFromCamera(); }); |
|
scene.addEventListener('click', shootFromCamera); |
|
|
|
updateDisplays(); |
|
setInterval(updateGame, 1000 / 60); |
|
})(); |
|
</script> |
|
</body> |
|
</html> |