Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

novo jogo: Breakout #19

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions .github/workflows/clippy.yml

This file was deleted.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"

members = [
"breakout",
"common",
"dino",
"flappy",
Expand Down
49 changes: 42 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,48 @@ Inspired by [python-games](https://inventwithpython.com/pygame/)

## Finished

- Snake
- InkSpill
- Flappy Bird
- 2048
- Chrome Dino
- Flappy Bird
- Space Invaders
### Snake

<p align="center">
<img src="https://github.com/lframosferreira/bevy-games/assets/84649544/d114e3d4-411c-4d1a-8362-63bf4b62a561">
</p>

### InkSpill

<p align="center">
<img src="https://github.com/lframosferreira/bevy-games/assets/84649544/bfd363b0-15fb-4518-b4a2-3c5fa8391748">
</p>

### 2048

<p align="center">
<img src="https://github.com/lframosferreira/bevy-games/assets/84649544/85cf8f86-756f-4b81-980c-10f7b540beb7">
</p>

### Chrome Dino

<p align="center">
<img src="https://github.com/lframosferreira/bevy-games/assets/84649544/3e7e3bfe-722d-4d06-a37d-d34c89d88d32">
</p>

### Flappy Bird

<p align="center">
<img src="https://github.com/lframosferreira/bevy-games/assets/84649544/7af6cc62-d5a2-4839-8443-3972be242255">
</p>

### Space Invaders

<p align="center">
<img src="https://github.com/lframosferreira/bevy-games/assets/84649544/b290439a-3d97-44b4-a6d2-2699f0dcf228">
</p>

### Breakout

<p align="center">
<img src="https://github.com/lframosferreira/bevy-games/assets/84649544/414a40e4-8656-434a-80df-f109ab285288">
</p>


## Current work

Expand Down
11 changes: 11 additions & 0 deletions breakout/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "breakout"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bevy = { version = "0.11.0", features = ["wayland"] }
rand = "0.8.5"
common = { path = "../common" }
Binary file added breakout/assets/fonts/FiraSans-Bold.ttf
Binary file not shown.
29 changes: 29 additions & 0 deletions breakout/src/game/components.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use bevy::prelude::Component;

#[derive(Component)]
pub struct Player;

#[derive(Component)]
pub struct Block(pub usize);

#[derive(Component)]
pub struct Ball {
pub is_going_right: bool,
pub is_going_up: bool,
pub x_speed: f32,
pub y_speed: f32,
}

const BALL_X_SPEED: f32 = 500.;
const BALL_Y_SPEED: f32 = 200.;

impl Default for Ball {
fn default() -> Self {
Ball {
is_going_right: true,
is_going_up: true,
x_speed: BALL_X_SPEED,
y_speed: BALL_Y_SPEED,
}
}
}
36 changes: 36 additions & 0 deletions breakout/src/game/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
mod components;
mod systems;

pub const WINDOW_X: f32 = 1000.;
pub const WINDOW_Y: f32 = 600.;
const MAX_LIVES: usize = 3;

use bevy::prelude::*;
use common::{
game::{Lives, LivesPlugin},
AppState,
};
use systems::*;

pub struct GamePlugin;

impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, spawn_player)
.add_systems(Startup, spawn_blocks)
.add_systems(Startup, spawn_ball)
.insert_resource(Lives::new(MAX_LIVES))
.add_plugins(LivesPlugin)
.add_systems(Update, move_player.run_if(in_state(AppState::InGame)))
.add_systems(Update, move_ball.run_if(in_state(AppState::InGame)))
.add_systems(
Update,
(collide_ball_with_player, collide_ball_with_blocks)
.run_if(in_state(AppState::InGame)),
)
.add_systems(
OnExit(AppState::GameOver),
(respawn_ball, respawn_blocks, reset_lives, respawn_player),
);
}
}
218 changes: 218 additions & 0 deletions breakout/src/game/systems.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use super::components::{Ball, Block, Player};
use super::MAX_LIVES;
use crate::game::{WINDOW_X, WINDOW_Y};
use bevy::prelude::*;
use bevy::sprite::collide_aabb::{self, Collision};
use common::game::{Lives, Score};

