use bevy::{
core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping},
dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin},
prelude::*,
};
use rand::prelude::random;
fn main() {
App::new()
.insert_resource(ClearColor(Color::oklch(0.796, 0.126, 299.66)))
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: (400., 400.).into(),
..default()
}),
..default()
}))
.add_plugins(FpsOverlayPlugin {
config: FpsOverlayConfig {
text_config: TextStyle {
font_size: 50.0,
color: Color::oklch(1.0, 0.0, 0.0),
font: default(),
},
},
})
.add_systems(Startup, (setup, spawn_snake))
.insert_resource(Time::<Fixed>::from_seconds(0.125))
.add_systems(FixedUpdate, snake_movement)
.add_systems(Update, snake_movement_input.before(snake_movement))
.add_systems(PostUpdate, (size_scaling, postion_translation))
.insert_resource(Time::<Fixed>::from_hz(1.))
.add_systems(FixedUpdate, food_spawner)
.insert_resource(SnakeSegments::default())
.run();
}
#[derive(Component)]
struct SnakeHead {
direction: Direction,
}
#[derive(Component)]
struct SnakeSegment;
#[derive(Resource, Default)]
struct SnakeSegments(Vec<Entity>);
const GRID_SIZE: u8 = 10;
#[derive(Component, Copy, Clone, Eq, PartialEq)]
struct Position {
x: i32,
y: i32,
}
#[derive(Component)]
struct Size(f32);
#[derive(Component)]
struct Food;
#[derive(PartialEq, Copy, Clone)]
enum Direction {
Left,
Up,
Right,
Down,
}
impl Direction {
pub fn opposite(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
Self::Up => Self::Down,
Self::Down => Self::Up,
}
}
}
fn setup(mut commands: Commands) {
commands.spawn((
Camera2dBundle {
camera: Camera {
hdr: true,
..default()
},
tonemapping: Tonemapping::AcesFitted,
..default()
},
BloomSettings::default(),
));
}
fn spawn_snake(mut commands: Commands, mut segments: ResMut<SnakeSegments>) {
*segments = SnakeSegments(vec![
commands
.spawn(SpriteBundle {
sprite: Sprite {
color: Color::oklch(1.1, 0.126, 299.66),
..default()
},
transform: Transform {
scale: Vec3::splat(10.0),
..default()
},
..default()
})
.insert(SnakeHead {
direction: Direction::Up,
})
.insert(Position { x: 3, y: 3 })
.insert(Size(0.8))
.id(),
spawn_segment(commands, Position { x: 3, y: 2 }),
]);
}
fn snake_movement_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut heads: Query<&mut SnakeHead>,
) {
if let Some(mut head) = heads.iter_mut().next() {
let dir: Direction = if keyboard_input.pressed(KeyCode::KeyA) {
Direction::Left
} else if keyboard_input.pressed(KeyCode::KeyD) {
Direction::Right
} else if keyboard_input.pressed(KeyCode::KeyS) {
Direction::Down
} else if keyboard_input.pressed(KeyCode::KeyW) {
Direction::Up
} else {
head.direction
};
if dir != head.direction.opposite() {
head.direction = dir;
}
}
}
fn snake_movement(mut heads: Query<(&mut Position, &SnakeHead)>) {
if let Some((mut pos, head)) = heads.iter_mut().next() {
match &head.direction {
Direction::Left => pos.x -= 1,
Direction::Right => pos.x += 1,
Direction::Up => pos.y += 1,
Direction::Down => pos.y -= 1,
}
};
}
fn size_scaling(windows: Query<&Window>, mut sprites: Query<(&Size, &mut Transform)>) {
let window = windows.single();
for (sz, mut transform) in sprites.iter_mut() {
transform.scale = Vec3::new(
sz.0 / GRID_SIZE as f32 * window.width(),
sz.0 / GRID_SIZE as f32 * window.height(),
1.,
);
}
}
fn postion_translation(windows: Query<&Window>, mut sprites: Query<(&Position, &mut Transform)>) {
fn convert(pos: f32, window_size: f32) -> f32 {
let tile_size_in_px = window_size / GRID_SIZE as f32;
pos / GRID_SIZE as f32 * window_size - (window_size / 2.) + (tile_size_in_px / 2.)
}
let window = windows.single();
for (pos, mut transform) in sprites.iter_mut() {
transform.translation = Vec3::new(
convert(pos.x as f32, window.width()),
convert(pos.y as f32, window.height()),
0.,
)
}
}
fn food_spawner(mut commands: Commands) {
commands
.spawn(SpriteBundle {
sprite: Sprite {
color: Color::oklch(0.7, 0.274, 330.35),
..default()
},
..default()
})
.insert(Food)
.insert(Position {
x: (random::<f32>() * GRID_SIZE as f32) as i32,
y: (random::<f32>() * GRID_SIZE as f32) as i32,
})
.insert(Size(0.8));
}
fn spawn_segment(mut commands: Commands, position: Position) -> Entity {
commands
.spawn(SpriteBundle {
sprite: Sprite {
color: Color::oklch(1.0, 0.126, 299.66),
..default()
},
..default()
})
.insert(SnakeSegment)
.insert(position)
.insert(Size(0.65))
.id()
}