brrrrbrrr

This commit is contained in:
Swrup 2021-11-28 17:53:11 +01:00
commit 250c3a510a
11 changed files with 662 additions and 0 deletions

14
Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "montociel"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.5"
bevy_rapier2d = "*"
rand = "*"
[package.metadata.android]
build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"]
target_sdk_version = 29
min_sdk_version = 16

BIN
assets/cloud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
assets/earth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Binary file not shown.

BIN
assets/montociel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

190
src/cloud.rs Normal file
View file

@ -0,0 +1,190 @@
use crate::AppState;
use crate::Materials;
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
extern crate rand;
pub struct Cloud(Vec2);
struct NewCloudTimer(Timer);
pub struct Evil;
pub struct CloudPlugin;
impl Plugin for CloudPlugin {
fn build(&self, app: &mut AppBuilder) {
app.init_resource::<NewCloudTimer>()
.add_system_set(
SystemSet::on_enter(AppState::InGame)
.with_system(cloud_belt.system())
.with_system(spawn_earth.system()),
)
.add_system_set(
SystemSet::on_update(AppState::InGame)
.with_system(cloud_kinematics.system())
.with_system(newcloud_maker.system()),
)
.add_system_set(
SystemSet::on_update(AppState::GameOver)
.with_system(cloud_kinematics.system())
.with_system(newcloud_maker.system()),
);
}
}
impl Default for NewCloudTimer {
fn default() -> Self {
NewCloudTimer(Timer::from_seconds(2., true))
}
}
fn newcloud_maker(
mut commands: Commands,
rapier_config: Res<RapierConfiguration>,
materials: Res<Materials>,
time: Res<Time>,
mut timer: ResMut<NewCloudTimer>,
) {
if timer.0.tick(time.delta()).just_finished() {
use rand::Rng;
let mut rng = rand::thread_rng();
let nb = 4;
for _ in 0..nb {
let theta = rng.gen_range(0.0..10000.) * 2. * std::f32::consts::PI / nb as f32;
let rho = 1.;
let speed = rng.gen_range(1.0..5.0);
let pos = Vec2::new(f32::cos(theta), f32::sin(theta)) * rho;
let vel = Vec2::new(pos.x, pos.y).normalize() * speed;
let is_evil = false;
spawn_cloud(&mut commands, &rapier_config, &materials, pos, vel, is_evil);
}
}
}
fn cloud_belt(
mut commands: Commands,
rapier_config: Res<RapierConfiguration>,
materials: Res<Materials>,
) {
use rand::Rng;
let mut rng = rand::thread_rng();
let nb = 10;
for i in 0..nb {
let theta = i as f32 * 2. * std::f32::consts::PI / nb as f32;
let rho = 10.;
let speed = rng.gen_range(0.0..5.0);
let pos = Vec2::new(f32::cos(theta), f32::sin(theta)) * rho;
let vel = Vec2::new(pos.x, pos.y).normalize() * speed;
let is_evil = false;
spawn_cloud(&mut commands, &rapier_config, &materials, pos, vel, is_evil);
}
}
fn spawn_earth(
mut commands: Commands,
rapier_config: Res<RapierConfiguration>,
materials: Res<Materials>,
) {
//TODO sapwn mother earth the root of all evil
let radius = 60. / rapier_config.scale;
let rigid_body = RigidBodyBundle {
body_type: RigidBodyType::Static,
position: Vec2::new(0., 0.).into(),
velocity: RigidBodyVelocity {
linvel: Vec2::new(0., 0.).into(),
angvel: 0.0,
},
..Default::default()
};
let collider = ColliderBundle {
shape: ColliderShape::ball(radius),
mass_properties: ColliderMassProps::Density(1.0),
material: ColliderMaterial {
restitution: 1.0,
..Default::default()
},
flags: (ActiveEvents::INTERSECTION_EVENTS | ActiveEvents::CONTACT_EVENTS).into(),
..Default::default()
};
commands
.spawn_bundle(rigid_body)
.insert_bundle(collider)
.insert_bundle(SpriteBundle {
material: materials.earth_material.clone(),
sprite: Sprite::new(Vec2::new(
2. * radius * rapier_config.scale,
2. * radius * rapier_config.scale,
)),
..Default::default()
})
.insert(RigidBodyPositionSync::Discrete)
.insert(Cloud(Vec2::new(0., 0.)))
.insert(Evil);
}
fn spawn_cloud(
commands: &mut Commands,
rapier_config: &Res<RapierConfiguration>,
materials: &Res<Materials>,
pos: Vec2,
vel: Vec2,
is_evil: bool,
) {
//Spawn a cloud
let radius = 15. / rapier_config.scale;
let rigid_body = RigidBodyBundle {
body_type: RigidBodyType::KinematicVelocityBased,
position: pos.into(),
velocity: RigidBodyVelocity {
linvel: vel.into(),
angvel: 0.0,
},
..Default::default()
};
let collider = ColliderBundle {
shape: ColliderShape::ball(radius),
mass_properties: ColliderMassProps::Density(1.0),
material: ColliderMaterial {
restitution: 1.0,
..Default::default()
},
flags: (ActiveEvents::INTERSECTION_EVENTS | ActiveEvents::CONTACT_EVENTS).into(),
..Default::default()
};
if is_evil {
commands
.spawn_bundle(rigid_body)
.insert_bundle(collider)
.insert_bundle(SpriteBundle {
material: materials.cloud_material.clone(),
sprite: Sprite::new(Vec2::new(
2. * radius * rapier_config.scale,
2. * radius * rapier_config.scale,
)),
..Default::default()
})
.insert(RigidBodyPositionSync::Discrete)
.insert(Cloud(vel))
.insert(Evil);
} else {
commands
.spawn_bundle(rigid_body)
.insert_bundle(collider)
.insert_bundle(SpriteBundle {
material: materials.cloud_material.clone(),
sprite: Sprite::new(Vec2::new(
2. * radius * rapier_config.scale,
2. * radius * rapier_config.scale,
)),
..Default::default()
})
.insert(RigidBodyPositionSync::Discrete)
.insert(Cloud(vel));
}
}
fn cloud_kinematics(mut velocities: Query<(&Cloud, &mut RigidBodyVelocity)>) {
for (vel, mut next_vel) in velocities.iter_mut() {
next_vel.linvel = vel.0.into();
}
}

