1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Example demonstrating resource animation and various transform shortcuts.
//!
//! The example animates the `AmbientLight` resource of Bevy's PBR renderer.
//! This is mostly for example purpose; you probably want to animate some other
//! (custom) resource.
use std::time::Duration;
use bevy::{color::palettes::css::*, prelude::*};
use bevy_tweening::{lens::TransformPositionLens, *};
mod utils;
#[derive(Component)]
struct Follower;
#[derive(Debug, Default, Clone, Copy)]
enum AnimOption {
OverwriteComponent,
#[default]
CallSetTweenable,
}
// Simple way to toggle between the two options. Don't do that in real code.
impl std::ops::Not for AnimOption {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Self::OverwriteComponent => Self::CallSetTweenable,
Self::CallSetTweenable => Self::OverwriteComponent,
}
}
}
#[derive(Default, Component)]
struct Anim {
pub option: AnimOption,
}
fn main() {
App::default()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Follow".to_string(),
resolution: bevy::window::WindowResolution::new(1200, 600),
present_mode: bevy::window::PresentMode::Fifo, // vsync
..default()
}),
..default()
}))
.add_systems(Update, utils::close_on_esc)
.add_plugins(TweeningPlugin)
.add_systems(Startup, setup)
.add_systems(PreUpdate, change_option)
.add_systems(Update, follow)
.run();
}
fn make_tween(start: Vec3, end: Vec3) -> Tween {
let lens = TransformPositionLens { start, end };
Tween::new(
// Start fast to catch-up with target, slow down when closer
EaseFunction::QuadraticOut,
// Short duration for the appareance to remain snappy
Duration::from_millis(200),
lens,
)
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2d::default());
// Spawn the follower entity
let entity = commands
.spawn((
Mesh2d(meshes.add(Rectangle::new(10., 10.))),
MeshMaterial2d(materials.add(ColorMaterial {
color: WHITE.into(),
..default()
})),
Follower,
))
.id();
// Spawn the TweenAnim animating the follower. We want to keep overwriting it as
// the cursor move, so we need to remember its Entity (we use a marker component
// here).
commands.spawn((
// Our marker
Anim::default(),
// The animation itself
TweenAnim::new(make_tween(Vec3::ZERO, Vec3::ZERO))
// For performance reason, we keep this animation around and overwrite it. Otherwise it
// will keep being removed and re-inserted.
.with_destroy_on_completed(false),
// The target of the animation, here a component on the given entity
AnimTarget::component::<Transform>(entity),
));
}
fn change_option(keyboard: Res<ButtonInput<KeyCode>>, mut q_anim: Single<&mut Anim>) {
if keyboard.just_pressed(KeyCode::Space) {
q_anim.option = !q_anim.option;
println!("Anim option : {:?}", q_anim.option);
}
}
fn follow(
mut commands: Commands,
mut message_reader: MessageReader<CursorMoved>,
q_follower: Single<&Transform, With<Follower>>,
q_camera: Single<(&GlobalTransform, &Camera)>,
mut q_anim: Single<(Entity, &Anim, &mut TweenAnim)>,
) {
let Some(ev) = message_reader.read().last() else {
return;
};
let (camera_transform, camera) = *q_camera;
let target_transform = *q_follower;
if let Ok(pos) = camera.viewport_to_world_2d(camera_transform, ev.position) {
if pos != target_transform.translation.truncate() {
// Note: Do NOT use move_to(), because it spawns a new separate TweenAnim and
// Entity each time, and we will end up with multiple of them animating the same
// target but aiming at different end positions, which will produce some visual
// jittering. We want to overwrite the same TweenAnim again and again with a new
// target position.
match q_anim.1.option {
// Option 1. Use the fact spawning a component overwrites a previous instance.
AnimOption::OverwriteComponent => {
commands.entity(q_anim.0).insert(
TweenAnim::new(make_tween(target_transform.translation, pos.extend(0.)))
// We again keep that component around even when the animation
// completed, both for performance and because we use a Single<> query
// which otherwise would fail once the animation completed and the
// TweenAnim was auto-destroyed.
.with_destroy_on_completed(false),
);
}
// Option 2. Keep the same component, simply use set_tweenable() to
// update the Tween with a new one targetting the new cursor position.
AnimOption::CallSetTweenable => {
q_anim
.2
.set_tweenable(make_tween(target_transform.translation, pos.extend(0.)))
.unwrap();
}
}
}
}
}