#![warn(
clippy::all,
clippy::restriction,
clippy::pedantic,
clippy::nursery,
clippy::cargo,
)]
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
use bevy::{
prelude::*, render::camera::Camera, render::camera::OrthographicProjection,
render::pass::ClearColor,
};
use bevy_egui::{egui, EguiContext, EguiPlugin};
use bevy_rapier2d::prelude::*;
use nalgebra::{Point2, Vector2};
mod util;
use crate::util::*;
mod particle;
use crate::particle::*;
fn main() {
#[cfg(target_arch = "wasm32")]
console_error_panic_hook::set_once();
let mut app = App::build();
app.insert_resource(WindowDescriptor {
title: "Bevy game".to_string(),
vsync: false,
..Default::default()
})
.insert_resource(RapierConfiguration {
gravity: Vector2::zeros(),
timestep_mode: bevy_rapier2d::physics::TimestepMode::FixedTimestep, ..Default::default()
})
.insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
.add_plugins(DefaultPlugins);
#[cfg(target_arch = "wasm32")]
app.add_plugin(bevy_webgl2::WebGL2Plugin);
app.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
.add_plugin(EguiPlugin)
.init_resource::<PlayerShipMesh>()
.init_resource::<BulletMesh>()
.init_resource::<BulletMat>()
.init_resource::<UiState>()
.add_startup_system(setup_player_ship.system().before("lel1").before("lel2"))
.add_startup_system(setup_graphics.system().label("lel1"))
.add_startup_system(setup_physics.system().label("lel2"))
.add_system(hehe.system())
.add_system(manipulate_rigidbody.system().label("move_playa"))
.add_plugin(ParticlePlugin)
.add_system(shoot.system())
.add_system_to_stage(CoreStage::PostUpdate, camera_fix.system())
.add_system(change_scale.system())
.run();
}
struct UiState {
label: String,
value: f32,
}
impl Default for UiState {
fn default() -> Self {
UiState {
label: "game".to_string(),
value: 15.0,
}
}
}
#[derive(Default)]
struct PlayerShipMesh {
mesh: Handle<Mesh>,
texture: Handle<Texture>,
}
#[derive(Default)]
struct BulletMesh(Handle<Mesh>);
#[derive(Default)]
struct BulletMat(Handle<StandardMaterial>);
struct PlayerShip {
last_shot: f64,
}
fn setup_player_ship(
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut playermesh: ResMut<PlayerShipMesh>,
mut bulletmesh: ResMut<BulletMesh>,
mut bulletmat: ResMut<BulletMat>,
server: Res<AssetServer>,
) {
let mut ship = Mesh::new(bevy::render::pipeline::PrimitiveTopology::TriangleList);
let v_pos = vec![
[0.5, 3.0, 0.0],
[0.0, 2.0, 0.0],
[1.0, 2.0, 0.0],
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
];
ship.set_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
let mut v_normal = vec![[0.0, 0.0, 1.0]];
v_normal.extend_from_slice(&[[0.0, 0.0, 1.0]; 4]);
ship.set_attribute("Vertex_Normal", v_normal);
let uv = vec![[0.5, 0.5], [0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
ship.set_attribute("Vertex_Uv", uv);
let indices = vec![0, 1, 2, 1, 3, 2, 2, 3, 4];
ship.set_indices(Some(bevy::render::mesh::Indices::U32(indices)));
playermesh.mesh = meshes.add(ship);
playermesh.texture = server.load("ship.png");
bulletmesh.0 = meshes.add(Mesh::from(bevy::prelude::shape::Icosphere {
radius: 0.25,
subdivisions: 2,
}));
let bullet_mat = bevy::prelude::StandardMaterial {
base_color: Color::rgb(1.0, 1.0, 0.0),
emissive: Color::rgb(1.0, 1.0, 0.0),
..Default::default()
};
bulletmat.0 = materials.add(bullet_mat);
}
fn setup_graphics(mut commands: Commands, mut configuration: ResMut<RapierConfiguration>) {
configuration.scale = 1.0;
let mut camera = OrthographicCameraBundle::new_3d();
camera.orthographic_projection.scale = 20.0;
camera.transform = Transform::from_xyz(0.01, 0.01, 5.01).looking_at(Vec3::ZERO, Vec3::Y);
let cam = commands.spawn_bundle(camera).id();
let light = commands
.spawn_bundle(LightBundle {
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 2000.0)),
light: Light {
intensity: 100_000_00.0,
range: 6000.0,
..Default::default()
},
..Default::default()
})
.id();
commands.entity(cam).push_children(&[light]);
}
fn setup_physics(
mut commands: Commands,
playermesh: Res<PlayerShipMesh>,
particle_m: Res<ParticleMaterial>,
mut materials2: ResMut<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
time: Res<Time>,
) {
let asteroid_mesh = util::generate_random_mesh2(1024, 0.5, 15.0).unwrap().0;
let asteroid_collider = util::generate_shape_collider_from_mesh(&asteroid_mesh).unwrap();
let asteroid_rigid_body = RigidBodyBundle {
position: Vec2::new(20.0, 0.0).into(),
forces: RigidBodyForces {
gravity_scale: 0.0,
..Default::default()
},
..Default::default()
};
let asteroid_pbr = PbrBundle {
mesh: meshes.add(asteroid_mesh),
material: materials2.add(bevy::prelude::StandardMaterial {
base_color_texture: Some(playermesh.texture.clone()),
roughness: 1.0,
..Default::default()
}),
..Default::default()
};
let asteroid_collider = ColliderBundle {
shape: asteroid_collider,
material: ColliderMaterial {
restitution: 0.7,
..Default::default()
},
mass_properties: ColliderMassProps::Density(200.0),
..Default::default()
};
commands
.spawn_bundle(asteroid_rigid_body)
.insert_bundle(asteroid_collider)
.insert_bundle(asteroid_pbr)
.insert(ColliderPositionSync::Discrete);
let asteroid_mesh = util::generate_random_mesh2(256, 0.5, 15.0).unwrap().0;
let asteroid_collider = util::generate_shape_collider_from_mesh(&asteroid_mesh).unwrap();
let asteroid_rigid_body = RigidBodyBundle {
position: Vec2::new(0.0, 10.0).into(),
forces: RigidBodyForces {
gravity_scale: 0.0,
..Default::default()
},
..Default::default()
};
let asteroid_pbr = PbrBundle {
mesh: meshes.add(asteroid_mesh),
material: materials2.add(bevy::prelude::StandardMaterial {
base_color_texture: Some(playermesh.texture.clone()),
roughness: 1.0,
..Default::default()
}),
..Default::default()
};
let asteroid_collider = ColliderBundle {
shape: asteroid_collider,
material: ColliderMaterial {
restitution: 0.7,
..Default::default()
},
mass_properties: ColliderMassProps::Density(200.0),
..Default::default()
};
commands
.spawn_bundle(asteroid_rigid_body)
.insert_bundle(asteroid_collider)
.insert_bundle(asteroid_pbr)
.insert(ColliderPositionSync::Discrete);
let asteroid_mesh = util::generate_random_mesh2(128, 0.5, 15.0).unwrap().0;
let asteroid_collider = util::generate_shape_collider_from_mesh(&asteroid_mesh).unwrap();
let asteroid_rigid_body = RigidBodyBundle {
position: Vec2::new(-7.0, -7.0).into(),
forces: RigidBodyForces {
gravity_scale: 0.0,
..Default::default()
},
..Default::default()
};
let asteroid_pbr = PbrBundle {
mesh: meshes.add(asteroid_mesh),
material: materials2.add(bevy::prelude::StandardMaterial {
base_color_texture: Some(playermesh.texture.clone()),
roughness: 1.0,
..Default::default()
}),
..Default::default()
};
let asteroid_collider = ColliderBundle {
shape: asteroid_collider,
material: ColliderMaterial {
restitution: 0.7,
..Default::default()
},
mass_properties: ColliderMassProps::Density(200.0),
..Default::default()
};
commands
.spawn_bundle(asteroid_rigid_body)
.insert_bundle(asteroid_collider)
.insert_bundle(asteroid_pbr)
.insert(ColliderPositionSync::Discrete);
let rigid_body = RigidBodyBundle {
position: Vec2::new(0.0, 0.0).into(),
forces: RigidBodyForces {
gravity_scale: 0.0,
..Default::default()
},
..Default::default()
};
let mesh = meshes.get(playermesh.mesh.clone()).unwrap();
let ShipCollider = ColliderBundle {
shape: util::generate_shape_collider_from_mesh(&mesh).unwrap(),
material: ColliderMaterial {
restitution: 0.7,
..Default::default()
},
mass_properties: ColliderMassProps::Density(200.0),
..Default::default()
};
commands
.spawn_bundle(rigid_body)
.insert_bundle(ShipCollider)
.insert_bundle(PbrBundle {
mesh: playermesh.mesh.clone(),
material: materials2.add(bevy::prelude::StandardMaterial {
base_color_texture: Some(playermesh.texture.clone()),
roughness: 1.0,
..Default::default()
}),
..Default::default()
})
.insert(ColliderPositionSync::Discrete)
.insert(PlayerShip { last_shot: 0.0 });
commands
.spawn_bundle(SpriteBundle {
material: particle_m.0.clone(),
transform: Transform::from_scale(Vec3::new(0.02, 0.02, 0.1)),
..Default::default()
})
.insert(Particle { lifetime: 10.0 })
.insert(ParticleLinearSizeOverTime { change: 0.01 });
}
fn hehe(
time: Res<Time>,
positions: Query<(&RigidBodyPosition, &RigidBodyVelocity), With<PlayerShip>>,
egui_context: ResMut<EguiContext>,
mut ui_state: ResMut<UiState>,
) {
egui::Window::new("Hello").show(egui_context.ctx(), |ui| {
let e = positions.iter().next().unwrap();
ui.label(format!(
"world {:.2}: time {:.2} : vel: {:.?} : pos {:.2}",
e.1.angvel,
time.seconds_since_startup(),
e.1.linvel.norm(),
e.0.position
));
ui.add(egui::Slider::new(&mut ui_state.value, 10.0..=30.0).text("zoom"));
});
}
fn manipulate_rigidbody(
keyboard_input: Res<Input<KeyCode>>,
q_camera: Query<(&Transform, &OrthographicProjection), With<Camera>>,
mut bodies: Query<
(
&mut RigidBodyForces,
&mut RigidBodyVelocity,
&RigidBodyMassProps,
&RigidBodyPosition,
),
With<PlayerShip>,
>,
windows: Res<Windows>,
egui_context: Res<EguiContext>,
time: Res<Time>,
) {
let multipler = 600.0 * time.delta_seconds();
let (mut body_forces, mut body_vel, body_mass_prop, body_pos) = bodies.single_mut().unwrap();
if keyboard_input.pressed(KeyCode::W) {
let forc = Vec2::new(0.0, 8.0 * multipler);
body_vel.apply_impulse(
body_mass_prop,
body_pos.position.transform_vector(&forc.into()),
);
}
if keyboard_input.pressed(KeyCode::S) {
let forc = Vec2::new(0.0, -2.0 * multipler);
body_vel.apply_impulse(
body_mass_prop,
body_pos.position.transform_vector(&forc.into()),
);
}
if keyboard_input.pressed(KeyCode::D) {
let forc = Vec2::new(6.0 * multipler, 0.0);
body_vel.apply_impulse(
body_mass_prop,
body_pos.position.transform_vector(&forc.into()),
);
}
if keyboard_input.pressed(KeyCode::A) {
let forc = Vec2::new(-6.0 * multipler, 0.0);
body_vel.apply_impulse(
body_mass_prop,
body_pos.position.transform_vector(&forc.into()),
);
}
if body_vel.linvel.norm() > 10.0 {
body_vel.linvel.try_set_magnitude(10.0, 5.0);
}
let window = windows.get_primary().unwrap();
let ang_vel = body_vel.angvel;
if !egui_context.ctx().wants_pointer_input() {
if let Some(pos) = window.cursor_position() {
let (camera_transform, ortoprojection) = q_camera.single().unwrap();
let size = Vec2::new(window.width() as f32, window.height() as f32);
let current = body_pos.position.transform_vector(&Vector2::new(0.0, 1.0));
let p = (pos - size / 2.0).normalize();
let angle = nalgebra::geometry::UnitComplex::rotation_between(
¤t,
&Vector2::new(p.x, p.y),
)
.angle();
let mut thrusters_power = 16.0;
let tanh_constant = 10.0;
let desired_angle_vel_fn = |angle: f32| {
angle.signum()
* (2.0
* thrusters_power
* (1.0 / tanh_constant)
* (((angle * tanh_constant).abs()).cosh()).ln())
.sqrt()
};
let mut desired_angle_vel =
desired_angle_vel_fn(angle - ang_vel * time.delta_seconds());
let mut torque = thrusters_power * (angle * tanh_constant).tanh().abs();
println!("{:.5} {:.5}", desired_angle_vel, ang_vel);
torque *= body_mass_prop.mass();
if angle < 0.0 {
if ang_vel > desired_angle_vel {
body_forces.torque = -torque;
} else {
body_forces.torque = torque;
}
} else {
if ang_vel < desired_angle_vel {
body_forces.torque = torque;
} else {
body_forces.torque = -torque;
}
}
}
}
if keyboard_input.pressed(KeyCode::E) {
if ang_vel < 5.0 {
body_vel.apply_torque_impulse(body_mass_prop, 4.0);
}
} else if keyboard_input.pressed(KeyCode::Q) {
if ang_vel > -5.0 {
body_vel.apply_torque_impulse(body_mass_prop, -4.0);
}
}
}
fn shoot(
mut commands: Commands,
keyboard_input: Res<Input<KeyCode>>,
buttons: Res<Input<MouseButton>>,
mut player_ship: Query<(&RigidBodyVelocity, &RigidBodyPosition, &mut PlayerShip)>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
time: Res<Time>,
egui_context: Res<EguiContext>,
bulletmesh: Res<BulletMesh>,
bulletmat: Res<BulletMat>,
particle_m: Res<ParticleMaterial>,
) {
if keyboard_input.pressed(KeyCode::Space)
|| (buttons.pressed(MouseButton::Left) && !egui_context.ctx().wants_pointer_input())
{
let (velocity, position, mut player_ships) =
player_ship.single_mut().expect("Multiple players!");
if time.seconds_since_startup() - player_ships.last_shot > 0.25 {
player_ships.last_shot = time.seconds_since_startup();
let bullet_pos = position.position * Point2::new(0.5, 3.5);
let rigid_body = RigidBodyBundle {
position: bullet_pos.into(),
forces: RigidBodyForces {
gravity_scale: 0.0,
..Default::default()
},
velocity: RigidBodyVelocity {
linvel: (velocity.linvel + position.position * Vector2::new(0.0, 50.0)).into(),
angvel: 0.0,
},
..Default::default()
};
let bullet_collider = ColliderBundle {
shape: ColliderShape::ball(0.5),
material: ColliderMaterial {
restitution: 0.7,
..Default::default()
},
mass_properties: ColliderMassProps::Density(2.0),
..Default::default()
};
let bullet_mat = bevy::prelude::StandardMaterial {
base_color: Color::rgb(1.0, 1.0, 0.0),
emissive: Color::rgb(1.0, 1.0, 0.0),
..Default::default()
};
let pbr_bundle = PbrBundle {
mesh: bulletmesh.0.clone_weak(),
transform: bevy::prelude::Transform::from_translation(Vec3::new(
bullet_pos.x,
bullet_pos.y,
0.0,
)),
material: bulletmat.0.clone_weak(),
..Default::default()
};
commands
.spawn_bundle(rigid_body)
.insert_bundle(pbr_bundle)
.insert_bundle(bullet_collider)
.insert(ColliderPositionSync::Discrete);
}
}
}
fn change_scale(mut cam: Query<&mut OrthographicProjection>, ui_state: Res<UiState>) {
let mut cam = cam.single_mut().unwrap();
cam.scale = ui_state.value;
}
fn camera_fix(
mut query: QuerySet<(
Query<&mut Transform, With<Camera>>,
Query<(&Transform, &RigidBodyMassProps), With<PlayerShip>>,
)>,
) {
let (playership_transform, playership_rigidbody) =
query.q1().single().expect("Playership not found");
let offset = playership_rigidbody.local_mprops.local_com.clone();
let offset = Vec3::new(offset.x, offset.y, 0.0);
let new_pos = *playership_transform * offset;
let mut cam_transform = query.q0_mut().single_mut().expect("Camera not found");
cam_transform.translation = Vec3::new(new_pos[0], new_pos[1], 0.0) + Vec3::new(0.0, 0.0, 5.01);
}