herd / files / src / game / Canvas.ts
Canvas.ts
| 1 | const GRASS_STEP = 40 |
| 2 | |
| 3 | export class Canvas { |
| 4 | readonly el: HTMLCanvasElement |
| 5 | readonly ctx: CanvasRenderingContext2D |
| 6 | private _width = 0 |
| 7 | private _height = 0 |
| 8 | private _dpr = 1 |
| 9 | |
| 10 | // offscreen canvas for the static grass texture — regenerated on resize |
| 11 | private grassCanvas: HTMLCanvasElement | null = null |
| 12 | |
| 13 | constructor(id: string) { |
| 14 | const el = document.getElementById(id) |
| 15 | if (!(el instanceof HTMLCanvasElement)) throw new Error(`#${id} is not a canvas`) |
| 16 | this.el = el |
| 17 | this.ctx = el.getContext('2d')! |
| 18 | this.resize() |
| 19 | window.addEventListener('resize', () => this.resize()) |
| 20 | } |
| 21 | |
| 22 | get width() { return this._width } |
| 23 | get height() { return this._height } |
| 24 | get dpr() { return this._dpr } |
| 25 | |
| 26 | private resize() { |
| 27 | this._dpr = window.devicePixelRatio || 1 |
| 28 | this._width = window.innerWidth |
| 29 | this._height = window.innerHeight |
| 30 | this.el.width = this._width * this._dpr |
| 31 | this.el.height = this._height * this._dpr |
| 32 | this.el.style.width = `${this._width}px` |
| 33 | this.el.style.height = `${this._height}px` |
| 34 | this.ctx.setTransform(this._dpr, 0, 0, this._dpr, 0, 0) |
| 35 | this.buildGrassTexture() |
| 36 | } |
| 37 | |
| 38 | private buildGrassTexture() { |
| 39 | const oc = document.createElement('canvas') |
| 40 | oc.width = this._width |
| 41 | oc.height = this._height |
| 42 | const ctx = oc.getContext('2d')! |
| 43 | |
| 44 | ctx.strokeStyle = 'rgba(60,110,30,0.3)' |
| 45 | ctx.lineWidth = 1 |
| 46 | for (let x = 0; x < this._width; x += GRASS_STEP) { |
| 47 | for (let y = 0; y < this._height; y += GRASS_STEP) { |
| 48 | // two blades per cell, randomized once at build time |
| 49 | for (let b = 0; b < 2; b++) { |
| 50 | const bx = x + Math.random() * GRASS_STEP |
| 51 | const by = y + Math.random() * GRASS_STEP |
| 52 | ctx.beginPath() |
| 53 | ctx.moveTo(bx, by) |
| 54 | ctx.lineTo(bx + (Math.random() - 0.5) * 6, by - 4 - Math.random() * 5) |
| 55 | ctx.stroke() |
| 56 | } |
| 57 | } |
| 58 | } |
| 59 | this.grassCanvas = oc |
| 60 | } |
| 61 | |
| 62 | drawField() { |
| 63 | const { ctx } = this |
| 64 | ctx.fillStyle = '#4a8c2a' |
| 65 | ctx.fillRect(0, 0, this._width, this._height) |
| 66 | if (this.grassCanvas) { |
| 67 | ctx.drawImage(this.grassCanvas, 0, 0) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | clear() { |
| 72 | this.ctx.clearRect(0, 0, this._width, this._height) |
| 73 | } |
| 74 | } |