2
\$\begingroup\$

I am working on a game, which is tile-based, similar to the way Terraria works. My player is 2 x 3 and can move over one-high obstacles, but this creates a very jittery effect, since my camera currently strictly follows the player. I was thinking of either somehow implementing a smoother camera movement or overhauling the player movement itself. I would love some feedback on how one might go about fixing this.

\$\endgroup\$
  • 1
    \$\begingroup\$ If you can show us how you currently move the player character, it might help us narrow in on suitable camera movement strategies to match. \$\endgroup\$ – DMGregory Nov 30 '18 at 2:32
1
\$\begingroup\$

In the scenario you describe, there are two points that could improve the smoothness of your camera: (1) the position of the camera with respect to the player, and (2) the movement of the camera towards its target position.

Position of the camera with respect to the player

You mention your camera is locked to the player. I assume you instantly snap your camera's center of focus to the player position. It is possible to handle this differently by having a small frame around the player, where the camera does not follow the player. As soon as the player moves beyond the frame, the camera is "pushed" back towards the player.

In your case, you mention the jitter being caused by "jumps" in the vertical player position when he climbs a one-tile ledge. You can apply the framing strategy above to the X- and Y-movement of the camera independently. If you do want to keep the player centered on screen horizontally, but want to avoid jitter due to sudden vertical movement, you can apply the strategy to the Y-axis only.

Your current camera strategy might look like this in pseudo-code:

// Update the player position
...

// Update the camera position
camera.x = player.x;
camera.y = player.y;

To apply framing, you could modify your code like this:

// Update the player position
...

// Update the camera position
const float camera_frame_size_hor = 48.f;
const float camera_frame_size_ver = 32.f;
camera.x = min(max(camera.x, player.x - camera_frame_size_hor), player.x + camera_frame_size_hor);
camera.y = min(max(camera.y, player.y - camera_frame_size_ver), player.y + camera_frame_size_ver);

In the above cas,e the camera will only follow the player when he moves more than 48 pixels away from the camera center horizontally, or more than 32 pixels away from the camera center vertically.

Movement of the camera towards its target position

You also mention you instantly snap the camera to the player position. Many games apply a form of smoothing to the camera movement. To apply smoothing, you calculate the target camera position as before. However instead of simple setting the camera's actual position to this target position, you gradually move its position towards the target.

Currently, you might simply set your camera position directly as follows:

// Update the player position
...

// Update the camera position
camera.x = player.x;
camera.y = player.y;

You could instead apply a simple low-pass filter as follows:

// Update the player position
...

// Update the camera position
const int camera_smoothing = 10;
float camera_target_x = player.x;
float camera_target_y = player.y;
camera.x = ((camera.x) * (camera_smoothing - 1) + camera_target_x) / camera_smoothing;
camera.y = ((camera.y) * (camera_smoothing - 1) + camera_target_y) / camera_smoothing;

Combined camera strategy

The two techniques above can be applied independently to smooth your camera movement in a more sophisticated way. Your camera code might look like this:

// Update the player position
...

// Update the camera position
const int camera_smoothing = 10;
const float camera_frame_size_hor = 48.f;
const float camera_frame_size_ver = 32.f;

float camera_target_x = min(max(camera.x, player.x - camera_frame_size_hor), player.x + camera_frame_size_hor);
float camera_target_y = min(max(camera.y, player.y - camera_frame_size_ver), player.y + camera_frame_size_ver);
camera.x = ((camera.x) * (camera_smoothing - 1) + camera_target_x) / camera_smoothing;
camera.y = ((camera.y) * (camera_smoothing - 1) + camera_target_y) / camera_smoothing;

Reference

I would like to recommend the following excellent article which analyses various camera strategies across many games. The articles has animated graphics to illustrate all strategies. Itay Keren - Scroll Back: The Theory and Practice of Cameras in Side-Scrollers

\$\endgroup\$
2
\$\begingroup\$

As Jelle van Kampen mentions in his answer; this is a valuable resource: Itay Keren - Scroll Back: The Theory and Practice of Cameras in Side-Scrollers

