local.ts 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import type { AnimationDef, Engine, IntentSystem, MapDef, ParticleDef, ShaderDef, ShopDef, SoundDef, SpriteDef, TimeSystem } from "@nengine/core";
  2. import type { RenderSnapshot, ViewportHandle } from "../viewport.js";
  3. import type { SoundPlayer } from "../sound.js";
  4. import { arrowDirection } from "../keys.js";
  5. export interface LocalSyncHandle {
  6. setTimeLabel(fn: (label: string) => void): void;
  7. refresh(): void;
  8. intent(action: string, args: Record<string, unknown>): void;
  9. input(input: string): void;
  10. destroy(): void;
  11. }
  12. export interface LocalSyncOptions {
  13. sound?: SoundPlayer;
  14. }
  15. export function connectLocal(engine: Engine, viewport: ViewportHandle, opts?: LocalSyncOptions): LocalSyncHandle {
  16. const time = engine.systems.get<TimeSystem>("time");
  17. const sound = opts?.sound;
  18. let label: ((label: string) => void) | undefined;
  19. function refresh(): void {
  20. const map = engine.registry.list<MapDef>("map")[0];
  21. if (map) {
  22. const snapshot: RenderSnapshot = {
  23. map,
  24. world: engine.world.snapshot(),
  25. sprites: engine.registry.list<SpriteDef>("sprite"),
  26. sounds: engine.registry.list<SoundDef>("sound"),
  27. particles: engine.registry.list<ParticleDef>("particle"),
  28. shaders: engine.registry.list<ShaderDef>("shader"),
  29. shops: engine.registry.list<ShopDef>("shop"),
  30. animations: engine.registry.list<AnimationDef>("animation"),
  31. assets: Object.fromEntries(engine.assets),
  32. };
  33. sound?.setCatalog(snapshot.sounds ?? []);
  34. sound?.setAssets(snapshot.assets ?? {});
  35. viewport.render(snapshot);
  36. } else {
  37. viewport.render();
  38. }
  39. const state = time?.get();
  40. if (state && label) {
  41. label(`Day ${state.day}, ${state.season}`);
  42. }
  43. }
  44. refresh();
  45. const unsubscribeSound = engine.events.on("sound-play", (payload) => {
  46. sound?.onEvent({ event: "sound-play", payload });
  47. });
  48. const unsubscribeBgmPlay = engine.events.on("bgm-play", (payload) => {
  49. sound?.onEvent({ event: "bgm-play", payload });
  50. });
  51. const unsubscribeBgmStop = engine.events.on("bgm-stop", (payload) => {
  52. sound?.onEvent({ event: "bgm-stop", payload });
  53. });
  54. const unsubscribeParticle = engine.events.on("particle", (payload) => {
  55. viewport.onEvent({ event: "particle", payload });
  56. });
  57. return {
  58. setTimeLabel(fn: (label: string) => void): void {
  59. label = fn;
  60. refresh();
  61. },
  62. refresh,
  63. intent(action: string, args: Record<string, unknown>): void {
  64. engine.systems.get<IntentSystem>("intent")?.handle(action, args);
  65. refresh();
  66. },
  67. input(input: string): void {
  68. const direction = arrowDirection(input);
  69. if (!direction) return;
  70. const player = engine.world.get("player");
  71. if (!player) return;
  72. const x = (typeof player.x === "number" ? player.x : 0) + direction.dx;
  73. const y = (typeof player.y === "number" ? player.y : 0) + direction.dy;
  74. engine.systems.get<IntentSystem>("intent")?.handle("move", { tile: { x, y } });
  75. refresh();
  76. },
  77. destroy(): void {
  78. unsubscribeSound();
  79. unsubscribeBgmPlay();
  80. unsubscribeBgmStop();
  81. unsubscribeParticle();
  82. label = undefined;
  83. },
  84. };
  85. }