66
src/main.rs Normal file
View file

@ -0,0 +1,66 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
mod cloud;
mod montociel;
mod score;
mod ui;
use cloud::*;
use montociel::*;
use score::*;
use ui::*;
struct Materials {
//TODO background image or smthing
montociel_material: Handle<ColorMaterial>,
cloud_material: Handle<ColorMaterial>,
earth_material: Handle<ColorMaterial>,
}
impl FromWorld for Materials {
fn from_world(world: &mut World) -> Self {
let asset_server = world.get_resource::<AssetServer>().unwrap();
let montociel_asset = asset_server.load("montociel.png");
let cloud_asset = asset_server.load("cloud.png");
let earth_asset = asset_server.load("earth.png");
let mut materials = world.get_resource_mut::<Assets<ColorMaterial>>().unwrap();
let montociel_material = materials.add(montociel_asset.into());
let cloud_material = materials.add(cloud_asset.into());
let earth_material = materials.add(earth_asset.into());
Materials {
montociel_material,
cloud_material,
earth_material,
}
}
}
fn setup(mut commands: Commands, mut rapier_config: ResMut<RapierConfiguration>) {
rapier_config.scale = 15.;
rapier_config.gravity = Vec2::new(0.0, 0.0).into();
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
commands.spawn_bundle(UiCameraBundle::default());
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum AppState {
Menu,
InGame,
GameOver,
}
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
.insert_resource(ClearColor(Color::rgb(1.0, 0.714, 0.757)))
.init_resource::<Materials>()
.add_plugin(MontocielPlugin)
.add_plugin(CloudPlugin)
.add_plugin(ScorePlugin)
.add_plugin(UIPlugin)
.add_state(AppState::Menu)
.add_system_set(SystemSet::on_enter(AppState::Menu).with_system(setup.system()))
.run();
}

214
src/montociel.rs Normal file
View file

@ -0,0 +1,214 @@
use crate::AppState;
use crate::Evil;
use crate::Materials;
use crate::Score;
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
pub struct Montociel;
pub struct MontocielPlugin;
impl Plugin for MontocielPlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_system_set(
SystemSet::on_enter(AppState::InGame).with_system(spawn_montociel.system()),
)
.add_system_set(
SystemSet::on_update(AppState::InGame)
.with_system(input_movement.system())
.with_system(montociel_aerodynamism.system())
.with_system(update_forces.system())
.with_system(clamp_velocity.system())
.with_system(cloud_collision.system()),
);
}
}
fn spawn_montociel(
mut commands: Commands,
rapier_config: ResMut<RapierConfiguration>,
materials: Res<Materials>,
) {
//Spawn Montociel
let radius = 30. / rapier_config.scale;
let rigid_body = RigidBodyBundle {
body_type: RigidBodyType::Dynamic,
position: Vec2::new(10., 10.).into(),
velocity: RigidBodyVelocity {
linvel: Vec2::new(0.0, 0.0).into(),
angvel: 0.4,
},
..Default::default()
};
let collider = ColliderBundle {
//collider_type: ColliderType::Sensor,
shape: ColliderShape::ball(radius),
mass_properties: ColliderMassProps::Density(1.0),
material: ColliderMaterial {
restitution: 0.0,
..Default::default()
},
flags: (ActiveEvents::INTERSECTION_EVENTS | ActiveEvents::CONTACT_EVENTS).into(),
..Default::default()
};
commands
.spawn_bundle(rigid_body)
.insert_bundle(collider)
.insert_bundle(SpriteBundle {
material: materials.montociel_material.clone(),
sprite: Sprite::new(Vec2::new(
2. * radius * rapier_config.scale,
2. * radius * rapier_config.scale,
)),
..Default::default()
})
.insert(RigidBodyPositionSync::Discrete)
.insert(Montociel);
}
fn input_movement(
mouse_input: Res<Input<MouseButton>>,
rapier_parameters: Res<RapierConfiguration>,
mut montociel_info: Query<(&Montociel, &mut RigidBodyVelocity, &RigidBodyPosition)>,
) {
for (_, mut velocity, pos) in montociel_info.iter_mut() {
if mouse_input.get_pressed().len() > 0 {
let x = pos.position.translation.x;
let y = pos.position.translation.y;
let theta = std::f32::consts::PI / 2.;
let mut move_delta = Vec2::new(
-x * f32::cos(theta) + y * f32::sin(theta),
-x * f32::sin(theta) - y * f32::cos(theta),
);
if move_delta != Vec2::new(0., 0.) {
// Note that the RapierConfiguration::Scale factor is also used here to transform
// the move_delta from: 'pixels/second' to 'physics_units/second'
move_delta.normalize();
move_delta /= rapier_parameters.scale;
}
// Update the velocity on the rigid_body_component,
// the bevy_rapier plugin will update the Sprite transform.
// cringeee
let power = 0.8;
let v_x = power * move_delta.x + velocity.linvel.x;
let v_y = power * move_delta.y + velocity.linvel.y;
velocity.linvel = Vec2::new(v_x, v_y).into();
}
}
}
fn montociel_aerodynamism(mut velocities: Query<&mut RigidBodyVelocity, With<Montociel>>) {
for mut velocity in velocities.iter_mut() {
let v_x = velocity.linvel.x;
let v_y = velocity.linvel.y;
let velocity_slowed_by_air = 0.95 * Vec2::new(v_x, v_y);
velocity.linvel = velocity_slowed_by_air.into();
}
}
fn update_forces(
mut rigid_bodies: Query<
(
&mut RigidBodyForces,
&RigidBodyPosition,
&RigidBodyMassProps,
),
With<Montociel>,
>,
) {
for (mut rb_forces, rb_pos, rb_mass) in rigid_bodies.iter_mut() {
let x = rb_pos.position.translation.x;
let y = rb_pos.position.translation.y;
let d2 = x * x + y * y;
let norm = f32::sqrt(d2);
let g = 100.;
let eps = 0.0001;
let nimp = -g * rb_mass.mass() * 1. / (norm + eps);
let gravity = Vec2::new(nimp * x, nimp * y).into();
rb_forces.force = gravity;
}
}
fn clamp_velocity(
mut bodies_info: Query<
&mut RigidBodyVelocity, //, With<Montociel>
>,
) {
for mut velocity in bodies_info.iter_mut() {
let v_x = velocity.linvel.x;
let v_y = velocity.linvel.y;
let magnitude = f32::sqrt(v_x * v_x + v_y * v_y);
let max_magnitude = 70.;
if magnitude > 0.01 {
let clamped_velocity =
Vec2::new(v_x, v_y) / magnitude * f32::min(magnitude, max_magnitude);
velocity.linvel = clamped_velocity.into();
}
}
}
fn cloud_collision(
mut commands: Commands,
mut score: ResMut<Score>,
mut state: ResMut<State<AppState>>,
mut montociel_info: Query<
(Entity, &mut RigidBodyVelocity, &RigidBodyPosition),
With<Montociel>,
>,
mut contact_events: EventReader<ContactEvent>,
query: Query<Entity, With<Evil>>,
rapier_config: Res<RapierConfiguration>,
) {
for contact_event in contact_events.iter() {
for (montociel_entity, mut vel, pos) in montociel_info.iter_mut() {
match contact_event {
ContactEvent::Started(collider1, collider2) => {
let entity1 = collider1.entity();
let entity2 = collider2.entity();
if entity1 != montociel_entity && entity2 != montociel_entity {
continue;
}
let entity = if entity1 == montociel_entity {
entity2
} else {
entity1
};
if query.get(entity).is_ok() {
state.set(AppState::GameOver).unwrap();
} else {
commands.entity(entity).despawn();
jump(pos, &mut vel, &rapier_config);
//increment score
score.incr();
}
}
ContactEvent::Stopped(_collider1, _collider2) => {}
}
}
}
}
fn jump(
pos: &RigidBodyPosition,
vel: &mut RigidBodyVelocity,
rapier_config: &Res<RapierConfiguration>,
) {
let x = pos.position.translation.x;
let y = pos.position.translation.y;
let theta = 3. * std::f32::consts::PI / 4.;
let mut move_delta = Vec2::new(
-x * f32::cos(theta) + y * f32::sin(theta),
-x * f32::sin(theta) - y * f32::cos(theta),
);
if move_delta != Vec2::new(0., 0.) {
move_delta.normalize();
move_delta /= rapier_config.scale;
}
let power = 70.;
let v_x = power * move_delta.x + vel.linvel.x;
let v_y = power * move_delta.y + vel.linvel.y;
vel.linvel = Vec2::new(v_x, v_y).into();
}

