feat: basic ux
This commit is contained in:
97
src/actors/link.js
Normal file
97
src/actors/link.js
Normal file
@ -0,0 +1,97 @@
|
||||
// Link: computer-controlled. Milestones 4-5-8 slice:
|
||||
// seek -> walk to the nearest pot
|
||||
// throw -> pick it up and hurl it (pots.js handles the arc + shatter)
|
||||
// leave -> no pots left: walk to the exit gap and off-screen (ends the round)
|
||||
// Distractions (grass/chickens) arrive in a later milestone.
|
||||
|
||||
import { TILE, LINK_SPEED } from "../config.js";
|
||||
import { visual } from "../sprites.js";
|
||||
import { throwPot } from "../pots.js";
|
||||
|
||||
const INTERACT_DIST = TILE * 0.8;
|
||||
|
||||
export function addLink(k, world, onLeave) {
|
||||
const p = world.tc(world.level.linkSpawn);
|
||||
const link = k.add([
|
||||
...visual(k, "link"),
|
||||
k.pos(p),
|
||||
k.area(),
|
||||
k.body(),
|
||||
k.state("seek", ["seek", "throw", "leave"]),
|
||||
"link",
|
||||
]);
|
||||
|
||||
// Move toward a world position; returns distance remaining.
|
||||
const stepToward = (target) => {
|
||||
const d = target.sub(link.pos);
|
||||
const dist = d.len();
|
||||
if (dist > 1) link.move(d.unit().scale(LINK_SPEED));
|
||||
return dist;
|
||||
};
|
||||
|
||||
const nearestPot = () => {
|
||||
const pots = k.get("pot");
|
||||
let best = null;
|
||||
let bestD = Infinity;
|
||||
for (const pot of pots) {
|
||||
const d = pot.pos.dist(link.pos);
|
||||
if (d < bestD) {
|
||||
bestD = d;
|
||||
best = pot;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
};
|
||||
|
||||
// --- SEEK ---
|
||||
link.onStateUpdate("seek", () => {
|
||||
const pot = nearestPot();
|
||||
if (!pot) {
|
||||
link.enterState("leave");
|
||||
return;
|
||||
}
|
||||
if (stepToward(pot.pos) <= INTERACT_DIST) {
|
||||
link.target = pot;
|
||||
link.enterState("throw");
|
||||
}
|
||||
});
|
||||
|
||||
// --- THROW ---
|
||||
link.onStateEnter("throw", () => {
|
||||
const pot = link.target;
|
||||
if (!pot || pot.destroyed) {
|
||||
link.enterState("seek");
|
||||
return;
|
||||
}
|
||||
const at = pot.pos.clone();
|
||||
pot.destroy(); // picked up
|
||||
link.throwing = true;
|
||||
throwPot(k, world, at, () => {
|
||||
link.throwing = false;
|
||||
link.enterState("seek");
|
||||
});
|
||||
});
|
||||
// Stand still while the throw animation plays.
|
||||
|
||||
// --- LEAVE ---
|
||||
link.onStateEnter("leave", () => {
|
||||
link.leaving = true;
|
||||
});
|
||||
link.onStateUpdate("leave", () => {
|
||||
const exitPos = world.tc(world.level.exit);
|
||||
stepToward(exitPos);
|
||||
// Once he reaches / passes the exit gap, he's gone.
|
||||
if (link.pos.dist(exitPos) < TILE * 0.5) {
|
||||
if (!link.gone) {
|
||||
link.gone = true;
|
||||
onLeave && onLeave();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
link.onUpdate(() => {
|
||||
link.z = link.pos.y;
|
||||
});
|
||||
|
||||
return link;
|
||||
}
|
||||
Reference in New Issue
Block a user