use avian2d::{math::*, prelude::*};
use bevy::{
color::palettes::{
css::WHITE,
tailwind::{CYAN_400, LIME_400, RED_400},
},
input::common_conditions::input_pressed,
prelude::*,
};
fn main() {
let mut app = App::new();
app.add_plugins((
DefaultPlugins,
PhysicsPlugins::default().with_length_unit(50.0),
));
app.insert_resource(Gravity(Vector::NEG_Y * 900.0));
app.insert_resource(Time::from_hz(10.0));
app.add_systems(Startup, (setup_scene, setup_balls, setup_text))
.add_systems(
Update,
(
change_timestep,
update_timestep_text,
reset_balls.run_if(input_pressed(KeyCode::KeyR)),
),
);
app.run();
}
#[derive(Component)]
struct Ball;
fn setup_scene(
mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
) {
commands.spawn(Camera2d);
commands.spawn((
Name::new("Ground"),
RigidBody::Static,
Collider::rectangle(500.0, 20.0),
Restitution::new(0.99).with_combine_rule(CoefficientCombine::Max),
Transform::from_xyz(0.0, -300.0, 0.0),
Mesh2d(meshes.add(Rectangle::new(500.0, 20.0))),
MeshMaterial2d(materials.add(Color::from(WHITE))),
));
}
fn setup_balls(
mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
) {
let circle = Circle::new(30.0);
let mesh = meshes.add(circle);
commands.spawn((
Name::new("Interpolation"),
Ball,
RigidBody::Dynamic,
Collider::from(circle),
TransformInterpolation,
Transform::from_xyz(-100.0, 300.0, 0.0),
Mesh2d(mesh.clone()),
MeshMaterial2d(materials.add(Color::from(CYAN_400)).clone()),
));
commands.spawn((
Name::new("Extrapolation"),
Ball,
RigidBody::Dynamic,
Collider::from(circle),
TransformExtrapolation,
Transform::from_xyz(0.0, 300.0, 0.0),
Mesh2d(mesh.clone()),
MeshMaterial2d(materials.add(Color::from(LIME_400)).clone()),
));
commands.spawn((
Name::new("No Interpolation"),
Ball,
RigidBody::Dynamic,
Collider::from(circle),
Transform::from_xyz(100.0, 300.0, 0.0),
Mesh2d(mesh.clone()),
MeshMaterial2d(materials.add(Color::from(RED_400)).clone()),
));
}
fn reset_balls(mut commands: Commands, query: Query<Entity, With<Ball>>) {
for entity in &query {
commands.entity(entity).despawn();
}
commands.run_system_cached(setup_balls);
}
#[derive(Component)]
struct TimestepText;
fn setup_text(mut commands: Commands) {
let font = TextFont {
font_size: 20.0,
..default()
};
commands
.spawn((
Text::new("Fixed Hz: "),
TextColor::from(WHITE),
font.clone(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(10.0),
left: Val::Px(10.0),
..default()
},
))
.with_child((TimestepText, TextSpan::default()));
commands.spawn((
Text::new("Change Timestep With Up/Down Arrow\nPress R to reset"),
TextColor::from(WHITE),
TextLayout::new_with_justify(Justify::Right),
font.clone(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(10.0),
right: Val::Px(10.0),
..default()
},
));
commands.spawn((
Text::new("Interpolation"),
TextColor::from(CYAN_400),
font.clone(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(50.0),
left: Val::Px(10.0),
..default()
},
));
commands.spawn((
Text::new("Extrapolation"),
TextColor::from(LIME_400),
font.clone(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(75.0),
left: Val::Px(10.0),
..default()
},
));
commands.spawn((
Text::new("No Interpolation"),
TextColor::from(RED_400),
font.clone(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(100.0),
left: Val::Px(10.0),
..default()
},
));
}
fn change_timestep(mut time: ResMut<Time<Fixed>>, keyboard_input: Res<ButtonInput<KeyCode>>) {
if keyboard_input.pressed(KeyCode::ArrowUp) {
let new_timestep = (time.delta_secs_f64() * 0.975).max(1.0 / 255.0);
time.set_timestep_seconds(new_timestep);
}
if keyboard_input.pressed(KeyCode::ArrowDown) {
let new_timestep = (time.delta_secs_f64() * 1.025).min(1.0 / 5.0);
time.set_timestep_seconds(new_timestep);
}
}
fn update_timestep_text(
mut text: Single<&mut TextSpan, With<TimestepText>>,
time: Res<Time<Fixed>>,
) {
let timestep = time.timestep().as_secs_f32().recip();
text.0 = format!("{timestep:.2}");
}