I'll provide my implementation of a camera system as well. The code below in in C# using Monogame. The principles should translate to any language.

The camera I use is of the type ‘Camera window’ with ‘LERP Smoothing’. It meams the camera follows the player, if the player pushes the boundaries of a window inside the camera viewport:

Camera: gameworld, viewport and boundaries

The blue area (1) is the gameworld. This area is bigger than the part shown on the player’s screen. The yellowish area (2) is the actual viewport that is rendered to the player screen. The reddish (3) part is the ‘camera window’: the player controlled character will always be inside that part. Only when the player actually pushes the boundaries of that inside section the view will move. Imagine the character moves to the right edge- the camera gets moved until the player is back inside the reddish area and will nog move again until the player reaches the edge again.

LERP smoothing means the camera ‘lags’ a bit and moves smoothly instead of immediate.

On to some code…

public class Camera
{
    private Rectangle _viewPort; // The portion rendered to the screen.

    private Rectangle _bounds;   // The total game area.
    private Rectangle _trackingBounds; // The area that an object is considered 'centered'.

    private Vector2 _targetPosition; // where do we want the camera to go?
    private Vector2 _currentPosition; // where is the camera really.

    private const float BOUNDSPERCENT = 0.30f; // The amount of border used to calculate the trackingbounds.

    public Camera(int width, int height, int worldwidth, int worldheight)
    {
        _viewPort = new Rectangle(0, 0, width, height);
        _bounds = new Rectangle(0, 0, worldwidth, worldheight);
    }

}

This code is the base of the camera class.

If you look at the constructor the camera initializes two rectangles:

the viewport– the dimension of the camera (the yellowish rectangle in the picture above) the bounds of the gameworld, the camera needs to know the dimensions of the gameworld; so it knows not to scroll outside the actual gameworld. This is the equivalent to the blue area in the picture. The variables should speak for themselves- note the ‘_trackingBounds’ variable is equivalent to the reddish area in the picture.

The trackingbounds are calculated in a function like this:

//sets when the viewport should move in relation to the preferred viewport.
private void SetTrackingBounds()
{
    _trackingBounds = new Rectangle(
        _viewPort.X + (int)(_viewPort.Width * BOUNDSPERCENT),
        _viewPort.Y + (int)(_viewPort.Height * BOUNDSPERCENT),
        (int)(_viewPort.Width * (1 - (2 * BOUNDSPERCENT))),
        (int)(_viewPort.Height * (1 - (2 * BOUNDSPERCENT)))
        );
}

Here we calculate the trackingbounds based on the current viewport location and the dimensions are a relative portion of the viewport size (using the const BOUNDSPERCENT).

Another function we need is to keep the camera within the boundaries of the gameworld. This function keeps the camera within the bounds:

private void KeepCameraWithinBounds()
{
    if (_viewPort.X < _bounds.X)
        _viewPort.X = _bounds.X;
    if (_viewPort.Y < _bounds.Y)
        _viewPort.Y = _bounds.Y;
    if (_viewPort.X + _viewPort.Width > _bounds.Width)
        _viewPort.X = _bounds.Width - _viewPort.Width;
    if (_viewPort.Y + _viewPort.Height > _bounds.Height)
        _viewPort.Y = _bounds.Height - _viewPort.Height;
}

It is a simple check, the code should be self explanatory.

Now we have the basic operations done, We’re going to actually track an object:

public void TrackObject(Vector2 objectposition)
{

    if(objectposition.X<_trackingBounds.Left)
    {
        _targetPosition.X = objectposition.X - _viewPort.Width * BOUNDSPERCENT;
    }
    if(objectposition.X>_trackingBounds.Right)
    {
        _targetPosition.X = _viewPort.X + (objectposition.X - _trackingBounds.Right);
    }
    if (objectposition.Y < _trackingBounds.Top)
    {
        _targetPosition.Y = objectposition.Y - _viewPort.Height * BOUNDSPERCENT;
    }
    if (objectposition.Y > _trackingBounds.Bottom)
    {
        _targetPosition.Y = _viewPort.Y + (objectposition.Y - _trackingBounds.Bottom);
    }
}

