use bevy::{ core::FixedTimestep, prelude::*, render::{camera::Camera, render_graph::base::camera::CAMERA_3D}, }; use rand::Rng; const STAGE: &str = "game"; #[derive(Clone, PartialEq, Debug)] enum GameState { Playing, GameOver, } fn main() { App::build() .add_resource(Msaa { samples: 4 }) .init_resource::() .add_plugins(DefaultPlugins) .add_resource(State::new(GameState::Playing)) .add_startup_system(setup_cameras.system()) .add_stage_after(stage::UPDATE, STAGE, StateStage::::default()) .on_state_enter(STAGE, GameState::Playing, setup.system()) .on_state_update(STAGE, GameState::Playing, move_player.system()) .on_state_update(STAGE, GameState::Playing, focus_camera.system()) .on_state_update(STAGE, GameState::Playing, rotate_bonus.system()) .on_state_update(STAGE, GameState::Playing, scoreboard_system.system()) .on_state_exit(STAGE, GameState::Playing, teardown.system()) .on_state_enter(STAGE, GameState::GameOver, display_score.system()) .on_state_update(STAGE, GameState::GameOver, gameover_keyboard.system()) .on_state_exit(STAGE, GameState::GameOver, teardown.system()) .add_stage_after( stage::UPDATE, "bonus_update", SystemStage::parallel() .with_run_criteria(FixedTimestep::step(5.0)) .with_system(spawn_bonus.system()), ) .run(); } struct Cell { height: f32, } #[derive(Default)] struct Player { entity: Option, i: usize, j: usize, } #[derive(Default)] struct Bonus { entity: Option, i: usize, j: usize, handle: Handle, } #[derive(Default)] struct Game { board: Vec>, player: Player, bonus: Bonus, score: i32, cake_eaten: u32, camera_should_focus: Vec3, camera_is_focus: Vec3, } const BOARD_SIZE_I: usize = 14; const BOARD_SIZE_J: usize = 21; const RESET_FOCUS: [f32; 3] = [ BOARD_SIZE_I as f32 / 2.0, 0.0, BOARD_SIZE_J as f32 / 2.0 - 0.5, ]; fn setup_cameras(commands: &mut Commands, mut game: ResMut) { game.camera_should_focus = Vec3::from(RESET_FOCUS); game.camera_is_focus = game.camera_should_focus; commands .spawn(Camera3dBundle { transform: Transform::from_xyz( -(BOARD_SIZE_I as f32 / 2.0), 2.0 * BOARD_SIZE_J as f32 / 3.0, BOARD_SIZE_J as f32 / 2.0 - 0.5, ) .looking_at(game.camera_is_focus, Vec3::unit_y()), ..Default::default() }) .spawn(CameraUiBundle::default()); } fn setup(commands: &mut Commands, asset_server: Res, mut game: ResMut) { // reset the game state game.cake_eaten = 0; game.score = 0; game.player.i = BOARD_SIZE_I / 2; game.player.j = BOARD_SIZE_J / 2; commands.spawn(LightBundle { transform: Transform::from_xyz(4.0, 5.0, 4.0), ..Default::default() }); // spawn the game board let cell_scene = asset_server.load("models/AlienCake/tile.glb#Scene0"); game.board = (0..BOARD_SIZE_J) .map(|j| { (0..BOARD_SIZE_I) .map(|i| { let height = rand::thread_rng().gen_range(-0.1..0.1); commands .spawn(( Transform::from_xyz(i as f32, height - 0.2, j as f32), GlobalTransform::default(), )) .with_children(|cell| { cell.spawn_scene(cell_scene.clone()); }); Cell { height } }) .collect() }) .collect(); // spawn the game character game.player.entity = commands .spawn(( Transform { translation: Vec3::new( game.player.i as f32, game.board[game.player.j][game.player.i].height, game.player.j as f32, ), rotation: Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2), ..Default::default() }, GlobalTransform::default(), )) .with_children(|cell| { cell.spawn_scene(asset_server.load("models/AlienCake/alien.glb#Scene0")); }) .current_entity(); // load the scene for the cake game.bonus.handle = asset_server.load("models/AlienCake/cakeBirthday.glb#Scene0"); // scoreboard commands.spawn(TextBundle { text: Text::with_section( "Score:", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 40.0, color: Color::rgb(0.5, 0.5, 1.0), }, Default::default(), ), style: Style { position_type: PositionType::Absolute, position: Rect { top: Val::Px(5.0), left: Val::Px(5.0), ..Default::default() }, ..Default::default() }, ..Default::default() }); } // remove all entities that are not a camera fn teardown(commands: &mut Commands, entities: Query>) { for entity in entities.iter() { commands.despawn_recursive(entity); } } // control the game character fn move_player( commands: &mut Commands, keyboard_input: Res>, mut game: ResMut, mut transforms: Query<&mut Transform>, ) { let mut moved = false; let mut rotation = 0.0; if keyboard_input.just_pressed(KeyCode::Up) { if game.player.i < BOARD_SIZE_I - 1 { game.player.i += 1; } rotation = -std::f32::consts::FRAC_PI_2; moved = true; } if keyboard_input.just_pressed(KeyCode::Down) { if game.player.i > 0 { game.player.i -= 1; } rotation = std::f32::consts::FRAC_PI_2; moved = true; } if keyboard_input.just_pressed(KeyCode::Right) { if game.player.j < BOARD_SIZE_J - 1 { game.player.j += 1; } rotation = std::f32::consts::PI; moved = true; } if keyboard_input.just_pressed(KeyCode::Left) { if game.player.j > 0 { game.player.j -= 1; } rotation = 0.0; moved = true; } // move on the board if moved { *transforms.get_mut(game.player.entity.unwrap()).unwrap() = Transform { translation: Vec3::new( game.player.i as f32, game.board[game.player.j][game.player.i].height, game.player.j as f32, ), rotation: Quat::from_rotation_y(rotation), ..Default::default() }; } // eat the cake! if let Some(entity) = game.bonus.entity { if game.player.i == game.bonus.i && game.player.j == game.bonus.j { game.score += 2; game.cake_eaten += 1; commands.despawn_recursive(entity); game.bonus.entity = None; } } } // change the focus of the camera fn focus_camera( time: Res