// https://en.wikipedia.org/wiki/Breakout_(video_game)
const PLAYER_Y_OFFSET: f32 = 40.;
const PLAYER_WIDTH: f32 = 200.;
const PLAYER_HEIGHT: f32 = 20.;
const PLAYER_SIZE: Vec2 = Vec2::new(PLAYER_WIDTH, PLAYER_HEIGHT);
const PLAYER_SPEED: f32 = 500.;
const BLOCK_WIDTH: f32 = 100.;
const BLOCK_HEIGHT: f32 = 30.;
const BLOCK_SIZE: Vec2 = Vec2::new(BLOCK_WIDTH, BLOCK_HEIGHT);
const BALL_LENGTH: f32 = 10.;
const BALL_SIZE: Vec2 = Vec2::new(BALL_LENGTH, BALL_LENGTH);

pub fn reset_lives(mut commands: Commands) {
commands.insert_resource(Lives::new(MAX_LIVES));
}

pub fn spawn_player(mut commands: Commands) {
commands.spawn((
Player,
SpriteBundle {
sprite: Sprite {
color: Color::GRAY,
custom_size: Some(PLAYER_SIZE),
..default()
},
transform: Transform::from_xyz(WINDOW_X / 2., PLAYER_Y_OFFSET, 0.0),
..default()
},
));
}

pub fn move_player(
mut player_query: Query<&mut Transform, With<Player>>,
keyboard_input: Res<Input<KeyCode>>,
time: Res<Time>,
) {
if let Ok(mut player) = player_query.get_single_mut() {
let translation = &mut player.translation;
let delta = PLAYER_SPEED * time.delta_seconds();
if keyboard_input.pressed(KeyCode::Right)
&& translation.x + delta < WINDOW_X - PLAYER_WIDTH / 2.
{
translation.x += delta;
}
if keyboard_input.pressed(KeyCode::Left) && translation.x - delta > PLAYER_WIDTH / 2. {
translation.x -= delta;
}
}
}

pub fn respawn_player(mut commands: Commands, player_query: Query<Entity, With<Player>>) {
if let Ok(entity) = player_query.get_single() {
commands.entity(entity).despawn();
}
spawn_player(commands);
}

pub fn spawn_blocks(mut commands: Commands) {
const BLOCK_Y_OFFSET: f32 = WINDOW_Y - 100.;
const BLOCK_COLORS: [Color; 6] = [
Color::RED,
Color::ORANGE_RED,
Color::ORANGE,
Color::YELLOW,
Color::GREEN,
Color::BLUE,
];
for (i, &color) in BLOCK_COLORS.iter().enumerate() {
for j in 0..(WINDOW_X / BLOCK_WIDTH) as u32 {
commands.spawn((
Block(BLOCK_COLORS.len() - i),
SpriteBundle {
sprite: Sprite {
color,
custom_size: Some(BLOCK_SIZE),
..default()
},
transform: Transform::from_xyz(
BLOCK_WIDTH / 2. + j as f32 * BLOCK_WIDTH,
BLOCK_Y_OFFSET - i as f32 * BLOCK_HEIGHT,
0.0,
),
..default()
},
));
}
}
}

pub fn respawn_blocks(mut commands: Commands, block_query: Query<Entity, With<Block>>) {
for entity in block_query.iter() {
commands.entity(entity).despawn();
}
spawn_blocks(commands);
}

pub fn spawn_ball(mut commands: Commands) {
const BALL_Y_OFFSET: f32 = PLAYER_Y_OFFSET + PLAYER_HEIGHT;
commands.spawn((
Ball::default(),
SpriteBundle {
sprite: Sprite {
color: Color::WHITE,
custom_size: Some(BALL_SIZE),
..default()
},
transform: Transform::from_xyz(WINDOW_X / 2., BALL_Y_OFFSET, 0.0),
..default()
},
));
}

