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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//! The camera system for both UI and the game camera.
/// Camera for offsetting sprites.
pub(crate) struct Camera {
/// Current horizontal position.
x: f32,
/// Current vertical position.
y: f32,
/// Horizontal position calculated for rendering.
render_x: f32,
/// Vertical position calculated for rendering.
render_y: f32,
/// Screen horizontal offset.
offset_x: f32,
/// Screen vertical offset.
offset_y: f32,
/// Target horizontal position.
target_x: f32,
/// Target vertical position.
target_y: f32,
/// Previous target horizontal position.
prev_target_x: f32,
/// Previous target vertical position.
prev_target_y: f32,
/// How fast to interpolate between the horizontal positions.
lerp_x: f32,
/// How fast to interpolate between the vertical positions.
lerp_y: f32,
/// Current horizontal shake state.
shake_x: Shake,
/// Current vertical shake state.
shake_y: Shake,
/// Shake time in total.
shake_duration: f32,
/// Shake time left.
shake_current_duration: f32,
/// Shake force in pixels.
shake_amplitude: f32,
/// Reciprocal of shake frequency in Hertz.
shake_frequency_recip: f32,
}
impl Camera {
/// Update the target.
///
/// Must be done in the update tick.
#[inline]
pub(crate) fn update_target(&mut self) {
self.prev_target_x = self.x;
self.prev_target_y = self.y;
// // Interpolate with the lerp factor
self.x = crate::math::lerp(self.x, self.target_x, self.lerp_x);
self.y = crate::math::lerp(self.y, self.target_y, self.lerp_y);
}
/// Update the camera.
///
/// Must be done in the render tick.
pub(crate) fn update(&mut self, dt: f32, blending_factor: f32) {
// Interpolate the targets with the blending factor, to reduce jitters
self.render_x = crate::math::lerp(self.prev_target_x, self.x, blending_factor);
self.render_y = crate::math::lerp(self.prev_target_y, self.y, blending_factor);
// Apply camera shake
if self.shake_current_duration > 0.0 {
// Decay the amplitude
let time_fraction =
1.0 - (self.shake_duration - self.shake_current_duration) / self.shake_duration;
let amplitude = self.shake_amplitude * time_fraction;
self.shake_x.update(
dt,
self.shake_current_duration,
self.shake_frequency_recip,
amplitude,
);
self.shake_y.update(
dt,
self.shake_current_duration,
self.shake_frequency_recip,
amplitude,
);
self.shake_current_duration -= dt;
// Reset the values
if self.shake_current_duration <= 0.0 {
self.shake_x = Shake::default();
self.shake_y = Shake::default();
}
}
}
/// Set the horizontal lerp.
#[inline]
pub(crate) const fn set_lerp_x(&mut self, lerp_x: f32) {
self.lerp_x = lerp_x;
}
/// Set the vertical lerp.
#[inline]
pub(crate) const fn set_lerp_y(&mut self, lerp_y: f32) {
self.lerp_y = lerp_y;
}
/// Set the target horizontal position.
#[inline]
pub(crate) const fn set_target_x(&mut self, x: f32) {
self.target_x = x;
}
/// Set the target vertical position.
#[inline]
pub(crate) const fn set_target_y(&mut self, y: f32) {
self.target_y = y;
}
/// How much to offset the horizontal position of the item to draw.
#[inline]
pub(crate) fn offset_x(&self) -> f32 {
-self.render_x + self.offset_x + self.shake_x.value()
}
/// How much to offset the vertical position of the item to draw.
#[inline]
pub(crate) fn offset_y(&self) -> f32 {
-self.render_y + self.offset_y + self.shake_y.value()
}
/// Center the camera at the middle of the screen.
#[inline]
pub(crate) fn center(&mut self, buffer_width: f32, buffer_height: f32) {
self.offset_x = buffer_width / 2.0;
self.offset_y = buffer_height / 2.0;
}
/// Center the camera at the top left corner of the screen.
#[inline]
pub(crate) const fn top_left(&mut self) {
self.offset_x = 0.0;
self.offset_y = 0.0;
}
/// Shake the camera.
pub(crate) const fn shake(&mut self, duration: f32, amplitude: f32, frequency: f32) {
self.shake_duration = duration;
self.shake_current_duration = duration;
self.shake_amplitude = amplitude;
self.shake_frequency_recip = frequency.recip();
}
}
impl Default for Camera {
fn default() -> Self {
Self {
x: 0.0,
y: 0.0,
render_x: 0.0,
render_y: 0.0,
target_x: 0.0,
target_y: 0.0,
prev_target_x: 0.0,
prev_target_y: 0.0,
lerp_x: 0.3,
lerp_y: 0.3,
offset_x: 0.0,
offset_y: 0.0,
shake_x: Shake::default(),
shake_y: Shake::default(),
shake_duration: 0.0,
shake_current_duration: 0.0,
shake_amplitude: 0.0,
shake_frequency_recip: 0.0,
}
}
}
/// Internal 1D shake pattern.
#[derive(Default)]
struct Shake {
/// Shake interpolation start, interval is the frequency.
random_point_start: f32,
/// Shake interpolation end, interval is the frequency.
random_point_end: f32,
/// Current interpolated render value.
value: f32,
}
impl Shake {
/// Update the shake.
///
/// Based on: <https://jonny.morrill.me/en/blog/gamedev-how-to-implement-a-camera-shake-effect/>
fn update(&mut self, dt: f32, duration: f32, frequency_recip: f32, amplitude: f32) {
// If the frequecy is higher than the time a frame takes there's no need to interpolate
if frequency_recip <= dt {
self.value = crate::random(-amplitude, amplitude);
return;
}
// Check if subtracting the delta time crosses the lerp point
let current_round = duration % frequency_recip;
if current_round >= frequency_recip - dt {
// We need to calculate a new lerp point to progress
self.random_point_start = self.random_point_end;
self.random_point_end = crate::random(-1.0, 1.0);
}
// Calculate what to interpolate by finding the distance until the next time unit
let lerp_offset = 1.0 - current_round / frequency_recip;
// Interpolate the random value
let direction =
crate::math::lerp(self.random_point_start, self.random_point_end, lerp_offset);
// Update the state
self.value = direction * amplitude;
}
/// Get the interpolated render value.
#[inline]
const fn value(&self) -> f32 {
self.value
}
}