smooth_follow/
smooth_follow.rs1use bevy::{
4 math::{prelude::*, vec3, NormedVectorSpace},
5 prelude::*,
6};
7use rand::SeedableRng;
8use rand_chacha::ChaCha8Rng;
9
10fn main() {
11 App::new()
12 .add_plugins(DefaultPlugins)
13 .add_systems(Startup, setup)
14 .add_systems(Update, (move_target, move_follower).chain())
15 .run();
16}
17
18#[derive(Component)]
20struct TargetSphere;
21
22#[derive(Resource)]
24struct TargetSphereSpeed(f32);
25
26#[derive(Resource)]
28struct TargetPosition(Vec3);
29
30#[derive(Resource)]
32struct DecayRate(f32);
33
34#[derive(Component)]
36struct FollowingSphere;
37
38#[derive(Resource)]
40struct RandomSource(ChaCha8Rng);
41
42fn setup(
43 mut commands: Commands,
44 mut meshes: ResMut<Assets<Mesh>>,
45 mut materials: ResMut<Assets<StandardMaterial>>,
46) {
47 commands.spawn((
49 Mesh3d(meshes.add(Plane3d::default().mesh().size(12.0, 12.0))),
50 MeshMaterial3d(materials.add(Color::srgb(0.3, 0.15, 0.3))),
51 Transform::from_xyz(0.0, -2.5, 0.0),
52 ));
53
54 commands.spawn((
56 Mesh3d(meshes.add(Sphere::new(0.3))),
57 MeshMaterial3d(materials.add(Color::srgb(0.3, 0.15, 0.9))),
58 TargetSphere,
59 ));
60
61 commands.spawn((
63 Mesh3d(meshes.add(Sphere::new(0.3))),
64 MeshMaterial3d(materials.add(Color::srgb(0.9, 0.3, 0.3))),
65 Transform::from_translation(vec3(0.0, -2.0, 0.0)),
66 FollowingSphere,
67 ));
68
69 commands.spawn((
71 PointLight {
72 intensity: 15_000_000.0,
73 shadows_enabled: true,
74 ..default()
75 },
76 Transform::from_xyz(4.0, 8.0, 4.0),
77 ));
78
79 commands.spawn((
81 Camera3d::default(),
82 Transform::from_xyz(-2.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
83 ));
84
85 commands.insert_resource(TargetSphereSpeed(5.0));
87 commands.insert_resource(DecayRate(2.0));
88 commands.insert_resource(TargetPosition(Vec3::ZERO));
89 commands.insert_resource(RandomSource(ChaCha8Rng::seed_from_u64(68941654987813521)));
90}
91
92fn move_target(
93 mut target: Single<&mut Transform, With<TargetSphere>>,
94 target_speed: Res<TargetSphereSpeed>,
95 mut target_pos: ResMut<TargetPosition>,
96 time: Res<Time>,
97 mut rng: ResMut<RandomSource>,
98) {
99 match Dir3::new(target_pos.0 - target.translation) {
100 Ok(dir) => {
103 let delta_time = time.delta_secs();
104 let abs_delta = (target_pos.0 - target.translation).norm();
105
106 let magnitude = f32::min(abs_delta, delta_time * target_speed.0);
108 target.translation += dir * magnitude;
109 }
110
111 Err(_) => {
113 let legal_region = Cuboid::from_size(Vec3::splat(4.0));
114 *target_pos = TargetPosition(legal_region.sample_interior(&mut rng.0));
115 }
116 }
117}
118
119fn move_follower(
120 mut following: Single<&mut Transform, With<FollowingSphere>>,
121 target: Single<&Transform, (With<TargetSphere>, Without<FollowingSphere>)>,
122 decay_rate: Res<DecayRate>,
123 time: Res<Time>,
124) {
125 let decay_rate = decay_rate.0;
126 let delta_time = time.delta_secs();
127
128 following
130 .translation
131 .smooth_nudge(&target.translation, decay_rate, delta_time);
132}