This is the last installment of the series on parallax scrolling in Love2d. In the previous posts, we first discussed how we could implement this technique by defining a camera component and the concept of layers, and how we could reason about the two different coordinate systems that we had: the world and the camera coordinate systems. Consider reading these previous posts before going on, because this installment builds upon them.
We will see now how we can represent an object that is in a given camera space in another camera space, which can be useful for multiplayer games. The problem statement is depicted in Figure 1. We see that we have two cameras, each one representing a fragment of the same world. One of the cameras (camera 1) is in the position (x1, y1), whereas camera 2 is in (x2, y2). Again, for the sake of simplicity, we will focus on the X dimension, but the same reasoning applies to the Y dimension. Each camera follows a different player, namely player 1 and player 2, which have their positions in their camera spaces posPlayer1_cam1 and posPlayer2_cam2, respectively. Now, assume that we want to represent a ghost version of player 2 in the camera space of player 1, so that player 1 can see the position of player 2. Which is the position of player 2 in the reference system of camera 1, that is, which is the value of posPlayer2_cam1?
Figure 1. Problem statement
At first sight, the problem seems trivial, and for most cases, it actually is. We simply need to represent the (world) position of player 2 in relation to the camera. Therefore, if the position of player 2 is posPlayer2, and as usual we assume that players belong to layers with scale = 1, we know that:
posPlayer2_cam1 = posPlayer2 - posCamera1 (1)
Likewise, the position where the player will be drawn would be:
posPlayer2_draw = posCamera1 + posPlayer2 - posCamera1 = posPlayer2 (2)
And that's it. This is what works in most of the cases.
However, there is a situation that we need to consider. Assume that there are obstacles with which the players can collide. If these obstacles belong to a layer with scale = 1, then everything works as we've just described. However, if obstacles belong to layers with a different scale, weird things happen. Suppose that obstacles belong to a layer with scale = 2. Then, as we saw in the previous post, advancing the camera u units result in the object being moved 2u units to the left. Therefore, if we used simply (1) or (2), player 1 would have the impression that player 2 collides (stops) before reaching the obstacle, as depicted in Figure 2.
Figure 2. Player 2 reaches the obstacle before it is perceived by player 1
This is happening because whereas the obstacles are moving with a rate of 2u units to the left, player 2 is being moved in camera 1 at a rate of 1u unit to the right. Therefore, player 2 reaches the obstacle in camera 2 before it reaches it in camera 1. The solution is moving player 2 in camera 1 at the same rate as the obstacles move but in opposite direction, that is, 2u units to the right.
If obstacles belong to layers with scale = n, 0<n<1, then player 2 would reach the obstacle in camera 1 before it reaches it in camera 2, and player 1 would therefore perceive as if player 2 had run through the obstacle, as depicted in Figure 3.
Figure 3. Player 1 perceives that player 2 run through the obstacle
Therefore, the solution consists of correcting this displacement: player 2 has to move in camera 1 at the same rate than obstacles. If camera 1 remains still and camera 2 advance u units, player 2 has to be moved 2u units to the right in camera 1. If camera 2 remains still and camera 1 advance u units, player 2 has to be moved 2u units to the left in camera 1. So the general pattern is: for every unit u of difference between the positions of camera 2 and camera 1, the position of player 2 has to advance 2u units.
posPlayer2_cam1 = posPlayer2_cam2 + 2*(x2 - x1) = posPlayer2 - x2 + 2*(x2 - x1) (3)
posPlayer2_draw = x1 + posPlayer2_cam1 = x1 + posPlayer2 - x2 + 2*(x2-x1) (4)
We can simplify (3) and (4) as follows:
posPlayer2_cam1 = posPlayer2 + x2 - 2 * x1
posPlayer2_draw = posPlayer2 + x2 - x1
Finally, we can generalize the formulas for a player in a layer with scale = n, and obstacles in a layer with scale = m:
posPlayer2_cam1 = posPlayer2 + (m - n)*x2 - m*x1
posPlayer2_draw = posPlayer2 + (m - n)*x2 + (1 - m)*x1
And this is all! Hope you enjoyed this series of posts as much as I enjoyed writing it. As I mentioned in the first post, these are the considerations that I took into account while developing the parallax scrolling system for our game to come, Breaking Fast, the result of which you can watch in the following video: