328 lines
12 KiB
Rust
328 lines
12 KiB
Rust
// ▄████████▄ + ███ + ▄█████████ ███ +
|
|
// ███▀ ▀███ + + ███ ███▀ + ███ + +
|
|
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
|
|
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
|
|
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
|
|
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
|
|
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
|
|
// + + + ███
|
|
// + ▀████████████████████████████████████████████████████▀
|
|
//
|
|
// This module manages visual effects.
|
|
|
|
use crate::prelude::*;
|
|
use bevy::prelude::*;
|
|
use bevy_xpbd_3d::prelude::*;
|
|
use std::time::Duration;
|
|
|
|
pub struct VisualPlugin;
|
|
|
|
impl Plugin for VisualPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_systems(Startup, setup.after(menu::setup).after(hud::setup));
|
|
app.add_systems(
|
|
Startup,
|
|
spawn_effects.after(setup).after(camera::setup_camera),
|
|
);
|
|
app.add_systems(Update, spawn_effects);
|
|
app.add_systems(Update, update_fadein);
|
|
app.add_systems(Update, update_fadeout);
|
|
app.add_systems(Update, update_fade_material);
|
|
app.add_systems(Update, update_grow);
|
|
app.add_systems(Update, play_animations);
|
|
// Blackout disabled for now
|
|
//app.add_systems(Update, update_blackout);
|
|
app.add_event::<SpawnEffectEvent>();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub enum Effects {
|
|
FadeIn(Color),
|
|
FadeOut(Color),
|
|
ThrusterParticle(Position, LinearVelocity),
|
|
}
|
|
|
|
// Blackout disabled for now
|
|
//#[derive(Component)] pub struct BlackOutOverlay;
|
|
|
|
#[derive(Component)]
|
|
pub struct IsEffect;
|
|
#[derive(Component)]
|
|
pub struct FadeInSprite;
|
|
#[derive(Component)]
|
|
pub struct FadeOutSprite;
|
|
#[derive(Component)]
|
|
pub struct FadeMaterial {
|
|
pub start_time: f64,
|
|
pub duration: f64,
|
|
pub value_start: f32,
|
|
pub value_end: f32,
|
|
}
|
|
#[derive(Component)]
|
|
pub struct Grow3DObject {
|
|
pub start_time: f64,
|
|
pub duration: f64,
|
|
pub scale_start: f32,
|
|
pub scale_end: f32,
|
|
}
|
|
#[derive(Component)]
|
|
pub struct Effect {
|
|
pub class: Effects,
|
|
pub duration: f64,
|
|
pub start_time: f64,
|
|
}
|
|
#[derive(Event)]
|
|
pub struct SpawnEffectEvent {
|
|
pub class: Effects,
|
|
pub duration: f64,
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
pub struct SuitAnimation {
|
|
index: AnimationNodeIndex,
|
|
graph: Handle<AnimationGraph>,
|
|
}
|
|
|
|
pub fn setup(
|
|
settings: Res<var::Settings>,
|
|
asset_server: Res<AssetServer>,
|
|
mut commands: Commands,
|
|
mut ew_effect: EventWriter<SpawnEffectEvent>,
|
|
mut graphs: ResMut<Assets<AnimationGraph>>,
|
|
) {
|
|
if !settings.dev_mode {
|
|
ew_effect.send(SpawnEffectEvent {
|
|
class: Effects::FadeIn(Color::BLACK),
|
|
duration: 4.0,
|
|
});
|
|
}
|
|
|
|
let mut graph = AnimationGraph::new();
|
|
let index = graph.add_clip(
|
|
asset_server.load(GltfAssetLabel::Animation(0).from_asset("models/suit_v2/suit_v2.glb")),
|
|
1.0,
|
|
graph.root,
|
|
);
|
|
|
|
let graph = graphs.add(graph);
|
|
commands.insert_resource(SuitAnimation { index, graph });
|
|
|
|
// Blackout disabled for now
|
|
// commands.spawn((
|
|
// BlackOutOverlay,
|
|
// NodeBundle {
|
|
// style: Style {
|
|
// width: Val::Vw(100.0),
|
|
// height: Val::Vh(100.0),
|
|
// position_type: PositionType::Absolute,
|
|
// top: Val::Px(0.0),
|
|
// left: Val::Px(0.0),
|
|
// ..default()
|
|
// },
|
|
// background_color: Color::BLACK.into(),
|
|
// ..default()
|
|
// },
|
|
// ));
|
|
}
|
|
|
|
pub fn spawn_effects(
|
|
mut commands: Commands,
|
|
settings: Res<var::Settings>,
|
|
mut er_effect: EventReader<SpawnEffectEvent>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
asset_server: Res<AssetServer>,
|
|
time: Res<Time>,
|
|
) {
|
|
let now = time.elapsed_seconds_f64();
|
|
for effect in er_effect.read() {
|
|
match effect.class {
|
|
Effects::FadeIn(color) => {
|
|
commands.spawn((
|
|
IsEffect,
|
|
Effect {
|
|
class: effect.class.clone(),
|
|
duration: effect.duration,
|
|
start_time: now,
|
|
},
|
|
FadeInSprite,
|
|
NodeBundle {
|
|
style: style_fullscreen(),
|
|
background_color: color.into(),
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
Effects::FadeOut(color) => {
|
|
commands.spawn((
|
|
IsEffect,
|
|
Effect {
|
|
class: effect.class.clone(),
|
|
duration: effect.duration,
|
|
start_time: now,
|
|
},
|
|
FadeOutSprite,
|
|
NodeBundle {
|
|
style: style_fullscreen(),
|
|
background_color: color.with_alpha(0.0).into(),
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
Effects::ThrusterParticle(pos, v) => {
|
|
let texture = asset_server.load("textures/exhaust.png");
|
|
commands.spawn((
|
|
IsEffect,
|
|
hud::ToggleableHudElement,
|
|
RigidBody::Kinematic,
|
|
bevy::pbr::NotShadowCaster,
|
|
pos,
|
|
v,
|
|
Grow3DObject {
|
|
start_time: now,
|
|
duration: effect.duration,
|
|
scale_start: 1.0,
|
|
scale_end: 20.0,
|
|
},
|
|
FadeMaterial {
|
|
start_time: now,
|
|
duration: effect.duration,
|
|
value_start: 0.01,
|
|
value_end: 0.0,
|
|
},
|
|
world::DespawnAt(now + effect.duration),
|
|
world::DespawnOnPlayerDeath,
|
|
actor::WantsToLookAt(cmd::ID_SPECIAL_PLAYERCAM.into()),
|
|
PbrBundle {
|
|
mesh: meshes.add(Mesh::from(Rectangle::new(0.2, 0.2))),
|
|
material: materials.add(StandardMaterial {
|
|
base_color_texture: Some(texture),
|
|
// Make sure to make the particle invisible on spawn,
|
|
// since rotation from WantsToLookAt hasn't been applied yet
|
|
base_color: Color::srgba(1.0, 1.0, 1.0, 0.0),
|
|
perceptual_roughness: 1.0,
|
|
metallic: 0.5,
|
|
unlit: true,
|
|
alpha_mode: AlphaMode::Blend,
|
|
..Default::default()
|
|
}),
|
|
visibility: if settings.hud_active {
|
|
Visibility::Inherited
|
|
} else {
|
|
Visibility::Hidden
|
|
},
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn update_fadein(
|
|
mut commands: Commands,
|
|
mut q_effect: Query<(Entity, &Effect, &mut BackgroundColor), With<FadeInSprite>>,
|
|
time: Res<Time>,
|
|
) {
|
|
for (entity, effect, mut bgcolor) in &mut q_effect {
|
|
let now = time.elapsed_seconds_f64();
|
|
if effect.start_time + effect.duration < now {
|
|
commands.entity(entity).despawn();
|
|
continue;
|
|
}
|
|
let alpha = (1.3 - 1.3 * (now - effect.start_time) / effect.duration).clamp(0.0, 1.0);
|
|
bgcolor.0.set_alpha(alpha as f32);
|
|
}
|
|
}
|
|
|
|
pub fn update_fadeout(
|
|
mut commands: Commands,
|
|
mut q_effect: Query<(Entity, &Effect, &mut BackgroundColor), With<FadeOutSprite>>,
|
|
time: Res<Time>,
|
|
) {
|
|
for (entity, effect, mut bgcolor) in &mut q_effect {
|
|
let now = time.elapsed_seconds_f64();
|
|
if effect.start_time + effect.duration < now {
|
|
commands.entity(entity).despawn();
|
|
continue;
|
|
}
|
|
let alpha = ((now - effect.start_time) / effect.duration).clamp(0.0, 1.0);
|
|
bgcolor.0.set_alpha(alpha as f32);
|
|
}
|
|
}
|
|
|
|
pub fn update_fade_material(
|
|
mut commands: Commands,
|
|
mut q_object: Query<(Entity, &Handle<StandardMaterial>, &FadeMaterial)>,
|
|
time: Res<Time>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
) {
|
|
let now = time.elapsed_seconds_f64();
|
|
for (entity, material_handle, data) in q_object.iter_mut() {
|
|
let end_time = data.start_time + data.duration;
|
|
let material = materials.get_mut(material_handle).unwrap();
|
|
if now > end_time {
|
|
material.base_color.set_alpha(data.value_end);
|
|
commands.entity(entity).remove::<FadeMaterial>();
|
|
} else if now < data.start_time {
|
|
material.base_color.set_alpha(data.value_start);
|
|
} else {
|
|
let progress = ((now - data.start_time) / data.duration) as f32;
|
|
let value = data.value_start + progress * (data.value_end - data.value_start);
|
|
material.base_color.set_alpha(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn update_grow(
|
|
mut commands: Commands,
|
|
mut q_object: Query<(Entity, &mut Transform, &Grow3DObject)>,
|
|
time: Res<Time>,
|
|
) {
|
|
let now = time.elapsed_seconds_f64();
|
|
for (entity, mut trans, data) in q_object.iter_mut() {
|
|
let end_time = data.start_time + data.duration;
|
|
if now > end_time {
|
|
trans.scale = Vec3::splat(data.scale_end);
|
|
commands.entity(entity).remove::<Grow3DObject>();
|
|
} else if now < data.start_time {
|
|
trans.scale = Vec3::splat(data.scale_start);
|
|
} else {
|
|
let progress = ((now - data.start_time) / data.duration) as f32;
|
|
let scale = data.scale_start + progress * (data.scale_end - data.scale_start);
|
|
trans.scale = Vec3::splat(scale);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn play_animations(
|
|
mut commands: Commands,
|
|
mut players: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
|
|
suit_animation: Res<SuitAnimation>,
|
|
) {
|
|
for (entity, mut player) in &mut players {
|
|
let mut transitions = AnimationTransitions::new();
|
|
transitions
|
|
.play(&mut player, suit_animation.index, Duration::ZERO)
|
|
.repeat();
|
|
commands
|
|
.entity(entity)
|
|
.insert(suit_animation.graph.clone())
|
|
.insert(transitions);
|
|
//player.play(suit_ani_node_index.0).repeat();
|
|
}
|
|
}
|
|
|
|
// Blackout disabled for now
|
|
//pub fn update_blackout(
|
|
// mut q_effect: Query<&mut BackgroundColor, With<BlackOutOverlay>>,
|
|
// q_player: Query<&actor::ExperiencesGForce, With<actor::Player>>
|
|
//) {
|
|
// if let (Ok(gforce), Ok(mut bgcolor)) = (q_player.get_single(), q_effect.get_single_mut()) {
|
|
// let threshold = 0.3;
|
|
// let factor = 1.0 / (1.0 - threshold);
|
|
// let alpha = (factor * (gforce.blackout - threshold)).clamp(0.0, 1.0);
|
|
// bgcolor.0.set_alpha(alpha as f32);
|
|
// }
|
|
//}
|