main.rs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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(
  117. mut commands: Commands,
  118. materials: Res<Materials>,
  119. ) {
  120. commands
  121. .spawn_bundle(SpriteBundle {
  122. material: materials.food_material.clone(),
  123. ..Default::default()
  124. })
  125. .insert(Food)
  126. .insert(Position {
  127. x: (random::<f32>() * ARENA_WIDTH as f32) as i32,
  128. y: (random::<f32>() * ARENA_HEIGHT as f32) as i32,
  129. })
  130. .insert(Size::square(0.8));
  131. }
  132. fn spawn_segment(
  133. mut commands: Commands,
  134. material: &Handle<ColorMaterial>,
  135. position: Position,
  136. ) -> Entity {
  137. commands
  138. .spawn_bundle(SpriteBundle {
  139. material: material.clone(),
  140. ..Default::default()
  141. })
  142. .insert(SnakeSegment)
  143. .insert(position)
  144. .insert(Size::square(0.65))
  145. .id()
  146. }
  147. fn spawn_snake(
  148. mut commands: Commands,
  149. materials: Res<Materials>,
  150. mut segments: ResMut<SnakeSegments>,
  151. ) {
  152. segments.segments = vec![
  153. commands
  154. .spawn_bundle(SpriteBundle {
  155. material: materials.head_material.clone(),
  156. sprite: Sprite::new(Vec2::new(10.0, 10.0)),
  157. ..Default::default()
  158. })
  159. .insert(SnakeHead {
  160. direction: Direction::Up,
  161. })
  162. .insert(Position { x: 3, y: 3})
  163. .insert(Size::square(0.8))
  164. .id(),
  165. spawn_segment(
  166. commands,
  167. &materials.segment_material,
  168. Position { x: 3, y: 2},
  169. ),
  170. ]
  171. }
  172. fn snake_movement_input(
  173. keyboard_input: Res<Input<KeyCode>>,
  174. mut heads: Query<&mut SnakeHead>,
  175. ) {
  176. if let Some(mut head) = heads.iter_mut().next() {
  177. let dir = if keyboard_input.pressed(KeyCode::Left) {
  178. Direction::Left
  179. } else if keyboard_input.pressed(KeyCode::Down) {
  180. Direction::Down
  181. } else if keyboard_input.pressed(KeyCode::Up) {
  182. Direction::Up
  183. } else if keyboard_input.pressed(KeyCode::Right) {
  184. Direction::Right
  185. } else {
  186. head.direction
  187. };
  188. if dir != head.direction.opposite() {
  189. head.direction = dir;
  190. }
  191. }
  192. }
  193. fn snake_movement(
  194. segments: ResMut<SnakeSegments>,
  195. mut heads: Query<(Entity, &SnakeHead)>,
  196. mut positions: Query<&mut Position>,
  197. ) {
  198. if let Some((head_entity, head)) = heads.iter_mut().next() {
  199. let segment_positions = segments
  200. .segments
  201. .iter()
  202. .map(|e| *positions.get_mut(*e).unwrap())
  203. .collect::<Vec<Position>>();
  204. let mut head_pos = positions.get_mut(head_entity).unwrap();
  205. match &head.direction {
  206. Direction::Left => head_pos.x -= 1,
  207. Direction::Right => head_pos.x += 1,
  208. Direction::Up => head_pos.y += 1,
  209. Direction::Down => head_pos.y -= 1,
  210. }
  211. segment_positions
  212. .iter()
  213. .zip(segments.segments.iter().skip(1))
  214. .for_each(|(pos, segment)| {
  215. *positions.get_mut(*segment).unwrap() = *pos;
  216. });
  217. }
  218. }
  219. fn snake_eating(
  220. mut commands: Commands,
  221. mut growth_writer: EventWriter<GrowthEvent>,
  222. food_positions: Query<(Entity, &Position), With<Food>>,
  223. head_positions: Query<&Position, With<SnakeHead>>,
  224. ) {
  225. for head_pos in head_positions.iter() {
  226. for (ent, food_pos) in food_positions.iter() {
  227. if food_pos == head_pos {
  228. commands.entity(ent).despawn();
  229. growth_writer.send(GrowthEvent);
  230. }
  231. }
  232. }
  233. }
  234. fn size_scaling(windows: Res<Windows>, mut q: Query<(&Size, &mut Sprite)>) {
  235. let window = windows.get_primary().unwrap();
  236. for (size, mut sprite) in q.iter_mut() {
  237. sprite.size = Vec2::new(
  238. size.width / ARENA_WIDTH as f32 * window.width() as f32,
  239. size.height / ARENA_HEIGHT as f32 * window.height() as f32,
  240. );
  241. }
  242. }
  243. fn position_translation(windows: Res<Windows>, mut q: Query<(&Position, &mut Transform)>) {
  244. fn convert(pos: f32, bound_window: f32, bound_game: f32) -> f32 {
  245. let tile_size = bound_window / bound_game;
  246. pos / bound_game * bound_window - (bound_window / 2.0) + (tile_size / 2.0)
  247. }
  248. let window = windows.get_primary().unwrap();
  249. for (pos, mut tf) in q.iter_mut() {
  250. tf.translation = Vec3::new(
  251. convert(pos.x as f32, window.width() as f32, ARENA_WIDTH as f32),
  252. convert(pos.y as f32, window.height() as f32, ARENA_HEIGHT as f32),
  253. 0.0,
  254. )
  255. }
  256. }