Tip:
Highlight text to annotate it
X
Ok, we're gonna talk about pixel collision detection using JavaScript and HTML5 canvas.
In order to watch this video you should also have an understanding of ternary operators
and axis-aligned bounding-box collision detection.
We have alot to go through.
You should already know how to create a 2d canvas context (ctx).
Once we have determined that an AABB collision has occurred, we now need to check pixels between
the intersecting area between the two sprites.
Let's say we have sprite A. And sprite B. And they are intersecting.
But let's say inside of here we have, an image like this.
And B is also something like this.
We know the bounding boxes are intersecting but that doesn't necessarily mean that the
sprites themselves are intersecting.
So what we need to find is the middle box by analyzing the edges of A and B.
(Ax,Ay and AX,AY are the top left and bottom right corners of A, and likewise for B and
C.)
(elaborating...)
X grows to the right of the screen, Y grows to the bottom of the screen.
So far so good?
Here are some other scenarios where this is still true.
This is true for all configurations of A and B.
(Formula will reappear shortly.)
(Movie magic!)
(elaborating...)
(in pain staking detail...)
(Jump to 11:20 to skip all this.)
Earlier I mentioned that you should have a canvas context (ctx). It's probably good to
represent your sprites as objects and then give them "draw()" methods that accept an
arbitrary context.
We're going to use a separate context, an "imaginary context" (ictx) to check for pixel
collisions.
You'll need to make two contexts: one that is tied to the DOM element that you display,
and another that is tied to an unattached canvas element that is never displayed, only
used as imaginary scratch space.
The sprite should know how to draw itself to whatever context is given.
The draw method should not change the state of the sprite or anything else. This is important,
as you might need to draw the same sprite numerous times to compare it to other AABB
collisions.
Now we need to clear the imaginary context (ictx) using the coordinates of our imaginary
box C.
ictx.clearRect(Cx,Cy,CX-cx,CY-cy);
This clears just enough space for us to check, there's no reason to waste cycles on clearing
away space we don't care about.
Now we need to "imagine" A.
A.draw(ictx);
Now we grab a slice of A in ictx defined by C.
We'll call this slice iA.
var iA = ictx.getImageData(Cx,Cy,CX-Cx,CY-Cy);
(elaborating...)
We do the same thing for B.
ictx.clearRect(Cx,Cy,CX-Cx,CY-cy);
B.draw(ictx);
var iB = ictx.getImageData(CX,CY,CX-Cx,CY-Cy);
The image data is an object containing an array of bytes, but we'll get to that...
Now we have our two slices (iA and iB), just them, nothing else drawn, A and B in their
own slices with nothing interfering.
The image data is an object containing an array of bytes, with every group of four elements
representing a pixel's red, green, blue, and alpha channels respectively.
(elaborating...)
iA.data[0] is the top left pixel's red channel...
iA.data[3] is the top left pixel's spooky alpha channel. This is the channel we care
about.
iB data contains similar data.
Infact, these are the same size, because we used C for both.
So now we have two arrays of equal length, whose indices mean the same thing, but contain
different data. Now we can compare pixels.
Since checking every pixel would be too exhaustive can cause your program to slow down,
We'll only check every fifth pixel. That gives us a speedup while staying reasonable to the
human eye. You can change this whenever you want, but this example uses every fifth.
var resolution = 4 * 5; // four channels per pixel, check every fifth.
You could hard code "20", but I wouldn't recommend doing that, as you might want to make the
"5" a variable and change it somewhere else. Keeping it 4 * 5 lends clarity.
We now need to precalculate the length of the slice data, since re-checking the length
(which remains constant) every iteration will slow us down.
Since iA.data and iB.data are the same length we'll just use iA.data.length.
Now we iterate pixels determined by the resolution and check the alpha channel.
If there is no opacity at the current pixel of A, or there is no opacity at the same pixel
in B, then there is no collision.
(elaborating...)
You can change it to allow half-or-more opaqueness to collide, for example. This would grant
you the ability to have fog or auras that don't technically "collide".
So at this point, if we're not skipping, we know a collision has occurred. Insert your
code to handle the collision and break out of the loop.
(elaborating ...)
If B is a little squiggly guy like that.
And A is a squiggly guy like that.
Notice that the boxes are indeed overlapping but that doesn't mean
that the pixels are colliding, so we need to check inside of
the magical area C.
We do everything in C
so we don't have to check every uninterested region of the sprites. C is
the intersecting area so
it's the only place where a collision is possible.
I chose every 5th because usually sprites have even numbers for width and height. Using
an odd number will check a diffuse pattern, instead of lines if an even number was used.
Image
data is
a one-dimensional array
of pixel bytes, it makes no differentiation between pixel rows, it consists of one long
scan-line reading of the slice from
top left to bottom right.
We're done.
Thank you very
much
for
watching
:)