In this post I would like to share some of my thoughts about collision handling. I’ll first state the final effect that I want (the goal), then list a few different schemes (including some that I have seen and I have come up with on my own) and compare them with the requirements (e.g. point out the problems), during that and at last ask a couple of questions.
I am going to develop a pixel 2D platform game and have already many ideas. One of them is that I wish it can hold a principe of “freedom”. There are some examples that satisfy this principe:
Requirement 1. The objects in a map should be able to have continuous coordinates - One can move every object pixel by pixel (unlike the tile map). Also every object should be able to own a script so that we can customize the behavior of it and the possibility is extended in a far way.
Requirement 2. The objects can have arbitrary sizes (for now we don’t consider the shape of the hit-box).
Requirement 3. The player can have an arbitrary speed, especially a pretty large one (this is a little unusual but I want it to be possible).
The first requirement seemed to be solved: See Free/Flexible tile map?. The difficulty of solving the other two is, the collision should still work perfectly while those points are achieved.
By the way, in the whole discussion we firstly assume that the hit-box shape of the player is a rectangle.
Now we have a look at some different collision handling schemes and evaluate them.
I think this is the most classical idea to handle collisions. In each frame it uses the messages that contain the information of collisions among kinematic collision objects to make a calculation to correct the player’s position when he goes into blocks. For details see for example Resolving collisions.
There are two problems:
Problem 1.1. Sometimes the collision direction (a vector) is determined differently from what people hope (see this explain), what can cause bugs like, that the player gets stuck on the wall (Wall-clipping collision issue) and that the player falls suddenly when walking on one-directional platforms (Directional platforms help). At the moment I don’t know if we can avoid such problems when the correction method is still used.
Question 1. Is it possible to solve the bugs mentioned above if we insist on using this algorithm? If yes, how?
klaytonkowalski advised using raycast instead, what we are going to analyze below. As he also mentioned, in Asset Portal there’re some modules made from others. Currently I have looked through the codes from platypus, Defold Simple Platformer (deleted?) as well as True Tile Collisions. The first two use raycast. It looks like - I’m not sure - the third one uses tile map and limit the tiles into specific formats. After a glance I think it describes collisions mathematically instead of using collision shapes. More important is, I would like to write the code from this part on my own since collision handling is a very basic core part in a game so I must understand the theory.
Another problem is:
Problem 1.2. If the speed of the player is too large, it’s possible that it runs through the wall.
This may happen because when the player is really fast, at the frame after the frame where he started to move, he has already reached the the opposite. So no collisions between the player and the wall are detected, and that contradicts Requirement 3.
It detects the collisions by drawing linear rays from the player’s center out and finding the intersections. See physics.raycast() from API reference. In the two examples above, platypus and Defold Simple Platformer, this method is used. It avoids the Problem 1.1 and Problem 1.2, but another bug may appear:
Problem 2.1. Sometimes the rays don’t intersect the obstacles.
Two reasons could cause this problem: The first is pretty small objects. Since the rays are only lines, “gaps” between them can’t be avoided. And if there’re objects that are smaller than the gap, they won’t be detected. The following figure shows the situation.
If we meet some sharp corner a similar problem will also occur:
I actually made a thin tile and a slope in platypus and got the expected result from the test:
(The correct effect should be that the player can stand on them stably.) That contradicts Requirement 2.
The second factor, again, is the possibly too large speed of the player. The distance between the rays becomes big when we look at a place far away from the player, and an obstacle may be missed by the rays even if its size is normal. This doesn’t matter when the player is not so fast, because the player can’t directly move so far, and when he approaches that obstacle the gap becomes small, then the rays are able to catch it. But if the speed is too large, the player may skip the obstacle.
(Here we can suppose the player tries to jump up vertically. It should be stopped by the bottom of the obstacle but it may skip it - in 1 frame - because of the high speed.)
Of course we can add more rays, but it doesn’t solve the problem perfectly.
Now I’m going to give a few approaches from my own thoughs.
This method uses raycast too, but instead of drawing 8 rays from the center of the player, it draws rays from the player to the place where the player is going to reach at the next frame. For instance, as the figure below shows, we can use rays to connect the corresponding vertexes of the player’s shape at the current frame and the shape that we plan to reach at the next frame, find the nearest intersection point on each ray, calculate the distances of these points on their rays, and find out the shortest one (the green segment in the figure). At last we calculate a new vector by reducing the length of player’s current velocity to this distance, and move the player by that vector.
In general this method solves the “high-speed problem” because the rays are parallel, but the problem with small obstacles still can’t be avoided. Again, we can add more rays (e.g. starting with the mid-points of the four sides) to make the detection more precise, but gaps always exist (extremely speaking, we want to be able to deal with a 1px × 1px obstacle). (I think this algorithm is good enough if we don’t require so small objects, though.)
However, what if we add so many rays, that from every pixel point on the sides a ray starts? The gaps won’t exist any more actually. It’s crazy, since so many calculations must be done every frame! But when we observe how the rays look like, we will find that parallelograms are formed.
Here is the scheme that I think could be the final solution:
Gaps always appear because the rays are one-dimensional. In order to continuously catch every point we need two-dimensional models. Thus I’m considering if the rays can be extended to have a width; or, if we can use parallelograms to detect intersections. If so, then we’ll be able to have a great algorithm. Like the figure below, we draw parallelograms between the current and the target place, and use them to get collision information.
(Maximally we only need to draw two parallelograms and which sides should be start from is determined by the direction of the velocity. When the player moves horizontally or vertically only one is needed.)
This method can be considered as “natural”. It looks like as if we try to move the player to the target place pixel by pixel, and after every small step the collision is tested. If we think of the pixels as very tiny, then it’s like a continuous physical process in our real world (at least from a macro perspective). It won’t meet any high-speed problem or small-obstacle problem.
But at the moment it’s only an idea and I don’t know if it’s actually good and if it’s realizable. That’s where I need your help:
Question 2. Is it possible to realize this method, which uses parallelograms to detect intersections? And concretely, how would we use it - would the result of the collision detection be a set of sides (since there’re so many intersection points) or something else?
Question 3. Is there any other problem? How difficult would the calculation be (this is relevant to the running fluency of the game)?
And, in the end, two general questions:
Question 4. Are there mistakes or points that I didn’t explain well in my statements? And would you like to add something?
Question 5. Do you know any other methods (especially a method that can achieve my goal)?
(Also I’m a little interested in how the engine deals with the collision detections, both kinematic and dynamic. It’s nice if someone can explain this in a summary.)
Thanks a lot!