Let’s have a closer look: the function takes a Vector2 position in the gameworld. We check how the position is outside the tracking boundaries. if the position is outside the boundaries of the tracking window; the top left position of the camera rectangle is calculated that puts the object back inside that window.

The top left location is stored in a Vector2 targetposition variable. So the camera itself is not yet moved- instead we set the position we want the camera to move towards.

So far we have the functions to initialize the camera, calculate the boundaries of the tracking window and set the target position of the camera in relation of an object. Let’s put these things together and move the camera!

public void Update(GameTime gt)
{
    _currentPosition = Vector2.Lerp(_currentPosition, _targetPosition, 5f * (float)gt.ElapsedGameTime.TotalSeconds);

    _viewPort.X = (int)_currentPosition.X;
    _viewPort.Y = (int)_currentPosition.Y;

    KeepCameraWithinBounds();
    SetTrackingBounds();
}

Every frame the camera is updated. I use the Vector2.Lerp function to smooth the movement. Every frame it gets a percentage closer to the target. If the distance between the current and target position is big, the camera will move quick until the distance becomes so tiny (subpixel effectively) so the camera has reached the destination. One can play with the magic number in there (the 5f) so make the camera respond quicker or slower. The speed should depend on how fast objects in the game move, so the camera can keep up.

Then the camera viewport rectangle is moved and is checked against the bounds. Once all movement is done- the tracking window is recalculated.

The main game loop now needs to know what the camera is showing so:

public Rectangle View
{
    get { return _viewPort; }
}

The result is we have everything required to make a smooth moving, windowtracked game camera!

To use it, in the main gameloop:

public void Update(GameTime gameTime)
{
    //handle game logic.

    //update the camera
    _camera.Update(gameTime);
    _camera.TrackObject(_player.Position);
}

public void Draw()
{
   Rectangle viewport = _camera.View;

   //do culling or something using the viewport that is just set
   // or
   //Draw the game, using the top left- 
   //   corner of the viewport to know how to position your entities.
}

Hope this gives some inspiration on a camera system.

\$\endgroup\$
  • \$\begingroup\$ This simple method has been used all the way back to the original consoles, and the idea works amazingly well! You can tune the scrolling gameplay by shrinking the inner rectangle until it feels good. \$\endgroup\$ – Patrick Hughes Dec 1 '18 at 0:31
0
\$\begingroup\$

Can you provide some code ?

Do you move the player in the FixedUpdate? and apply camera movement in the Update ?

You should use FixedUpdate for camera movement AND player movement if your movement are physic based. You should use LateUpdate for the camera movement AND Update for player movement if your movement are not physic based

\$\endgroup\$
  • \$\begingroup\$ I would not recommend using FixedUpdate for camera movement (unless it has its own rigidbody), since it's typically running at a different rate than the rendering, this can make your game look unnecessarily choppy. Using interpolation and adjusting for variable delta time, you can get smooth movement with a camera in LateUpdate and physics-based character movement. \$\endgroup\$ – DMGregory Nov 30 '18 at 2:12
  • \$\begingroup\$ LateUpdate is always better, however there are some case when you, if your player move with physics and the camera that focus on player isn't, it will be very jittery \$\endgroup\$ – Bobby Nov 30 '18 at 3:09
  • \$\begingroup\$ Hence the interpolation & deltaTime adjustment I mentioned. Do that right and you eliminate the judder, and get buttery-smooth movement. :) \$\endgroup\$ – DMGregory Nov 30 '18 at 3:11
0
\$\begingroup\$

You could easily implement a smoother like you suggest by filtering the
camera position in a box car averager (aka: moving average).

Store the points in a fifo queue large enough to span the time it takes to jump one stair (x2) and make the camera position be the average of those points.

When a new position arrives remove the oldest and add the new one and preform a new average calculation.

\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.