Hello everyone, I am developing a strategy game and was trying to figure out, how I can smoothly zoom into a flat horizontal map. I originally wanted to post this as a question, but figured out the solution in the meantime. So this is just for people to read who want to do something similar.
I have a perspective camera that looks at a point on a flat map (plane_point
) has some distance
to the map (or zoom), that is determines how much of the map you can see. So the target plane_point
should always be in the center of the screen. I want to set the target distance
and plane_point
freely and lerp towards it every frame (exponential smoothing). This is the structure of the camera:
struct LerpedCamera{
target: Camera
lerped: Camera
}
struct Camera{
plane_point: Vec2,
distance: f32
}
Now let's say a player wants to zoom into a part of the map. We get scroll: f32 (-1.0 or +1.0)
and cursor_pos: Vec2
as input. Also we need to have a function project_plane_point(cursor_pos: Vec2, camera: Camera)
that returns the plane_point
we would hit on the flat map, when we shoot a ray through cursor_pos
, if the camera was camera.distance
units apart from the map and is currently pointing at camera.plane_point
(current middle of the screen). How you implement your project_plane_point
depends on your camera type (e.g. 2d vs orthographic vs perspective).
There is one invariant we want to keep while zooming: the plane point the cursor point at should be the same before and after the zoom. That means, no matter what position our cursor is at, zooming in and out does not change what position on the map the cursor points at.
To keep this invariant, we can determine how much the plane point would shift after zooming in/out:
// get the plane point the cursor is currently pointing at:
plane_point_before = project_plane_point(cursor_pos, camera.target);
// just modify the distance of the camera
camera.target.distance += scroll;
// get the plane point the cursor will be pointing it with the camera zoomed in:
plane_point_after = project_plane_point(cursor_pos, camera.target);
// get the difference between both points
let shift = plane_point_after-plane_point_before;
// correct for the shift, so that the cursor still points at the same plane point after zooming in:
camera.target.plane_point -= shift;
Here is how it looks like:
There is just one thing we need to be very careful about: The lerp speed of distance
and plane_point
need to be exactly the same. Otherwise we get jiggly results like this: