//! Example from //! with minimal changes to inject revy. //! //! This is part of the Bevy project and licensed separately from Revy under MIT & Apache-2.0. //! For details see //! //! ------------------------------------------------------------------------------------------------ //! //! Eat the cakes. Eat them all. An example 3D game. #![allow(clippy::unwrap_used)] #![allow(clippy::needless_pass_by_value)] #![allow(elided_lifetimes_in_paths)] #![allow(clippy::missing_assert_message)] #![allow(clippy::disallowed_methods)] #![allow(clippy::str_to_string)] #![allow(clippy::type_complexity)] #![allow(clippy::explicit_iter_loop)] use std::f32::consts::PI; use bevy::prelude::*; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)] enum GameState { #[default] Playing, GameOver, } #[derive(Resource)] struct BonusSpawnTimer(Timer); fn main() { App::new() .add_plugins(DefaultPlugins) // ==== Instantiating the Rerun plugin =========================================== // // This is the only modification that was applied to this example. // // This will start a Rerun Viewer in the background and stream the recording data to it. // Check out the `RecordingStreamBuilder` () // docs for other options (saving to file, connecting to a remote viewer, etc). .add_plugins({ let rec = revy::RecordingStreamBuilder::new("alien_cake_addict") .spawn() .unwrap(); revy::RerunPlugin { rec } }) // =============================================================================== .init_resource::() .insert_resource(BonusSpawnTimer(Timer::from_seconds( 5.0, TimerMode::Repeating, ))) .init_state::() .enable_state_scoped_entities::() .add_systems(Startup, setup_cameras) .add_systems(OnEnter(GameState::Playing), setup) .add_systems( Update, ( move_player, focus_camera, rotate_bonus, scoreboard_system, spawn_bonus, ) .run_if(in_state(GameState::Playing)), ) .add_systems(OnEnter(GameState::GameOver), display_score) .add_systems( Update, gameover_keyboard.run_if(in_state(GameState::GameOver)), ) .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, } #[derive(Resource, Deref, DerefMut)] struct Random(ChaCha8Rng); 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(( Camera3d::default(), 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), )); } fn setup(mut commands: Commands, asset_server: Res, mut game: ResMut) { let mut rng = if std::env::var("GITHUB_ACTIONS") == Ok("true".to_string()) { // We're seeding the PRNG here to make this example deterministic for testing purposes. // This isn't strictly required in practical use unless you need your app to be deterministic. ChaCha8Rng::seed_from_u64(19878367467713) } else { ChaCha8Rng::from_entropy() }; // 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, TimerMode::Once); commands.spawn(( StateScoped(GameState::Playing), PointLight { intensity: 2_000_000.0, shadows_enabled: true, range: 30.0, ..default() }, Transform::from_xyz(4.0, 10.0, 4.0), )); // spawn the game board let cell_scene = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/tile.glb")); game.board = (0..BOARD_SIZE_J) .map(|j| { (0..BOARD_SIZE_I) .map(|i| { let height = rng.gen_range(-0.1..0.1); commands.spawn(( StateScoped(GameState::Playing), Transform::from_xyz(i as f32, height - 0.2, j as f32), SceneRoot(cell_scene.clone()), )); Cell { height } }) .collect() }) .collect(); // spawn the game character game.player.entity = Some( commands .spawn(( StateScoped(GameState::Playing), 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() }, SceneRoot( asset_server .load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/alien.glb")), ), )) .id(), ); // load the scene for the cake game.bonus.handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/cakeBirthday.glb")); // scoreboard commands.spawn(( StateScoped(GameState::Playing), Text::new("Score:"), TextFont { font_size: 33.0, ..default() }, TextColor(Color::srgb(0.5, 0.5, 1.0)), Node { position_type: PositionType::Absolute, top: Val::Px(5.0), left: Val::Px(5.0), ..default() }, )); commands.insert_resource(Random(rng)); } // control the game character fn move_player( mut commands: Commands, keyboard_input: Res>, mut game: ResMut, mut transforms: Query<&mut Transform>, time: Res