//! Eat the cakes. Eat them all. An example 3D game. use std::f32::consts::PI; use bevy::{ecs::schedule::SystemSet, prelude::*}; use rand::Rng; #[derive(Clone, Eq, PartialEq, Debug, Hash)] enum GameState { Playing, GameOver, } #[derive(Resource)] struct BonusSpawnTimer(Timer); fn main() { App::new() .init_resource::() .insert_resource(BonusSpawnTimer(Timer::from_seconds(5.0, true))) .add_plugins(DefaultPlugins) .add_state(GameState::Playing) .add_startup_system(setup_cameras) .add_system_set(SystemSet::on_enter(GameState::Playing).with_system(setup)) .add_system_set( SystemSet::on_update(GameState::Playing) .with_system(move_player) .with_system(focus_camera) .with_system(rotate_bonus) .with_system(scoreboard_system) .with_system(spawn_bonus), ) .add_system_set(SystemSet::on_exit(GameState::Playing).with_system(teardown)) .add_system_set(SystemSet::on_enter(GameState::GameOver).with_system(display_score)) .add_system_set(SystemSet::on_update(GameState::GameOver).with_system(gameover_keyboard)) .add_system_set(SystemSet::on_exit(GameState::GameOver).with_system(teardown)) .add_system(bevy::window::close_on_esc) .run(); } struct Cell { height: f32, } #[derive(Default)] struct Player { entity: Option, i: usize, j: usize, move_cooldown: Timer, } #[derive(Default)] struct Bonus { entity: Option, i: usize, j: usize, handle: Handle, } #[derive(Resource, 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(mut commands: 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::Y), ..default() }); } fn setup(mut commands: 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; game.player.move_cooldown = Timer::from_seconds(0.3, false); commands.spawn(PointLightBundle { transform: Transform::from_xyz(4.0, 10.0, 4.0), point_light: PointLight { intensity: 3000.0, shadows_enabled: true, range: 30.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(SceneBundle { transform: Transform::from_xyz(i as f32, height - 0.2, j as f32), scene: cell_scene.clone(), ..default() }); Cell { height } }) .collect() }) .collect(); // spawn the game character game.player.entity = Some( commands .spawn(SceneBundle { transform: 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(-PI / 2.), ..default() }, scene: asset_server.load("models/AlienCake/alien.glb#Scene0"), ..default() }) .id(), ); // load the scene for the cake game.bonus.handle = asset_server.load("models/AlienCake/cakeBirthday.glb#Scene0"); // scoreboard commands.spawn( TextBundle::from_section( "Score:", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 40.0, color: Color::rgb(0.5, 0.5, 1.0), }, ) .with_style(Style { position_type: PositionType::Absolute, position: UiRect { top: Val::Px(5.0), left: Val::Px(5.0), ..default() }, ..default() }), ); } // remove all entities that are not a camera fn teardown(mut commands: Commands, entities: Query>) { for entity in &entities { commands.entity(entity).despawn_recursive(); } } // control the game character fn move_player( mut commands: Commands, keyboard_input: Res>, mut game: ResMut, mut transforms: Query<&mut Transform>, time: Res