Spaces:
Running
Running
Update index.html
Browse files- index.html +61 -196
index.html
CHANGED
@@ -95,7 +95,6 @@
|
|
95 |
<div class="ui-container" id="race-ui">
|
96 |
<h2>Starship Circuit Commander</h2>
|
97 |
<div id="time">Time: 0</div>
|
98 |
-
<div id="score">Score: 0</div>
|
99 |
<div id="status">Status: Active</div>
|
100 |
</div>
|
101 |
<div class="sidebar" id="sidebar">
|
@@ -192,7 +191,6 @@
|
|
192 |
// Game state
|
193 |
let gameState = 'racing';
|
194 |
let raceTime = 0;
|
195 |
-
let score = 0;
|
196 |
let currentTrack = 'Phelps Island Drift';
|
197 |
let lastGatePass = 0;
|
198 |
let isJumping = false;
|
@@ -264,15 +262,10 @@
|
|
264 |
// Player ship
|
265 |
let playerShip = null;
|
266 |
let playerData = null;
|
267 |
-
function createShip(typeIndex, isPlayer
|
268 |
const type = shipTypes[typeIndex];
|
269 |
const ship = new THREE.Group();
|
270 |
-
const
|
271 |
-
x: type.scale.x * scaleMultiplier,
|
272 |
-
y: type.scale.y * scaleMultiplier,
|
273 |
-
z: type.scale.z * scaleMultiplier
|
274 |
-
};
|
275 |
-
const bodyGeometry = new THREE.BoxGeometry(scale.x, scale.y, scale.z);
|
276 |
const bodyMaterial = new THREE.MeshStandardMaterial({
|
277 |
color: isPlayer ? 0x00ff00 : 0xff0000,
|
278 |
metalness: 0.8,
|
@@ -281,43 +274,38 @@
|
|
281 |
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
282 |
ship.add(body);
|
283 |
if (type.name !== 'Rocketman') {
|
284 |
-
const thrusterGeometry = new THREE.CylinderGeometry(0.2
|
285 |
const thrusterMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
|
286 |
for (let i = 0; i < 4; i++) {
|
287 |
const thruster = new THREE.Mesh(thrusterGeometry, thrusterMaterial);
|
288 |
thruster.position.set(
|
289 |
-
(i % 2 === 0 ? 0.5 : -0.5) * scale.x * 0.8,
|
290 |
0,
|
291 |
-
(i < 2 ? 0.5 : -0.5) * scale.z * 0.8
|
292 |
);
|
293 |
thruster.rotation.x = Math.PI / 2;
|
294 |
ship.add(thruster);
|
295 |
}
|
296 |
} else {
|
297 |
-
const jetpackGeometry = new THREE.CylinderGeometry(0.3
|
298 |
const jetpack = new THREE.Mesh(jetpackGeometry, new THREE.MeshStandardMaterial({ color: 0xaaaaaa }));
|
299 |
-
jetpack.position.set(0, -0.5
|
300 |
ship.add(jetpack);
|
301 |
}
|
302 |
ship.position.y = 10;
|
303 |
ship.castShadow = true;
|
304 |
-
const maxHP = type.maxHP * scaleMultiplier;
|
305 |
ship.userData = {
|
306 |
type: type.name,
|
307 |
maxSpeed: type.speed,
|
308 |
acceleration: type.accel,
|
309 |
-
thrusters: Array(4).fill(maxHP),
|
310 |
-
body: { front: maxHP, back: maxHP, left: maxHP, right: maxHP },
|
311 |
active: true,
|
312 |
speed: 0,
|
313 |
currentWaypoint: 0,
|
314 |
-
lastGatePass: 0
|
315 |
-
isPlayer: isPlayer,
|
316 |
-
splitCount: splitCount,
|
317 |
-
typeIndex: typeIndex
|
318 |
};
|
319 |
scene.add(ship);
|
320 |
-
if (!isPlayer) aiShips.push(ship);
|
321 |
return ship;
|
322 |
}
|
323 |
|
@@ -326,9 +314,8 @@
|
|
326 |
const typeIndex = Math.floor(Math.random() * shipTypes.length);
|
327 |
playerShip = createShip(typeIndex, true);
|
328 |
playerData = playerShip.userData;
|
|
|
329 |
Object.keys(skills).forEach(skill => skills[skill].level = 0);
|
330 |
-
score = 0;
|
331 |
-
document.getElementById('score').textContent = `Score: ${score}`;
|
332 |
updateCharacterSheet();
|
333 |
updateSkillsSheet();
|
334 |
}
|
@@ -346,51 +333,20 @@
|
|
346 |
const startGate = new THREE.Mesh(gateGeometry, gateMaterial);
|
347 |
scene.add(startGate);
|
348 |
|
349 |
-
//
|
350 |
-
const
|
351 |
-
function
|
352 |
-
const geometry = new THREE.
|
353 |
-
const material = new THREE.
|
354 |
-
const
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
// Flame trail
|
360 |
-
const flameCount = 10;
|
361 |
-
const flameGeometry = new THREE.BufferGeometry();
|
362 |
-
const flamePositions = new Float32Array(flameCount * 3);
|
363 |
-
const flameColors = new Float32Array(flameCount * 3);
|
364 |
-
for (let i = 0; i < flameCount; i++) {
|
365 |
-
const i3 = i * 3;
|
366 |
-
flamePositions[i3] = 0;
|
367 |
-
flamePositions[i3 + 1] = 0;
|
368 |
-
flamePositions[i3 + 2] = 0;
|
369 |
-
flameColors[i3] = 1;
|
370 |
-
flameColors[i3 + 1] = 0;
|
371 |
-
flameColors[i3 + 2] = 0;
|
372 |
-
}
|
373 |
-
flameGeometry.setAttribute('position', new THREE.BufferAttribute(flamePositions, 3));
|
374 |
-
flameGeometry.setAttribute('color', new THREE.BufferAttribute(flameColors, 3));
|
375 |
-
const flameMaterial = new THREE.PointsMaterial({
|
376 |
-
size: 0.2,
|
377 |
-
vertexColors: true,
|
378 |
-
transparent: true,
|
379 |
-
opacity: 0.5
|
380 |
-
});
|
381 |
-
const flame = new THREE.Points(flameGeometry, flameMaterial);
|
382 |
-
flame.position.z = -0.3;
|
383 |
-
missile.add(flame);
|
384 |
-
|
385 |
-
missile.userData = {
|
386 |
-
target: target,
|
387 |
-
speed: 1,
|
388 |
-
lifetime: 300,
|
389 |
damage: skills.shotPower.effect(skills.shotPower.level)
|
390 |
};
|
391 |
-
scene.add(
|
392 |
-
|
393 |
-
return missile;
|
394 |
}
|
395 |
|
396 |
// Particle system for explosions
|
@@ -433,25 +389,17 @@
|
|
433 |
}
|
434 |
|
435 |
// Building and column generation
|
436 |
-
function createBuilding(x, z, width, depth, height
|
437 |
-
const geometry = new THREE.BoxGeometry(width
|
438 |
const material = new THREE.MeshStandardMaterial({
|
439 |
color: Math.random() * 0xffffff,
|
440 |
roughness: 0.7,
|
441 |
metalness: 0.3
|
442 |
});
|
443 |
const building = new THREE.Mesh(geometry, material);
|
444 |
-
building.position.set(x,
|
445 |
building.castShadow = true;
|
446 |
building.receiveShadow = true;
|
447 |
-
building.userData = {
|
448 |
-
splitCount: splitCount,
|
449 |
-
maxHP: 10 * scaleMultiplier,
|
450 |
-
currentHP: 10 * scaleMultiplier,
|
451 |
-
width: width,
|
452 |
-
depth: depth,
|
453 |
-
height: height
|
454 |
-
};
|
455 |
return building;
|
456 |
}
|
457 |
|
@@ -723,24 +671,17 @@
|
|
723 |
// Shooting
|
724 |
let canShoot = true;
|
725 |
let shotCooldown = 200;
|
726 |
-
const raycaster = new THREE.Raycaster();
|
727 |
-
const mouse = new THREE.Vector2();
|
728 |
document.addEventListener('mousedown', (event) => {
|
729 |
if (event.button === 0 && canShoot && gameState === 'racing' && playerData.active) {
|
730 |
canShoot = false;
|
731 |
setTimeout(() => canShoot = true, shotCooldown);
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
const
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
const position = playerShip.position.clone().add(new THREE.Vector3(0, 0, -2));
|
740 |
-
const count = skills.shotCount.effect(skills.shotCount.level);
|
741 |
-
for (let i = 0; i < count; i++) {
|
742 |
-
createMissile(position.clone().add(new THREE.Vector3((Math.random() - 0.5) * 0.5, (Math.random() - 0.5) * 0.5, 0)), target);
|
743 |
-
}
|
744 |
}
|
745 |
}
|
746 |
});
|
@@ -805,36 +746,13 @@
|
|
805 |
checkShipStatus(ship);
|
806 |
}
|
807 |
|
808 |
-
function checkShipStatus(ship
|
809 |
const totalHP = ship.userData.thrusters.reduce((a, b) => a + b, 0) +
|
810 |
Object.values(ship.userData.body).reduce((a, b) => a + b, 0);
|
811 |
if (totalHP <= 0) {
|
812 |
ship.userData.active = false;
|
813 |
ship.visible = false;
|
814 |
createExplosion(ship.position);
|
815 |
-
if (ship.userData.splitCount < 3) {
|
816 |
-
const newScale = 0.5;
|
817 |
-
const offsets = [
|
818 |
-
new THREE.Vector3(1, 0, 1),
|
819 |
-
new THREE.Vector3(-1, 0, -1)
|
820 |
-
];
|
821 |
-
offsets.forEach(offset => {
|
822 |
-
const newShip = createShip(
|
823 |
-
ship.userData.typeIndex,
|
824 |
-
false,
|
825 |
-
newScale,
|
826 |
-
ship.userData.splitCount + 1
|
827 |
-
);
|
828 |
-
newShip.position.copy(ship.position).add(offset.multiplyScalar(2));
|
829 |
-
newShip.userData.currentWaypoint = ship.userData.currentWaypoint;
|
830 |
-
});
|
831 |
-
}
|
832 |
-
if (!ship.userData.isPlayer) {
|
833 |
-
const index = aiShips.indexOf(ship);
|
834 |
-
if (index > -1) aiShips.splice(index, 1);
|
835 |
-
score += 100;
|
836 |
-
document.getElementById('score').textContent = `Score: ${score}`;
|
837 |
-
}
|
838 |
if (ship === playerShip) {
|
839 |
document.getElementById('status').textContent = 'Status: Destroyed';
|
840 |
endRace();
|
@@ -842,36 +760,6 @@
|
|
842 |
}
|
843 |
}
|
844 |
|
845 |
-
// Building damage
|
846 |
-
function applyBuildingDamage(building, damage, hitPosition) {
|
847 |
-
building.userData.currentHP -= damage;
|
848 |
-
createExplosion(hitPosition);
|
849 |
-
if (building.userData.currentHP <= 0) {
|
850 |
-
currentTrackGroup.remove(building);
|
851 |
-
if (building.userData.splitCount < 3) {
|
852 |
-
const newScale = 0.5;
|
853 |
-
const offsets = [
|
854 |
-
new THREE.Vector3(5, 0, 5),
|
855 |
-
new THREE.Vector3(-5, 0, -5)
|
856 |
-
];
|
857 |
-
offsets.forEach(offset => {
|
858 |
-
const newBuilding = createBuilding(
|
859 |
-
building.position.x + offset.x,
|
860 |
-
building.position.z + offset.z,
|
861 |
-
building.userData.width,
|
862 |
-
building.userData.depth,
|
863 |
-
building.userData.height,
|
864 |
-
newScale,
|
865 |
-
building.userData.splitCount + 1
|
866 |
-
);
|
867 |
-
currentTrackGroup.add(newBuilding);
|
868 |
-
});
|
869 |
-
}
|
870 |
-
score += 100;
|
871 |
-
document.getElementById('score').textContent = `Score: ${score}`;
|
872 |
-
}
|
873 |
-
}
|
874 |
-
|
875 |
// Pickup handling
|
876 |
function updatePickups() {
|
877 |
for (let i = pickups.length - 1; i >= 0; i--) {
|
@@ -888,48 +776,6 @@
|
|
888 |
}
|
889 |
}
|
890 |
|
891 |
-
// Missile update
|
892 |
-
function updateMissiles() {
|
893 |
-
for (let i = missiles.length - 1; i >= 0; i--) {
|
894 |
-
const missile = missiles[i];
|
895 |
-
missile.userData.lifetime--;
|
896 |
-
if (missile.userData.lifetime <= 0 || !missile.userData.target || (!missile.userData.target.userData?.active && !missile.userData.target.isMesh)) {
|
897 |
-
scene.remove(missile);
|
898 |
-
missiles.splice(i, 1);
|
899 |
-
continue;
|
900 |
-
}
|
901 |
-
const targetPos = missile.userData.target.isMesh ? missile.userData.target.position : missile.userData.target.position;
|
902 |
-
const direction = targetPos.clone().sub(missile.position).normalize();
|
903 |
-
missile.position.add(direction.multiplyScalar(missile.userData.speed));
|
904 |
-
missile.lookAt(targetPos);
|
905 |
-
missile.rotation.x += Math.PI / 2;
|
906 |
-
|
907 |
-
// Update flame trail
|
908 |
-
const flame = missile.children[0];
|
909 |
-
const positions = flame.geometry.attributes.position.array;
|
910 |
-
for (let j = 0; j < positions.length / 3; j++) {
|
911 |
-
const j3 = j * 3;
|
912 |
-
positions[j3] = (Math.random() - 0.5) * 0.1;
|
913 |
-
positions[j3 + 1] = (Math.random() - 0.5) * 0.1;
|
914 |
-
positions[j3 + 2] = -0.3 - Math.random() * 0.2;
|
915 |
-
}
|
916 |
-
flame.geometry.attributes.position.needsUpdate = true;
|
917 |
-
|
918 |
-
// Check collision
|
919 |
-
const missileBox = new THREE.Box3().setFromObject(missile);
|
920 |
-
const targetBox = new THREE.Box3().setFromObject(missile.userData.target);
|
921 |
-
if (missileBox.intersectsBox(targetBox)) {
|
922 |
-
if (missile.userData.target.isMesh && missile.userData.target.geometry.type === 'BoxGeometry') {
|
923 |
-
applyBuildingDamage(missile.userData.target, missile.userData.damage, missile.position);
|
924 |
-
} else {
|
925 |
-
applyDamage(missile.userData.target, missile.userData.damage, missile.position);
|
926 |
-
}
|
927 |
-
scene.remove(missile);
|
928 |
-
missiles.splice(i, 1);
|
929 |
-
}
|
930 |
-
}
|
931 |
-
}
|
932 |
-
|
933 |
// Player movement
|
934 |
let rotation = 0;
|
935 |
let bankAngle = 0;
|
@@ -1007,6 +853,29 @@
|
|
1007 |
});
|
1008 |
}
|
1009 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1010 |
// Explosion update
|
1011 |
const explosions = [];
|
1012 |
function updateExplosions() {
|
@@ -1055,9 +924,7 @@
|
|
1055 |
currentTrack = trackName;
|
1056 |
if (currentTrackGroup) scene.remove(currentTrackGroup);
|
1057 |
pickups.forEach(p => scene.remove(p));
|
1058 |
-
missiles.forEach(m => scene.remove(m));
|
1059 |
pickups.length = 0;
|
1060 |
-
missiles.length = 0;
|
1061 |
const { trackGroup, waypoints: newWaypoints, startPosition } = generateTrack(trackConfigs[trackName]);
|
1062 |
currentTrackGroup = trackGroup;
|
1063 |
waypoints = newWaypoints;
|
@@ -1081,9 +948,7 @@
|
|
1081 |
startGate.position.y = 12;
|
1082 |
startGate.rotation.y = Math.atan2(waypoints[1].x - waypoints[0].x, waypoints[1].z - waypoints[0].z);
|
1083 |
raceTime = 0;
|
1084 |
-
score = 0;
|
1085 |
document.getElementById('time').textContent = `Time: 0`;
|
1086 |
-
document.getElementById('score').textContent = `Score: ${score}`;
|
1087 |
document.getElementById('status').textContent = `Status: Active`;
|
1088 |
updateCharacterSheet();
|
1089 |
updateSkillsSheet();
|
@@ -1092,12 +957,12 @@
|
|
1092 |
// Restart race
|
1093 |
function restartRace() {
|
1094 |
scene.remove(playerShip);
|
|
|
1095 |
explosions.forEach(e => scene.remove(e));
|
1096 |
pickups.forEach(p => scene.remove(p));
|
1097 |
-
|
1098 |
explosions.length = 0;
|
1099 |
pickups.length = 0;
|
1100 |
-
missiles.length = 0;
|
1101 |
initPlayer();
|
1102 |
startRace(currentTrack);
|
1103 |
}
|
@@ -1106,7 +971,7 @@
|
|
1106 |
function endRace() {
|
1107 |
gameState = 'menu';
|
1108 |
const activeShips = aiShips.filter(s => s.userData.active).length + (playerData.active ? 1 : 0);
|
1109 |
-
alert(`Race Over! Ships Remaining: ${activeShips}, Time: ${raceTime.toFixed(2)}s
|
1110 |
}
|
1111 |
|
1112 |
// Gallery
|
@@ -1123,7 +988,7 @@
|
|
1123 |
if (gameState === 'racing') {
|
1124 |
updatePlayer();
|
1125 |
updateAI();
|
1126 |
-
|
1127 |
updateExplosions();
|
1128 |
raceTime += 1 / 60;
|
1129 |
document.getElementById('time').textContent = `Time: ${raceTime.toFixed(2)}`;
|
|
|
95 |
<div class="ui-container" id="race-ui">
|
96 |
<h2>Starship Circuit Commander</h2>
|
97 |
<div id="time">Time: 0</div>
|
|
|
98 |
<div id="status">Status: Active</div>
|
99 |
</div>
|
100 |
<div class="sidebar" id="sidebar">
|
|
|
191 |
// Game state
|
192 |
let gameState = 'racing';
|
193 |
let raceTime = 0;
|
|
|
194 |
let currentTrack = 'Phelps Island Drift';
|
195 |
let lastGatePass = 0;
|
196 |
let isJumping = false;
|
|
|
262 |
// Player ship
|
263 |
let playerShip = null;
|
264 |
let playerData = null;
|
265 |
+
function createShip(typeIndex, isPlayer) {
|
266 |
const type = shipTypes[typeIndex];
|
267 |
const ship = new THREE.Group();
|
268 |
+
const bodyGeometry = new THREE.BoxGeometry(type.scale.x, type.scale.y, type.scale.z);
|
|
|
|
|
|
|
|
|
|
|
269 |
const bodyMaterial = new THREE.MeshStandardMaterial({
|
270 |
color: isPlayer ? 0x00ff00 : 0xff0000,
|
271 |
metalness: 0.8,
|
|
|
274 |
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
275 |
ship.add(body);
|
276 |
if (type.name !== 'Rocketman') {
|
277 |
+
const thrusterGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.5, 8);
|
278 |
const thrusterMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
|
279 |
for (let i = 0; i < 4; i++) {
|
280 |
const thruster = new THREE.Mesh(thrusterGeometry, thrusterMaterial);
|
281 |
thruster.position.set(
|
282 |
+
(i % 2 === 0 ? 0.5 : -0.5) * type.scale.x * 0.8,
|
283 |
0,
|
284 |
+
(i < 2 ? 0.5 : -0.5) * type.scale.z * 0.8
|
285 |
);
|
286 |
thruster.rotation.x = Math.PI / 2;
|
287 |
ship.add(thruster);
|
288 |
}
|
289 |
} else {
|
290 |
+
const jetpackGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.8, 8);
|
291 |
const jetpack = new THREE.Mesh(jetpackGeometry, new THREE.MeshStandardMaterial({ color: 0xaaaaaa }));
|
292 |
+
jetpack.position.set(0, -0.5, 0);
|
293 |
ship.add(jetpack);
|
294 |
}
|
295 |
ship.position.y = 10;
|
296 |
ship.castShadow = true;
|
|
|
297 |
ship.userData = {
|
298 |
type: type.name,
|
299 |
maxSpeed: type.speed,
|
300 |
acceleration: type.accel,
|
301 |
+
thrusters: Array(4).fill(type.maxHP),
|
302 |
+
body: { front: type.maxHP, back: type.maxHP, left: type.maxHP, right: type.maxHP },
|
303 |
active: true,
|
304 |
speed: 0,
|
305 |
currentWaypoint: 0,
|
306 |
+
lastGatePass: 0
|
|
|
|
|
|
|
307 |
};
|
308 |
scene.add(ship);
|
|
|
309 |
return ship;
|
310 |
}
|
311 |
|
|
|
314 |
const typeIndex = Math.floor(Math.random() * shipTypes.length);
|
315 |
playerShip = createShip(typeIndex, true);
|
316 |
playerData = playerShip.userData;
|
317 |
+
// Reset skills
|
318 |
Object.keys(skills).forEach(skill => skills[skill].level = 0);
|
|
|
|
|
319 |
updateCharacterSheet();
|
320 |
updateSkillsSheet();
|
321 |
}
|
|
|
333 |
const startGate = new THREE.Mesh(gateGeometry, gateMaterial);
|
334 |
scene.add(startGate);
|
335 |
|
336 |
+
// Particle system for bullets
|
337 |
+
const bullets = [];
|
338 |
+
function createBullet(position, direction) {
|
339 |
+
const geometry = new THREE.SphereGeometry(0.2, 8, 8);
|
340 |
+
const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
|
341 |
+
const bullet = new THREE.Mesh(geometry, material);
|
342 |
+
bullet.position.copy(position);
|
343 |
+
bullet.userData = {
|
344 |
+
direction: direction.clone(),
|
345 |
+
lifetime: 120,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
346 |
damage: skills.shotPower.effect(skills.shotPower.level)
|
347 |
};
|
348 |
+
scene.add(bullet);
|
349 |
+
bullets.push(bullet);
|
|
|
350 |
}
|
351 |
|
352 |
// Particle system for explosions
|
|
|
389 |
}
|
390 |
|
391 |
// Building and column generation
|
392 |
+
function createBuilding(x, z, width, depth, height) {
|
393 |
+
const geometry = new THREE.BoxGeometry(width, height, depth);
|
394 |
const material = new THREE.MeshStandardMaterial({
|
395 |
color: Math.random() * 0xffffff,
|
396 |
roughness: 0.7,
|
397 |
metalness: 0.3
|
398 |
});
|
399 |
const building = new THREE.Mesh(geometry, material);
|
400 |
+
building.position.set(x, height / 2, z);
|
401 |
building.castShadow = true;
|
402 |
building.receiveShadow = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
403 |
return building;
|
404 |
}
|
405 |
|
|
|
671 |
// Shooting
|
672 |
let canShoot = true;
|
673 |
let shotCooldown = 200;
|
|
|
|
|
674 |
document.addEventListener('mousedown', (event) => {
|
675 |
if (event.button === 0 && canShoot && gameState === 'racing' && playerData.active) {
|
676 |
canShoot = false;
|
677 |
setTimeout(() => canShoot = true, shotCooldown);
|
678 |
+
const direction = new THREE.Vector3();
|
679 |
+
camera.getWorldDirection(direction);
|
680 |
+
const position = playerShip.position.clone().add(direction.clone().multiplyScalar(2));
|
681 |
+
const count = skills.shotCount.effect(skills.shotCount.level);
|
682 |
+
for (let i = 0; i < count; i++) {
|
683 |
+
const offset = new THREE.Vector3((Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.1, 0);
|
684 |
+
createBullet(position.clone().add(offset), direction);
|
|
|
|
|
|
|
|
|
|
|
685 |
}
|
686 |
}
|
687 |
});
|
|
|
746 |
checkShipStatus(ship);
|
747 |
}
|
748 |
|
749 |
+
function checkShipStatus(ship) {
|
750 |
const totalHP = ship.userData.thrusters.reduce((a, b) => a + b, 0) +
|
751 |
Object.values(ship.userData.body).reduce((a, b) => a + b, 0);
|
752 |
if (totalHP <= 0) {
|
753 |
ship.userData.active = false;
|
754 |
ship.visible = false;
|
755 |
createExplosion(ship.position);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
756 |
if (ship === playerShip) {
|
757 |
document.getElementById('status').textContent = 'Status: Destroyed';
|
758 |
endRace();
|
|
|
760 |
}
|
761 |
}
|
762 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
763 |
// Pickup handling
|
764 |
function updatePickups() {
|
765 |
for (let i = pickups.length - 1; i >= 0; i--) {
|
|
|
776 |
}
|
777 |
}
|
778 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
779 |
// Player movement
|
780 |
let rotation = 0;
|
781 |
let bankAngle = 0;
|
|
|
853 |
});
|
854 |
}
|
855 |
|
856 |
+
// Bullet update
|
857 |
+
function updateBullets() {
|
858 |
+
const raycaster = new THREE.Raycaster();
|
859 |
+
for (let i = bullets.length - 1; i >= 0; i--) {
|
860 |
+
const bullet = bullets[i];
|
861 |
+
bullet.userData.lifetime--;
|
862 |
+
if (bullet.userData.lifetime <= 0) {
|
863 |
+
scene.remove(bullet);
|
864 |
+
bullets.splice(i, 1);
|
865 |
+
continue;
|
866 |
+
}
|
867 |
+
bullet.position.add(bullet.userData.direction.clone().multiplyScalar(1));
|
868 |
+
raycaster.set(bullet.position, bullet.userData.direction);
|
869 |
+
const intersects = raycaster.intersectObjects([...aiShips, playerShip].filter(s => s.userData.active));
|
870 |
+
if (intersects.length > 0) {
|
871 |
+
const hitShip = intersects[0].object.parent;
|
872 |
+
applyDamage(hitShip, bullet.userData.damage, bullet.position);
|
873 |
+
scene.remove(bullet);
|
874 |
+
bullets.splice(i, 1);
|
875 |
+
}
|
876 |
+
}
|
877 |
+
}
|
878 |
+
|
879 |
// Explosion update
|
880 |
const explosions = [];
|
881 |
function updateExplosions() {
|
|
|
924 |
currentTrack = trackName;
|
925 |
if (currentTrackGroup) scene.remove(currentTrackGroup);
|
926 |
pickups.forEach(p => scene.remove(p));
|
|
|
927 |
pickups.length = 0;
|
|
|
928 |
const { trackGroup, waypoints: newWaypoints, startPosition } = generateTrack(trackConfigs[trackName]);
|
929 |
currentTrackGroup = trackGroup;
|
930 |
waypoints = newWaypoints;
|
|
|
948 |
startGate.position.y = 12;
|
949 |
startGate.rotation.y = Math.atan2(waypoints[1].x - waypoints[0].x, waypoints[1].z - waypoints[0].z);
|
950 |
raceTime = 0;
|
|
|
951 |
document.getElementById('time').textContent = `Time: 0`;
|
|
|
952 |
document.getElementById('status').textContent = `Status: Active`;
|
953 |
updateCharacterSheet();
|
954 |
updateSkillsSheet();
|
|
|
957 |
// Restart race
|
958 |
function restartRace() {
|
959 |
scene.remove(playerShip);
|
960 |
+
bullets.forEach(b => scene.remove(b));
|
961 |
explosions.forEach(e => scene.remove(e));
|
962 |
pickups.forEach(p => scene.remove(p));
|
963 |
+
bullets.length = 0;
|
964 |
explosions.length = 0;
|
965 |
pickups.length = 0;
|
|
|
966 |
initPlayer();
|
967 |
startRace(currentTrack);
|
968 |
}
|
|
|
971 |
function endRace() {
|
972 |
gameState = 'menu';
|
973 |
const activeShips = aiShips.filter(s => s.userData.active).length + (playerData.active ? 1 : 0);
|
974 |
+
alert(`Race Over! Ships Remaining: ${activeShips}, Time: ${raceTime.toFixed(2)}s`);
|
975 |
}
|
976 |
|
977 |
// Gallery
|
|
|
988 |
if (gameState === 'racing') {
|
989 |
updatePlayer();
|
990 |
updateAI();
|
991 |
+
updateBullets();
|
992 |
updateExplosions();
|
993 |
raceTime += 1 / 60;
|
994 |
document.getElementById('time').textContent = `Time: ${raceTime.toFixed(2)}`;
|