|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>Enhanced A-Frame Space Shooter with 3D Editor</title> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/aframe/1.2.0/aframe.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script> |
|
<style> |
|
.ui-panel { |
|
position: fixed; |
|
top: 20px; |
|
left: 20px; |
|
z-index: 9999; |
|
background: rgba(0,0,0,0.7); |
|
padding: 10px; |
|
border-radius: 5px; |
|
color: white; |
|
font-family: Arial, sans-serif; |
|
} |
|
.ui-panel button { |
|
display: block; |
|
margin: 5px 0; |
|
padding: 5px 10px; |
|
} |
|
.animation-panel { |
|
position: fixed; |
|
top: 20px; |
|
right: 20px; |
|
z-index: 9999; |
|
background: rgba(0,0,0,0.7); |
|
padding: 10px; |
|
border-radius: 5px; |
|
color: white; |
|
font-family: Arial, sans-serif; |
|
} |
|
.animation-panel input { |
|
width: 50px; |
|
margin-right: 5px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="ui-panel"> |
|
<button onclick="addPrimitive('box')">Add Box</button> |
|
<button onclick="addPrimitive('sphere')">Add Sphere</button> |
|
<button onclick="addPrimitive('cylinder')">Add Cylinder</button> |
|
<button onclick="addPrimitive('cone')">Add Cone</button> |
|
<button onclick="addPrimitive('torus')">Add Torus</button> |
|
<button onclick="toggleGameMode()">Toggle Game Mode</button> |
|
</div> |
|
|
|
<div class="animation-panel"> |
|
<h3>Object Controls</h3> |
|
<p>Position:</p> |
|
<input id="px" type="number" placeholder="X"> |
|
<input id="py" type="number" placeholder="Y"> |
|
<input id="pz" type="number" placeholder="Z"> |
|
<p>Rotation (degrees):</p> |
|
<input id="rx" type="number" placeholder="X"> |
|
<input id="ry" type="number" placeholder="Y"> |
|
<input id="rz" type="number" placeholder="Z"> |
|
<button onclick="applyTransform()">Apply Transform</button> |
|
<button onclick="placeInFrontOfCamera()">Place in Front of Camera</button> |
|
</div> |
|
|
|
<a-scene fog="type: linear; color: #87CEEB; near: 0; far: 50"> |
|
<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, .editable"></a-entity> |
|
</a-entity> |
|
</a-entity> |
|
<a-entity id="scoreBar" position="0 3 -5"> |
|
<a-box width="1" height="0.1" depth="0.1" color="#444444"></a-box> |
|
<a-box id="scoreIndicator" width="1" height="0.1" depth="0.1" color="#FFA500" scale="0 1 1"></a-box> |
|
<a-text value="Score" position="0 0.1 0" align="center" color="white"></a-text> |
|
</a-entity> |
|
<a-entity id="healthDisplay" position="0 2.5 -5" scale="5 5 5"></a-entity> |
|
<a-entity id="unitCounters" position="-0.8 0.6 -1.5" scale="0.5 0.5 0.5"></a-entity> |
|
</a-scene> |
|
|
|
<script> |
|
|
|
|
|
|
|
let selectedObject = null; |
|
let isGameMode = true; |
|
const simplex = new SimplexNoise(); |
|
|
|
function generateProceduralTexture(size = 256) { |
|
const canvas = document.createElement('canvas'); |
|
canvas.width = canvas.height = size; |
|
const ctx = canvas.getContext('2d'); |
|
const imageData = ctx.createImageData(size, size); |
|
const data = imageData.data; |
|
|
|
for (let x = 0; x < size; x++) { |
|
for (let y = 0; y < size; y++) { |
|
const i = (x + y * size) * 4; |
|
const value = (simplex.noise2D(x / 20, y / 20) + 1) / 2; |
|
const hue = Math.floor(value * 360); |
|
const [r, g, b] = HSVtoRGB(hue / 360, 0.7, 0.9); |
|
data[i] = r; |
|
data[i + 1] = g; |
|
data[i + 2] = b; |
|
data[i + 3] = 255; |
|
} |
|
} |
|
|
|
ctx.putImageData(imageData, 0, 0); |
|
return canvas.toDataURL(); |
|
} |
|
|
|
function generateBumpMap(size = 256) { |
|
const canvas = document.createElement('canvas'); |
|
canvas.width = canvas.height = size; |
|
const ctx = canvas.getContext('2d'); |
|
const imageData = ctx.createImageData(size, size); |
|
const data = imageData.data; |
|
|
|
for (let x = 0; x < size; x++) { |
|
for (let y = 0; y < size; y++) { |
|
const i = (x + y * size) * 4; |
|
const value = (simplex.noise2D(x / 10, y / 10) + 1) / 2; |
|
const intensity = Math.floor(value * 255); |
|
data[i] = data[i + 1] = data[i + 2] = intensity; |
|
data[i + 3] = 255; |
|
} |
|
} |
|
|
|
ctx.putImageData(imageData, 0, 0); |
|
return canvas.toDataURL(); |
|
} |
|
|
|
function HSVtoRGB(h, s, v) { |
|
let r, g, b, i, f, p, q, t; |
|
i = Math.floor(h * 6); |
|
f = h * 6 - i; |
|
p = v * (1 - s); |
|
q = v * (1 - f * s); |
|
t = v * (1 - (1 - f) * s); |
|
switch (i % 6) { |
|
case 0: r = v, g = t, b = p; break; |
|
case 1: r = q, g = v, b = p; break; |
|
case 2: r = p, g = v, b = t; break; |
|
case 3: r = p, g = q, b = v; break; |
|
case 4: r = t, g = p, b = v; break; |
|
case 5: r = v, g = p, b = q; break; |
|
} |
|
return [ |
|
Math.round(r * 255), |
|
Math.round(g * 255), |
|
Math.round(b * 255) |
|
]; |
|
} |
|
|
|
function addPrimitive(type) { |
|
const scene = document.querySelector('a-scene'); |
|
const newEntity = document.createElement('a-entity'); |
|
newEntity.setAttribute('geometry', `primitive: ${type}`); |
|
|
|
const textureUrl = generateProceduralTexture(); |
|
const bumpMapUrl = generateBumpMap(); |
|
|
|
newEntity.setAttribute('material', `src: ${textureUrl}; normalMap: ${bumpMapUrl}`); |
|
newEntity.setAttribute('position', '0 1.5 -3'); |
|
newEntity.setAttribute('class', 'editable'); |
|
newEntity.addEventListener('click', function() { selectObject(this); }); |
|
scene.appendChild(newEntity); |
|
} |
|
|
|
function selectObject(obj) { |
|
if (selectedObject) { |
|
selectedObject.setAttribute('material', 'emissive', '#000000'); |
|
} |
|
selectedObject = obj; |
|
selectedObject.setAttribute('material', 'emissive', '#FF0000'); |
|
|
|
|
|
const position = selectedObject.getAttribute('position'); |
|
document.getElementById('px').value = position.x; |
|
document.getElementById('py').value = position.y; |
|
document.getElementById('pz').value = position.z; |
|
|
|
|
|
const rotation = selectedObject.getAttribute('rotation'); |
|
document.getElementById('rx').value = rotation.x; |
|
document.getElementById('ry').value = rotation.y; |
|
document.getElementById('rz').value = rotation.z; |
|
} |
|
|
|
function applyTransform() { |
|
if (!selectedObject) { |
|
alert('Please select an object first'); |
|
return; |
|
} |
|
|
|
const px = parseFloat(document.getElementById('px').value) || 0; |
|
const py = parseFloat(document.getElementById('py').value) || 0; |
|
const pz = parseFloat(document.getElementById('pz').value) || 0; |
|
const rx = parseFloat(document.getElementById('rx').value) || 0; |
|
const ry = parseFloat(document.getElementById('ry').value) || 0; |
|
const rz = parseFloat(document.getElementById('rz').value) || 0; |
|
|
|
selectedObject.setAttribute('position', `${px} ${py} ${pz}`); |
|
selectedObject.setAttribute('rotation', `${rx} ${ry} ${rz}`); |
|
} |
|
|
|
function placeInFrontOfCamera() { |
|
if (!selectedObject) { |
|
alert('Please select an object first'); |
|
return; |
|
} |
|
|
|
const camera = document.querySelector('[camera]'); |
|
const cameraPosition = camera.getAttribute('position'); |
|
const cameraRotation = camera.getAttribute('rotation'); |
|
|
|
|
|
const radians = THREE.MathUtils.degToRad(cameraRotation.y); |
|
const x = cameraPosition.x - Math.sin(radians) * 3; |
|
const z = cameraPosition.z - Math.cos(radians) * 3; |
|
|
|
selectedObject.setAttribute('position', `${x} ${cameraPosition.y} ${z}`); |
|
|
|
|
|
document.getElementById('px').value = x; |
|
document.getElementById('py').value = cameraPosition.y; |
|
document.getElementById('pz').value = z; |
|
} |
|
|
|
function toggleGameMode() { |
|
isGameMode = !isGameMode; |
|
const cursor = document.querySelector('#cursor'); |
|
if (isGameMode) { |
|
cursor.setAttribute('raycaster', 'objects', '.enemy, .spawner'); |
|
} else { |
|
cursor.setAttribute('raycaster', 'objects', '.editable'); |
|
} |
|
} |
|
|
|
|
|
const scene = document.querySelector('a-scene'); |
|
scene.addEventListener('click', (event) => { |
|
if (isGameMode) { |
|
fireLaser(player, '#00FF00'); |
|
} else if (event.detail.intersectedEl && event.detail.intersectedEl.classList.contains('editable')) { |
|
selectObject(event.detail.intersectedEl); |
|
} |
|
}); |
|
|
|
|
|
updateHealthDisplay(); |
|
updateUnitCounters(); |
|
|
|
setInterval(() => { |
|
updateGame(); |
|
score++; |
|
updateScoreDisplay(); |
|
}, 1000); |
|
</script> |
|
</body> |
|
</html> |