How To – 2D Camera That Follows The Player

Long time, no post! πŸ˜‰ Now that Christmas and New Year festivities are in the past, I’m going to tell you about one of the ideas I started lazily implementing during the holidays. Basically it will be (if I ever complete it) a simple 2D arcade snowfight with a side-view camera.

After toying with GLScene and ODE (a physics engine) I created a simple map and a grey cylinder-like object that represents the player. Then it occured to me that I needed to make the camera smoothly follow the player…

The Requirements

  • The view should move smoothly, avoiding jerks and sudden jumps.
  • If the player is moving just around the center of the screen, the camera shouldn’t move (so that it doesn’t sway back and forth needlessly).
  • The camera model should be flexible.

For the second requirement, I created an array that describes the maximum distance the camera may move away from the player in any direction without triggering the tracking behaviour :

Camera_MaxDelta:array[0..3] of single=(-3.5, 6, 3.5, -2);
//left / up / right / down

The Algorithm

To move the camera smoothly I assigned a velocity and acceleration vectors to the camera. Each frame the velocity is modified my acceleration*time_since_last_frame. So we need a way to calculate the acceleration.

As we want the player to stay approximately in the center of the view, we can calculate the “delta” distance between players position and the location of the player. Once we know this value, we decide that we want the camera to smoothly move to the player so that after 1 second (or another arbitrary time “t”) it points at the player. Let’s see some equations!

s = v*t + (a*(t^2)) / 2
( … derivation happens here… )
a = (2 * (s-v*t)) / (t^2)

This formula is used to calculate the acceleration each frame. But what about the “dead zone” in the center of the screen? Well, I couldn’t think of a “correct” solution, so I used a hacky one and it worked for me. See the following code :

procedure Camera_FollowPoint(const APoint:TVector3f;
const DeltaTime:double);
const
  t=0.5;
  min_vel=0.8;
  st_coef=1;
var
  Delta:TVector3f;
begin
 Delta:=VectorSubtract(APoint,Kamera.Position.AsAffineVector);

 //CameraAccel and CameraVelocity are global arrays.
 //Shame on me for not implementing a full-featured
 //camera class.
 CameraAccel[0]:=(2*(Delta[0]-CameraVelocity[0]*t))/(t*t);
 CameraAccel[1]:=(2*(Delta[1]-CameraVelocity[1]*t))/(t*t);

 //if inside the no-tracking rectangle, cancel movement
 if not ((Delta[0]>Camera_MaxDelta[2]) or (Delta[0]<Camera_MaxDelta[0])) then
  begin
   if abs(CameraVelocity[0])<min_vel then begin
    CameraVelocity[0]:=0;
    CameraAccel[0]:=0;
   end;
 end;

 if not ((Delta[1]>Camera_MaxDelta[1]) or (Delta[1]<Camera_MaxDelta[3]))
  then
  begin

   if abs(CameraVelocity[1])<min_vel then begin
    CameraVelocity[1]:=0;
    CameraAccel[1]:=0;
   end;
 end;

 CameraVelocity[0]:=CameraVelocity[0]+CameraAccel[0]*deltaTime;
 CameraVelocity[1]:=CameraVelocity[1]+CameraAccel[1]*deltaTime;

 Kamera.Translate(
   CameraVelocity[0]*deltaTime,
   CameraVelocity[1]*deltaTime,
   CameraVelocity[2]*deltaTime);
end;

About extensibility – remember that APoint doesn’t neccessarily have to be the players’ exact position – it could be the average of his position and cursors’ position (for aiming), or player position + speed vector, etc.

I hope you find this useful πŸ™‚

Related posts :

Leave a Reply