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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! Zero-cost abstraction types for configuring the camera systems.
use crate::Context;
/// Specify how the text should be drawn.
///
/// Used by [`Context::main_camera`](crate::Context::main_camera) and [`Context::ui_camera`](crate::Context::ui_camera).
pub struct CameraContext<'ctx> {
/// Reference to the context the text will draw in when finished.
pub(crate) ctx: &'ctx Context,
/// Whether to use the UI camera for positioning the text, `false` uses the regular game camera.
pub(crate) is_ui_camera: bool,
}
impl CameraContext<'_> {
/// Apply a basic omni-directional screenshake effect with a linear decay.
///
/// Based on: <https://jonny.morrill.me/en/blog/gamedev-how-to-implement-a-camera-shake-effect/>
///
/// # Arguments
///
/// * `duration` - Seconds the screen shake lasts.
/// * `amplitude` - How many pixels the camera is moved to.
/// * `frequency` - Frequency of the shake in Hertz.
#[inline]
pub fn shake(&self, duration: f32, amplitude: f32, frequency: f32) {
self.ctx.write(|ctx| {
ctx.camera_mut(self.is_ui_camera)
.shake(duration, amplitude, frequency);
});
}
/// Make the camera move towards the location on the horizontal axis only.
///
/// # Arguments
///
/// * `x` - Horizontal target position in world space to follow.
#[inline]
pub fn follow_x(&self, x: f32) {
self.ctx.write(|ctx| {
ctx.camera_mut(self.is_ui_camera).set_target_x(x);
});
}
/// Make the camera move towards the location on the vertical axis only.
///
/// # Arguments
///
/// * `y` - Vertical target position in world space to follow.
#[inline]
pub fn follow_y(&self, y: f32) {
self.ctx.write(|ctx| {
ctx.camera_mut(self.is_ui_camera).set_target_y(y);
});
}
/// Make the camera move towards the location.
///
/// # Arguments
///
/// * `(x, y)` - Tuple of the target position in world space to follow.
#[inline]
pub fn follow(&self, target: impl Into<(f32, f32)>) {
// Reduce compilation times
fn inner(this: &CameraContext, (x, y): (f32, f32)) {
this.ctx.write(|ctx| {
let camera = ctx.camera_mut(this.is_ui_camera);
camera.set_target_x(x);
camera.set_target_y(y);
});
}
inner(self, target.into());
}
/// Get the relative position if the mouse is inside the viewport frame.
///
/// This is `Some(..`) if the mouse is inside the viewport frame, not the entire window.
/// The value of the coordinates corresponds to the pixel, when the frame is scaled this also encodes the subpixel in the fractional part.
///
/// # Returns
///
/// - `None` when the mouse is not on the buffer of pixels.
/// - `Some(..)` with the coordinates of the pixel if the mouse is on the buffer of pixels.
#[inline]
#[must_use]
pub fn mouse(&self) -> Option<(f32, f32)> {
self.ctx.read(|ctx| {
ctx.input.mouse().map(|(mouse_x, mouse_y)| {
let camera = ctx.camera(self.is_ui_camera);
(mouse_x - camera.offset_x(), mouse_y - camera.offset_y())
})
})
}
/// Get the relative horizontal position if the mouse is inside the viewport frame.
///
/// This is `Some(..`) if the mouse is inside the viewport frame, not the entire window.
/// The value of the coordinates corresponds to the pixel, when the frame is scaled this also encodes the subpixel in the fractional part.
///
/// # Returns
///
/// - `None` when the mouse is not on the buffer of pixels.
/// - `Some(..)` with the X coordinate of the pixel if the mouse is on the buffer of pixels.
#[inline]
#[must_use]
pub fn mouse_x(&self) -> Option<f32> {
self.ctx.read(|ctx| {
ctx.input.mouse().map(|(mouse_x, _)| {
let camera = ctx.camera(self.is_ui_camera);
mouse_x - camera.offset_x()
})
})
}
/// Get the relative vertical position if the mouse is inside the viewport frame.
///
/// This is `Some(..`) if the mouse is inside the viewport frame, not the entire window.
/// The value of the coordinates corresponds to the pixel, when the frame is scaled this also encodes the subpixel in the fractional part.
///
/// # Returns
///
/// - `None` when the mouse is not on the buffer of pixels.
/// - `Some(..)` with the Y coordinate of the pixel if the mouse is on the buffer of pixels.
#[inline]
#[must_use]
pub fn mouse_y(&self) -> Option<f32> {
self.ctx.read(|ctx| {
ctx.input
.mouse()
.map(|(_, mouse_y)| mouse_y - ctx.camera(self.is_ui_camera).offset_y())
})
}
/// Set the horizontal linear interpolation factor applied every render tick.
///
/// # Arguments
///
/// * `lerp_x` - Horizontal linear interpolation applied to the camera every render tick.
#[inline]
pub fn set_lerp_x(&self, lerp_x: f32) {
self.ctx.write(|ctx| {
ctx.camera_mut(self.is_ui_camera).set_lerp_x(lerp_x);
});
}
/// Set the vertical linear interpolation factor applied every render tick.
///
/// # Arguments
///
/// * `lerp_y` - Vertical linear interpolation applied to the camera every render tick.
#[inline]
pub fn set_lerp_y(&self, lerp_y: f32) {
self.ctx.write(|ctx| {
ctx.camera_mut(self.is_ui_camera).set_lerp_y(lerp_y);
});
}
/// Set both the the horizontal and vertical linear interpolation factor applied every render tick.
///
/// # Arguments
///
/// * `lerp` - Horizontal and vertical linear interpolation applied to the camera every render tick.
#[inline]
pub fn set_lerp(&self, lerp: f32) {
self.ctx.write(|ctx| {
let camera = ctx.camera_mut(self.is_ui_camera);
camera.set_lerp_x(lerp);
camera.set_lerp_y(lerp);
});
}
/// Center the camera at the middle of the screen.
///
/// This is the default for the main camera.
#[inline]
pub fn set_center(&self) {
self.ctx.write(|ctx| {
let width = ctx.config.buffer_width;
let height = ctx.config.buffer_height;
ctx.camera_mut(self.is_ui_camera).center(width, height);
});
}
/// Center the camera at the top left corner of the screen.
///
/// This is the default for the UI camera.
#[inline]
pub fn set_top_left(&self) {
self.ctx.write(|ctx| {
ctx.camera_mut(self.is_ui_camera).top_left();
});
}
}
/// Configuration methods for cameras.
impl Context {
/// Configure the main game camera.
///
/// This is the default camera that will be used to position all graphical elements on the screen and move them in the game world.
/// If you want the sprites or text to not move with the game use [`Context::ui_camera`].
///
/// <div class="warning">
///
/// If the object you are following is moving with a lot of jitters you are probably missing a call to [`SpriteContext::translate_previous`](crate::context::sprite::SpriteContext::translate_previous), see that documentation for more information.
///
/// </div>
///
/// # Returns
///
/// - A helper struct allowing you to configure the camera.
#[inline(always)]
#[must_use]
pub const fn main_camera(&self) -> CameraContext<'_> {
CameraContext {
ctx: self,
is_ui_camera: false,
}
}
/// Configure the camera for drawing user interfaces.
///
/// This is the default camera that will be used to position all graphical elements on the screen specified with [`SpriteContext::use_ui_camera`](crate::context::sprite::SpriteContext::use_ui_camera) and [`TextContext::use_ui_camera`](crate::context::text::TextContext::use_ui_camera).
/// If you want the sprites or text to move with the game use [`Context::main_camera`].
///
/// # Returns
///
/// - A helper struct allowing you to configure the camera.
#[inline(always)]
#[must_use]
pub const fn ui_camera(&self) -> CameraContext<'_> {
CameraContext {
ctx: self,
is_ui_camera: true,
}
}
}