Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm trying to make a camera that follows a race car. Right now, I'm smoothing the camera movement out by moving the camera's position a fixed percentage lerp'ed from the camera's position to the car's position.

However, this makes the car appear closer to the edge of the window when it's moving toward that edge. I want the camera to appear some distance ahead of the car. This makes more sense to me--The player wants to see what's ahead.

I tried using the velocity of the car, and putting it through a logistic growth function so that it would throw out an offset for the camera position. This made close-quarters collisions too jerky. This was especially true when the car obtains a boost that applies a greater force over some time.

Then, I tried applying the logistic function only when the speed was greater than a certain amount. This didn't work. When the car obtained a boost, the camera would offset itself. This made it jerky in close-quarters collisions. When the car was moving in open space without boosts, the camera wouldn't move toward the velocity.


How do I implement a camera that follows a race car so that the window shows more of what's in front of the car without introducing jerkiness? My game is viewed from the top-down.

share|improve this question
add comment

3 Answers 3

up vote 3 down vote accepted

What I'd try would be:

  1. Multiply the car's velocity by some constant Δt and add it to its location. (That is, calculate what the car's location would be Δt time steps in the future if its velocity remained constant.) Call this the target point.
  2. Have the camera chase the target point in some natural manner. A simple way to do this would be to let the camera position be the exponentially weighted moving average of the target point position; this is essentially what you're doing by repeatedly interpolating between the current camera position and the target. You could also try adding some inertia to the camera movement, so that it takes a while to change direction.
share|improve this answer
    
Thanks, I'll try it out. I like the point about inertia. –  skyuzo Oct 25 '11 at 20:08
add comment

Use the velocity and just multiply it with some value(you need to test witch one is good for you) and use that as an offset but then make sure that you don't position the screen to far off.

You could also use the function you had in the beginning but use the position+velocity*x as the cameras goal where x should be some value you choose depending on what looks good.

And the third way I would try is again to use the velocity*x as an offset and then have a camera offset that lerps towards the one the velocity offset.

share|improve this answer
    
I've already tried multiplying the velocity by some value, but that made it jerky at lower speeds. –  skyuzo Oct 25 '11 at 8:59
add comment

What you're looking for is a chase camera, but you might have to do some tweaks to go from the usual 3D chase camera to the 2D chase camera.

I've got a small snippet here for you that I used a long time ago. It's in C# for XNA, but I think you will get the general idea.

It's a bit long, sorry for that.

    public class ChaseCamera
{
    #region Chased object properties (set externally each frame, or via Update)

    /// <summary>
    /// Position of object being chased.
    /// </summary>
    public Vector3 ChasePosition
    {
        get { return chasePosition; }
        set { chasePosition = value; }
    }
    private Vector3 chasePosition;

    /// <summary>
    /// Direction the chased object is facing.
    /// </summary>
    public Vector3 ChaseDirection
    {
        get { return chaseDirection; }
        set { chaseDirection = value; }
    }
    private Vector3 chaseDirection;

    /// <summary>
    /// Chased object's Up vector.
    /// </summary>
    public Vector3 Up
    {
        get { return up; }
        set { up = value; }
    }
    private Vector3 up = Vector3.Up;

    #endregion

    #region Desired camera positioning (set when creating camera or changing view)

    /// <summary>
    /// Desired camera position in the chased object's coordinate system.
    /// </summary>
    public Vector3 DesiredPositionOffset
    {
        get { return desiredPositionOffset; }
        set { desiredPositionOffset = value; }
    }
    private Vector3 desiredPositionOffset = new Vector3(0, 6.0f, 15.0f);

    /// <summary>
    /// Desired camera position in world space.
    /// </summary>
    public Vector3 DesiredPosition
    {
        get
        {
            // Ensure correct value even if update has not been called this frame
            UpdateWorldPositions();

            return desiredPosition;
        }
    }
    private Vector3 desiredPosition;

    /// <summary>
    /// Look at point in the chased object's coordinate system.
    /// </summary>
    public Vector3 LookAtOffset
    {
        get { return lookAtOffset; }
        set { lookAtOffset = value; }
    }
    private Vector3 lookAtOffset = new Vector3(0, 2.8f, 0);

    /// <summary>
    /// Look at point in world space.
    /// </summary>
    public Vector3 LookAt
    {
        get
        {
            // Ensure correct value even if update has not been called this frame
            UpdateWorldPositions();

            return lookAt;
        }
    }
    private Vector3 lookAt;

