main.rs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. use std::default::Default;
  2. use bevy::core::FixedTimestep;
  3. use bevy::prelude::*;
  4. use bevy::render::pass::ClearColor;
  5. use rand::prelude::random;
  6. pub mod components;
  7. use crate::components as c;
  8. pub mod snake;
  9. fn main() {
  10. App::build()
  11. .insert_resource(WindowDescriptor {
  12. title: "Snake!".to_string(),
  13. width: 500.0,
  14. height: 500.0,
  15. ..Default::default()
  16. })
  17. .insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04)))
  18. .insert_resource(c::SnakeSegments::default())
  19. .insert_resource(c::LastTailPosition::default())
  20. .add_startup_system(setup.system())
  21. .add_startup_stage("game_setup", SystemStage::single(snake::spawn.system()))
  22. .add_plugins(DefaultPlugins)
  23. .add_system(
  24. snake::input
  25. .system()
  26. .label(c::SnakeMovement::Input)
  27. .before(c::SnakeMovement::Movement),
  28. )
  29. .add_system(game_over.system().after(c::SnakeMovement::Movement))
  30. .add_system_set(
  31. SystemSet::new()
  32. .with_run_criteria(FixedTimestep::step(0.3))
  33. .with_system(snake::movement.system().label(c::SnakeMovement::Movement))
  34. .with_system(
  35. snake::eating
  36. .system()
  37. .label(c::SnakeMovement::Eating)
  38. .after(c::SnakeMovement::Movement),
  39. )
  40. .with_system(
  41. snake::growth
  42. .system()
  43. .label(c::SnakeMovement::Growth)
  44. .after(c::SnakeMovement::Eating),
  45. ),
  46. )
  47. .add_system_set_to_stage(
  48. CoreStage::PostUpdate,
  49. SystemSet::new()
  50. .with_system(position_translation.system())
  51. .with_system(size_scaling.system()),
  52. )
  53. .add_system_set(
  54. SystemSet::new()
  55. .with_run_criteria(FixedTimestep::step(0.9))
  56. .with_system(food_spawner.system()),
  57. )
  58. .add_event::<c::GrowthEvent>()
  59. .add_event::<c::GameOverEvent>()
  60. .run();
  61. }
  62. fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
  63. commands.spawn_bundle(OrthographicCameraBundle::new_2d());
  64. commands.insert_resource(c::Materials {
  65. head_material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()),
  66. segment_material: materials.add(Color::rgb(0.3, 0.3, 0.3).into()),
  67. food_material: materials.add(Color::rgb(1.0, 0.0, 1.0).into()),
  68. });
  69. }
  70. fn food_spawner(
  71. mut commands: Commands,
  72. materials: Res<c::Materials>,
  73. segments: Query<&c::Position, With<c::SnakeSegment>>,
  74. ) {
  75. let snake_positions = segments.iter().collect::<Vec<&c::Position>>();
  76. // try three times, giving up if we still haven't found a free spot
  77. let mut x;
  78. let mut y;
  79. let mut attempts = 3;
  80. loop {
  81. x = (random::<f32>() * c::ARENA_WIDTH as f32) as i32;
  82. y = (random::<f32>() * c::ARENA_HEIGHT as f32) as i32;
  83. if !snake_positions.contains(&&c::Position {x, y}) {
  84. break;
  85. }
  86. attempts -= 1;
  87. if attempts == 0 {
  88. return;
  89. }
  90. }
  91. commands
  92. .spawn_bundle(SpriteBundle {
  93. material: materials.food_material.clone(),
  94. ..Default::default()
  95. })
  96. .insert(c::Food)
  97. .insert(c::Position { x, y })
  98. .insert(c::GridSize::square(0.8));
  99. }
  100. fn size_scaling(windows: Res<Windows>, mut q: Query<(&c::GridSize, &mut Sprite)>) {
  101. let window = windows.get_primary().unwrap();
  102. for (size, mut sprite) in q.iter_mut() {
  103. sprite.size = Vec2::new(
  104. size.width / c::ARENA_WIDTH as f32 * window.width() as f32,
  105. size.height / c::ARENA_HEIGHT as f32 * window.height() as f32,
  106. );
  107. }
  108. }
  109. fn position_translation(windows: Res<Windows>, mut q: Query<(&c::Position, &mut Transform)>) {
  110. fn convert(pos: f32, bound_window: f32, bound_game: f32) -> f32 {
  111. let tile_size = bound_window / bound_game;
  112. pos / bound_game * bound_window - (bound_window / 2.0) + (tile_size / 2.0)
  113. }
  114. let window = windows.get_primary().unwrap();
  115. for (pos, mut tf) in q.iter_mut() {
  116. tf.translation = Vec3::new(
  117. convert(pos.x as f32, window.width() as f32, c::ARENA_WIDTH as f32),
  118. convert(pos.y as f32, window.height() as f32, c::ARENA_HEIGHT as f32),
  119. 0.0,
  120. )
  121. }
  122. }
  123. fn game_over(
  124. mut commands: Commands,
  125. mut reader: EventReader<c::GameOverEvent>,
  126. materials: Res<c::Materials>,
  127. segments_res: ResMut<c::SnakeSegments>,
  128. food: Query<Entity, With<c::Food>>,
  129. segments: Query<Entity, With<c::SnakeSegment>>,
  130. ) {
  131. if reader.iter().next().is_some() {
  132. for ent in food.iter().chain(segments.iter()) {
  133. commands.entity(ent).despawn();
  134. }
  135. snake::spawn(commands, materials, segments_res);
  136. }
  137. }