/ src / Joystick.ts
Joystick.ts
  1  import { Point } from './Point'
  2  
  3  export class Joystick {
  4    private readonly canvas: HTMLCanvasElement
  5    private readonly ctx: CanvasRenderingContext2D
  6  
  7    private readonly outerRadius: number
  8    private readonly innerRadius: number
  9  
 10    private startPosition: Point
 11    private pointerPosition: Point
 12  
 13    private scale: number
 14  
 15    public constructor() {
 16      this.canvas = document.getElementById('joystick') as HTMLCanvasElement
 17      this.ctx = this.canvas.getContext('2d')
 18  
 19      this.outerRadius = 8
 20      this.innerRadius = 5
 21  
 22      this.startPosition = new Point(0, 0)
 23      this.pointerPosition = new Point(0, 0)
 24  
 25      this.scale = 1
 26    }
 27  
 28    public render(): void {
 29      const margin = 120 // arbitrary number
 30  
 31      this.canvas.width = this.outerRadius * 2 * this.scale + margin
 32      this.canvas.height = this.outerRadius * 2 * this.scale + margin
 33  
 34      this.canvas.style.left = `${this.startPosition.x - (this.canvas.width / 2)}px`
 35      this.canvas.style.top = `${this.startPosition.y - (this.canvas.height / 2)}px`
 36  
 37      this.ctx.save()
 38      this.ctx.scale(this.scale, this.scale)
 39      this.ctx.translate(margin / this.scale / 2, margin / this.scale / 2)
 40  
 41      this.ctx.lineWidth = 1
 42  
 43      this.ctx.beginPath()
 44      this.ctx.arc(this.outerRadius, this.outerRadius, this.outerRadius, 0, 2 * Math.PI, false)
 45      this.ctx.fillStyle = '#999'
 46      this.ctx.fill()
 47      this.ctx.strokeStyle = '#666'
 48      this.ctx.stroke()
 49  
 50      const pointerPosition = this.getAxisValues()
 51      pointerPosition.x *= this.innerRadius
 52      pointerPosition.y *= this.innerRadius
 53  
 54      this.ctx.beginPath()
 55      this.ctx.arc(
 56        this.outerRadius + pointerPosition.x,
 57        this.outerRadius + pointerPosition.y,
 58        this.innerRadius,
 59        0,
 60        2 * Math.PI,
 61        false,
 62      )
 63      this.ctx.fillStyle = '#555'
 64      this.ctx.fill()
 65      this.ctx.strokeStyle = '#333'
 66      this.ctx.stroke()
 67  
 68      this.ctx.restore()
 69    }
 70  
 71    public getStartPosition(): Point {
 72      return this.startPosition
 73    }
 74  
 75    public setStartPosition(position: Point): void {
 76      this.startPosition = position
 77    }
 78  
 79    public getPointerPosition(): Point {
 80      return this.pointerPosition
 81    }
 82  
 83    public setPointerPosition(position: Point): void {
 84      this.pointerPosition = position
 85    }
 86  
 87    public show(): void {
 88      this.canvas.style.display = 'block'
 89    }
 90  
 91    public hide(): void {
 92      this.canvas.style.display = 'none'
 93    }
 94  
 95    public getScale(): number {
 96      return this.scale
 97    }
 98  
 99    public setScale(scale: number): void {
100      this.scale = scale
101    }
102  
103    // @see https://codepen.io/jiffy/pen/zrqwON
104    // @see https://stackoverflow.com/a/20916980
105    public getAxisValues(): Point {
106      const startPosition = this.startPosition.clone()
107      startPosition.x /= this.scale
108      startPosition.y /= this.scale
109  
110      const pointerPosition = this.pointerPosition.clone()
111      pointerPosition.x /= this.scale
112      pointerPosition.y /= this.scale
113  
114      const angle = Point.angleBetween(startPosition, pointerPosition)
115  
116      const diffX = pointerPosition.x - startPosition.x
117      const diffY = pointerPosition.y - startPosition.y
118  
119      let distance = Math.sqrt(diffX * diffX + diffY * diffY)
120  
121      const coords = new Point(0, 0)
122      distance = Math.min(distance, this.innerRadius)
123  
124      coords.x = distance * Math.cos(angle)
125      coords.y = distance * Math.sin(angle)
126  
127      coords.x /= this.innerRadius
128      coords.y /= this.innerRadius
129  
130      return coords
131    }
132  }