Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jitter when making the camera follow a physics object #211

Closed
umut-sahin opened this issue Oct 29, 2023 · 15 comments
Closed

Jitter when making the camera follow a physics object #211

umut-sahin opened this issue Oct 29, 2023 · 15 comments

Comments

@umut-sahin
Copy link

umut-sahin commented Oct 29, 2023

Hi, I'm trying to make the camera follow a physics object using this system:

pub fn camera_follow_player(
    mut camera_query: Query<&mut Transform, With<MainCamera>>,
    player_query: Query<&Transform, (With<Player>, Changed<Transform>, Without<MainCamera>)>,
) {
    if let Ok(player_transform) = player_query.get_single() {
        let mut camera_transform = camera_query.single_mut();
        camera_transform.translation.x = player_transform.translation.x;
        camera_transform.translation.y = player_transform.translation.y;
    }
}

which is added to the application using:

.add_systems(PostUpdate, camera_follow_player)

but I end up getting jitters. What's the proper way to solve this?

Here is the issue visualized:
jitter

(it's even worse in the actual game that I'm working on)

And the minimal reproduction code:

use bevy::prelude::*;
use bevy::sprite::MaterialMesh2dBundle;
use bevy_xpbd_2d::{math::*, prelude::*};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(Gravity::ZERO)
        .add_plugins(PhysicsPlugins::default())
        .add_systems(Startup, setup)
        .add_systems(PostUpdate, camera_follow_player)
        .run()
}

#[derive(Component)]
pub struct Player;

#[derive(Component)]
pub struct MainCamera;

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    commands.spawn((MainCamera, Camera2dBundle::default()));
    commands.spawn((
        Player,
        RigidBody::Dynamic,
        Collider::ball(30.00),
        Position(Vector::new(0.00, 0.00)),
        LinearVelocity(Vector::new(80.00, 0.00)),
        MaterialMesh2dBundle {
            mesh: meshes.add(shape::Circle::new(30.00).into()).into(),
            material: materials.add(ColorMaterial::from(Color::BLACK)),
            transform: Transform::from_translation(Vec3::new(0.00, 0.00, 2.00)),
            ..default()
        },
    ));

    const MAP_SIZE: u8 = 50;
    const GRID_SPACING: f32 = 50.00;
    const GRID_WIDTH: f32 = 2.00;

    // Spawn horizontal lines.
    for i in 0..=MAP_SIZE {
        commands.spawn((
            Name::new(format!("Horizontal Line {}", i + 1)),
            SpriteBundle {
                transform: Transform::from_translation(Vec3::new(
                    0.0,
                    (((MAP_SIZE as f32) / 2.0) - (i as f32)) * GRID_SPACING,
                    0.0,
                )),
                sprite: Sprite {
                    color: Color::rgb(0.27, 0.27, 0.27),
                    custom_size: Some(Vec2::new(MAP_SIZE as f32 * GRID_SPACING, GRID_WIDTH)),
                    ..default()
                },
                ..default()
            },
        ));
    }
    // Spawn vertical lines.
    for i in 0..=MAP_SIZE {
        commands.spawn((
            Name::new(format!("Vertical Line {}", i + 1)),
            SpriteBundle {
                transform: Transform::from_translation(Vec3::new(
                    ((i as f32) - ((MAP_SIZE as f32) / 2.0)) * GRID_SPACING,
                    0.0,
                    0.0,
                )),
                sprite: Sprite {
                    color: Color::rgb(0.27, 0.27, 0.27),
                    custom_size: Some(Vec2::new(GRID_WIDTH, MAP_SIZE as f32 * GRID_SPACING)),
                    ..default()
                },
                ..default()
            },
        ));
    }
}

pub fn camera_follow_player(
    mut camera_query: Query<&mut Transform, With<MainCamera>>,
    player_query: Query<&Transform, (With<Player>, Changed<Transform>, Without<MainCamera>)>,
) {
    if let Ok(player_transform) = player_query.get_single() {
        let mut camera_transform = camera_query.single_mut();
        camera_transform.translation.x = player_transform.translation.x;
        camera_transform.translation.y = player_transform.translation.y;
    }
}
@pintariching
Copy link

I had the same problem and what worked for me was updating the camera on FixedUpdate and running it .after(PhysicsSet::Sync). It's not optimal but it works.

@umut-sahin
Copy link
Author

It does solve the issue when physics plugins are added like:

app.add_plugins(PhysicsPlugins::new(FixedUpdate));

This works well enough, but it'd be nice if there is a better solution.

Thanks @pintariching!

@Jondolf
Copy link
Owner

Jondolf commented Oct 29, 2023

Personally, I'm only getting the jitter if I run in debug mode with optimizations configured like this:

[profile.dev.package."*"]
opt-level = 3

Some other people seem to be having the issue even with other configurations though, so it could be somewhat device/architecture specific, or maybe dependent on frame rate. We can keep this issue open until the proper reason and fix for the jitter is found

@adnanademovic
Copy link

I had the same problem and what worked for me was updating the camera on FixedUpdate and running it .after(PhysicsSet::Sync). It's not optimal but it works.

You don't need to use fixed update, just the after call.

This ensures that the camera is updated once the object you're following is done with all of its transforming.

I personally use the Position instead of Transform for the player query, allowing me to place things .in_set(PhysicsSet::Sync), as the general point of the sync physics set is to take the data internal to the physics (Position components) and apply it back to the world that bevy sees (Transform components)

@pintariching
Copy link

I made a repo with a few examples that all either jitter around or have jerky movement: https://github.com/pintariching/bevy_xpbd_jitter_test/tree/master/examples

Here's the examples from my system with a Ryzen 5 3600, 32 GB, AMD Radeon RX 6950 XT running on PopOS 22.04 Wayland:

Basic example:

Screencast.from.01.11.2023.12.13.18.webm

Basic example with no lerp:

2023-11-01.12-29-19.mp4

Running camera logic in FixedUpdate:

2023-11-01.12-31-08.mp4

Running camera logic in FixedUpdate after PhysicsSet::Sync

2023-11-01.12-32-15.mp4

@tbillington
Copy link

Here's a link to my repo with a similar issue I think, video included tbillington/driving-physics-repro#1

@tim-blackbird
Copy link

tim-blackbird commented Nov 1, 2023

The jitter is caused by the camera's position lagging on frame behind the player.
Because the jitter is based on framerate it becomes hard to notice when the frame-rate is high or stable so I used bevy_framepace to limit the framerate in the reproduction.

The issue is that the camera follow system needs to:

  • run after the physics system so it has the up-to-date position of the player
  • run before transform propagation so the changes to the Transform you make in the system are written to the camera's GlobalTransform before the end of the frame.

The following ordering constraints should fix the issue.

.add_systems(
    PostUpdate,
    camera_follow_player
        .after(PhysicsSet::Sync)
        .before(TransformSystem::TransformPropagate),
)

@umut-sahin
Copy link
Author

This solves the issue completely, and it's explained perfectly. Thanks @devil-ira!

@numfin
Copy link

numfin commented Nov 6, 2023

@Jondolf Could you add this hack somewhere in docs please? ❤️ 🙏

@Jondolf
Copy link
Owner

Jondolf commented Nov 6, 2023

Sure, I'll put it in the docs for the next release

Jondolf added a commit that referenced this issue Jan 20, 2024
# Objective

#211 has a solution for camera jitter when following physics entities: #211 (comment)

This is such a common issue that it should at least be mentioned in the FAQ.

## Solution

Add solution for camera following jitter to documentation FAQ.
andrewexton373 added a commit to andrewexton373/geometry-wars that referenced this issue Feb 26, 2024
@xx1adfasd
Copy link

xx1adfasd commented Aug 4, 2024

anyone still having jitter problem after applying the hack?

.add_systems(
    PostUpdate,
    camera_follow_player
        .after(PhysicsSet::Sync)
        .before(TransformSystem::TransformPropagate),
)

I still got the problem... It seems that whether write this code or not doesn't change anything??

Also that my jitter problem isn't "always" there, no matter the above code is applied or not. Sometimes if you walk around for a while, it will become without jitter.

by the way, I’m using Dolly, so my code is in fact:

  app.add_systems(
            PostUpdate,
            (update_camera, Dolly::<MainCamera>::update_active)
                .chain()
                .after(PhysicsSet::Sync)
                .before(bevy::transform::TransformSystem::TransformPropagate), 
        );

I can also confirm that if in dev profile, with opt-level = 3 , the jitter problem is worse, and without opt-level = 3, the jitter is almost gone. But the jitter still exists in release profile. So it seems that the more opt-level, the more jitter.

And even it it's in dev profile, no opt-level, sometimes I still got the jitter. Seems it randomized for each run.

Ok, I'm just reporting what I'm experiencing now. I'm using .add_plugins(bevy_atmosphere::prelude::AtmospherePlugin), and when I comment out this line, the jitter problem is got much lesser, and almost gone in the dev profile (but still happens in release profile) . So why is this happening?? These things are totally unrelated. It seems that some Hardware overhead could cause some problem?

Ok, I got my partial answer here. At least it's an avian problem. I just replaced the whole avian dependency with rapier3d counterpart. The codes are very alike, and the result is different. Rapier3d is more stable. No jitter at all!
Not that I'm saying avian is bad. I like avian. But it's just what I have just experienced. I think it's a performance related issue.

@numfin
Copy link

numfin commented Aug 4, 2024

@xx1adfasd

  1. issue is closed
  2. you might have big transform values. I created another issue in bevy repo about this. Someone recommended using bigger float numbers and provided repository.
  3. please don't respond to this, go find issues about jitter in bevy repository

@xx1adfasd
Copy link

xx1adfasd commented Aug 5, 2024

@xx1adfasd

1. issue is closed

2. you might have big transform values. I created another issue in bevy repo about this. Someone recommended using bigger float numbers and provided repository.

3. please don't respond to this, go find issues about jitter in bevy repository

By replacing the same code with Rapier counterparts, it's easily confirmed that it's not related to any other codes.

Closed issue will need to be reopened if the issue is not fixed entirely, and in my case this worth investigating.

Saying don't respond to oneself's comment will make people feel unwelcomed since people only be here to be helpful to something. And it might reflect the possibility that one thinks he is absolutely right, without any chances to be argued, which is not true. Also, I can say the same thing to you, please don't respond to this. But I guess that won't work right? Just don't be personal against any people who only wants to help you.

The jitter proplem is not in bevy I can confirm this. Because When I Stop using any physic engine, the issue is gone. (By manually apply the same value directly to the camera and the object.

@numfin
Copy link

numfin commented Aug 5, 2024

@xx1adfasd

  1. stop crying
  2. stop triggering necrothread
  3. open new issue with attached reproduction

@Jondolf
Copy link
Owner

Jondolf commented Aug 5, 2024

@numfin You're coming off a bit hostile here, please try to be a bit more welcoming in the future :) Their question is fair and might not strictly be a Bevy issue. I do agree with you that it would be preferable to open a new issue though, ideally with a minimal reproduction.

@xx1adfasd It's hard to say what the issue could be without a reproduction and/or video. If the "jitter" is just periodic stutters roughly once or twice a second, it could be related to Avian running physics at a fixed timestep by default, like most engines. This causes an issue where physics can run more than one time on some frames, and zero times on others, causing a visible stutter. The common fixes to this issue are:

  • Perform transform interpolation to smooth out movement. This isn't currently a built-in feature, but I have it mostly implemented locally, and it could also be done manually with a separate visual entity that smoothly follows the physics entity.
  • Use a variable timestep with app.insert_resource(Time::new_with(Physics::variable(1.0 / 60.0))) (this will be different once Use FixedPostUpdate by default and simplify scheduling #457 is merged). The main tradeoff is that behavior will be frame-rate dependent and inconsistent without a fixed timestep.
  • Increase the physics timestep frequency from 60 Hz to e.g. 144 Hz to run physics more often and try to hide the issue. Not really a proper fix and can hurt performance, but worth mentioning.

If you don't get the issue in Rapier though, it's very possible that it's a different problem. In that case I would suggest opening a new issue like @numfin mentioned. It is unclear if the jitter is related to camera following, which this issue is about, so it's better to document and investigate in its own issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants