src/actors/Actor.js 3.1 K raw
1
import sprites from '../sprites.js'
2
3
const cache = new Map()
4
5
// analyze the pixels and create a map of the transparent
6
// and non-transparent pixels for hit testing
7
function getSpriteAlphaMap(imageData, name) {
8
  if (cache.has(name)) {
9
    return cache.get(name)
10
  }
11
12
  const sprite = sprites[name]
13
  const lines = []
14
  const initIVal = imageData.width * sprite.y * 4
15
16
  // for each line of pixels
17
  for (
18
    let i = initIVal;
19
    i < initIVal + sprite.h * imageData.width * 4;
20
    // (increments by 8 because it skips every other pixel due to pixel density)
21
    i += imageData.width * 8
22
  ) {
23
    const line = []
24
    const initJVal = i + sprite.x * 4
25
    // for each pixel in the line
26
    // (increments by 8 because it skips every other pixel due to pixel density)
27
    for (let j = initJVal; j < initJVal + sprite.w * 4; j += 8) {
28
      // 0 for transparent, 1 for not
29
      line.push(imageData.data[j + 3] === 0 ? 0 : 1)
30
    }
31
32
    lines.push(line)
33
  }
34
35
  cache.set(name, lines)
36
  return lines
37
}
38
39
export default class Actor {
40
  constructor(imageData) {
41
    this._sprite = null
42
    this.height = 0
43
    this.width = 0
44
    this.x = 0
45
    this.y = 0
46
47
    // the spriteImage should only be passed into actors that will
48
    // use hit detection; otherwise don't waste cpu on generating
49
    // the alpha map every time the sprite is set
50
    if (imageData) {
51
      this.imageData = imageData
52
      this.alphaMap = []
53
    }
54
  }
55
56
  set sprite(name) {
57
    this._sprite = name
58
    this.height = sprites[name].h / 2
59
    this.width = sprites[name].w / 2
60
61
    if (this.imageData) {
62
      this.alphaMap = getSpriteAlphaMap(this.imageData, name)
63
    }
64
  }
65
66
  get sprite() {
67
    return this._sprite
68
  }
69
70
  // the x value of the right side of it
71
  get rightX() {
72
    return this.width + this.x
73
  }
74
75
  // the y value of the bottom of it
76
  get bottomY() {
77
    return this.height + this.y
78
  }
79
80
  hits(actors) {
81
    return actors.some((actor) => {
82
      if (!actor) return false
83
84
      if (this.x >= actor.rightX || actor.x >= this.rightX) {
85
        return false
86
      }
87
88
      if (this.y >= actor.bottomY || actor.y >= this.bottomY) {
89
        return false
90
      }
91
92
      // actors' coords are intersecting, but they still might not be hitting
93
      // each other if they intersect at transparent pixels
94
      if (this.alphaMap && actor.alphaMap) {
95
        const startY = Math.round(Math.max(this.y, actor.y))
96
        const endY = Math.round(Math.min(this.bottomY, actor.bottomY))
97
        const startX = Math.round(Math.max(this.x, actor.x))
98
        const endX = Math.round(Math.min(this.rightX, actor.rightX))
99
        const thisY = Math.round(this.y)
100
        const actorY = Math.round(actor.y)
101
        const thisX = Math.round(this.x)
102
        const actorX = Math.round(actor.x)
103
104
        for (let y = startY; y < endY; y++) {
105
          for (let x = startX; x < endX; x++) {
106
            // doesn't hit if either are transparent at these coords
107
            if (this.alphaMap[y - thisY][x - thisX] === 0) continue
108
            if (actor.alphaMap[y - actorY][x - actorX] === 0) continue
109
110
            return true
111
          }
112
        }
113
114
        return false
115
      }
116
117
      return true
118
    })
119
  }
120
}