85
src/score.rs Normal file
View file

@ -0,0 +1,85 @@
use crate::AppState;
use bevy::prelude::*;
#[derive(Clone, Copy, Debug)]
pub struct Score {
score: u32,
}
struct ScoreUI;
impl Default for Score {
fn default() -> Self {
Score { score: 0 }
}
}
impl Score {
pub fn incr(&mut self) {
//TODO incr more for each turn
self.score += 1;
}
pub fn reset(&mut self) {
self.score = 0;
}
}
pub struct ScorePlugin;
impl Plugin for ScorePlugin {
fn build(&self, app: &mut AppBuilder) {
app.init_resource::<Score>()
.add_system_set(
SystemSet::on_enter(AppState::InGame).with_system(setup_score_ui.system()),
)
.add_system_set(
SystemSet::on_update(AppState::InGame).with_system(update_score_ui.system()),
);
}
}
fn update_score_ui(score: Res<Score>, mut query: Query<&mut Text, With<ScoreUI>>) {
for mut text in query.iter_mut() {
text.sections[1].value = format!("{:.2}", score.score);
}
}
fn setup_score_ui(
mut commands: Commands,
mut score: ResMut<Score>,
asset_server: Res<AssetServer>,
) {
//commands.spawn_bundle(UiCameraBundle::default());
score.reset();
commands
.spawn_bundle(TextBundle {
style: Style {
align_self: AlignSelf::FlexEnd,
..Default::default()
},
// Use `Text` directly
text: Text {
// Construct a `Vec` of `TextSection`s
sections: vec![
TextSection {
value: "Score: ".to_string(),
style: TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 60.0,
color: Color::WHITE,
},
},
TextSection {
value: "0".to_string(),
style: TextStyle {
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 60.0,
color: Color::GOLD,
},
},
],
..Default::default()
},
..Default::default()
})
.insert(ScoreUI);
}

93
src/ui.rs Normal file
View file

@ -0,0 +1,93 @@
use crate::AppState;
use bevy::prelude::*;
pub struct UIPlugin;
impl Plugin for UIPlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_system_set(SystemSet::on_enter(AppState::Menu).with_system(setup_button.system()))
.add_system_set(
SystemSet::on_enter(AppState::GameOver).with_system(setup_button.system()),
)
.add_system(button_system.system());
}
}
fn button_system(
//button_materials: Res<ButtonMaterials>,
mut commands: Commands,
mut interaction_query: Query<&Interaction, (Changed<Interaction>, With<Button>)>,
entities: Query<Entity, Without<bevy::render::camera::Camera>>,
button: Query<Entity, With<Button>>,
mut state: ResMut<State<AppState>>,
) {
for interaction in interaction_query.iter_mut() {
match *interaction {
Interaction::Clicked => {
match state.current() {
AppState::Menu => {
for button in button.iter() {
commands.entity(button).despawn_recursive();
}
state.set(AppState::InGame).unwrap();
}
AppState::GameOver => {
//despawn all entities
for entity in entities.iter() {
commands.entity(entity).despawn();
}
for button in button.iter() {
commands.entity(button).despawn_recursive();
}
state.set(AppState::InGame).unwrap();
}
AppState::InGame => panic!(),
}
}
Interaction::Hovered => {}
Interaction::None => {}
}
}
}
fn setup_button(
mut commands: Commands,
asset_server: Res<AssetServer>,
state: Res<State<AppState>>,
//button_materials: Res<ButtonMaterials>,
) {
let text = match state.current() {
AppState::Menu => "Play!",
AppState::InGame => panic!(),
AppState::GameOver => "Revive!",
};
commands
.spawn_bundle(ButtonBundle {
style: Style {
size: Size::new(Val::Px(150.0), Val::Px(65.0)),
// center button
margin: Rect::all(Val::Auto),
// horizontally center child text
justify_content: JustifyContent::Center,
// vertically center child text
align_items: AlignItems::Center,
..Default::default()
},
//material: button_materials.normal.clone(),
..Default::default()
})
.with_children(|parent| {
parent.spawn_bundle(TextBundle {
text: Text::with_section(
text,
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::rgb(0.9, 0.9, 0.9),
},
Default::default(),
),
..Default::default()
});
});
}