astara / src /camera.js
cloudronin's picture
Village one: the bowl, the houses, the map, the population, the gate
2428d78 verified
Raw History Blame Contribute Delete
1.93 kB
// Follows the prince horizontally with a dead zone across the middle third.
// No vertical movement. No easing, no overshoot. Spec 2.2: "It should be
// boring."
//
// This is the only place world coordinates become screen coordinates.
import { TUNING } from './tuning.js';
export function createCamera(viewportW, worldMin, worldMax) {
const halfDead = (viewportW * TUNING.CAMERA_DEAD_ZONE) / 2;
// Mutable, because the village changes them exactly once. On the road the
// camera follows forever; at the mouth it stops following and starts being
// bounded, and that one switch IS the transition between the two modes.
let minX = worldMin;
let maxX = Math.max(worldMin, worldMax - viewportW);
return {
x: worldMin, prevX: worldMin,
snapshot() { this.prevX = this.x; },
reset(targetX) {
this.x = this.prevX = this.clamp(targetX - viewportW / 2);
},
/**
* Re-bound the view. Takes CAMERA positions, not world edges — the mouth of
* the village fixes where the view may go, not where the world stops, and
* passing world edges through the viewport subtraction twice is exactly the
* off-by-a-half-screen this file has already produced once.
*/
setBounds(minCamX, maxCamX) {
minX = minCamX;
maxX = Math.max(minCamX, maxCamX);
this.x = this.clamp(this.x);
},
clamp(x) {
return Math.min(maxX, Math.max(minX, x));
},
/** Move only when the target leaves the middle third. */
follow(targetX) {
const centre = this.x + viewportW / 2;
const offset = targetX - centre;
if (offset > halfDead) this.x += offset - halfDead;
else if (offset < -halfDead) this.x += offset + halfDead;
this.x = this.clamp(this.x);
},
/** World x -> screen x at a given parallax rate. 1.0 is the actor layer. */
toScreen(worldX, rate = 1) {
return worldX - this.x * rate;
},
};
}