    #endregion

    #region Camera physics (typically set when creating camera)

    /// <summary>
    /// Physics coefficient which controls the influence of the camera's position
    /// over the spring force. The stiffer the spring, the closer it will stay to
    /// the chased object.
    /// </summary>
    public float Stiffness
    {
        get { return stiffness; }
        set { stiffness = value; }
    }
    private float stiffness;

    /// <summary>
    /// Physics coefficient which approximates internal friction of the spring.
    /// Sufficient damping will prevent the spring from oscillating infinitely.
    /// </summary>
    public float Damping
    {
        get { return damping; }
        set { damping = value; }
    }
    private float damping;

    /// <summary>
    /// Mass of the camera body. Heaver objects require stiffer springs with less
    /// damping to move at the same rate as lighter objects.
    /// </summary>
    public float Mass
    {
        get { return mass; }
        set { mass = value; }
    }
    private float mass;

    #endregion

    #region Current camera properties (updated by camera physics)

    /// <summary>
    /// Position of camera in world space.
    /// </summary>
    public Vector3 Position
    {
        get { return position; }
    }
    private Vector3 position;

    /// <summary>
    /// Velocity of camera.
    /// </summary>
    public Vector3 Velocity
    {
        get { return velocity; }
    }
    private Vector3 velocity;

    #endregion




    public ChaseCamera(float aspectRatio) : this(aspectRatio, 7500, 750, 50){}        

    public ChaseCamera(float aspectRatio, float stiffness, float damping, float mass)
    {
        this.aspectRatio = aspectRatio;
        this.stiffness = stiffness;
        this.damping = damping;
        this.mass = mass;
    }

    #region Helper Methods

    /// <summary>
    /// Rebuilds object space values in world space. Invoke before publicly
    /// returning or privately accessing world space values.
    /// </summary>
    private void UpdateWorldPositions()
    {
        // Construct a matrix to transform from object space to worldspace
        Matrix transform = Matrix.Identity;
        transform.Forward = ChaseDirection;
        transform.Up = Up;
        transform.Right = Vector3.Cross(Up, ChaseDirection);

        // Calculate desired camera properties in world space
        desiredPosition = ChasePosition +
            Vector3.TransformNormal(DesiredPositionOffset, transform);
        lookAt = ChasePosition +
            Vector3.TransformNormal(LookAtOffset, transform);
    }        

    /// <summary>
    /// Forces camera to be at desired position and to stop moving. The is useful
    /// when the chased object is first created or after it has been teleported.
    /// Failing to call this after a large change to the chased object's position
    /// will result in the camera quickly flying across the world.
    /// </summary>
    public void Reset()
    {
        UpdateWorldPositions();

        // Stop motion
        velocity = Vector3.Zero;

        // Force desired position
        position = desiredPosition;
    }
    #endregion


    /// <summary>
    /// Animates the camera from its current position towards the desired offset
    /// behind the chased object. The camera's animation is controlled by a simple
    /// physical spring attached to the camera and anchored to the desired position.
    /// </summary>
    public void Update(GameTime gameTime, Vector3 chasePosition, Vector3 chaseDirection, Vector3 chaseUp)
    {
        this.chasePosition = chasePosition;
        this.chaseDirection = chaseDirection;
        this.Up = up;

        UpdateWorldPositions();

        float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

        // Calculate spring force
        Vector3 stretch = position - desiredPosition;
        Vector3 force = -stiffness * stretch - damping * velocity;

        // Apply acceleration
        Vector3 acceleration = force / mass;
        velocity += acceleration * elapsed;

        // Apply velocity
        position += velocity * elapsed;
    }              
}

Edit: oh btw, I didn't come up with this myself, I think the above code was a modified version of the chase camera on creators.xna.com, but I'm not 100% sure anymore.

share|improve this answer
    
My game is viewed from the top-down, so there won't be any perspective. I want the DesiredPositionOffset to depend on the velocity, instead of being a constant. Let me know if I overlooked some parts of the code. (I added a note about being top-down.) –  skyuzo Oct 25 '11 at 6:33
    
@skyuzo, everything in the Update method, except for the UpdateMatrices() call is relevant to the position of the camera, so just use that. I've removed the rest from the answer. –  Roy T. Oct 25 '11 at 12:22
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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