├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── assets ├── AlfaSlabOne-Regular.ttf ├── crossair_black.png ├── crossair_blackOutline.png ├── crossair_blue.png ├── crossair_blueOutline.png ├── crossair_red.png ├── crossair_redOutline.png ├── crossair_white.png ├── crossair_whiteOutline.png ├── gameover.ogg ├── gameover.wav ├── glass_panel.png ├── green_boxCheckmark.png ├── grey_box.png ├── menu_click.ogg ├── menu_click.wav ├── pattern_blueprint.png ├── roboto.ttf ├── spaceShooter2_spritesheet_2X.png ├── spaceShooter2_spritesheet_2X.xml ├── space_sheet.png ├── space_sheet.xml ├── uipackSpace_sheet.png └── uipackSpace_sheet.xml ├── examples ├── kenney.rs └── spritesheet_viewer.rs ├── readme ├── choose_ship.avif ├── menu.avif └── particles-and-lives.avif └── src ├── assets.rs ├── colors.rs ├── controls.rs ├── kenney_assets.rs ├── levels.rs ├── lib.rs ├── lives.rs ├── main.rs ├── meteors.rs ├── movement.rs ├── scores.rs ├── scoring └── display.rs ├── settings.rs ├── ship.rs ├── ufo.rs ├── ui.rs └── ui ├── button.rs ├── choose_ship.rs └── pause.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "space-shooter" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | bevy = "0.13.1" 10 | rand = "0.8.5" 11 | bevy_asset_loader = { version = "0.20", features = ["2d"] } 12 | bevy_xpbd_2d = { version = "0.4.2", features = ["debug-plugin"] } 13 | roxmltree = "0.19.0" 14 | bevy_hanabi = { version = "0.10.0", default-features = false, features = [ 15 | "2d", 16 | ] } 17 | 18 | [profile.dev.package."*"] 19 | opt-level = 3 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asteroids in Bevy 2 | 3 | This is a work-in-progress implementation of Asteroids in Bevy. Some modifications are being made from the original game, such as using sprites instead of line art. 4 | 5 | ![menu](readme/menu.avif) 6 | 7 | ![choose_ship](readme/choose_ship.avif) 8 | 9 | ![gameplay](readme/particles-and-lives.avif) 10 | -------------------------------------------------------------------------------- /assets/AlfaSlabOne-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/AlfaSlabOne-Regular.ttf -------------------------------------------------------------------------------- /assets/crossair_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/crossair_black.png -------------------------------------------------------------------------------- /assets/crossair_blackOutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/crossair_blackOutline.png -------------------------------------------------------------------------------- /assets/crossair_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/crossair_blue.png -------------------------------------------------------------------------------- /assets/crossair_blueOutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/crossair_blueOutline.png -------------------------------------------------------------------------------- /assets/crossair_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/crossair_red.png -------------------------------------------------------------------------------- /assets/crossair_redOutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/crossair_redOutline.png -------------------------------------------------------------------------------- /assets/crossair_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/crossair_white.png -------------------------------------------------------------------------------- /assets/crossair_whiteOutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/crossair_whiteOutline.png -------------------------------------------------------------------------------- /assets/gameover.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/gameover.ogg -------------------------------------------------------------------------------- /assets/gameover.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/gameover.wav -------------------------------------------------------------------------------- /assets/glass_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/glass_panel.png -------------------------------------------------------------------------------- /assets/green_boxCheckmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/green_boxCheckmark.png -------------------------------------------------------------------------------- /assets/grey_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/grey_box.png -------------------------------------------------------------------------------- /assets/menu_click.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/menu_click.ogg -------------------------------------------------------------------------------- /assets/menu_click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/menu_click.wav -------------------------------------------------------------------------------- /assets/pattern_blueprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/pattern_blueprint.png -------------------------------------------------------------------------------- /assets/roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/roboto.ttf -------------------------------------------------------------------------------- /assets/spaceShooter2_spritesheet_2X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/spaceShooter2_spritesheet_2X.png -------------------------------------------------------------------------------- /assets/spaceShooter2_spritesheet_2X.xml: -------------------------------------------------------------------------------- 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 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /assets/space_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/space_sheet.png -------------------------------------------------------------------------------- /assets/space_sheet.xml: -------------------------------------------------------------------------------- 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 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /assets/uipackSpace_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/assets/uipackSpace_sheet.png -------------------------------------------------------------------------------- /assets/uipackSpace_sheet.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /examples/kenney.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use space_shooter::kenney_assets::*; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins((DefaultPlugins, KenneyAssetPlugin)) 8 | .init_resource::() 9 | .insert_resource(Time::::from_seconds(0.25)) 10 | .add_systems(Startup, setup) 11 | .add_systems(Update, print_on_load) 12 | .add_systems(FixedUpdate, update) 13 | .run(); 14 | } 15 | 16 | #[derive(Resource, Default)] 17 | struct State { 18 | handle: Handle, 19 | } 20 | 21 | fn setup( 22 | mut state: ResMut, 23 | asset_server: Res, 24 | ) { 25 | // Recommended way to load an asset 26 | state.handle = asset_server 27 | .load("spaceShooter2_spritesheet_2X.xml"); 28 | 29 | // File extensions are optional, but are 30 | // recommended for project management and 31 | // last-resort inference 32 | // state.other_handle = 33 | // asset_server.load("data/ 34 | // asset_no_extension"); 35 | } 36 | 37 | fn print_on_load( 38 | mut commands: Commands, 39 | state: ResMut, 40 | spritesheets: Res>, 41 | mut printed: Local, 42 | ) { 43 | let custom_asset = spritesheets.get(&state.handle); 44 | 45 | if *printed || custom_asset.is_none() { 46 | return; 47 | } 48 | 49 | let kenney_sheet = custom_asset.unwrap(); 50 | info!("image {:?}", kenney_sheet.sheet); 51 | info!( 52 | "first texture: {:?}", 53 | kenney_sheet.textures.first() 54 | ); 55 | 56 | commands.spawn(Camera2dBundle::default()); 57 | commands.spawn(( 58 | SpriteBundle { 59 | texture: kenney_sheet.sheet.clone(), 60 | ..default() 61 | }, 62 | TextureAtlas { 63 | index: 60, 64 | layout: kenney_sheet 65 | .texture_atlas_layout 66 | .clone(), 67 | }, 68 | )); 69 | // Once printed, we won't print again 70 | *printed = true; 71 | } 72 | 73 | fn update( 74 | mut atlas: Query<&mut TextureAtlas>, 75 | state: ResMut, 76 | spritesheets: Res>, 77 | ) { 78 | let custom_asset = spritesheets.get(&state.handle); 79 | 80 | for mut atlas in &mut atlas { 81 | let kenney_sheet = custom_asset.unwrap(); 82 | 83 | if atlas.index + 1 == kenney_sheet.textures.len() { 84 | atlas.index = 0; 85 | } else { 86 | atlas.index += 1; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/spritesheet_viewer.rs: -------------------------------------------------------------------------------- 1 | // TODO: bevy_asset_loader currently doesn't 2 | // support custom asset types. 3 | 4 | use bevy::prelude::*; 5 | use bevy_asset_loader::prelude::*; 6 | use space_shooter::kenney_assets::{ 7 | KenneyAssetPlugin, KenneySpriteSheetAsset, 8 | }; 9 | 10 | fn main() { 11 | App::new() 12 | .init_state::() 13 | .add_loading_state( 14 | LoadingState::new(MyStates::AssetLoading) 15 | .continue_to_state(MyStates::Next) 16 | .load_collection::(), 17 | ) 18 | .add_plugins((DefaultPlugins, KenneyAssetPlugin)) 19 | .add_systems(OnEnter(MyStates::Next), setup) 20 | .add_systems( 21 | Update, 22 | input.run_if(in_state(MyStates::Next)), 23 | ) 24 | .run() 25 | } 26 | 27 | #[derive( 28 | Clone, Eq, PartialEq, Debug, Hash, Default, States, 29 | )] 30 | enum MyStates { 31 | #[default] 32 | AssetLoading, 33 | Next, 34 | } 35 | 36 | #[derive(AssetCollection, Resource)] 37 | pub struct ImageAssets { 38 | #[asset(path = "spaceShooter2_spritesheet_2X.xml")] 39 | pub space_sheet: Handle, 40 | } 41 | 42 | fn setup( 43 | mut commands: Commands, 44 | spritesheets: Res>, 45 | images: Res, 46 | ) { 47 | let kenney_sheet = 48 | spritesheets.get(&images.space_sheet).unwrap(); 49 | commands.spawn(Camera2dBundle::default()); 50 | commands.spawn(( 51 | SpriteBundle { 52 | texture: kenney_sheet.sheet.clone(), 53 | ..default() 54 | }, 55 | TextureAtlas { 56 | index: 0, 57 | layout: kenney_sheet 58 | .texture_atlas_layout 59 | .clone(), 60 | }, 61 | )); 62 | } 63 | fn input( 64 | input: Res>, 65 | spritesheets: Res>, 66 | images: Res, 67 | mut atlas: Query<&mut TextureAtlas>, 68 | ) { 69 | let kenney_sheet = 70 | spritesheets.get(&images.space_sheet).unwrap(); 71 | let mut atlas = atlas.single_mut(); 72 | 73 | if input.just_pressed(KeyCode::Space) { 74 | atlas.index += 1; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /readme/choose_ship.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/readme/choose_ship.avif -------------------------------------------------------------------------------- /readme/menu.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/readme/menu.avif -------------------------------------------------------------------------------- /readme/particles-and-lives.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-adventure/asteroids/3ae4b64a5c7b2ffd5cb9f280796271188a86ffdd/readme/particles-and-lives.avif -------------------------------------------------------------------------------- /src/assets.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_asset_loader::prelude::*; 3 | 4 | use crate::{ 5 | kenney_assets::{ 6 | KenneyAssetPlugin, KenneySpriteSheetAsset, 7 | }, 8 | GameState, 9 | }; 10 | 11 | pub struct AssetsPlugin; 12 | 13 | impl Plugin for AssetsPlugin { 14 | fn build(&self, app: &mut App) { 15 | app.add_plugins(KenneyAssetPlugin) 16 | .add_loading_state( 17 | LoadingState::new(GameState::AssetLoading) 18 | .continue_to_state(GameState::Menu) 19 | .load_collection::() 20 | .load_collection::() 21 | .load_collection::(), 22 | ); 23 | } 24 | } 25 | 26 | #[derive(AssetCollection, Resource)] 27 | pub struct FontAssets { 28 | #[asset(path = "AlfaSlabOne-Regular.ttf")] 29 | pub alfa_slab_one_regular: Handle, 30 | #[asset(path = "roboto.ttf")] 31 | pub roboto: Handle, 32 | } 33 | 34 | #[derive(AssetCollection, Resource)] 35 | pub struct AudioAssets { 36 | #[asset(path = "menu_click.ogg")] 37 | pub menu_click: Handle, 38 | } 39 | 40 | #[derive(AssetCollection, Resource)] 41 | pub struct ImageAssets { 42 | #[asset(path = "grey_box.png")] 43 | pub box_unchecked: Handle, 44 | #[asset(path = "green_boxCheckmark.png")] 45 | pub box_checked: Handle, 46 | #[asset(path = "glass_panel.png")] 47 | pub panel_glass: Handle, 48 | #[asset(path = "pattern_blueprint.png")] 49 | pub pattern_blueprint: Handle, 50 | #[asset(path = "space_sheet.xml")] 51 | pub space_sheet: Handle, 52 | } 53 | -------------------------------------------------------------------------------- /src/colors.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::Color; 2 | 3 | pub const TEXT: Color = Color::BLACK; 4 | -------------------------------------------------------------------------------- /src/controls.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | assets::ImageAssets, 3 | kenney_assets::KenneySpriteSheetAsset, 4 | ship::{PlayerEngineFire, PlayerShipType}, 5 | ui::pause::Pausable, 6 | GameState, Player, 7 | }; 8 | use bevy::prelude::*; 9 | use bevy_xpbd_2d::prelude::*; 10 | use std::time::Duration; 11 | 12 | pub struct ControlsPlugin; 13 | 14 | impl Plugin for ControlsPlugin { 15 | fn build(&self, app: &mut App) { 16 | app.init_resource::().add_systems( 17 | Update, 18 | ( 19 | player_movement_system 20 | .run_if(in_state(GameState::Playing)), 21 | weapon_system 22 | .run_if(in_state(GameState::Playing)), 23 | engine_fire 24 | .run_if(in_state(GameState::Playing)), 25 | laser_movement, 26 | ) 27 | .run_if(resource_equals( 28 | Pausable::NotPaused, 29 | )), 30 | ); 31 | } 32 | } 33 | 34 | #[derive(Component)] 35 | pub struct Laser{ 36 | /// movement factor is ship's movement speed at time of firing 37 | pub movement_factor: Vec2, 38 | /// speed is laser's inherent movement speed 39 | pub speed: f32, 40 | } 41 | 42 | #[derive(Resource, Default, Deref, DerefMut)] 43 | pub struct MovementFactor(pub Vec2); 44 | 45 | fn laser_movement( 46 | mut lasers: Query<(&mut Transform, &Laser)>, 47 | time: Res