use bevy::{
input::mouse::AccumulatedMouseMotion,
prelude::*,
window::{CursorGrabMode, CursorOptions, PrimaryWindow},
};
use bevy_a5::geometry::build_grid_line_mesh;
use bevy_a5::prelude::*;
use bevy_a5::WORLD_CELL;
const PLANET_RADIUS: f64 = 100.0;
const RESOLUTION: i32 = 5;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(BevyA5Plugin)
.insert_resource(
PlanetSettings::earth().with_radius(PLANET_RADIUS),
)
.add_systems(Startup, setup)
.add_systems(Update, (camera_controller, cursor_grab))
.run();
}
#[derive(Component)]
struct CameraController {
speed: f32,
sensitivity: f32,
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut cursor_query: Query<&mut CursorOptions, With<PrimaryWindow>>,
) {
commands.spawn((
Mesh3d(meshes.add(Sphere::new(PLANET_RADIUS as f32 * 0.99))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::linear_rgb(0.05, 0.10, 0.20),
..default()
})),
Transform::default(),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 0.0, PLANET_RADIUS as f32 * 2.5)
.looking_at(Vec3::ZERO, Vec3::Y),
CameraController {
speed: 80.0,
sensitivity: 0.003,
},
));
commands.spawn((
DirectionalLight {
illuminance: 18_000.0,
shadows_enabled: false,
..default()
},
Transform::from_xyz(300.0, 500.0, 300.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let world = GeoCell::new(WORLD_CELL);
let cells = world.children(RESOLUTION).unwrap_or_default();
let grid = build_grid_line_mesh(&cells, PLANET_RADIUS);
commands.spawn((
Mesh3d(meshes.add(grid)),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.0, 1.0, 0.75),
emissive: LinearRgba::new(0.0, 1.8, 1.2, 1.0),
unlit: true,
..default()
})),
));
info!(
"A5 planet grid ready — {} cells at resolution {} on a sphere of radius {}",
cells.len(),
RESOLUTION,
PLANET_RADIUS,
);
info!("Controls: WASD to fly, Q/E down/up, Shift to boost, mouse to look (Esc releases cursor)");
if let Ok(mut cursor_opts) = cursor_query.single_mut() {
cursor_opts.grab_mode = CursorGrabMode::Locked;
cursor_opts.visible = false;
}
}
fn camera_controller(
time: Res<Time>,
keyboard: Res<ButtonInput<KeyCode>>,
mouse: Res<AccumulatedMouseMotion>,
mut query: Query<(&mut Transform, &CameraController), With<Camera3d>>,
) {
let dt = time.delta_secs();
let Ok((mut transform, controller)) = query.single_mut() else {
return;
};
let forward = Vec3::from(transform.forward());
let right = Vec3::from(transform.right());
let mut velocity = Vec3::ZERO;
if keyboard.pressed(KeyCode::KeyW) {
velocity += forward;
}
if keyboard.pressed(KeyCode::KeyS) {
velocity -= forward;
}
if keyboard.pressed(KeyCode::KeyA) {
velocity -= right;
}
if keyboard.pressed(KeyCode::KeyD) {
velocity += right;
}
if keyboard.pressed(KeyCode::KeyQ) {
velocity.y -= 1.0;
}
if keyboard.pressed(KeyCode::KeyE) {
velocity.y += 1.0;
}
if velocity != Vec3::ZERO {
velocity = velocity.normalize();
}
let boost = if keyboard.pressed(KeyCode::ShiftLeft) || keyboard.pressed(KeyCode::ShiftRight) {
4.0
} else {
1.0
};
transform.translation += velocity * controller.speed * boost * dt;
if mouse.delta != Vec2::ZERO {
let yaw = -mouse.delta.x * controller.sensitivity;
let pitch = -mouse.delta.y * controller.sensitivity;
transform.rotate_y(yaw);
transform.rotate_local_x(pitch);
}
}
fn cursor_grab(
mut cursor_query: Query<&mut CursorOptions, With<PrimaryWindow>>,
mouse: Res<ButtonInput<MouseButton>>,
key: Res<ButtonInput<KeyCode>>,
) {
let Ok(mut cursor_opts) = cursor_query.single_mut() else {
return;
};
if mouse.just_pressed(MouseButton::Left) {
cursor_opts.grab_mode = CursorGrabMode::Locked;
cursor_opts.visible = false;
}
if key.just_pressed(KeyCode::Escape) {
cursor_opts.grab_mode = CursorGrabMode::None;
cursor_opts.visible = true;
}
}