main.rs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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. const ARENA_WIDTH: u32 = 10;
  7. const ARENA_HEIGHT: u32 = 10;
  8. struct SnakeHead {
  9. direction: Direction,
  10. }
  11. struct Food;
  12. struct GrowthEvent;
  13. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
  14. struct Position {
  15. x: i32,
  16. y: i32,
  17. }
  18. struct Size {
  19. width: f32,
  20. height: f32,
  21. }
  22. impl Size {
  23. pub fn square(x: f32) -> Size {
  24. Self {
  25. width: x,
  26. height: x,
  27. }
  28. }
  29. }
  30. struct Materials {
  31. head_material: Handle<ColorMaterial>,
  32. segment_material: Handle<ColorMaterial>,
  33. food_material: Handle<ColorMaterial>,
  34. }
  35. #[derive(PartialEq, Copy, Clone)]
  36. enum Direction {
  37. Left,
  38. Up,
  39. Right,
  40. Down,
  41. }
  42. impl Direction {
  43. fn opposite(self) -> Self {
  44. match self {
  45. Self::Left => Self::Right,
  46. Self::Right => Self::Left,
  47. Self::Up => Self::Down,
  48. Self::Down => Self::Up,
  49. }
  50. }
  51. }
  52. #[derive(SystemLabel, Debug, Hash, PartialEq, Eq, Clone)]
  53. pub enum SnakeMovement {
  54. Input,
  55. Movement,
  56. Eating,
  57. Growth,
  58. }
  59. struct SnakeSegment;
  60. #[derive(Default)]
  61. struct SnakeSegments {
  62. segments: Vec<Entity>,
  63. }
  64. fn main() {
  65. App::build()
  66. .insert_resource(WindowDescriptor {
  67. title: "Snake!".to_string(),
  68. width: 500.0,
  69. height: 500.0,
  70. ..Default::default()
  71. })
  72. .insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04)))
  73. .insert_resource(SnakeSegments::default())
  74. .add_startup_system(setup.system())
  75. .add_startup_stage("game_setup", SystemStage::single(spawn_snake.system()))
  76. .add_plugins(DefaultPlugins)
  77. .add_system(
  78. snake_movement_input
  79. .system()
  80. .label(SnakeMovement::Input)
  81. .before(SnakeMovement::Movement),
  82. )
  83. .add_system_set(
  84. SystemSet::new()
  85. .with_run_criteria(FixedTimestep::step(0.3))
  86. .with_system(snake_movement.system().label(SnakeMovement::Movement))
  87. .with_system(
  88. snake_eating
  89. .system()
  90. .label(SnakeMovement::Eating)
  91. .after(SnakeMovement::Movement),
  92. ),
  93. )
  94. .add_system_set_to_stage(
  95. CoreStage::PostUpdate,
  96. SystemSet::new()
  97. .with_system(position_translation.system())
  98. .with_system(size_scaling.system()),
  99. )
  100. .add_system_set(
  101. SystemSet::new()
  102. .with_run_criteria(FixedTimestep::step(0.9))
  103. .with_system(food_spawner.system()),
  104. )
  105. .add_event::<GrowthEvent>()
  106. .run();
  107. }
  108. fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
  109. commands.spawn_bundle(OrthographicCameraBundle::new_2d());
  110. commands.insert_resource(Materials {
  111. head_material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()),
  112. segment_material: materials.add(Color::rgb(0.3, 0.3, 0.3).into()),
  113. food_material: materials.add(Color::rgb(1.0, 0.0, 1.0).into()),
  114. });
  115. }
  116. fn food_spawner(mut commands: Commands, materials: Res<Materials>) {
  117. commands
  118. .spawn_bundle(SpriteBundle {
  119. material: materials.food_material.clone(),
  120. ..Default::default()
  121. })
  122. .insert(Food)
  123. .insert(Position {
  124. x: (random::<f32>() * ARENA_WIDTH as f32) as i32,
  125. y: (random::<f32>() * ARENA_HEIGHT as f32) as i32,
  126. })
  127. .insert(Size::square(0.8));
  128. }
  129. fn spawn_segment(
  130. mut commands: Commands,
  131. material: &Handle<ColorMaterial>,
  132. position: Position,
  133. ) -> Entity {
  134. commands
  135. .spawn_bundle(SpriteBundle {
  136. material: material.clone(),
  137. ..Default::default()
  138. })
  139. .insert(SnakeSegment)
  140. .insert(position)
  141. .insert(Size::square(0.65))
  142. .id()
  143. }
  144. fn spawn_snake(
  145. mut commands: Commands,
  146. materials: Res<Materials>,
  147. mut segments: ResMut<SnakeSegments>,
  148. ) {
  149. segments.segments = vec![
  150. commands
  151. .spawn_bundle(SpriteBundle {
  152. material: materials.head_material.clone(),
  153. sprite: Sprite::new(Vec2::new(10.0, 10.0)),
  154. ..Default::default()
  155. })
  156. .insert(SnakeHead {
  157. direction: Direction::Up,
  158. })
  159. .insert(Position { x: 3, y: 3 })
  160. .insert(Size::square(0.8))
  161. .id(),
  162. spawn_segment(
  163. commands,
  164. &materials.segment_material,
  165. Position { x: 3, y: 2 },
  166. ),
  167. ]
  168. }
  169. fn snake_movement_input(keyboard_input: Res<Input<KeyCode>>, mut heads: Query<&mut SnakeHead>) {
  170. if let Some(mut head) = heads.iter_mut().next() {
  171. let dir = if keyboard_input.pressed(KeyCode::Left) {
  172. Direction::Left
  173. } else if keyboard_input.pressed(KeyCode::Down) {
  174. Direction::Down
  175. } else if keyboard_input.pressed(KeyCode::Up) {
  176. Direction::Up
  177. } else if keyboard_input.pressed(KeyCode::Right) {
  178. Direction::Right
  179. } else {
  180. head.direction
  181. };
  182. if dir != head.direction.opposite() {
  183. head.direction = dir;
  184. }
  185. }
  186. }
  187. fn snake_movement(
  188. segments: ResMut<SnakeSegments>,
  189. mut heads: Query<(Entity, &SnakeHead)>,
  190. mut positions: Query<&mut Position>,
  191. ) {
  192. if let Some((head_entity, head)) = heads.iter_mut().next() {
  193. let segment_positions = segments
  194. .segments
  195. .iter()
  196. .map(|e| *positions.get_mut(*e).unwrap())
  197. .collect::<Vec<Position>>();
  198. let mut head_pos = positions.get_mut(head_entity).unwrap();
  199. match &head.direction {
  200. Direction::Left => head_pos.x -= 1,
  201. Direction::Right => head_pos.x += 1,
  202. Direction::Up => head_pos.y += 1,
  203. Direction::Down => head_pos.y -= 1,
  204. }
  205. segment_positions
  206. .iter()
  207. .zip(segments.segments.iter().skip(1))
  208. .for_each(|(pos, segment)| {
  209. *positions.get_mut(*segment).unwrap() = *pos;
  210. });
  211. }
  212. }
  213. fn snake_eating(
  214. mut commands: Commands,
  215. mut growth_writer: EventWriter<GrowthEvent>,
  216. food_positions: Query<(Entity, &Position), With<Food>>,
  217. head_positions: Query<&Position, With<SnakeHead>>,
  218. ) {
  219. for head_pos in head_positions.iter() {
  220. for (ent, food_pos) in food_positions.iter() {
  221. if food_pos == head_pos {
  222. commands.entity(ent).despawn();
  223. growth_writer.send(GrowthEvent);
  224. }
  225. }
  226. }
  227. }
  228. fn size_scaling(windows: Res<Windows>, mut q: Query<(&Size, &mut Sprite)>) {
  229. let window = windows.get_primary().unwrap();
  230. for (size, mut sprite) in q.iter_mut() {
  231. sprite.size = Vec2::new(
  232. size.width / ARENA_WIDTH as f32 * window.width() as f32,
  233. size.height / ARENA_HEIGHT as f32 * window.height() as f32,
  234. );
  235. }
  236. }
  237. fn position_translation(windows: Res<Windows>, mut q: Query<(&Position, &mut Transform)>) {
  238. fn convert(pos: f32, bound_window: f32, bound_game: f32) -> f32 {
  239. let tile_size = bound_window / bound_game;
  240. pos / bound_game * bound_window - (bound_window / 2.0) + (tile_size / 2.0)
  241. }
  242. let window = windows.get_primary().unwrap();
  243. for (pos, mut tf) in q.iter_mut() {
  244. tf.translation = Vec3::new(
  245. convert(pos.x as f32, window.width() as f32, ARENA_WIDTH as f32),
  246. convert(pos.y as f32, window.height() as f32, ARENA_HEIGHT as f32),
  247. 0.0,
  248. )
  249. }
  250. }