pub fn respawn_ball(mut commands: Commands, ball_query: Query<Entity, With<Ball>>) {
if let Ok(entity) = ball_query.get_single() {
commands.entity(entity).despawn();
}
spawn_ball(commands);
}

pub fn move_ball(
mut ball_query: Query<(&mut Transform, &mut Ball, Entity), With<Ball>>,
time: Res<Time>,
mut lives: ResMut<Lives>,
mut commands: Commands,
) {
if let Ok((mut transform, mut ball, entity)) = ball_query.get_single_mut() {
let translation = &mut transform.translation;
let delta_x = ball.x_speed * time.delta_seconds();
if ball.is_going_right {
if translation.x + delta_x <= WINDOW_X - BALL_LENGTH / 2. {
translation.x += delta_x;
} else {
ball.is_going_right = false;
}
} else if translation.x - delta_x >= BALL_LENGTH / 2. {
translation.x -= delta_x;
} else {
ball.is_going_right = true;
}
let delta_y = ball.y_speed * time.delta_seconds();
if ball.is_going_up {
if translation.y + delta_y <= WINDOW_Y - BALL_LENGTH / 2. {
translation.y += delta_y;
} else {
ball.is_going_up = false;
}
} else if translation.y - delta_y > PLAYER_Y_OFFSET {
translation.y -= delta_y;
} else {
lives.decrement();
commands.entity(entity).despawn();
if lives.get() > 0 {
spawn_ball(commands);
}
}
}
}

pub fn collide_ball_with_player(
mut ball_query: Query<(&Transform, &mut Ball), With<Ball>>,
player_query: Query<&Transform, With<Player>>,
) {
if let Ok(player_transform) = player_query.get_single() {
if let Ok((ball_transform, mut ball)) = ball_query.get_single_mut() {
let player_pos = player_transform.translation;
let ball_pos = ball_transform.translation;
if collide_aabb::collide(player_pos, PLAYER_SIZE, ball_pos, BALL_SIZE).is_some() {
ball.is_going_right = ball_pos.x >= player_pos.x;
ball.is_going_up = true;
}
}
}
}

pub fn collide_ball_with_blocks(
mut ball_query: Query<(&Transform, &mut Ball), With<Ball>>,
block_query: Query<(&Transform, Entity, &Block), With<Block>>,
mut commands: Commands,
mut score: ResMut<Score>,
) {
if let Ok((ball_transform, mut ball)) = ball_query.get_single_mut() {
for (block_transform, entity, block) in block_query.iter() {
if let Some(collision) = collide_aabb::collide(
block_transform.translation,
BLOCK_SIZE,
ball_transform.translation,
BALL_SIZE,
) {
match collision {
Collision::Top | Collision::Bottom => ball.is_going_up = !ball.is_going_up,
_ => ball.is_going_right = !ball.is_going_right,
}

commands.entity(entity).despawn();

score.increment(block.0);

// We destroyed the last block
if block_query.iter().len() == 1 {
spawn_blocks(commands);
}

if (block_query.iter().len() - 1) % 10 == 0 {
ball.x_speed *= 1.05;
}

return;
}
}
}
}
21 changes: 21 additions & 0 deletions breakout/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod game;

use bevy::prelude::*;
use common::CommonPlugin;
use game::{GamePlugin, WINDOW_X, WINDOW_Y};

fn main() {
App::new()
.insert_resource(Msaa::default())
.insert_resource(ClearColor(Color::BLACK))
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: (WINDOW_X, WINDOW_Y).into(),
title: "Bevy Breakout".to_string(),
..default()
}),
..default()
}))
.add_plugins((CommonPlugin::default(), GamePlugin))
.run()
}
Loading