aboutsummaryrefslogtreecommitdiff
path: root/static/src/engine/render.ts
blob: 4ff1ec05590b509a239de30599d1e5e14eaf5d5d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import {
  ComponentType,
  PositionComponent,
  TrailingPositionComponent,
  SpriteSheetComponent,
  SpriteSpec,
} from "./component";
import { Game } from "./game";
import { System, SystemType } from "./system";
import { drawLaserPen } from "laser-pen";

export class RenderSystem extends System {
  constructor(private readonly canvas: HTMLCanvasElement) {
    super(SystemType.RENDER);
  }

  public set_world_dimensions(width: number, height: number) {
    this.canvas.width = width;
    this.canvas.height = height;
  }

  public update(dt: number, game: Game) {
    const ctx = this.canvas.getContext("2d");
    if (ctx === null) return;
    ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

    game.for_each_entity_with_component(ComponentType.RENDERABLE, (entity) => {
      if (ComponentType.TRAILING_POSITION in entity.components) {
        const trailing_position = entity.components[
          ComponentType.TRAILING_POSITION
        ] as TrailingPositionComponent;
        if (trailing_position.trails.length < 3) return;
        drawLaserPen(ctx, trailing_position.trails);
        return;
      }

      if (
        ComponentType.POSITION in entity.components &&
        ComponentType.SPRITESHEET in entity.components
      ) {
        const position = entity.components[
          ComponentType.POSITION
        ] as PositionComponent;
        const spritesheet = entity.components[
          ComponentType.SPRITESHEET
        ] as SpriteSheetComponent;

        if (typeof spritesheet.sheet === "undefined") {
          const img = new Image();
          img.src = spritesheet.source;
          spritesheet.sheet = img;
        }
        const spritespec =
          spritesheet.state_to_spritespec[spritesheet.current_state];

        this.blit_sprite(ctx, spritesheet, position);

        spritesheet.last_update += dt;
        if (spritesheet.last_update > spritespec.ms_per_frame) {
          spritesheet.current_frame++;
          spritesheet.current_frame %= spritespec.frames;
          spritesheet.last_update = 0;
        }

        return;
      }
    });
  }

  private blit_sprite(
    ctx: CanvasRenderingContext2D,
    spritesheet: SpriteSheetComponent,
    position: { x: number; y: number },
  ) {
    ctx.save();
    ctx.translate(position.x, position.y);
    ctx.translate(-position.x, -position.y);

    if (typeof spritesheet.sheet === "undefined") return;

    const spritespec =
      spritesheet.state_to_spritespec[spritesheet.current_state];

    ctx.drawImage(
      spritesheet.sheet,
      ...this.get_sprite_args(spritesheet.current_frame, spritespec),
      ...this.get_draw_args(spritespec, position),
    );

    ctx.restore();
  }

  private get_sprite_args(
    current_frame: number,
    sprite_spec: SpriteSpec,
  ): [sx: number, sy: number, sw: number, sh: number] {
    const [width, height] = this.get_dimensions(sprite_spec);
    return [
      sprite_spec.top_x,
      sprite_spec.top_y + current_frame * height,
      width,
      height,
    ];
  }

  private get_draw_args(
    sprite_spec: SpriteSpec,
    position: { x: number; y: number },
  ): [dx: number, dy: number, dw: number, dh: number] {
    const [width, height] = this.get_dimensions(sprite_spec);
    return [position.x - width / 2, position.y - height / 2, width, height];
  }

  private get_dimensions(sprite_spec: SpriteSpec): [number, number] {
    return [
      sprite_spec.end_x - sprite_spec.top_x,
      (sprite_spec.end_y - sprite_spec.top_y) / sprite_spec.frames,
    ];
  }
}