diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ac77f6383d401844e7ed78028f18fca9b16b4877
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1 @@
+pub mod system;
diff --git a/src/main.rs b/src/main.rs
index 09047ae96b29b25b80fb9defae910467a1575359..dfe777435ae9b5c1be0c760990634385d01ffd1e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,45 +1,16 @@
 use bevy::asset::AssetServer;
-use bevy::core_pipeline::clear_color::ClearColorConfig;
-use bevy::math::Vec3;
-use bevy::prelude::{
-	App, Camera2d, Camera2dBundle, Color, Commands, OrthographicProjection, PluginGroup, Rect, Res,
-	Transform, Vec2, Window, WindowPlugin,
-};
-use bevy::render::camera::ScalingMode;
+use bevy::prelude::{App, Commands, PluginGroup, Res, Transform, Window, WindowPlugin};
 use bevy::sprite::SpriteBundle;
 use bevy::window::{WindowMode, WindowResolution};
 use bevy::DefaultPlugins;
-
-static VIEWPORT_WIDTH: f32 = 680.0;
-static VIEWPORT_HEIGHT: f32 = 300.0;
-
-fn spawn_camera(mut commands: Commands) {
-	commands.spawn(Camera2dBundle {
-		projection: OrthographicProjection {
-			area: Rect::from_center_size(Vec2::ZERO, Vec2::new(VIEWPORT_WIDTH, VIEWPORT_HEIGHT)),
-			scaling_mode: ScalingMode::FixedVertical(VIEWPORT_HEIGHT),
-			..Default::default()
-		},
-		camera_2d: Camera2d {
-			clear_color: ClearColorConfig::Custom(Color::WHITE),
-		},
-		transform: Transform::from_translation(Vec3::new(
-			VIEWPORT_WIDTH / 2.0,
-			VIEWPORT_HEIGHT / 2.0,
-			0.0,
-		)),
-		..Default::default()
-	});
-}
+use shoot_the_revival::system::window_bounds;
 
 fn spawn_ship(mut commands: Commands, assets: Res<AssetServer>) {
+	let bounds = window_bounds();
+
 	commands.spawn(SpriteBundle {
 		texture: assets.load("sprites/ship.png"),
-		transform: Transform::from_translation(Vec3::new(
-			VIEWPORT_WIDTH / 2.0,
-			VIEWPORT_HEIGHT / 2.0,
-			0.0,
-		)),
+		transform: Transform::from_translation(bounds.half_size().extend(0.0)),
 		..Default::default()
 	});
 }
@@ -57,7 +28,7 @@ fn main() {
 			}),
 			..Default::default()
 		}))
-		.add_startup_system(spawn_camera)
+		.add_plugin(shoot_the_revival::system::SystemPlugin)
 		.add_startup_system(spawn_ship)
 		.run();
 }
diff --git a/src/system/camera.rs b/src/system/camera.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ee29be94e7c4945256d6e325bc37067be52a5662
--- /dev/null
+++ b/src/system/camera.rs
@@ -0,0 +1,36 @@
+use crate::system::window_bounds;
+use bevy::core_pipeline::clear_color::ClearColorConfig;
+use bevy::prelude::{
+	Camera2d, Camera2dBundle, Color, Commands, Component, OrthographicProjection, Rect, Transform,
+	Vec2,
+};
+use bevy::render::camera::ScalingMode;
+
+pub fn default_projection() -> OrthographicProjection {
+	let bounds = window_bounds();
+
+	OrthographicProjection {
+		area: Rect::from_center_size(Vec2::ZERO, bounds.size()),
+		scaling_mode: ScalingMode::FixedVertical(bounds.height()),
+		..Default::default()
+	}
+}
+
+#[derive(Component)]
+pub struct GameCamera;
+
+pub fn spawn_2d_camera(mut commands: Commands) {
+	let bounds = window_bounds();
+
+	commands.spawn((
+		GameCamera,
+		Camera2dBundle {
+			projection: default_projection(),
+			camera_2d: Camera2d {
+				clear_color: ClearColorConfig::Custom(Color::WHITE),
+			},
+			transform: Transform::from_translation(bounds.half_size().extend(0.0)),
+			..Default::default()
+		},
+	));
+}
diff --git a/src/system/mod.rs b/src/system/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1a93ae621aeefd6fa2de44d8beb98ab5578e2211
--- /dev/null
+++ b/src/system/mod.rs
@@ -0,0 +1,19 @@
+mod camera;
+mod window;
+
+mod _plugin {
+	use bevy::app::App;
+	use bevy::prelude::Plugin;
+
+	pub struct SystemPlugin;
+	impl Plugin for SystemPlugin {
+		fn build(&self, app: &mut App) {
+			app.add_startup_system(super::camera::spawn_2d_camera);
+		}
+	}
+}
+
+pub use camera::default_projection;
+pub use window::{window_bounds, VIEWPORT_HEIGHT, VIEWPORT_WIDTH};
+
+pub use _plugin::SystemPlugin;
diff --git a/src/system/window.rs b/src/system/window.rs
new file mode 100644
index 0000000000000000000000000000000000000000..352c7afd085ed4f75fba9855e098f9867a183812
--- /dev/null
+++ b/src/system/window.rs
@@ -0,0 +1,11 @@
+use bevy::prelude::{Rect, Vec2};
+
+pub static VIEWPORT_WIDTH: f32 = 680.0;
+pub static VIEWPORT_HEIGHT: f32 = 300.0;
+
+pub fn window_bounds() -> Rect {
+	Rect::from_center_size(
+		Vec2::new(VIEWPORT_WIDTH / 2.0, VIEWPORT_HEIGHT / 2.0),
+		Vec2::new(VIEWPORT_WIDTH, VIEWPORT_HEIGHT),
+	)
+}