brrrrbrrr
This commit is contained in:
commit
250c3a510a
11 changed files with 662 additions and 0 deletions
14
Cargo.toml
Normal file
14
Cargo.toml
Normal 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
BIN
assets/cloud.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
assets/earth.png
Normal file
BIN
assets/earth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 245 KiB |
BIN
assets/fonts/FiraMono-Medium.ttf
Normal file
BIN
assets/fonts/FiraMono-Medium.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/FiraSans-Bold.ttf
Normal file
BIN
assets/fonts/FiraSans-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/montociel.png
Normal file
BIN
assets/montociel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
190
src/cloud.rs
Normal file
190
src/cloud.rs
Normal 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
66
src/main.rs
Normal 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
214
src/montociel.rs
Normal 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
85
src/score.rs
Normal 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
93
src/ui.rs
Normal 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()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue