{"id":19756773,"url":"https://github.com/thebracket/bevy-test","last_synced_at":"2025-10-07T23:42:00.840Z","repository":{"id":114153646,"uuid":"394772036","full_name":"thebracket/bevy-test","owner":"thebracket","description":"A quick and dirty Space Invaders type game in Bevy, with attached tutorial.","archived":false,"fork":false,"pushed_at":"2021-08-11T14:20:15.000Z","size":51,"stargazers_count":19,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-30T12:31:44.495Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thebracket.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-08-10T20:20:08.000Z","updated_at":"2024-01-27T01:26:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"13f37574-eaad-45d3-bfff-c9a18f9005c1","html_url":"https://github.com/thebracket/bevy-test","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thebracket/bevy-test","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebracket%2Fbevy-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebracket%2Fbevy-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebracket%2Fbevy-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebracket%2Fbevy-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thebracket","download_url":"https://codeload.github.com/thebracket/bevy-test/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebracket%2Fbevy-test/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278866766,"owners_count":26059669,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T03:16:53.550Z","updated_at":"2025-10-07T23:42:00.832Z","avatar_url":"https://github.com/thebracket.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e This article is in-development and will be released in full form soon. It'll appear on Medium (my publisher likes that), with this as a the accompanying Github repo.\n\n# Bevy-Invaders\n\nAbout the time that *Hands-on Rust* emerged from its technical review, the Rust game development world was abuzz with news of a new engine: *Bevy*. It was too late to incorporate Bevy into my book--but it looked like a great engine. Bevy has now been available for a year, and is rapidly improving--and capturing mind-share amongst Rust users.\n\nBevy is a good fit for *Hands-on Rust* readers. It's Entity-Component System based, easy to use, and supports everything you need to make simple games.\n\nLet's take Bevy for a test-drive. We'll make a simple *Space Invaders* game in under 200 lines of code.\n\n## Creating a New Project\n\nFind your usual Rust project folder, and create a new project by typing `cargo new bevy_test` into your terminal. You need to edit `Cargo.toml` to include the Bevy engine:\n\n```toml\n[package]\nname = \"bevytest\"\nversion = \"0.1.0\"\nedition = \"2018\"\n\n[dependencies]\nbevy = \"0.5\"\n```\n\nBevy doesn't require any additional dependencies--so you're good to go.\n\n## Programmer Art Time\n\nOur simple *Space Invaders* game will use 3 graphics: the player's spaceship (which I sketched in The Gimp), a bug monster (from [OpenGameArt](https://opengameart.org/content/some-invaders) - by Matriax) and a laser burst (also made in The Gimp).\n\nEach is 24x24 pixels in size, and has a transparent background. I combined them onto a \"sprite sheet\" as follows:\n\n![](assets/spritesheet.png)\n\nNotice that the sprites are 24 pixels apart, on the same row. This makes it easy to index them. This file belongs in a new folder named `assets`, off of the project directory.\n\n## Initializing a Window\n\nOpen your `main.rs` file, and let's change the `main` function to create a Bevy application and open a window:\n\n```rust\nuse bevy::prelude::*;\n\nfn main() {\n    App::build()\n        .insert_resource(WindowDescriptor {\n            title: \"Bevy Invaders\".to_string(),\n            width: 640.0,\n            height: 480.0,\n            vsync: true,\n            ..Default::default()\n        })\n        .add_plugins(DefaultPlugins)\n        .run();\n}\n```\n\nYou start out by including Bevy's prelude. This provides everything you will need. The `App::build()` system uses a *builder* pattern---just like `bracket-lib` in *Hands-on Rust*. You insert a `WindowDescriptor`, making a 640x480 window with a title. Then you add the default plugins from Bevy---this includes everything you need to make a basic game.\n\nRunning your program now opens a 640x480 grey window.\n\n## Loading Sprites\n\nBevy takes the *systems* concept used in *Hands-on Rust* to another level. You don't pass control to your own game loop at all---you give Bevy systems to run, and it executes them. The first type of system you will use is a \"Startup System\", one that runs when the application starts.\n\nLet's add a startup system to `main.rs`:\n\n```rust\nfn setup(\n    mut commands: Commands,\n    asset_server: Res\u003cAssetServer\u003e,\n    mut texture_atlases: ResMut\u003cAssets\u003cTextureAtlas\u003e\u003e,\n) {\n    // Setup the sprite sheet\n    let texture_handle = asset_server.load(\"spritesheet.png\");\n    let texture_atlas = TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 3, 1);\n    let texture_atlas_handle = texture_atlases.add(texture_atlas);\n\n    // Add a 2D Camera\n    commands.spawn_bundle(OrthographicCameraBundle::new_2d());\n\n    // Spawn the player\n    commands.spawn().insert_bundle(SpriteSheetBundle {\n        texture_atlas: texture_atlas_handle.clone(),\n        transform: Transform::from_translation(Vec3::new(0.0, -220.0, 0.0)),\n        sprite: TextureAtlasSprite::new(0),\n        ..Default::default()\n    });\n}\n```\n\nThe function header has 3 parameters. Don't worry, the contents is injected automatically---you don't need to worry about filling them up. Let's take a look at the parameters:\n\n* `mut commands : Commands` requests a command buffer, just like the command buffers you used with Legion in *Hands-on Rust*. You'll use this to spawn entities.\n* `asset_server: Res\u003cAssetServer\u003e` is requesting a *resource* (just like the Legion resources you used in my book). It's requesting a built-in resource, an \"asset server\". This serves as a centralized repository of all of the assets---game graphics, sounds, etc.---that your game will use.\n* `mut texture_atlases: ResMut\u003cAssets\u003cTextureAtlas\u003e\u003e` is another resource, this time mutable. \"Texture Atlasses\" are sprite sheets---images containing all of your sprites.\n\nSetting up your sprite sheet requires that you load the image into the asset server with `asset_server.load`. Then you create a new `texture_atlas`, specifying that you have 3 images, spaced 24 pixels apart on your sprite sheet. Finally, you obtain a \"handle\" - a means of specifying which sprite sheet you want and store it for later use.\n\n`commands.spawn_bundle(OrthographicCameraBundle::new_2d());` creates a default 2D camera. Since the game is 2D, this is exactly what you need. The only difference from other cameras is that `0,0` is the *center* of your screen---so your 640x480 window actually ranges from -320 to 320 on the x axis, and -240 to 240 on the y axis.\n\nFinally, we'll spawn a spaceship to make sure that the sprite system works. `commands.spawn()` adds a command to the queue to create a new entity. Subsequent functions add details to the entity; it's the builder pattern--but for entities and components.\n\nIn this case, we're adding a `SpriteSheetBundle`. This is a Bevy-provided bundle of components, specifying everything you need to render a sprite on the screen. You have to provide it with the texture atlas handle we made earlier, and a sprite index--0 in this case, the first sprite on the sprite sheet. The `transform` is a little more mysterious. Bevy sprites can be moved, scaled and rotated. We only want movement, so we call `Transform::from_translation(Vec3::new(0.0, -220.0, 0.0))` to spawn the spaceship at `0, -220`.\n\nAll that remains is to *use* your new `setup` function. In your `main` function, add it to the app builder:\n\n```rust\nfn main() {\n    App::build()\n        .insert_resource(WindowDescriptor {\n            title: \"Bevy Invaders\".to_string(),\n            width: 640.0,\n            height: 480.0,\n            vsync: true,\n            ..Default::default()\n        })\n        .add_plugins(DefaultPlugins)\n        .add_startup_system(setup.system())\n        .run();\n}\n```\n\nIf you run the game now, you'll see a grey window with a spaceship in it.\n\n## Moving the Player\n\nWe'll move the spaceship in response to the left and right arrows (you can change it by changing the code). We'll also reserve `spacebar` for firing your lasers--but we'll get to that later.\n\nLet's start by adding a component to represent the player:\n\n```rust\nstruct Player {\n    delta_x: f32,\n}\n```\n\nWe already have the player's transformation attached to their sprite, so we'll use that for their current position. Simple \"press button, move 1 pixel\" movement feels sluggish and unintuitive, so we're going to use `delta_x` to represent the current movement rate of the player--and make keypresses represent acceleration.\n\nCreate a new function named `player`. This will handle all things relating to the player. Add the following code:\n\n```rust\nfn player(\n    keyboard_input: Res\u003cInput\u003cKeyCode\u003e\u003e,\n    mut commands: Commands,\n    mut query: Query\u003c(\u0026mut Player, \u0026mut Transform, \u0026Handle\u003cTextureAtlas\u003e)\u003e,\n) {\n    const ACCELERATION: f32 = 1.0;\n    const MAX_VELOCITY: f32 = 16.0;\n\n    for (mut player, mut trans, atlas_handle) in query.iter_mut() {\n        let mut firing = false;\n\n        if keyboard_input.pressed(KeyCode::Left) {\n            player.delta_x -= ACCELERATION;\n        }\n        if keyboard_input.pressed(KeyCode::Right) {\n            player.delta_x += ACCELERATION;\n        }\n        if keyboard_input.just_pressed(KeyCode::Space) {\n            firing = true;\n        }\n\n        // Apply movement deltas\n        player.delta_x = player.delta_x.clamp(-MAX_VELOCITY, MAX_VELOCITY);\n        trans.translation.x += player.delta_x;\n        trans.translation.x = trans.translation.x.clamp(-320.0, 320.0);\n\n        // Decelerate\n        player.delta_x *= 0.75;\n    }\n}\n```\n\nThe function parameters will once again be injected by Bevy--you don't need to worry about populating them. The function is requesting:\n\n* `keyboard_input: Res\u003cInput\u003cKeyCode\u003e\u003e,`, a resource containing current keyboard input. Bevy provides this automatically.\n* `mut commands: Commands,`, an ECS command buffer. You won't be using this yet, but keep it around for later.\n* `mut query: Query\u003c(\u0026mut Player, \u0026mut Transform, \u0026Handle\u003cTextureAtlas\u003e)\u003e,` is an ECS query. It's very similar to the Legion queries you wrote in *Hands-on Rust*. You want the query to iterate entities that have a `Player` component, a `Transform` (for current position) and a `Handle\u003cTextureAtlas\u003e`. The latter provides a way to access the sprite-sheet. You won't need it yet, we'll need it when we start shooting things.\n\nNext, we define some constants. I played with these values until they felt right to me. Feel free to adjust. `ACCELERATION` provides the rate-of-change to the X-axis when a movement key is pressed. `MAX_VELOCITY` acts as a limiter--the ship won't move faster than this many pixels per frame.\n\nWe then iterate the query. This will run once for each entity which has all of the components we listed in the query--just like a Legion query. Since we only have one `Player`, we can be sure it will only run once.\n\nWithin the query, we check `keyboard_input.pressed` to see if different keys are pressed. If the left movement arrow key is pressed, we reduce the player's `delta_x` by `ACCELERATION`--we do the inverse if the right arrow key is pressed. We also set `firing` to `true` if the spacebar is pressed, but we won't worry about that yet.\n\nThe section commented \"apply the movement delta\" uses `clamp` to ensure that acceleration is within the range `-MAX_VELOCITY .. MAX_VELOCITY`--the player can't exceed the speed limit. It then adds `delta_x` to the player's x translation--moving the player. Finally, it clamps once again to ensure that the player remains on the screen.\n\nThe last command gradually reduces `delta_x` over time. The ship will gradually slide to a halt. This isn't entirely realistic in space, but it's intuitive to players--and common in this type of game.\n\nThe last thing you need to do is add `player` to the list of systems you run in `main.rs`:\n\n```rust\nfn main() {\n    App::build()\n        .insert_resource(WindowDescriptor {\n            title: \"Bevy Invaders\".to_string(),\n            width: 640.0,\n            height: 480.0,\n            vsync: true,\n            ..Default::default()\n        })\n        .add_plugins(DefaultPlugins)\n        .add_startup_system(setup.system())\n        .add_system(player.system())\n        .run();\n}\n```\n\nRun the game now. You can move your player left and right. Movement is smooth, with acceleration and apparent friction. Not bad for 75 lines of code!\n\n## Adding Bugs\n\nAdding bugs to code is easy. Fortunately, adding intentional invader bugs isn't too hard either. We want to create a few rows of alien bug-monsters, cycling left or right and moving down a row when they reach the edge of the screen. Fortunately, this isn't too hard to accomplish.\n\n### Buggy Data\n\nWe'll start by codifying the bug's movement in an `enum` and component type:\n\n```rust\nenum BugMovement {\n    Left,\n    Right,\n    Down { n: f32, next_left: bool },\n}\n\nstruct Bug {\n    movement: BugMovement,\n}\n```\n\nWe're deriving `Copy` and `Clone` because we'll need to make copies of the AI state later on. Otherwise, this is very standard Rust: a bug is either moving left, right or down. If its moving down, we track how far down it has moved. Then we store a `movement` entry in a structure to act as the bug's AI component.\n\n### Moving the Bugs\n\nWe'll create another system to move the bugs. Create a new function as follows:\n\n```rust\nfn bug_movement(mut query: Query\u003c(\u0026mut Bug, \u0026mut Transform)\u003e) {\n    for (mut bug, mut trans) in query.iter_mut() {\n        let mut new_movement = bug.movement;\n        match bug.movement {\n            BugMovement::Left =\u003e {\n                trans.translation.x -= 2.0;\n                if trans.translation.x \u003c -300.0 {\n                    new_movement = BugMovement::Down {\n                        n: 12.0,\n                        next_left: false,\n                    };\n                }\n            }\n            BugMovement::Right =\u003e {\n                trans.translation.x += 2.0;\n                if trans.translation.x \u003e 300.0 {\n                    new_movement = BugMovement::Down {\n                        n: 12.0,\n                        next_left: true,\n                    };\n                }\n            }\n            BugMovement::Down { n, next_left } =\u003e {\n                trans.translation.y -= 2.0;\n                new_movement = BugMovement::Down {\n                    n: n - 1.0,\n                    next_left,\n                };\n                if n \u003c 1.0 {\n                    new_movement = if next_left {\n                        BugMovement::Left\n                    } else {\n                        BugMovement::Right\n                    };\n                }\n            }\n        }\n        bug.movement = new_movement;\n    }\n}\n```\n\nThe logic should be easy enough to follow here. We're iterating through every bug in the system and moving it left or right. If it reaches the edge of the screen, we change its mode to `Down`--and set `n` to 12, indicating that it may move down 12 times. If a bug is moving downwards, we move it down and increase `n`.\n\n### Spawning some Bugs\n\nNext, we need to extend our `setup` function to create some bugs. Add this after you finish creating the player in `setup`:\n\n```rust\n// Spawn rows of enemies\nfor bug_row in 0..4 {\n    let y = 200.0 - (bug_row as f32 * 30.0);\n    for bug_col in 0..20 {\n        let x = -300.0 + (bug_col as f32 * 30.0);\n        commands\n            .spawn()\n            .insert_bundle(SpriteSheetBundle {\n                texture_atlas: texture_atlas_handle.clone(),\n                transform: Transform::from_translation(Vec3::new(x, y, 0.0)),\n                sprite: TextureAtlasSprite::new(1),\n                ..Default::default()\n            })\n            .insert(Bug {\n                movement: if bug_row % 2 == 0 {\n                    BugMovement::Left\n                } else {\n                    BugMovement::Right\n                },\n            });\n    }\n}\n```\n\nWe create 4 rows of bugs, and 20 bugs per row. The bugs are nicely spaced apart, and alternating rows are either moving left or right.\n\nAll that remains is to add our new system to our `main` function:\n\n```rust\nfn main() {\n    App::build()\n        .insert_resource(WindowDescriptor {\n            title: \"Bevy Invaders\".to_string(),\n            width: 640.0,\n            height: 480.0,\n            vsync: true,\n            ..Default::default()\n        })\n        .add_plugins(DefaultPlugins)\n        .add_startup_system(setup.system())\n        .add_system(player.system())\n        .add_system(bug_movement.system())\n        .run();\n}\n```\n\nRun the program now, and you have waves of bugs working their way down towards the player. There's no way to defend yourself yet, but it's a great start!\n\n## Zapping Bugs\n\nFiring at bugs is the core of *Space Invaders* type game-play, so let's implement that. We'll start by creating an empty component (a \"tag\") to indicate that an entity is in fact a laser blast:\n\n```rust\nstruct Laser;\n```\n\nRemember the `firing` variable in your `player` function? We'll add some code to spawn a new laser blast above the player when the spacebar is pressed. Add this code to the end of the `player` function (inside the query loop):\n\n```rust\nif firing {\n    commands\n        .spawn()\n        .insert_bundle(SpriteSheetBundle {\n            texture_atlas: atlas_handle.clone(),\n            transform: Transform::from_translation(Vec3::new(\n                trans.translation.x,\n                trans.translation.y + 24.0,\n                0.0,\n            )),\n            sprite: TextureAtlasSprite::new(2),\n            ..Default::default()\n        })\n        .insert(Laser {});\n}\n```\n\nThis is very similar to the entity spawning from the `setup` function. It spawns a laser blast just above the player.\n\n### Moving Lasers\n\nIdeally, laser blasts should shoot upwards--and remove themselves from the game when they hit the top of the screen. Let's make a simple system for this. Add the following function:\n\n```rust\nfn laser_movement(mut query: Query\u003c(Entity, \u0026Laser, \u0026mut Transform)\u003e, mut commands: Commands) {\n    for (entity, _, mut trans) in query.iter_mut() {\n        trans.translation += Vec3::new(0.0, 4.0, 0.0);\n\n        if trans.translation.y \u003e 240.0 {\n            commands.entity(entity).despawn();\n        }\n    }\n}\n```\n\nThe only new concept here is that we check for `y` hitting the edge of the screen--and remove the laser blast if it did. In the query, notice we include `Entity`; this includes a unique identifier for the laser blast we are examining in the query. We access it via the `commands.entity(entity)` function--and add the command `despawn`, which deletes the entity from the game.\n\nYou need to insert the `laser_movement` system into your `main` function's app builder:\n\n```rust\n...\n.add_system(laser_movement.system())\n.run();\n```\n\nIf you run the game now, you can fire lasers--but you can't affect the bugs.\n\n## Zapping Bugs\n\nThe last system we need removes bugs from the game if they are hit by laser fire. Add one more function to your game:\n\n```rust\nfn bug_zapper(\n    laser_query: Query\u003c(Entity, \u0026Laser, \u0026Transform)\u003e,\n    collider_query: Query\u003c(Entity, \u0026Bug, \u0026Transform)\u003e,\n    mut commands: Commands,\n) {\n    for (entity, _, trans) in laser_query.iter() {\n        let laser_pos = Vec2::new(trans.translation.x, trans.translation.y);\n        for (bug_entity, _, bug_transform) in collider_query.iter() {\n            let bug_pos = Vec2::new(bug_transform.translation.x, bug_transform.translation.y);\n\n            if bug_pos.distance(laser_pos) \u003c 24.0 {\n                commands.entity(bug_entity).despawn();\n                commands.entity(entity).despawn();\n            }\n        }\n    }\n}\n```\n\nThis function is a little more complicated. Let's look at the parameters it requests from Bevy:\n\n* `laser_query: Query\u003c(Entity, \u0026Laser, \u0026Transform)\u003e` creates a query that provides the entity and position of laser blasts.\n* `collider_query: Query\u003c(Entity, \u0026Bug, \u0026Transform)\u003e` creates a query that lists the positions of bugs.\n* `mut commands` once again gives you a command buffer with which you may remove entities.\n\nThe function iterates the `laser_query` first, running once per active laser blast. Inside the query, it:\n\n* Stores the laser's current position in a `Vec2` type. This is not to be confused with `Vec` (the container)--it is a 2-entry vector. Bevy includes fast vector math functions.\n* Runs the `collider_query` and for each bug position:\n   * Store the bug's position in another `Vec2`.\n   * Use the `distance` function, built into `Vec2` to calculate how far away each bug is from the laser.\n   * If the bug is within 24 pixels, it removes both the bug and the laser blast from the ECS.\n\nOnce again, you need to add the new system to your `main` function's builder:\n\n```rust\n...\n.add_system(laser_movement.system())\n.add_system(bug_zapper.system())\n.run();\n```\n\nRun the game now. You can move left and right, and shoot the ever encroaching waves of aliens.\n\n## The Finished Program\n\nThe completed program weighs in at 198 lines of code:\n\n```rust\nuse bevy::prelude::*;\n\nfn main() {\n    App::build()\n        .insert_resource(WindowDescriptor {\n            title: \"Bevy Invaders\".to_string(),\n            width: 640.0,\n            height: 480.0,\n            vsync: true,\n            ..Default::default()\n        })\n        .add_plugins(DefaultPlugins)\n        .add_startup_system(setup.system())\n        .add_system(player.system())\n        .add_system(bug_movement.system())\n        .add_system(laser_movement.system())\n        .add_system(bug_zapper.system())\n        .run();\n}\n\nstruct Player {\n    delta_x: f32,\n}\n\n#[derive(Copy, Clone)]\nenum BugMovement {\n    Left,\n    Right,\n    Down { n: f32, next_left: bool },\n}\n\nstruct Bug {\n    movement: BugMovement,\n}\n\nstruct Laser;\n\nfn player(\n    keyboard_input: Res\u003cInput\u003cKeyCode\u003e\u003e,\n    mut commands: Commands,\n    mut query: Query\u003c(\u0026mut Player, \u0026mut Transform, \u0026Handle\u003cTextureAtlas\u003e)\u003e,\n) {\n    const ACCELERATION: f32 = 1.0;\n    const MAX_VELOCITY: f32 = 16.0;\n\n    for (mut player, mut trans, atlas_handle) in query.iter_mut() {\n        let mut firing = false;\n\n        if keyboard_input.pressed(KeyCode::Left) {\n            player.delta_x -= ACCELERATION;\n        }\n        if keyboard_input.pressed(KeyCode::Right) {\n            player.delta_x += ACCELERATION;\n        }\n        if keyboard_input.just_pressed(KeyCode::Space) {\n            firing = true;\n        }\n\n        // Apply movement deltas\n        player.delta_x = player.delta_x.clamp(-MAX_VELOCITY, MAX_VELOCITY);\n        trans.translation.x += player.delta_x;\n        trans.translation.x = trans.translation.x.clamp(-320.0, 320.0);\n\n        // Decelerate\n        player.delta_x *= 0.75;\n\n        if firing {\n            commands\n                .spawn()\n                .insert_bundle(SpriteSheetBundle {\n                    texture_atlas: atlas_handle.clone(),\n                    transform: Transform::from_translation(Vec3::new(\n                        trans.translation.x,\n                        trans.translation.y + 24.0,\n                        0.0,\n                    )),\n                    sprite: TextureAtlasSprite::new(2),\n                    ..Default::default()\n                })\n                .insert(Laser {});\n        }\n    }\n}\n\nfn bug_movement(mut query: Query\u003c(\u0026mut Bug, \u0026mut Transform)\u003e) {\n    for (mut bug, mut trans) in query.iter_mut() {\n        let mut new_movement = bug.movement;\n        match bug.movement {\n            BugMovement::Left =\u003e {\n                trans.translation.x -= 2.0;\n                if trans.translation.x \u003c -300.0 {\n                    new_movement = BugMovement::Down {\n                        n: 12.0,\n                        next_left: false,\n                    };\n                }\n            }\n            BugMovement::Right =\u003e {\n                trans.translation.x += 2.0;\n                if trans.translation.x \u003e 300.0 {\n                    new_movement = BugMovement::Down {\n                        n: 12.0,\n                        next_left: true,\n                    };\n                }\n            }\n            BugMovement::Down { n, next_left } =\u003e {\n                trans.translation.y -= 2.0;\n                new_movement = BugMovement::Down {\n                    n: n - 1.0,\n                    next_left,\n                };\n                if n \u003c 1.0 {\n                    new_movement = if next_left {\n                        BugMovement::Left\n                    } else {\n                        BugMovement::Right\n                    };\n                }\n            }\n        }\n        bug.movement = new_movement;\n    }\n}\n\nfn laser_movement(mut query: Query\u003c(Entity, \u0026Laser, \u0026mut Transform)\u003e, mut commands: Commands) {\n    for (entity, _, mut trans) in query.iter_mut() {\n        trans.translation += Vec3::new(0.0, 4.0, 0.0);\n\n        if trans.translation.y \u003e 240.0 {\n            commands.entity(entity).despawn();\n        }\n    }\n}\n\nfn bug_zapper(\n    laser_query: Query\u003c(Entity, \u0026Laser, \u0026Transform)\u003e,\n    collider_query: Query\u003c(Entity, \u0026Bug, \u0026Transform)\u003e,\n    mut commands: Commands,\n) {\n    for (entity, _, trans) in laser_query.iter() {\n        let laser_pos = Vec2::new(trans.translation.x, trans.translation.y);\n        for (bug_entity, _, bug_transform) in collider_query.iter() {\n            let bug_pos = Vec2::new(bug_transform.translation.x, bug_transform.translation.y);\n\n            if bug_pos.distance(laser_pos) \u003c 24.0 {\n                commands.entity(bug_entity).despawn();\n                commands.entity(entity).despawn();\n            }\n        }\n    }\n}\n\nfn setup(\n    mut commands: Commands,\n    asset_server: Res\u003cAssetServer\u003e,\n    mut texture_atlases: ResMut\u003cAssets\u003cTextureAtlas\u003e\u003e,\n) {\n    // Setup the sprite sheet\n    let texture_handle = asset_server.load(\"spritesheet.png\");\n    let texture_atlas = TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 3, 1);\n    let texture_atlas_handle = texture_atlases.add(texture_atlas);\n    commands.spawn_bundle(OrthographicCameraBundle::new_2d());\n\n    // Spawn the player\n    commands\n        .spawn()\n        .insert_bundle(SpriteSheetBundle {\n            texture_atlas: texture_atlas_handle.clone(),\n            transform: Transform::from_translation(Vec3::new(0.0, -220.0, 0.0)),\n            sprite: TextureAtlasSprite::new(0),\n            ..Default::default()\n        })\n        .insert(Player { delta_x: 0.0 });\n\n    // Spawn rows of enemies\n    for bug_row in 0..4 {\n        let y = 200.0 - (bug_row as f32 * 30.0);\n        for bug_col in 0..20 {\n            let x = -300.0 + (bug_col as f32 * 30.0);\n            commands\n                .spawn()\n                .insert_bundle(SpriteSheetBundle {\n                    texture_atlas: texture_atlas_handle.clone(),\n                    transform: Transform::from_translation(Vec3::new(x, y, 0.0)),\n                    sprite: TextureAtlasSprite::new(1),\n                    ..Default::default()\n                })\n                .insert(Bug {\n                    movement: if bug_row % 2 == 0 {\n                        BugMovement::Left\n                    } else {\n                        BugMovement::Right\n                    },\n                });\n        }\n    }\n}\n```\n\n## Wrap-Up\n\nIn this article, we kicked Bevy's tires to get a quick feeling for what the engine can do. It's impressive. ECS queries are expressive, with very little added boilerplate. Systems are remarkably easy to create and use. With only a short snippet of code, we've created a playable game.\n\nThere's obviously more that could be added to the game: the aliens firing back, keeping score, beginning and ending screens. This quick-start should get you going with Bevy. Now it's up to you to imagine your perfect game.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthebracket%2Fbevy-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthebracket%2Fbevy-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthebracket%2Fbevy-test/lists"}