Drop parachuted parcels behind the plane as it crosses
Adds a sprite-based parcel system. The plane spawns a new parcel-on-parachute (assets/parcel.png, 236 KB transparent) every ~1.4 s while it's visible (progress 0.04-0.92). Each parcel: - Spawns at the plane's current position with a tiny random offset. - Falls at 1.4 world units/s, with a small horizontal drift and a parachute sway sinusoid for character. - Scales 0.35 → 2.6 over its lifetime, simulating the perspective of falling toward the camera. - Fades in over 6% of life, fades out over the last 15%. - Cleaned up (removed from scene + material disposed) when its lifetime expires or it drops below y = -10. Implemented as THREE.Sprite so it always faces the camera, no need to track per-parcel orientation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6fcc772bf0
commit
3f773380c4
BIN
assets/parcel.png
Normal file
BIN
assets/parcel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
@ -65,6 +65,72 @@ loader.load(
|
||||
(err) => console.error('Failed to load airplane.glb:', err)
|
||||
);
|
||||
|
||||
/* ── Colis parachutés ──────────────────────────────────────────────────
|
||||
À intervalle régulier (et tant que l'avion est dans le cadre), on
|
||||
spawn un sprite « parachute + colis » à la position de l'avion.
|
||||
Chaque colis tombe lentement et grossit (effet de zoom dû à la
|
||||
descente vers la caméra). Il s'efface en fin de course.
|
||||
*/
|
||||
const parcelTex = new THREE.TextureLoader().load('assets/parcel.png');
|
||||
parcelTex.colorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
const PARCEL_SPAWN_EVERY = 1.4; // un colis toutes les ~1.4s
|
||||
const PARCEL_FALL_SPEED = 1.4; // unités monde/s
|
||||
const PARCEL_DRIFT = 0.45; // dérive horizontale (sortie de soute)
|
||||
const PARCEL_INITIAL_SCALE = 0.35;
|
||||
const PARCEL_FINAL_SCALE = 2.6;
|
||||
const PARCEL_LIFETIME = 5.5; // s avant de disparaître
|
||||
|
||||
const parcels = [];
|
||||
let lastSpawn = 0;
|
||||
|
||||
function spawnParcel(x, y) {
|
||||
const mat = new THREE.SpriteMaterial({
|
||||
map: parcelTex, transparent: true, opacity: 0
|
||||
});
|
||||
const sprite = new THREE.Sprite(mat);
|
||||
sprite.position.set(x + (Math.random() - 0.5) * 0.4, y - 0.2, 0.2);
|
||||
sprite.scale.setScalar(PARCEL_INITIAL_SCALE);
|
||||
scene.add(sprite);
|
||||
parcels.push({
|
||||
sprite, age: 0,
|
||||
vx: (Math.random() - 0.5) * PARCEL_DRIFT, // dérive latérale légère
|
||||
sway: 0.5 + Math.random() * 0.6, // freq de balancement
|
||||
swayAmp: 0.08 + Math.random() * 0.08
|
||||
});
|
||||
}
|
||||
|
||||
function updateParcels(dt, t) {
|
||||
for (let i = parcels.length - 1; i >= 0; i--) {
|
||||
const p = parcels[i];
|
||||
p.age += dt;
|
||||
const life = p.age / PARCEL_LIFETIME;
|
||||
|
||||
/* Trajectoire : chute douce + dérive + balancement parachute */
|
||||
p.sprite.position.y -= PARCEL_FALL_SPEED * dt;
|
||||
p.sprite.position.x += p.vx * dt;
|
||||
p.sprite.position.x += Math.sin(t * p.sway) * p.swayAmp * dt;
|
||||
|
||||
/* Grandit en tombant — perspective */
|
||||
const k = Math.min(1, life * 1.1);
|
||||
p.sprite.scale.setScalar(
|
||||
PARCEL_INITIAL_SCALE + (PARCEL_FINAL_SCALE - PARCEL_INITIAL_SCALE) * k
|
||||
);
|
||||
|
||||
/* Fade-in rapide au spawn, fade-out lent à la fin */
|
||||
let opacity = 1;
|
||||
if (life < 0.06) opacity = life / 0.06;
|
||||
else if (life > 0.85) opacity = (1 - life) / 0.15;
|
||||
p.sprite.material.opacity = Math.max(0, Math.min(1, opacity));
|
||||
|
||||
if (life >= 1 || p.sprite.position.y < -10) {
|
||||
scene.remove(p.sprite);
|
||||
p.sprite.material.dispose();
|
||||
parcels.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Souris ─────────────────────────────────────────────────────────────
|
||||
L'avion avance automatiquement à vitesse de croisière (BASE_SPEED).
|
||||
Bouger la souris ajoute un boost qui le pousse plus vite vers la fin.
|
||||
@ -137,6 +203,14 @@ function tick() {
|
||||
|
||||
planeHolder.position.set(px, py + bob, 0);
|
||||
|
||||
/* Spawn d'un colis parachuté à intervalle régulier — uniquement
|
||||
pendant que l'avion est dans (ou proche de) la zone visible. */
|
||||
if (p > 0.04 && p < 0.92 && t - lastSpawn > PARCEL_SPAWN_EVERY) {
|
||||
spawnParcel(px, py + bob);
|
||||
lastSpawn = t;
|
||||
}
|
||||
updateParcels(dt, t);
|
||||
|
||||
/* Avec nez à -X et up = +Y :
|
||||
- rotation.z = PITCH (négatif = nez en l'air)
|
||||
- rotation.x = ROLL
|
||||
|
||||
Loading…
Reference in New Issue
Block a user