123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- use std::default::Default;
- use bevy::core::FixedTimestep;
- use bevy::prelude::*;
- use bevy::render::pass::ClearColor;
- use rand::prelude::random;
- const ARENA_WIDTH: u32 = 10;
- const ARENA_HEIGHT: u32 = 10;
- struct SnakeHead {
- direction: Direction,
- }
- struct Food;
- struct GrowthEvent;
- #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
- struct Position {
- x: i32,
- y: i32,
- }
- struct Size {
- width: f32,
- height: f32,
- }
- impl Size {
- pub fn square(x: f32) -> Size {
- Self {
- width: x,
- height: x,
- }
- }
- }
- struct Materials {
- head_material: Handle<ColorMaterial>,
- segment_material: Handle<ColorMaterial>,
- food_material: Handle<ColorMaterial>,
- }
- #[derive(PartialEq, Copy, Clone)]
- enum Direction {
- Left,
- Up,
- Right,
- Down,
- }
- impl Direction {
- fn opposite(self) -> Self {
- match self {
- Self::Left => Self::Right,
- Self::Right => Self::Left,
- Self::Up => Self::Down,
- Self::Down => Self::Up,
- }
- }
- }
- #[derive(SystemLabel, Debug, Hash, PartialEq, Eq, Clone)]
- pub enum SnakeMovement {
- Input,
- Movement,
- Eating,
- Growth,
- }
- struct SnakeSegment;
- #[derive(Default)]
- struct SnakeSegments {
- segments: Vec<Entity>,
- }
- fn main() {
- App::build()
- .insert_resource(WindowDescriptor {
- title: "Snake!".to_string(),
- width: 500.0,
- height: 500.0,
- ..Default::default()
- })
- .insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04)))
- .insert_resource(SnakeSegments::default())
- .add_startup_system(setup.system())
- .add_startup_stage("game_setup", SystemStage::single(spawn_snake.system()))
- .add_plugins(DefaultPlugins)
- .add_system(
- snake_movement_input
- .system()
- .label(SnakeMovement::Input)
- .before(SnakeMovement::Movement),
- )
- .add_system_set(
- SystemSet::new()
- .with_run_criteria(FixedTimestep::step(0.3))
- .with_system(snake_movement.system().label(SnakeMovement::Movement))
- .with_system(
- snake_eating
- .system()
- .label(SnakeMovement::Eating)
- .after(SnakeMovement::Movement),
- ),
- )
- .add_system_set_to_stage(
- CoreStage::PostUpdate,
- SystemSet::new()
- .with_system(position_translation.system())
- .with_system(size_scaling.system()),
- )
- .add_system_set(
- SystemSet::new()
- .with_run_criteria(FixedTimestep::step(0.9))
- .with_system(food_spawner.system()),
- )
- .add_event::<GrowthEvent>()
- .run();
- }
- fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
- commands.spawn_bundle(OrthographicCameraBundle::new_2d());
- commands.insert_resource(Materials {
- head_material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()),
- segment_material: materials.add(Color::rgb(0.3, 0.3, 0.3).into()),
- food_material: materials.add(Color::rgb(1.0, 0.0, 1.0).into()),
- });
- }
- fn food_spawner(mut commands: Commands, materials: Res<Materials>) {
- commands
- .spawn_bundle(SpriteBundle {
- material: materials.food_material.clone(),
- ..Default::default()
- })
- .insert(Food)
- .insert(Position {
- x: (random::<f32>() * ARENA_WIDTH as f32) as i32,
- y: (random::<f32>() * ARENA_HEIGHT as f32) as i32,
- })
- .insert(Size::square(0.8));
- }
- fn spawn_segment(
- mut commands: Commands,
- material: &Handle<ColorMaterial>,
- position: Position,
- ) -> Entity {
- commands
- .spawn_bundle(SpriteBundle {
- material: material.clone(),
- ..Default::default()
- })
- .insert(SnakeSegment)
- .insert(position)
- .insert(Size::square(0.65))
- .id()
- }
- fn spawn_snake(
- mut commands: Commands,
- materials: Res<Materials>,
- mut segments: ResMut<SnakeSegments>,
- ) {
- segments.segments = vec![
- commands
- .spawn_bundle(SpriteBundle {
- material: materials.head_material.clone(),
- sprite: Sprite::new(Vec2::new(10.0, 10.0)),
- ..Default::default()
- })
- .insert(SnakeHead {
- direction: Direction::Up,
- })
- .insert(Position { x: 3, y: 3 })
- .insert(Size::square(0.8))
- .id(),
- spawn_segment(
- commands,
- &materials.segment_material,
- Position { x: 3, y: 2 },
- ),
- ]
- }
- fn snake_movement_input(keyboard_input: Res<Input<KeyCode>>, mut heads: Query<&mut SnakeHead>) {
- if let Some(mut head) = heads.iter_mut().next() {
- let dir = if keyboard_input.pressed(KeyCode::Left) {
- Direction::Left
- } else if keyboard_input.pressed(KeyCode::Down) {
- Direction::Down
- } else if keyboard_input.pressed(KeyCode::Up) {
- Direction::Up
- } else if keyboard_input.pressed(KeyCode::Right) {
- Direction::Right
- } else {
- head.direction
- };
- if dir != head.direction.opposite() {
- head.direction = dir;
- }
- }
- }
- fn snake_movement(
- segments: ResMut<SnakeSegments>,
- mut heads: Query<(Entity, &SnakeHead)>,
- mut positions: Query<&mut Position>,
- ) {
- if let Some((head_entity, head)) = heads.iter_mut().next() {
- let segment_positions = segments
- .segments
- .iter()
- .map(|e| *positions.get_mut(*e).unwrap())
- .collect::<Vec<Position>>();
- let mut head_pos = positions.get_mut(head_entity).unwrap();
- match &head.direction {
- Direction::Left => head_pos.x -= 1,
- Direction::Right => head_pos.x += 1,
- Direction::Up => head_pos.y += 1,
- Direction::Down => head_pos.y -= 1,
- }
- segment_positions
- .iter()
- .zip(segments.segments.iter().skip(1))
- .for_each(|(pos, segment)| {
- *positions.get_mut(*segment).unwrap() = *pos;
- });
- }
- }
- fn snake_eating(
- mut commands: Commands,
- mut growth_writer: EventWriter<GrowthEvent>,
- food_positions: Query<(Entity, &Position), With<Food>>,
- head_positions: Query<&Position, With<SnakeHead>>,
- ) {
- for head_pos in head_positions.iter() {
- for (ent, food_pos) in food_positions.iter() {
- if food_pos == head_pos {
- commands.entity(ent).despawn();
- growth_writer.send(GrowthEvent);
- }
- }
- }
- }
- fn size_scaling(windows: Res<Windows>, mut q: Query<(&Size, &mut Sprite)>) {
- let window = windows.get_primary().unwrap();
- for (size, mut sprite) in q.iter_mut() {
- sprite.size = Vec2::new(
- size.width / ARENA_WIDTH as f32 * window.width() as f32,
- size.height / ARENA_HEIGHT as f32 * window.height() as f32,
- );
- }
- }
- fn position_translation(windows: Res<Windows>, mut q: Query<(&Position, &mut Transform)>) {
- fn convert(pos: f32, bound_window: f32, bound_game: f32) -> f32 {
- let tile_size = bound_window / bound_game;
- pos / bound_game * bound_window - (bound_window / 2.0) + (tile_size / 2.0)
- }
- let window = windows.get_primary().unwrap();
- for (pos, mut tf) in q.iter_mut() {
- tf.translation = Vec3::new(
- convert(pos.x as f32, window.width() as f32, ARENA_WIDTH as f32),
- convert(pos.y as f32, window.height() as f32, ARENA_HEIGHT as f32),
- 0.0,
- )
- }
- }
|