website/app/src/main.rs
2026-09-14 18:19:58 +02:00

173 lines
5.1 KiB
Rust

use avian2d::prelude::*;
use bevy::{
input::common_conditions::input_just_pressed,
prelude::*,
window::{PrimaryWindow, WindowResized},
};
fn main() {
App::new()
.insert_resource(ClearColor(Color::srgb_u8(26, 26, 46)))
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
fit_canvas_to_parent: true,
prevent_default_event_handling: false,
canvas: Some("#bevy-canvas".into()),
..default()
}),
..default()
}),
PhysicsPlugins::default(),
))
.insert_resource(Time::<Physics>::default().with_relative_speed(5.0))
.add_systems(Startup, setup)
.add_systems(
Update,
(
update_screen_edges,
spawn_ball.run_if(input_just_pressed(MouseButton::Left)),
),
)
.run();
}
#[derive(Component, Clone, Copy)]
enum ScreenEdge {
Top,
Bottom,
Left,
Right,
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
window: Query<&Window, With<PrimaryWindow>>,
) {
commands.spawn(Camera2d);
if let Ok(window) = window.single() {
spawn_screen_edges(&mut commands, &mut meshes, &mut materials, window.size());
}
}
#[derive(Component, Clone, Copy)]
struct Ball;
fn spawn_ball(
mouse_button: Res<ButtonInput<MouseButton>>,
window: Single<&Window, With<PrimaryWindow>>,
camera_query: Single<(&Camera, &GlobalTransform)>,
time: Res<Time>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
const CIRCLE_RADIUS: f32 = 5.0;
let (camera, camera_transform) = *camera_query;
if mouse_button.just_pressed(MouseButton::Left)
&& let Some(cursor_position) = window.cursor_position()
&& let Ok(world_position) = camera.viewport_to_world_2d(camera_transform, cursor_position)
{
commands.spawn((
RigidBody::Dynamic,
Collider::circle(CIRCLE_RADIUS),
Restitution::PERFECTLY_ELASTIC.with_combine_rule(CoefficientCombine::Max),
Mesh2d(meshes.add(Circle::new(CIRCLE_RADIUS))),
MeshMaterial2d(materials.add(Color::hsl(time.elapsed_secs().sin() * 360.0, 1.0, 0.5))),
Transform::from_xyz(world_position.x, world_position.y, 0.0),
Ball,
));
}
}
fn spawn_screen_edges(
commands: &mut Commands,
meshes: &mut Assets<Mesh>,
materials: &mut Assets<ColorMaterial>,
size: Vec2,
) {
const EDGE_COLOR: Color = Color::srgb_u8(255, 255, 255);
let material = materials.add(EDGE_COLOR);
for edge in [
ScreenEdge::Top,
ScreenEdge::Bottom,
ScreenEdge::Left,
ScreenEdge::Right,
] {
let (collider, transform, rect_size) = edge_shape(edge, size);
commands.spawn((
RigidBody::Static,
collider,
transform,
edge,
Mesh2d(meshes.add(Rectangle::from_size(rect_size))),
MeshMaterial2d(material.clone()),
));
}
}
fn edge_shape(edge: ScreenEdge, size: Vec2) -> (Collider, Transform, Vec2) {
const EDGE_THICKNESS: f32 = 5.0;
let half = size / 2.0;
let thickness = EDGE_THICKNESS;
match edge {
ScreenEdge::Top => {
let rect = Vec2::new(size.x, thickness);
(
Collider::rectangle(rect.x, rect.y),
Transform::from_xyz(0.0, half.y - thickness / 2.0, 0.0),
rect,
)
}
ScreenEdge::Bottom => {
let rect = Vec2::new(size.x, thickness);
(
Collider::rectangle(rect.x, rect.y),
Transform::from_xyz(0.0, -half.y + thickness / 2.0, 0.0),
rect,
)
}
ScreenEdge::Left => {
let rect = Vec2::new(thickness, size.y);
(
Collider::rectangle(rect.x, rect.y),
Transform::from_xyz(-half.x + thickness / 2.0, 0.0, 0.0),
rect,
)
}
ScreenEdge::Right => {
let rect = Vec2::new(thickness, size.y);
(
Collider::rectangle(rect.x, rect.y),
Transform::from_xyz(half.x - thickness / 2.0, 0.0, 0.0),
rect,
)
}
}
}
fn update_screen_edges(
mut resized: PopulatedMessageReader<WindowResized>,
mut edges: Query<(&ScreenEdge, &mut Collider, &mut Transform, &Mesh2d)>,
mut meshes: ResMut<Assets<Mesh>>,
) {
for event in resized.read() {
let size = Vec2::new(event.width, event.height);
for (edge, mut collider, mut transform, mesh2d) in &mut edges {
let (new_collider, new_transform, rect_size) = edge_shape(*edge, size);
*collider = new_collider;
*transform = new_transform;
if let Some(mut mesh) = meshes.get_mut(&mesh2d.0) {
*mesh = Rectangle::from_size(rect_size).into();
}
}
}
}