Skip to content

Commit 5c4f1e1

Browse files
committed
build: prep 0.1.0
1 parent 3a1879a commit 5c4f1e1

File tree

6 files changed

+124
-91
lines changed

6 files changed

+124
-91
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
22
resolver = "2"
3-
members = ["examples/run_wasm", "examples/simple"]
3+
members = ["examples/run_wasm", "examples/demo"]
44

55
[workspace.package]
66
edition = "2021"
@@ -10,11 +10,11 @@ repository = "https://github.com/loopystudios/bevy_ogle"
1010

1111
[package]
1212
name = "bevy_ogle"
13+
description = "A multi-mode camera library for 2d vector games"
1314
version.workspace = true
1415
license.workspace = true
1516
edition.workspace = true
1617
repository.workspace = true
17-
description = "A multi-mode camera library for 2d games"
1818
authors = ["Spencer C. Imbleau"]
1919
keywords = ["bevy"]
2020
categories = ["game-development", "wasm"]

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
# Bevy Ogle
77

8-
**A multi-mode camera for 2D games in [Bevy](https://bevyengine.org).**
8+
**A multi-mode camera for 2D vector games in [Bevy](https://bevyengine.org).**
99

1010
[![Discord](https://img.shields.io/discord/913957940560531456.svg?label=Loopy&logo=discord&logoColor=ffffff&color=ffffff&labelColor=000000)](https://discord.gg/zrjnQzdjCB)
1111
[![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](#license)

examples/simple/Cargo.toml renamed to examples/demo/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "simple"
2+
name = "demo"
33
version.workspace = true
44
license.workspace = true
55
edition.workspace = true

examples/simple/src/main.rs renamed to examples/demo/src/main.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ fn main() {
1010
App::new()
1111
.add_plugins(DefaultPlugins)
1212
.add_plugins(EguiPlugin)
13-
.add_plugins(OglePlugin::default())
13+
.add_plugins(OglePlugin {
14+
initial_settings: OgleSettings {
15+
min_x: Some(-500.0),
16+
max_x: Some(500.0),
17+
min_y: Some(-500.0),
18+
max_y: Some(500.0),
19+
..default()
20+
},
21+
})
1422
.add_systems(Startup, setup_scene)
1523
.add_systems(Update, move_target)
1624
.add_systems(Update, control_camera_ui)
@@ -19,6 +27,16 @@ fn main() {
1927

2028
fn setup_scene(mut commands: Commands) {
2129
// Background
30+
31+
commands.spawn(SpriteBundle {
32+
sprite: Sprite {
33+
color: css::ORANGE.into(),
34+
custom_size: Some(Vec2::new(600.0, 600.0)),
35+
..default()
36+
},
37+
transform: Transform::from_xyz(0.0, 0.0, 0.),
38+
..default()
39+
});
2240
commands.spawn(SpriteBundle {
2341
sprite: Sprite {
2442
color: css::LIME.into(),
@@ -62,12 +80,14 @@ fn control_camera_ui(
6280
target: Res<OgleTarget>,
6381
mode: Res<State<OgleMode>>,
6482
mut next_mode: ResMut<NextState<OgleMode>>,
83+
proj: Query<&OrthographicProjection>,
6584
) {
6685
let window = egui::Window::new("Camera Controls")
6786
.anchor(egui::Align2::LEFT_TOP, [25.0, 25.0])
6887
.resizable(false)
6988
.title_bar(true);
7089
window.show(contexts.ctx_mut(), |ui| {
90+
ui.label(format!("Cameral scale: {}", proj.single().scale));
7191
ui.heading("Mode");
7292
let mut set_mode = mode.clone();
7393
if ui

src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ impl Default for OgleRig {
4444
Self(
4545
CameraRig::builder()
4646
.with(Position::new(pos))
47-
.with(Arm::new(pos))
4847
.with(Smooth::new_position(1.5).predictive(false))
4948
.build(),
5049
)

src/plugin.rs

+99-85
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,22 @@ use dolly::prelude::*;
1010

1111
#[derive(Default)]
1212
pub struct OglePlugin {
13-
pub initial_settings: Option<OgleSettings>,
13+
pub initial_settings: OgleSettings,
1414
}
1515

1616
impl Plugin for OglePlugin {
1717
fn build(&self, app: &mut App) {
1818
app.init_resource::<OgleRig>()
1919
.init_resource::<OgleTarget>()
2020
.init_state::<OgleMode>()
21-
.insert_resource(self.initial_settings.unwrap_or_default())
21+
.insert_resource(self.initial_settings)
2222
.add_plugins(bevy_pancam::PanCamPlugin)
2323
.add_systems(Startup, setup)
2424
.add_systems(
2525
Update,
26-
(follow_target).run_if(in_state(OgleMode::Following)),
26+
((follow_target, keep_within_settings).chain())
27+
.run_if(in_state(OgleMode::Following)),
2728
)
28-
// TODO: Implement choreographing
29-
//.add_systems(
30-
// Update,
31-
// choreograph_target.run_if(in_state(OgleMode::Choreographed)),
32-
//)
3329
.add_systems(OnEnter(OgleMode::Frozen), on_enter_frozen)
3430
.add_systems(OnExit(OgleMode::Frozen), on_exit_frozen)
3531
.add_systems(OnEnter(OgleMode::Pancam), on_enter_pancam)
@@ -39,13 +35,18 @@ impl Plugin for OglePlugin {
3935
}
4036
}
4137

42-
fn setup(mut commands: Commands) {
43-
// TODO: Settings for pancam handled consistently with other ogle settings
38+
fn setup(mut commands: Commands, settings: Res<OgleSettings>) {
4439
commands.spawn((
4540
Camera2dBundle::default(),
46-
// Because the default mode of ogle is `OgleMode::Frozen`, not `OgleMode::Pancam`, we need to disable it.
4741
bevy_pancam::PanCam {
42+
// Because the default mode of ogle is `OgleMode::Frozen`, not `OgleMode::Pancam`, we need to disable it.
4843
enabled: false,
44+
min_scale: settings.min_scale,
45+
max_scale: settings.max_scale,
46+
min_x: settings.min_x,
47+
max_x: settings.max_x,
48+
min_y: settings.min_y,
49+
max_y: settings.max_y,
4950
..default()
5051
},
5152
));
@@ -71,20 +72,7 @@ fn on_exit_pancam(mut query: Query<&mut bevy_pancam::PanCam>) {
7172
pancam.enabled = false;
7273
}
7374

74-
fn on_enter_following(
75-
query_cam: Query<&Transform, With<Camera>>,
76-
query_proj: Query<&OrthographicProjection>,
77-
mut rig: ResMut<OgleRig>,
78-
) {
79-
let camera_transform = query_cam.single(); // TODO: error handling
80-
let projection = query_proj.single();
81-
82-
rig.driver_mut::<Position>().position = mint::Point3 {
83-
x: camera_transform.translation.x,
84-
y: camera_transform.translation.y,
85-
z: projection.scale,
86-
};
87-
//rig.driver_mut::<Arm>().offset.z = projection.scale;
75+
fn on_enter_following() {
8876
info!("Enabling follow camera");
8977
}
9078

@@ -98,13 +86,12 @@ fn follow_target(
9886
target: Res<OgleTarget>,
9987
mut rig: ResMut<OgleRig>,
10088
query_transform: Query<&Transform, Without<Camera>>,
101-
settings: Res<OgleSettings>,
10289
mut query_cam: Query<(&mut OrthographicProjection, &mut Transform), With<Camera>>,
10390
mut scroll_events: EventReader<MouseWheel>,
104-
primary_window: Query<&Window, With<PrimaryWindow>>,
10591
) {
106-
// TODO: Handle errors
107-
let (mut proj, mut camera_transform) = query_cam.single_mut();
92+
let Ok((mut proj, mut camera_transform)) = query_cam.get_single_mut() else {
93+
return;
94+
};
10895
let prev_z = rig.driver::<Position>().position.z;
10996
match *target {
11097
OgleTarget::Position(pos) => {
@@ -115,14 +102,14 @@ fn follow_target(
115102
};
116103
}
117104
OgleTarget::Entity(e) => {
118-
// TODO: Handle errors
119-
let transform = query_transform
120-
.get(e)
121-
.expect("entity target has no transform");
122-
rig.driver_mut::<Position>().position = mint::Point3 {
123-
x: transform.translation.x,
124-
y: transform.translation.y,
125-
z: prev_z,
105+
if let Ok(transform) = query_transform.get(e) {
106+
rig.driver_mut::<Position>().position = mint::Point3 {
107+
x: transform.translation.x,
108+
y: transform.translation.y,
109+
z: prev_z,
110+
};
111+
} else {
112+
error!("entity target has no transform");
126113
};
127114
}
128115
OgleTarget::None => {}
@@ -138,67 +125,94 @@ fn follow_target(
138125
.sum::<f32>();
139126

140127
if scroll != 0. {
141-
let window = primary_window.single();
142-
let window_size = Vec2::new(window.width(), window.height());
143-
144-
let old_scale = rig.driver::<Arm>().offset.z;
145-
let mut new_scale = (old_scale * (1. + -scroll * 0.001)).max(settings.min_scale);
146-
147-
// Apply max scale constraint
148-
if let Some(max_scale) = settings.max_scale {
149-
new_scale = new_scale.min(max_scale);
150-
}
151-
152-
// If there is both a min and max boundary, that limits how far we can zoom. Make sure we don't exceed that
153-
let scale_constrained = BVec2::new(
154-
settings.min_x.is_some() && settings.max_x.is_some(),
155-
settings.min_y.is_some() && settings.max_y.is_some(),
156-
);
157-
158-
if scale_constrained.x || scale_constrained.y {
159-
let bounds_width = if let (Some(min_x), Some(max_x)) = (settings.min_x, settings.max_x)
160-
{
161-
max_x - min_x
162-
} else {
163-
f32::INFINITY
164-
};
165-
166-
let bounds_height = if let (Some(min_y), Some(max_y)) = (settings.min_y, settings.max_y)
167-
{
168-
max_y - min_y
169-
} else {
170-
f32::INFINITY
171-
};
172-
173-
let bounds_size = vec2(bounds_width, bounds_height);
174-
let max_safe_scale = max_scale_within_bounds(bounds_size, &proj, window_size);
175-
176-
if scale_constrained.x {
177-
new_scale = new_scale.min(max_safe_scale.x);
178-
}
179-
180-
if scale_constrained.y {
181-
new_scale = new_scale.min(max_safe_scale.y);
182-
}
183-
}
184-
185-
rig.driver_mut::<Position>().position.z = new_scale;
186-
rig.driver_mut::<Arm>().offset.z = new_scale;
128+
rig.driver_mut::<Position>().position.z *= 1. + -scroll * 0.001;
187129
}
188130

189131
rig.update(time.delta_seconds());
190132

191-
camera_transform.translation.x = rig.final_transform.position.x;
192-
camera_transform.translation.y = rig.final_transform.position.y;
193133
camera_transform.rotation = Quat::from_xyzw(
194134
rig.final_transform.rotation.v.x,
195135
rig.final_transform.rotation.v.y,
196136
rig.final_transform.rotation.v.z,
197137
rig.final_transform.rotation.s,
198138
);
139+
camera_transform.translation.x = rig.final_transform.position.x;
140+
camera_transform.translation.y = rig.final_transform.position.y;
199141
proj.scale = rig.final_transform.position.z;
200142
}
201143

144+
fn keep_within_settings(
145+
mut rig: ResMut<OgleRig>,
146+
primary_window: Query<&Window, With<PrimaryWindow>>,
147+
settings: Res<OgleSettings>,
148+
mut query_cam: Query<(&mut OrthographicProjection, &mut Transform), With<Camera>>,
149+
) {
150+
let Ok((mut proj, mut camera_transform)) = query_cam.get_single_mut() else {
151+
return;
152+
};
153+
154+
let window = primary_window.single();
155+
let window_size = Vec2::new(window.width(), window.height());
156+
157+
// Apply scaling constraints
158+
let (min_scale, max_scale) = (
159+
settings.min_scale,
160+
settings.max_scale.unwrap_or(f32::INFINITY),
161+
);
162+
163+
// If there is both a min and max boundary, that limits how far we can zoom. Make sure we don't exceed that
164+
let scale_constrained = BVec2::new(
165+
settings.min_x.is_some() && settings.max_x.is_some(),
166+
settings.min_y.is_some() && settings.max_y.is_some(),
167+
);
168+
169+
if scale_constrained.x || scale_constrained.y {
170+
let bounds_width = if let (Some(min_x), Some(max_x)) = (settings.min_x, settings.max_x) {
171+
max_x - min_x
172+
} else {
173+
f32::INFINITY
174+
};
175+
176+
let bounds_height = if let (Some(min_y), Some(max_y)) = (settings.min_y, settings.max_y) {
177+
max_y - min_y
178+
} else {
179+
f32::INFINITY
180+
};
181+
182+
let bounds_size = vec2(bounds_width, bounds_height);
183+
let max_safe_scale = max_scale_within_bounds(bounds_size, &proj, window_size);
184+
185+
if scale_constrained.x {
186+
proj.scale = proj.scale.min(max_safe_scale.x);
187+
}
188+
if scale_constrained.y {
189+
proj.scale = proj.scale.min(max_safe_scale.y);
190+
}
191+
}
192+
proj.scale = proj.scale.clamp(min_scale, max_scale);
193+
rig.driver_mut::<Position>().position.z = proj.scale;
194+
195+
// Keep within bounds
196+
let proj_size = proj.area.size();
197+
let half_of_viewport = proj_size / 2.;
198+
if let Some(min_x_bound) = settings.min_x {
199+
let min_safe_cam_x = min_x_bound + half_of_viewport.x;
200+
camera_transform.translation.x = camera_transform.translation.x.max(min_safe_cam_x);
201+
}
202+
if let Some(max_x_bound) = settings.max_x {
203+
let max_safe_cam_x = max_x_bound - half_of_viewport.x;
204+
camera_transform.translation.x = camera_transform.translation.x.min(max_safe_cam_x);
205+
}
206+
if let Some(min_y_bound) = settings.min_y {
207+
let min_safe_cam_y = min_y_bound + half_of_viewport.y;
208+
camera_transform.translation.y = camera_transform.translation.y.max(min_safe_cam_y);
209+
}
210+
if let Some(max_y_bound) = settings.max_y {
211+
let max_safe_cam_y = max_y_bound - half_of_viewport.y;
212+
camera_transform.translation.y = camera_transform.translation.y.min(max_safe_cam_y);
213+
}
214+
}
215+
202216
/// max_scale_within_bounds is used to find the maximum safe zoom out/projection
203217
/// scale when we have been provided with minimum and maximum x boundaries for
204218
/// the camera.

0 commit comments

Comments
 (0)