
In most modern games, it is impossible to do without detecting and further processing collisions (shot, jump, banal rebound from an obstacle). At first glance, their implementation seems quite simple, but is it really so? I will try to briefly explain the essence of the problems that I encountered.
By tradition, after reading a few articles, you begin to feel like a god, capable of doing anything. I think many people have encountered a similar problem and can imagine what follows it ... correctly, a series of big problems. But first things first.
Preparation of the "landscape"
So where to start? It seems to me that it is best to start with static objects, because it is much easier to work with them (no need to worry about redrawing, changing position / shape and other things).
var background = new LibCanvas( '#canv' ) .size(512, 512).start(); // LibCanvas
background.createShaper({ //
shape : shape,
stroke: '#00f' ,
fill : '#004'
});
that's all, the background is complete. Ideally, we should not touch him anymore.
Creating an object concept
Then you can consider, in fact, the “object”
(that which moves along the canvas and will bounce off obstacles) . I believe that the consideration of the object should start from the end, namely, from what it should be able to do in the end.
Everyone will have their own list, but for me it looks like this:
- the object must know its position and size
- the object must know the direction and speed of its movement
- the object must know how it looks
- the object must be able to be printed on the canvas, as well as “reprinted” (removed from one place and appear in another)
- the object must be able to define collisions with the edges of the canvas
- the object must be able to identify and handle collisions with obstacles
With the list of "possibilities" of the object, we are now more or less familiar. We start implementation.
Object implementation
First, we need to create a separate layer for the objects being moved
(so as not to redraw other layers each time, for example, background) :
var cont = background.createLayer( 'cont' );
Now it is necessary to place an object on this layer, which will be defined as follows:
var object = {
center: {x, y}, //
speed: {x, y}, //
size, //
buffer, //
redrawBuffer(), // , object.buffer
print(), //
redraw(), //
animate(), //
findBounce() //
};
The following question arises: how to fill the attributes of this object? Let's go in order.
')
Center, speed and size
Let us catch 2 clicks on the canvas - the coordinates of the first are the center of the object, and using the coordinates of the second, it will be easy to calculate the components 'x' and 'y' of the velocity of the object.
No sooner said than done. We implement:
cont.listenMouse(); // “”
var mouse = cont.mouse;
var flag = false ; // –
mouse.addEvent( 'mousedown' , function () {
flag = !flag;
});
cont.addRender( function () {
if (mouse.inCanvas) {
if (flag) {
object .size = size; //
object .center.center.moveTo( mouse.point );
object .speed.x = 0; //
object .speed.y = 0;
}
else { //
object .speed.x = mouse.point.x – object .center.x; //
object .speed.y = mouse.point.y - object .center.y;
}
}
});
So, let's go further.
What is object.buffer, object.redrawBuffer, object.redraw and why are they actually needed?
A bit of theory - when you call the
cont.update () function, a request for redrawing is sent to the next rendering stage
(if you synchronously call “update” five times in a row, the redraw will be done only once - when the next frame is rendered) . But, anyway, the whole canvas will be redrawn, which is rather inefficient
(a bit of math: canvas (512 * 512) = 262144 points, object size - even 50 * 50 = 2500 points, which is about 10 times smaller than the entire canvas ) .
Since we will move our object, which means redrawing the canvas many times, it will be much more efficient to just cut the image from one place and paste it into another. This is the easiest way, for those who do not want to think, but want to solve problems right in the forehead. The following option is much more interesting, at least for me: we do not cut an image of an object from the canvas and save it to the buffer with each move, but have two functions -
redrawBuffer () and
redraw (fromX, fromY, toX, toY) . In case of changing the type of object, the first one is suitable: redrawBuffer (), which changes the contents of the buffer. To move, you can use the redraw function (fromX, fromY, toX, toY), which “cleans” the space under the object in the “old” space and inserts the image from the buffer into the “new”:
redraw: function (canvas, beforeX, beforeY, afterX, afterY) {
var params = {
fromX: (beforeX - this .size),
fromY: (beforeY - this .size),
size: ( this .size*2),
toX: (afterX - this .size),
toY: (afterY - this .size)
}
canvas.ctx.clearRect( params .fromX - 1, params .fromY - 1, params .size + 2, params .size + 2);
canvas.ctx.drawImage( this .buffer, params .toX, params .toY);
}
Object reaction to collisions with canvas borders
This is realized to the horror simply - if the object has reached the top or bottom edge, then:
object .speed.y = - object .speed.y;
if right or left:
object .speed.x = - object .speed.x;
Identify and handle obstacles
How to understand where an object should bounce after a collision with an obstacle of arbitrary shape? The answer to this question can be given by old school textbooks with a physics / geometry course - “the angle of incidence is equal to the angle of reflection”. Since physics and geometry can remember not everything, I think it is worth recalling that these angles are counted from the normal (perpendicular) to the surface at the point:
Here, 'H' and 'G' are normals. 'A' and 'D' are the speeds before the collision. 'C' and 'F' - speeds after a collision.But, as is often the case, in words everything is quite simple, but in practice, alas, no.
So, let's start with a fairly general algorithm:
- Move the object until it encounters an obstacle
- Find the normal
- Change speed
- Returning to p.1
Let's start to sort this algorithm on points:
Move the object until it encounters an obstacle
With the movement, everything is simple: we draw an object, change the position of its center, erase the old one, draw a new one — that's all the movement. But how to understand that two objects (object and background) collided? For this I want to remind that now the canvas canvas consists of two layers
(“background” - static and “object” - dynamic) , and we need to understand that 2 objects intersected (or just touched). After reading
this article, I more or less imagined the algorithm for finding the intersection:
- get the image of the layer with the object and the image of the layer with an obstacle
- recall that: “ Pixels are stored in objects of type ImageData. Each object has three properties: width, height, and data. The data property is of type CanvasPixelArray and contains an array of elements of width * height * 4 bytes in size; that means each pixel contains an RGBA color. ”
- we write a simple cycle in which there is only one condition
var pixels1 = background.ctx.getImageData( x,y,size,size);
var pixels2 = cont.ctx.getImageData( x,y,size,size);
for ( var i = 0; i < pixels2.length; i += 4 ) {
if ((pixels1[i+3] != 0) && (pixels2[i+3] != 0)) {
/* */
break ;
}
}
At this stage, I would again advise to ask about efficiency - is it worth it to run 2 * 512 * 512 pixels with each object movement? Of course not. You can, without any consequences, reduce the “search zone” to a square in which you can fit an object
(of course, if the object to be moved is only one) . But is it possible to reduce this area even more, say, to 1 pixel? Yes you can. But this further “reduction” will affect the accuracy of finding the collision. As an example, I will give a picture where the green circle is an object, and the blue bar is an obstacle:

If for calculation we took the “search zone” equal to (object.size * 2) * (object.size * 2)
(object.size is the radius) , then a collision would be set, but if only 1 pixel was selected for the calculation (center ), then the collision is not defined.
How to do to you is a purely personal matter, but I decided to sacrifice accuracy and began working with the center of the object.
The collision is established, but what next? How to deal with the scary word “normal”?
Find the normal
There are two ways - right and mine.
Method 1. Correct:- find the “zone” of touch
- find the boundary point of the obstacle from this zone
- interpolate them and find the normal equation at the tangency point
Method 2: Incorrect:- get the zone of intersection of the object and obstacles.
- find the center of this zone:
var avgPoint{x:0, y:0, number:0};
for ( var i = 0; i < pixels2.length; i += 4 ) {
if ((pixels1[i+3] != 0) && (pixels2[i+3] != 0)) {
avgPoint.x += (i/4)%( this .size*2);
avgPoint.x += Math.round((i/4)/( this .size*2));
}
}
avgPoint.x = Math.round((avgPoint.x / avgPoint.number) + this .center.x - this .size);
avgPoint.y = Math.round((avgPoint.y / avgPoint.number) + this .center.y - this .size);
- connect the found center of the “intersection zone” (avgPoint.x, avgPoint.y) with the center of the object. This will be normal.

Change speed
So, open the textbooks again and find out that upon reflection the normal component of the velocity changes sign, and the tangential (perpendicular normal) remains unchanged.
Therefore, we have the task of decomposing the velocities from the (x, y) coordinate system into (n, t) coordinate system. Here comes the usual geometry from the upper classes:
var hyp = Math.hypotenuse((avgPoint.y - object .center.y),(avgPoint.x - object .center.x))
var sinNA = (avgPoint.y – object .center.y)/hyp;
var cosNA = ( object .center.x - avgPoint.x)/hyp;
var nSpeed = this .speed.x * cosNA - this .speed.y * sinNA;
var tSpeed = this .speed.x * sinNA + this .speed.y * cosNA;
nSpeed = -nSpeed;
object .speed.x = (tSpeed * sinNA + nSpeed * cosNA);
object .speed.y = (tSpeed * cosNA - nSpeed * sinNA);
That's all, our object is able to change the directions of speeds depending on the form of the encountered obstacle. It remains to combine all the functions and objects in the final script and see
the result .
At this my article is finished, thank you for your attention!