|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>Chofko the Octopus: Dynamic Life Simulator</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/cannon.js/0.6.2/cannon.min.js"></script> |
|
<style> |
|
.controls { |
|
position: absolute; |
|
bottom: 20px; |
|
left: 50%; |
|
transform: translateX(-50%); |
|
display: flex; |
|
gap: 10px; |
|
} |
|
.controls button { |
|
padding: 10px 20px; |
|
font-size: 16px; |
|
cursor: pointer; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<a-scene physics="debug: true;"> |
|
<a-assets> |
|
<img id="sky" src="/api/placeholder/1024/512" alt="surrealist sky"> |
|
</a-assets> |
|
|
|
<a-sky src="#sky"></a-sky> |
|
|
|
<a-entity id="chofko" dynamic-body position="0 1.5 -3"> |
|
<a-sphere color="#FF69B4" radius="0.5"></a-sphere> |
|
<a-text value="Chofko" position="0 0.75 0" align="center" color="#FFF" scale="0.5 0.5 0.5"></a-text> |
|
<a-entity id="tentacles"></a-entity> |
|
</a-entity> |
|
|
|
<a-plane static-body position="0 0 -4" rotation="-90 0 0" width="50" height="50" color="#7BC8A4"></a-plane> |
|
|
|
<a-entity id="particles" position="0 0.1 -4"></a-entity> |
|
|
|
<a-entity id="camera" camera look-controls position="0 10 0" rotation="-45 0 0"></a-entity> |
|
</a-scene> |
|
|
|
<div class="controls"> |
|
<button onclick="move('left')">Left</button> |
|
<button onclick="move('right')">Right</button> |
|
<button onclick="move('up')">Up</button> |
|
<button onclick="move('down')">Down</button> |
|
</div> |
|
|
|
<script> |
|
|
|
const chofko = document.querySelector('#chofko'); |
|
let velocity = new THREE.Vector3(); |
|
const maxSpeed = 0.1; |
|
const acceleration = 0.01; |
|
const deceleration = 0.995; |
|
|
|
function move(direction) { |
|
switch(direction) { |
|
case 'left': |
|
velocity.x -= acceleration; |
|
break; |
|
case 'right': |
|
velocity.x += acceleration; |
|
break; |
|
case 'up': |
|
velocity.z -= acceleration; |
|
break; |
|
case 'down': |
|
velocity.z += acceleration; |
|
break; |
|
} |
|
velocity.clampScalar(-maxSpeed, maxSpeed); |
|
} |
|
|
|
|
|
const tentacleColors = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF', '#FFA500', '#800080']; |
|
const tentaclesEntity = document.querySelector('#tentacles'); |
|
|
|
for (let i = 0; i < 8; i++) { |
|
const angle = (i / 8) * Math.PI * 2; |
|
const x = Math.cos(angle) * 0.5; |
|
const z = Math.sin(angle) * 0.5; |
|
const tentacle = document.createElement('a-entity'); |
|
tentacle.setAttribute('geometry', {primitive: 'cylinder', radius: 0.05, height: 1}); |
|
tentacle.setAttribute('material', {color: tentacleColors[i]}); |
|
tentacle.setAttribute('position', {x: x, y: -0.5, z: z}); |
|
tentacle.setAttribute('rotation', {x: 90, y: 0, z: (i * 45) + 90}); |
|
tentaclesEntity.appendChild(tentacle); |
|
} |
|
|
|
|
|
const particlesEntity = document.querySelector('#particles'); |
|
const particleCount = 100; |
|
const particles = []; |
|
|
|
class Particle { |
|
constructor(x, z, color) { |
|
this.entity = document.createElement('a-sphere'); |
|
this.entity.setAttribute('radius', 0.1); |
|
this.entity.setAttribute('color', color); |
|
this.entity.setAttribute('position', {x: x, y: 0.1, z: z}); |
|
particlesEntity.appendChild(this.entity); |
|
|
|
this.velocity = new THREE.Vector3( |
|
(Math.random() - 0.5) * 0.02, |
|
0, |
|
(Math.random() - 0.5) * 0.02 |
|
); |
|
|
|
particles.push(this); |
|
} |
|
|
|
update() { |
|
const position = this.entity.getAttribute('position'); |
|
position.x += this.velocity.x; |
|
position.z += this.velocity.z; |
|
|
|
|
|
if (Math.abs(position.x) > 25) this.velocity.x *= -1; |
|
if (Math.abs(position.z) > 25) this.velocity.z *= -1; |
|
|
|
this.entity.setAttribute('position', position); |
|
} |
|
} |
|
|
|
|
|
for (let i = 0; i < particleCount; i++) { |
|
new Particle( |
|
(Math.random() - 0.5) * 50, |
|
(Math.random() - 0.5) * 50, |
|
`hsl(${Math.random() * 360}, 100%, 50%)` |
|
); |
|
} |
|
|
|
|
|
function breed(p1, p2) { |
|
const x = (p1.entity.getAttribute('position').x + p2.entity.getAttribute('position').x) / 2; |
|
const z = (p1.entity.getAttribute('position').z + p2.entity.getAttribute('position').z) / 2; |
|
const color = p1.entity.getAttribute('color'); |
|
new Particle(x, z, color); |
|
} |
|
|
|
|
|
function gameLoop() { |
|
|
|
velocity.multiplyScalar(deceleration); |
|
const position = chofko.getAttribute('position'); |
|
position.x += velocity.x; |
|
position.z += velocity.z; |
|
chofko.setAttribute('position', position); |
|
|
|
|
|
particles.forEach(p => p.update()); |
|
|
|
|
|
for (let i = 0; i < particles.length; i++) { |
|
for (let j = i + 1; j < particles.length; j++) { |
|
const p1 = particles[i].entity.getAttribute('position'); |
|
const p2 = particles[j].entity.getAttribute('position'); |
|
const distance = new THREE.Vector3(p1.x, p1.y, p1.z).distanceTo(new THREE.Vector3(p2.x, p2.y, p2.z)); |
|
|
|
if (distance < 0.3 && Math.random() < 0.001 && particles.length < 200) { |
|
breed(particles[i], particles[j]); |
|
} |
|
} |
|
} |
|
|
|
requestAnimationFrame(gameLoop); |
|
} |
|
|
|
gameLoop(); |
|
|
|
|
|
document.addEventListener('keydown', (event) => { |
|
switch(event.key) { |
|
case 'ArrowLeft': |
|
move('left'); |
|
break; |
|
case 'ArrowRight': |
|
move('right'); |
|
break; |
|
case 'ArrowUp': |
|
move('up'); |
|
break; |
|
case 'ArrowDown': |
|
move('down'); |
|
break; |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |