feat: system view with star, planets, gates, concrete ships
This commit is contained in:
+23
-3
@@ -1,17 +1,37 @@
|
||||
import { createWorld, advance } from './sim/world.js';
|
||||
import { createRenderer } from './render/app.js';
|
||||
import { GalaxyView } from './render/galaxyView.js';
|
||||
import { goTo } from './sim/player.js';
|
||||
import { SystemView } from './render/systemView.js';
|
||||
import { goTo, playerSystem } from './sim/player.js';
|
||||
|
||||
const world = createWorld('neon-1');
|
||||
const renderer = await createRenderer(document.getElementById('stage'));
|
||||
const galaxyView = new GalaxyView(renderer, world, {
|
||||
|
||||
const handlers = {
|
||||
onSelectSystem: (id) => console.log('select system', id),
|
||||
onJump: (laneId) => goTo(world, { laneId }),
|
||||
onGotoPlanet: (planetId) => goTo(world, { planetId }),
|
||||
};
|
||||
const galaxyView = new GalaxyView(renderer, world, handlers);
|
||||
const systemView = new SystemView(renderer, world, handlers);
|
||||
|
||||
let view = 'system';
|
||||
function setView(name) {
|
||||
view = name;
|
||||
galaxyView.setVisible(name === 'galaxy');
|
||||
systemView.setVisible(name === 'system');
|
||||
}
|
||||
setView('system');
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
setView(view === 'galaxy' ? 'system' : 'galaxy');
|
||||
}
|
||||
});
|
||||
|
||||
const DAYS_PER_SEC = 0.25;
|
||||
renderer.app.ticker.add(() => {
|
||||
advance(world, world.t + (renderer.app.ticker.deltaMS / 1000) * DAYS_PER_SEC);
|
||||
galaxyView.update();
|
||||
if (view === 'galaxy') galaxyView.update();
|
||||
else if (playerSystem(world) != null) systemView.update();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Container, Graphics, Text } from 'pixi.js';
|
||||
import { shipsIn } from '../sim/npc.js';
|
||||
import { playerSystem } from '../sim/player.js';
|
||||
import { otherEnd } from '../sim/galaxy.js';
|
||||
|
||||
const GATE_RADIUS = 290;
|
||||
|
||||
export class SystemView {
|
||||
constructor(renderer, world, { onGotoPlanet, onJump }) {
|
||||
this.renderer = renderer;
|
||||
this.world = world;
|
||||
this.onGotoPlanet = onGotoPlanet;
|
||||
this.onJump = onJump;
|
||||
this.container = new Container();
|
||||
this.labelContainer = new Container();
|
||||
renderer.world.addChild(this.container);
|
||||
renderer.labels.addChild(this.labelContainer);
|
||||
this.staticG = new Graphics();
|
||||
this.shipG = new Graphics();
|
||||
this.container.addChild(this.staticG, this.shipG);
|
||||
this.hitNodes = new Container();
|
||||
this.container.addChild(this.hitNodes);
|
||||
this.builtFor = null;
|
||||
}
|
||||
|
||||
center() {
|
||||
return { x: this.renderer.app.renderer.width / 2, y: this.renderer.app.renderer.height / 2 };
|
||||
}
|
||||
|
||||
planetXY(planet) {
|
||||
const c = this.center();
|
||||
return { x: c.x + Math.cos(planet.orbit.a) * planet.orbit.r, y: c.y + Math.sin(planet.orbit.a) * planet.orbit.r };
|
||||
}
|
||||
|
||||
gateXY(lane, systemId) {
|
||||
const { galaxy } = this.world;
|
||||
const here = galaxy.systems[systemId];
|
||||
const there = galaxy.systems[otherEnd(lane, systemId)];
|
||||
const ang = Math.atan2(there.y - here.y, there.x - here.x);
|
||||
const c = this.center();
|
||||
return { x: c.x + Math.cos(ang) * GATE_RADIUS, y: c.y + Math.sin(ang) * GATE_RADIUS, ang };
|
||||
}
|
||||
|
||||
build(systemId) {
|
||||
this.builtFor = systemId;
|
||||
const { galaxy } = this.world;
|
||||
const sys = galaxy.systems[systemId];
|
||||
const c = this.center();
|
||||
const g = this.staticG;
|
||||
g.clear();
|
||||
this.labelContainer.removeChildren();
|
||||
this.hitNodes.removeChildren();
|
||||
|
||||
g.circle(c.x, c.y, 30).fill({ color: 0xffe9a8 }); // the star, brightest thing on screen
|
||||
|
||||
for (const planetId of sys.planets) {
|
||||
const planet = galaxy.planets[planetId];
|
||||
const p = this.planetXY(planet);
|
||||
g.circle(c.x, c.y, planet.orbit.r).stroke({ width: 1, color: 0x1b3a55, alpha: 0.6 });
|
||||
g.circle(p.x, p.y, 10).fill({ color: 0x7fb4ff });
|
||||
const label = new Text({
|
||||
text: `${planet.name} (${planet.kind})`,
|
||||
style: { fill: '#7fa8c9', fontSize: 11, fontFamily: 'system-ui' },
|
||||
});
|
||||
label.position.set(p.x + 14, p.y - 6);
|
||||
this.labelContainer.addChild(label);
|
||||
const hit = new Graphics().circle(0, 0, 18).fill({ color: 0xffffff, alpha: 0.001 });
|
||||
hit.position.set(p.x, p.y);
|
||||
hit.eventMode = 'static';
|
||||
hit.cursor = 'pointer';
|
||||
hit.on('pointertap', () => this.onGotoPlanet(planetId));
|
||||
this.hitNodes.addChild(hit);
|
||||
}
|
||||
|
||||
for (const laneId of sys.lanes) {
|
||||
const lane = galaxy.lanes[laneId];
|
||||
const gp = this.gateXY(lane, systemId);
|
||||
g.poly([gp.x, gp.y - 8, gp.x + 8, gp.y, gp.x, gp.y + 8, gp.x - 8, gp.y]).fill({ color: 0x9fd8ff });
|
||||
const name = galaxy.systems[otherEnd(lane, systemId)].name;
|
||||
const label = new Text({ text: `→ ${name}`, style: { fill: '#7fa8c9', fontSize: 11, fontFamily: 'system-ui' } });
|
||||
label.position.set(gp.x + 10, gp.y - 6);
|
||||
this.labelContainer.addChild(label);
|
||||
const hit = new Graphics().circle(0, 0, 16).fill({ color: 0xffffff, alpha: 0.001 });
|
||||
hit.position.set(gp.x, gp.y);
|
||||
hit.eventMode = 'static';
|
||||
hit.cursor = 'pointer';
|
||||
hit.on('pointertap', () => this.onJump(laneId));
|
||||
this.hitNodes.addChild(hit);
|
||||
}
|
||||
}
|
||||
|
||||
// A ship triangle pointing along its heading.
|
||||
drawShip(g, x, y, ang, color) {
|
||||
const cos = Math.cos(ang), sin = Math.sin(ang);
|
||||
const pt = (dx, dy) => [x + dx * cos - dy * sin, y + dx * sin + dy * cos];
|
||||
g.poly([...pt(10, 0), ...pt(-7, 6), ...pt(-7, -6)]).fill({ color });
|
||||
}
|
||||
|
||||
update() {
|
||||
const sim = this.world;
|
||||
const sysId = playerSystem(sim);
|
||||
if (sysId == null) return;
|
||||
if (this.builtFor !== sysId) this.build(sysId);
|
||||
const { galaxy } = sim;
|
||||
const c = this.center();
|
||||
const g = this.shipG;
|
||||
g.clear();
|
||||
|
||||
// Concrete NPC ships.
|
||||
for (const entry of shipsIn(sim, sysId)) {
|
||||
if (entry.kind === 'docked') {
|
||||
const p = this.planetXY(galaxy.planets[entry.planetId]);
|
||||
this.drawShip(g, p.x + 18, p.y - 14, -Math.PI / 2, 0xffa94d);
|
||||
} else if (entry.kind === 'arriving' || entry.kind === 'departing') {
|
||||
const lane = galaxy.lanes[entry.laneId];
|
||||
const gp = this.gateXY(lane, sysId);
|
||||
const dest = entry.npc.plan ? galaxy.planets[entry.npc.plan.destPlanet] : null;
|
||||
const target = dest && dest.systemId === sysId ? this.planetXY(dest) : c;
|
||||
const f = entry.kind === 'arriving' ? entry.progress : 1 - entry.progress;
|
||||
const x = gp.x + (target.x - gp.x) * f * 0.5;
|
||||
const y = gp.y + (target.y - gp.y) * f * 0.5;
|
||||
const ang = entry.kind === 'arriving'
|
||||
? Math.atan2(target.y - gp.y, target.x - gp.x)
|
||||
: Math.atan2(gp.y - target.y, gp.x - target.x);
|
||||
this.drawShip(g, x, y, ang, 0xffa94d);
|
||||
} else if (entry.kind === 'local-hop') {
|
||||
const from = this.planetXY(galaxy.planets[entry.npc.plan.originPlanet]);
|
||||
const to = this.planetXY(galaxy.planets[entry.npc.plan.destPlanet]);
|
||||
const x = from.x + (to.x - from.x) * entry.progress;
|
||||
const y = from.y + (to.y - from.y) * entry.progress;
|
||||
this.drawShip(g, x, y, Math.atan2(to.y - from.y, to.x - from.x), 0xffa94d);
|
||||
}
|
||||
}
|
||||
|
||||
// The player.
|
||||
const loc = sim.player.loc;
|
||||
if (loc.kind === 'docked') {
|
||||
const p = this.planetXY(galaxy.planets[loc.planetId]);
|
||||
this.drawShip(g, p.x - 18, p.y - 14, -Math.PI / 2, 0x4dffd2);
|
||||
} else if (loc.kind === 'at-gate') {
|
||||
const gp = this.gateXY(galaxy.lanes[loc.laneId], sysId);
|
||||
this.drawShip(g, gp.x, gp.y - 16, -Math.PI / 2, 0x4dffd2);
|
||||
} else if (loc.kind === 'insys-leg') {
|
||||
const resolve = (d) => d.type === 'planet'
|
||||
? this.planetXY(galaxy.planets[d.id])
|
||||
: this.gateXY(galaxy.lanes[d.laneId], sysId);
|
||||
const from = resolve(loc.from), to = resolve(loc.to);
|
||||
const f = Math.min(1, (sim.t - loc.departT) / (loc.arriveT - loc.departT));
|
||||
const x = from.x + (to.x - from.x) * f;
|
||||
const y = from.y + (to.y - from.y) * f;
|
||||
this.drawShip(g, x, y, Math.atan2(to.y - from.y, to.x - from.x), 0x4dffd2);
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(on) {
|
||||
this.container.visible = on;
|
||||
this.labelContainer.visible = on;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user