You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1460 lines
51 KiB
Rust
1460 lines
51 KiB
Rust
use bevy::core_pipeline::{
|
|
contrast_adaptive_sharpening::ContrastAdaptiveSharpeningSettings, fxaa::Fxaa,
|
|
};
|
|
|
|
use crate::prelude::*;
|
|
|
|
pub(crate) struct Display3dPlugin;
|
|
|
|
impl Plugin for Display3dPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_plugins((
|
|
TemporalAntiAliasPlugin,
|
|
MaterialPlugin::<DissolveMaterial>::default(),
|
|
))
|
|
.init_resource::<PiecePointer>()
|
|
.init_resource::<AnimationSpeed>()
|
|
.insert_resource(Msaa::Off)
|
|
.insert_resource(AmbientLight {
|
|
color: Color::WHITE,
|
|
brightness: 100.0,
|
|
})
|
|
.insert_resource(PointLightShadowMap {
|
|
size: 512
|
|
})
|
|
.insert_resource(DirectionalLightShadowMap {
|
|
size: 512
|
|
})
|
|
.add_systems(
|
|
OnExit(GameState::Loading),
|
|
(
|
|
fix_skybox,
|
|
initialize.after(fix_skybox),
|
|
),
|
|
)
|
|
// Systems related to color and camera
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
color_grading_tweak
|
|
.run_if(resource_exists::<tweak::GameTweaks>)
|
|
.run_if(on_event::<AssetEvent<Tweaks>>()),
|
|
fog_tweak
|
|
.run_if(resource_exists::<tweak::GameTweaks>)
|
|
.run_if(on_event::<AssetEvent<Tweaks>>()),
|
|
bloom_tweak
|
|
.run_if(resource_exists::<tweak::GameTweaks>)
|
|
.run_if(on_event::<AssetEvent<Tweaks>>()),
|
|
),
|
|
)
|
|
// Piece selection
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
entity_pointer
|
|
.run_if(in_state(GameState::Play))
|
|
.run_if(in_state(DisplayState::Display3d))
|
|
// Run if in debug state on mouse events
|
|
// Or just run when the left mouse button is clicked
|
|
.run_if(
|
|
in_state(debug::DebugState::Enabled)
|
|
.and_then(on_event::<MouseMotion>())
|
|
.or_else(just_pressed(MouseButton::Left)),
|
|
)
|
|
.before(select),
|
|
select
|
|
.run_if(in_state(GameState::Play))
|
|
.run_if(in_state(DisplayState::Display3d))
|
|
.run_if(not(in_state(MenuState::On)))
|
|
.run_if(just_pressed(MouseButton::Left)),
|
|
pick_up.run_if(any_component_added::<game::Selected>()),
|
|
de_select.run_if(just_pressed(MouseButton::Right)),
|
|
put_down.run_if(any_component_removed::<game::Selected>()),
|
|
)
|
|
)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
debug_textures,
|
|
load_assets
|
|
.run_if(in_state(GameState::Loading))
|
|
.run_if(on_event::<AssetEvent<Tweaks>>()),
|
|
hydrate_camera.run_if(any_component_added::<Camera3d>()),
|
|
update_tweaks
|
|
.run_if(on_event::<AssetEvent<Tweaks>>())
|
|
.run_if(resource_exists::<tweak::GameTweaks>),
|
|
switch_sides
|
|
.run_if(in_state(GameState::Play))
|
|
.run_if(state_changed::<game::TurnState>)
|
|
.run_if(any_component_added::<Selected>()
|
|
.or_else(any_component_removed::<Selected>())
|
|
)
|
|
.run_if(should_switch_sides),
|
|
// Camera moving up when first piece is selected in the game
|
|
vantage_point
|
|
.run_if(in_state(GameState::Play))
|
|
.run_if(any_component_added::<Selected>()),
|
|
update_pieces
|
|
.run_if(resource_exists::<tweak::GameTweaks>)
|
|
.run_if(in_state(GameState::Play))
|
|
.run_if(
|
|
any_component_added::<Piece>()
|
|
.or_else(any_component_changed::<Piece>())
|
|
.or_else(any_component_added::<Side>())
|
|
.or_else(any_component_changed::<Side>())
|
|
.or_else(any_component_added::<BoardIndex>())
|
|
.or_else(any_component_changed::<BoardIndex>())
|
|
.or_else(any_component_removed::<Animating>())
|
|
.or_else(any_component_removed::<Captured>())
|
|
.or_else(any_component_removed::<Promoted>())
|
|
),
|
|
set_models
|
|
.run_if(resource_exists::<tweak::GameTweaks>)
|
|
.run_if(
|
|
any_component_changed::<DisplayModel>()
|
|
.or_else(any_component_added::<DisplayModel>())
|
|
.or_else(any_component_removed::<Animating>())
|
|
),
|
|
dissolve_animation.run_if(any_with_component::<Dissolving>),
|
|
capture_piece_start.run_if(any_component_added::<game::BeingCaptured>()),
|
|
capture_piece_end.run_if(any_component_removed::<Dissolving>()),
|
|
monitor_animations
|
|
.run_if(in_state(GameState::Play))
|
|
.run_if(any_component_changed::<AnimationPlayer>()),
|
|
set_animation_player_speed
|
|
.run_if(any_component_added::<Animating>()
|
|
.or_else(resource_changed::<AnimationSpeed>)
|
|
),
|
|
set_animation_speed
|
|
.run_if(in_state(GameState::Play))
|
|
.run_if(not(in_state(MenuState::On)))
|
|
.run_if(
|
|
just_pressed(KeyCode::Enter)
|
|
.or_else(just_pressed(MouseButton::Left))
|
|
.or_else(just_released(KeyCode::Enter))
|
|
.or_else(just_released(MouseButton::Left))
|
|
)
|
|
),
|
|
)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
move_camera.run_if(on_event::<MouseMotion>()),
|
|
mouse_zoom.run_if(on_event::<MouseWheel>()),
|
|
gizmo_system,
|
|
selected_gizmo,
|
|
moves_gizmo,
|
|
debug_selected.run_if(any_with_component::<game::Selected>),
|
|
)
|
|
.run_if(in_state(debug::DebugState::Enabled))
|
|
.run_if(in_state(GameState::Play))
|
|
.run_if(in_state(DisplayState::Display3d)),
|
|
)
|
|
.add_systems(
|
|
OnEnter(GameState::Intro),
|
|
(
|
|
color_grading_tweak.run_if(resource_exists::<tweak::GameTweaks>),
|
|
fog_tweak.run_if(resource_exists::<tweak::GameTweaks>),
|
|
bloom_tweak.run_if(resource_exists::<tweak::GameTweaks>),
|
|
set_models.run_if(resource_exists::<tweak::GameTweaks>),
|
|
update_pieces.run_if(resource_exists::<tweak::GameTweaks>).after(set_models),
|
|
),
|
|
)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
setup_dissolve_materials
|
|
.run_if(any_component_added::<Handle<StandardMaterial>>()),
|
|
)
|
|
)
|
|
.add_systems(
|
|
OnEnter(GameState::Play),
|
|
(
|
|
opening_animation
|
|
.run_if(run_once())
|
|
.run_if(in_state(DisplayState::Display3d)),
|
|
),
|
|
)
|
|
.add_systems(
|
|
OnEnter(GameState::Title),
|
|
(
|
|
fade_title_in,
|
|
fixup_shadows
|
|
)
|
|
)
|
|
.add_systems(OnExit(GameState::Title), fade_title_out)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
fade_title
|
|
.run_if(any_with_component::<Fading>),
|
|
continue_title
|
|
.run_if(in_state(GameState::Title))
|
|
.run_if(not(any_with_component::<Fading>))
|
|
.run_if(just_pressed(KeyCode::Enter).or_else(just_pressed(MouseButton::Left))),
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Component, PartialEq)]
|
|
pub(crate) struct Display3d;
|
|
|
|
#[derive(Debug, Component)]
|
|
pub(crate) struct TitleText;
|
|
|
|
#[derive(Debug, Component)]
|
|
pub(crate) struct Dissolvable {
|
|
start: f32,
|
|
duration: f32,
|
|
}
|
|
|
|
#[derive(Debug, Component)]
|
|
pub(crate) enum Dissolving {
|
|
In(f32),
|
|
Out(f32),
|
|
}
|
|
|
|
#[derive(Debug, Component)]
|
|
pub(crate) enum Fading {
|
|
In(f32),
|
|
Out(f32),
|
|
}
|
|
|
|
#[derive(Debug, Resource)]
|
|
pub(crate) struct AnimationSpeed {
|
|
pub movement: f32,
|
|
pub dissolve: f32,
|
|
}
|
|
|
|
impl Default for AnimationSpeed {
|
|
fn default() -> Self {
|
|
AnimationSpeed {
|
|
movement: 1.0,
|
|
dissolve: 1.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Resource, Clone)]
|
|
struct AssetsMap {
|
|
hitbox_shape: Handle<Mesh>,
|
|
hitbox_material: Handle<StandardMaterial>,
|
|
title_image: Handle<Image>,
|
|
}
|
|
|
|
/// Load 3d models
|
|
/// This is kind of pulling double duty.
|
|
/// Both loads the GLTF file _and_ populates the ModelMap once that is loaded.
|
|
fn load_assets(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
) {
|
|
let hitbox_shape = meshes.add(Cuboid::new(1.0, 0.1, 1.0));
|
|
let hitbox_material = materials.add(StandardMaterial {
|
|
base_color: Color::NONE,
|
|
perceptual_roughness: 0.0,
|
|
reflectance: 0.0,
|
|
alpha_mode: AlphaMode::Blend,
|
|
..default()
|
|
});
|
|
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let title_image = tweak.get_handle::<Image>("title_image").unwrap();
|
|
|
|
commands.insert_resource(AssetsMap {
|
|
hitbox_shape,
|
|
hitbox_material,
|
|
title_image,
|
|
});
|
|
}
|
|
|
|
/// Initialize the board and pieces
|
|
fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<AssetsMap>) {
|
|
debug!("Initializing root");
|
|
// Title
|
|
commands.spawn((
|
|
DisplayState::Display3d,
|
|
Display3d,
|
|
ImageBundle {
|
|
style: Style {
|
|
width: Val::Percent(75.0),
|
|
height: Val::Percent(75.0),
|
|
align_self: AlignSelf::Center,
|
|
justify_self: JustifySelf::Center,
|
|
..default()
|
|
},
|
|
background_color: Color::WHITE.with_a(0.0).into(),
|
|
image: UiImage {
|
|
texture: assets.title_image.clone(),
|
|
..default()
|
|
},
|
|
visibility: Visibility::Hidden,
|
|
..default()
|
|
},
|
|
TitleText,
|
|
));
|
|
|
|
// 3D objects root
|
|
commands
|
|
.spawn((
|
|
SpatialBundle {
|
|
..default()
|
|
},
|
|
Display3d,
|
|
DisplayState::Display3d,
|
|
))
|
|
.with_children(|parent| {
|
|
debug!("Intializing 3D Board!");
|
|
|
|
// Board
|
|
parent
|
|
.spawn((
|
|
Display3d,
|
|
DisplayState::Display3d,
|
|
game::BoardComponent,
|
|
DisplayModel("display3d_models_scenes_board"),
|
|
SceneBundle {
|
|
visibility: Visibility::Hidden,
|
|
..default()
|
|
},
|
|
Dissolvable {
|
|
start: 0.0,
|
|
duration: 12.0,
|
|
}, // Marks pieces as dissolving
|
|
));
|
|
|
|
// Hitboxes
|
|
game::tiles().for_each(|(index, tile)| {
|
|
let side = Board::side(index).expect("Spawn valid side");
|
|
|
|
parent.spawn((
|
|
DisplayState::Display3d,
|
|
Display3d,
|
|
index,
|
|
tile,
|
|
PbrBundle {
|
|
mesh: assets.hitbox_shape.clone(),
|
|
material: assets.hitbox_material.clone(),
|
|
visibility: Visibility::Hidden,
|
|
transform: Transform::from_translation(board_translation(&index)),
|
|
..default()
|
|
},
|
|
side,
|
|
game::Selectable,
|
|
));
|
|
});
|
|
|
|
// Valid move indicators
|
|
game::tiles().for_each(|(index, _)| {
|
|
let side = Board::side(index).expect("Spawn valid side");
|
|
|
|
parent.spawn((
|
|
DisplayState::Display3d,
|
|
Display3d,
|
|
DisplayModel("display3d_models_scenes_valid_move"),
|
|
index,
|
|
SceneBundle {
|
|
visibility: Visibility::Hidden,
|
|
transform: Transform::from_translation(board_translation(&index)),
|
|
..default()
|
|
},
|
|
side,
|
|
game::ValidMove,
|
|
));
|
|
});
|
|
|
|
// Pieces
|
|
let mut angle = 0.0;
|
|
board.pieces().iter().for_each(|(index, piece)| {
|
|
let side = Board::side(*index).expect("Spawn valid side");
|
|
|
|
// Rotates each piece 90 degrees offset from the previous piece
|
|
let rotation = Quat::from_rotation_y(angle);
|
|
let transform = Transform::default().with_rotation(rotation);
|
|
angle += std::f32::consts::PI / 2.0;
|
|
|
|
parent.spawn((
|
|
side,
|
|
DisplayState::Display3d,
|
|
Display3d,
|
|
*piece,
|
|
*index,
|
|
SceneBundle {
|
|
visibility: Visibility::Hidden,
|
|
transform,
|
|
..default()
|
|
},
|
|
DisplayModel(game::piece_model_key(*piece, side)),
|
|
game::Selectable,
|
|
Dissolvable {
|
|
start: 1.0,
|
|
duration: 3.0,
|
|
}, // Marks pieces as dissolving
|
|
));
|
|
});
|
|
});
|
|
}
|
|
|
|
fn hydrate_camera(
|
|
events: Query<(&Name, Entity), Added<Camera>>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
state: Res<State<game::TurnState>>,
|
|
mut players: Query<&mut AnimationPlayer>,
|
|
mut commands: Commands,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
events
|
|
.iter()
|
|
.filter(|(name, _)| name.as_str() == "GameCam")
|
|
.for_each(|(_, entity)| {
|
|
debug!("Initialize 3d camera");
|
|
let skybox_handle = tweak
|
|
.get_handle::<Image>("display3d_models_skybox_file")
|
|
.unwrap();
|
|
let skybox_brightness = tweak.get::<f32>("display3d_skybox_brightness").unwrap();
|
|
let environment_map_intensity = tweak
|
|
.get::<f32>("display3d_environment_map_light_intensity")
|
|
.unwrap();
|
|
|
|
let side = state.get().0;
|
|
|
|
debug!("Hydrating camera {:?}", entity);
|
|
// Populate the components for the camera
|
|
commands.entity(entity).insert((
|
|
DisplayState::Display3d,
|
|
Display3d,
|
|
side,
|
|
Camera3dBundle {
|
|
camera: Camera {
|
|
is_active: true,
|
|
hdr: true,
|
|
..default()
|
|
},
|
|
dither: DebandDither::Enabled,
|
|
color_grading: ColorGrading {
|
|
exposure: 1.0,
|
|
gamma: 1.0,
|
|
..default()
|
|
},
|
|
tonemapping: Tonemapping::BlenderFilmic,
|
|
..default()
|
|
},
|
|
Skybox {
|
|
image: skybox_handle.clone(),
|
|
brightness: skybox_brightness,
|
|
},
|
|
EnvironmentMapLight {
|
|
diffuse_map: skybox_handle.clone(),
|
|
specular_map: skybox_handle.clone(),
|
|
intensity: environment_map_intensity,
|
|
},
|
|
Fxaa {
|
|
enabled: true,
|
|
..default()
|
|
},
|
|
ContrastAdaptiveSharpeningSettings {
|
|
enabled: false,
|
|
..default()
|
|
},
|
|
BloomSettings {
|
|
intensity: 0.0,
|
|
..default()
|
|
},
|
|
FogSettings {
|
|
color: Color::rgba(0.25, 0.25, 0.25, 1.0),
|
|
falloff: FogFalloff::Exponential { density: 0.0 },
|
|
..default()
|
|
},
|
|
));
|
|
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
// Set it to the default position by starting the initial animation
|
|
if let Ok(mut player) = players.get_mut(entity) {
|
|
debug!("Animations: {:?}", gltf.named_animations.keys());
|
|
// GameCamIntro1, GameCamIntro2, GameCamSide1>2, GameCamSide2>1
|
|
let animation = match state.get() {
|
|
game::TurnState(game::Side::A) => gltf.named_animations.get(
|
|
tweak
|
|
.get::<String>("display3d_models_animations_intro_a")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
game::TurnState(game::Side::B) => gltf.named_animations.get(
|
|
tweak
|
|
.get::<String>("display3d_models_animations_intro_b")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
}
|
|
.expect("Camera Startup");
|
|
player.play(animation.clone()).pause();
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Update display3d tweaks in the game
|
|
/// Triggered on entering 3d state and when the tweakfile is updated.
|
|
fn update_tweaks(
|
|
mut camera_settings: Query<(&mut Skybox, &mut EnvironmentMapLight), With<Display3d>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
) {
|
|
if let Some(tweak) = tweaks.get(tweaks_file.handle.clone()) {
|
|
debug!("Updating tweaks!");
|
|
camera_settings
|
|
.iter_mut()
|
|
.for_each(|(mut skybox, mut environment_map_light)| {
|
|
skybox.brightness = tweak.get::<f32>("display3d_skybox_brightness").unwrap();
|
|
environment_map_light.intensity = tweak
|
|
.get::<f32>("display3d_environment_map_light_intensity")
|
|
.unwrap();
|
|
});
|
|
}
|
|
}
|
|
|
|
fn fix_skybox(
|
|
mut images: ResMut<Assets<Image>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
) {
|
|
let tweak = tweaks.get(tweaks_file.handle.clone()).unwrap();
|
|
let handle = tweak
|
|
.get_handle_unchecked::<Image>("display3d_models_skybox_file")
|
|
.unwrap();
|
|
let image = images.get_mut(handle).unwrap();
|
|
|
|
debug!("Loaded skybox image");
|
|
// NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture,
|
|
// so they appear as one texture. The following code reconfigures the texture as necessary.
|
|
if image.texture_descriptor.array_layer_count() == 1 {
|
|
image.reinterpret_stacked_2d_as_array(
|
|
image.texture_descriptor.size.height / image.texture_descriptor.size.width,
|
|
);
|
|
image.texture_view_descriptor = Some(TextureViewDescriptor {
|
|
dimension: Some(TextureViewDimension::Cube),
|
|
..default()
|
|
});
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Component)]
|
|
struct DisplayModel(&'static str);
|
|
|
|
fn set_models(
|
|
mut query: Query<(Entity, &mut Handle<Scene>, &DisplayModel)>,
|
|
children: Query<&Children>,
|
|
active_animation_players: Query<&AnimationPlayer, With<Animating>>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweakfile");
|
|
let assets_handle = tweak.get_handle::<Gltf>("display3d_models_assets_file").unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
|
|
query.iter_mut().for_each(|(entity, mut handle, DisplayModel(key))| {
|
|
// Check if any children are animating
|
|
if active_animation_players
|
|
.iter_many(children.iter_descendants(entity))
|
|
.count() > 0 {
|
|
debug!("Piece {:?} is animating. Skipping...", entity);
|
|
} else {
|
|
let scene = tweak.get::<String>(key).unwrap();
|
|
let new_handle = gltf.named_scenes.get(scene.as_str()).expect("Game board model");
|
|
if *new_handle != *handle {
|
|
debug!("Updating piece for {:?}", entity);
|
|
*handle = new_handle.clone();
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Given a board index returns the Vec3 location in space
|
|
fn board_translation(&BoardIndex { x, y }: &BoardIndex) -> Vec3 {
|
|
// Scale x down by 4 to account for -4..4 scaling
|
|
let x = x as i8 - 4;
|
|
// Mirror y axis because our board index is inverted...
|
|
let y = -(y as i8) + 1;
|
|
|
|
let x = if x < 0 {
|
|
x as f32 * 1.3 + 0.275 // 0.325
|
|
} else {
|
|
x as f32 * 1.3 + 1.05 // 1.0
|
|
};
|
|
|
|
let y = y as f32 * 1.3 + 0.65;
|
|
|
|
Vec3::new(x, 0.0, y)
|
|
}
|
|
|
|
fn capture_translation(side: &Side, num: usize) -> Vec3 {
|
|
debug!("Side: {:?} Num: {:?}", side, num);
|
|
let x = 5.0 - ((num % 4) as f32 * 1.3);
|
|
let y = -1.3;
|
|
let z = 4.0 + ((num / 4) as f32 * 1.3);
|
|
match side {
|
|
Side::B => Vec3::new(-x, y, z),
|
|
Side::A => Vec3::new(x, y, -z),
|
|
}
|
|
}
|
|
|
|
fn gizmo_system(mut gizmos: Gizmos) {
|
|
for y in 0..4 {
|
|
for x in 0..8 {
|
|
gizmos.cuboid(
|
|
Transform::from_translation(board_translation(&BoardIndex { x, y }))
|
|
.with_scale(Vec3::new(1.0, 0.1, 1.0)),
|
|
Color::PURPLE,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// TODO: This has bad feel, needs to be tuned
|
|
fn move_camera(
|
|
buttons: Res<ButtonInput<MouseButton>>,
|
|
mut events: EventReader<MouseMotion>,
|
|
mut camera: Query<&mut Transform, (With<Display3d>, With<Camera>)>,
|
|
) {
|
|
events.read().for_each(|MouseMotion { delta }| {
|
|
if buttons.pressed(MouseButton::Left) {
|
|
camera.iter_mut().for_each(|mut t| {
|
|
t.rotate_around(Vec3::ZERO, Quat::from_rotation_y(delta.x / 256.0));
|
|
t.rotate_around(Vec3::ZERO, Quat::from_rotation_x(delta.y / 256.0));
|
|
t.look_at(Vec3::ZERO, Vec3::Y);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
fn mouse_zoom(
|
|
mut events: EventReader<MouseWheel>,
|
|
mut camera: Query<&mut Transform, (With<Display3d>, With<Camera>)>,
|
|
) {
|
|
events.read().for_each(|MouseWheel { unit, y, .. }| {
|
|
camera.iter_mut().for_each(|mut t| {
|
|
match unit {
|
|
MouseScrollUnit::Line => {
|
|
t.translation *= 1.0 - (*y / 4.0);
|
|
}
|
|
MouseScrollUnit::Pixel => {
|
|
t.translation *= 1.0 - (*y / 64.0);
|
|
}
|
|
}
|
|
t.look_at(Vec3::ZERO, Vec3::Y);
|
|
});
|
|
});
|
|
}
|
|
|
|
// The dumbest way to achieve this goal
|
|
// Essentially we iterate over every piece and set the appropriate model and texture
|
|
fn update_pieces(
|
|
mut query: Query<(
|
|
&BoardIndex,
|
|
&mut Transform,
|
|
&mut DisplayModel,
|
|
&Side,
|
|
&Piece,
|
|
)>,
|
|
) {
|
|
query.iter_mut().for_each(
|
|
|(board_index, mut transform, mut display_model, side, piece)| {
|
|
// Set position of piece
|
|
let new_translation = board_translation(board_index);
|
|
if transform.translation != new_translation {
|
|
debug!("Updating piece transform");
|
|
transform.translation = new_translation;
|
|
}
|
|
|
|
let key = game::piece_model_key(*piece, *side);
|
|
if display_model.0 != key {
|
|
*display_model = DisplayModel(key);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
fn select(
|
|
// Query Selectable with BoardIndex
|
|
query: Query<(&BoardIndex, &Side), With<Selectable>>,
|
|
piece: Res<PiecePointer>,
|
|
selected: Query<&BoardIndex, With<game::Selected>>,
|
|
state: Res<State<game::TurnState>>,
|
|
mut selections: EventWriter<game::Selection>,
|
|
mut moves: EventWriter<Move>
|
|
) {
|
|
match *piece {
|
|
// Something is selected, so send an event saying such
|
|
// Why send the event instead of just detect the resource update?
|
|
PiecePointer(Some(e)) => {
|
|
query.get(e).iter().for_each(|(board_index, side)| {
|
|
let side_check = !selected.is_empty() || state.get().0 == **side;
|
|
|
|
if side_check {
|
|
selections.send(game::Selection(**board_index));
|
|
}
|
|
});
|
|
}
|
|
// Nothing selected, cancel the selection
|
|
PiecePointer(None) => {
|
|
selected.iter().for_each(|board_index| {
|
|
moves.send(Move {
|
|
from: *board_index,
|
|
to: Some(*board_index),
|
|
..default()
|
|
});
|
|
})
|
|
},
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Resource)]
|
|
pub(crate) struct PiecePointer(pub Option<Entity>);
|
|
|
|
/// Select tiles and pieces in 3d
|
|
/// There is a bug where we are selecting multiple entities...
|
|
/// TODO: Selectable generalize picking pieces **and** hitboxes
|
|
fn entity_pointer(
|
|
query: Query<(Entity, &Handle<Mesh>, &GlobalTransform)>,
|
|
meshes: Res<Assets<Mesh>>,
|
|
cameras: Query<(&Camera, &GlobalTransform)>,
|
|
windows: Query<&Window, With<PrimaryWindow>>,
|
|
selectable: Query<Entity, (With<game::Selectable>, With<Display3d>)>,
|
|
children: Query<&Children>,
|
|
mut pointer: ResMut<PiecePointer>,
|
|
) {
|
|
// For each window (there should be only one)
|
|
windows.iter().for_each(|window| {
|
|
// Get the cursor position
|
|
if let Some(pos) = window.cursor_position() {
|
|
// iterate over every camera
|
|
cameras.iter().for_each(|(camera, gt)| {
|
|
// Get a ray from the camera through the cursor into the world
|
|
if let Some(ray) = camera.viewport_to_world(gt, pos) {
|
|
// Iterate over every entity with a 3d scene
|
|
let selected = query
|
|
.iter()
|
|
// Transform the scene handle into mesh data
|
|
.filter_map(|(entity, handle, gt)| {
|
|
meshes.get(handle).map(|mesh| (entity, mesh, gt))
|
|
})
|
|
// Iterate over every mesh + global transform
|
|
.filter_map(|(entity, mesh, gt)| {
|
|
// If the camera -> cursor -> * ray intersects with the mesh
|
|
hit3d::intersects3d(&ray, mesh, gt).map(|hit| (entity, hit))
|
|
})
|
|
.filter_map(|(entity, hit)| {
|
|
// Find this entity in the set of selectable entities
|
|
selectable.iter().find_map(|e| {
|
|
let hit_check = {
|
|
// This entity was hit (tile hitboxes)
|
|
let primary = entity == e;
|
|
|
|
// A child was hit (pieces)
|
|
let secondary =
|
|
children.iter_descendants(e).any(|child| child == entity);
|
|
|
|
primary || secondary
|
|
};
|
|
|
|
// Return the board index of this piece
|
|
hit_check.then_some((e, hit.clone()))
|
|
})
|
|
})
|
|
// Compare the distance of all hits, choosing the closest one
|
|
.min_by(|(_, hit_a), (_, hit_b)| {
|
|
hit_a.distance.partial_cmp(&hit_b.distance).unwrap()
|
|
})
|
|
.map(|(e, _)| e);
|
|
|
|
*pointer = PiecePointer(selected)
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
fn selected_gizmo(
|
|
selected: Query<&Transform, (With<game::Selected>, With<Display3d>)>,
|
|
mut gizmos: Gizmos,
|
|
) {
|
|
selected.iter().for_each(|transform| {
|
|
gizmos.cuboid(*transform, Color::GREEN);
|
|
})
|
|
}
|
|
|
|
fn moves_gizmo(
|
|
selected: Query<&BoardIndex, (With<game::Piece>, With<game::Selected>, With<Display3d>)>,
|
|
board: Res<Board>,
|
|
mut gizmos: Gizmos,
|
|
) {
|
|
selected.iter().for_each(|idx| {
|
|
board
|
|
.valid_moves(*idx)
|
|
.iter()
|
|
.map(|i| Transform::from_translation(board_translation(i)))
|
|
.for_each(|t| gizmos.cuboid(t, Color::WHITE))
|
|
});
|
|
}
|
|
|
|
fn pick_up(
|
|
mut events: Query<
|
|
(Entity, &game::Piece),
|
|
(With<game::Piece>, With<Display3d>, Added<game::Selected>),
|
|
>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
children: Query<&Children>,
|
|
mut players: Query<(&Name, &mut AnimationPlayer)>,
|
|
clips: Res<Assets<AnimationClip>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
events.iter_mut().for_each(|(entity, piece)| {
|
|
debug!("Picking up piece");
|
|
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
debug!("Pickup animation for {:?}", entity);
|
|
children.iter_descendants(entity).for_each(|child| {
|
|
debug!(" Child: {:?}", child);
|
|
if let Ok((name, mut player)) = players.get_mut(child) {
|
|
let pickup_animation =
|
|
format!("display3d_models_animations_pick_up_{}", name.as_str(),);
|
|
debug!(
|
|
"Picking up {:?} ({:?}) {:?} {:?}",
|
|
name, entity, piece, pickup_animation
|
|
);
|
|
let pickup_handle = gltf
|
|
.named_animations
|
|
.get(
|
|
tweak
|
|
.get::<String>(pickup_animation.as_str())
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Pickup Animation");
|
|
let idle_animation = format!("display3d_models_animations_idle_{}", name.as_str());
|
|
let idle_handle = gltf
|
|
.named_animations
|
|
.get(
|
|
tweak
|
|
.get::<String>(idle_animation.as_str())
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Idle animation");
|
|
if let Some(pickup_clip) = clips.get(pickup_handle) {
|
|
if let Some(idle_clip) = clips.get(idle_handle) {
|
|
if pickup_clip.compatible_with(name) && idle_clip.compatible_with(name) {
|
|
player
|
|
.start_with_transition(
|
|
pickup_handle.clone(),
|
|
Duration::from_secs_f32(0.75),
|
|
)
|
|
.start_with_transition(
|
|
idle_handle.clone(),
|
|
Duration::from_secs_f32(1.5),
|
|
)
|
|
.set_repeat(RepeatAnimation::Forever);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
})
|
|
});
|
|
}
|
|
|
|
fn de_select(
|
|
query: Query<Entity, With<Selected>>,
|
|
mut commands: Commands,
|
|
) {
|
|
query.iter().for_each(|e| {
|
|
commands.entity(e).remove::<Selected>();
|
|
})
|
|
}
|
|
|
|
fn put_down(
|
|
mut events: RemovedComponents<game::Selected>,
|
|
mut query: Query<Entity, (With<game::Piece>, With<game::Selectable>, With<Display3d>)>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
children: Query<&Children>,
|
|
mut players: Query<(&Name, &mut AnimationPlayer)>,
|
|
clips: Res<Assets<AnimationClip>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(&tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
events.read().for_each(|entity| {
|
|
if let Ok(_) = query.get_mut(entity) {
|
|
debug!("Putting down piece");
|
|
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
children.iter_descendants(entity).for_each(|child| {
|
|
if let Ok((name, mut player)) = players.get_mut(child) {
|
|
debug!("Putting down {:?}", entity);
|
|
let putdown_animation =
|
|
format!("display3d_models_animations_put_down_{}", name.as_str());
|
|
let putdown_handle = gltf
|
|
.named_animations
|
|
.get(
|
|
tweak
|
|
.get::<String>(putdown_animation.as_str())
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("PutDown Animation");
|
|
if let Some(putdown_clip) = clips.get(putdown_handle) {
|
|
if putdown_clip.compatible_with(name) {
|
|
debug!("Compatible with put-down clip!");
|
|
player
|
|
.start_with_transition(
|
|
putdown_handle.clone(),
|
|
Duration::from_secs_f32(0.75),
|
|
)
|
|
.set_repeat(RepeatAnimation::Never);
|
|
} else {
|
|
debug!(
|
|
"Clip {:?}({:?}) not compatible with {:?}",
|
|
putdown_animation, putdown_clip, name
|
|
);
|
|
}
|
|
} else {
|
|
debug!("Clip not found");
|
|
}
|
|
} else {
|
|
debug!("Player not found");
|
|
}
|
|
})
|
|
} else {
|
|
debug!("Piece+Side not found for entity");
|
|
}
|
|
})
|
|
}
|
|
|
|
fn opening_animation(
|
|
mut players: Query<&mut AnimationPlayer, (With<Camera>, With<Display3d>)>,
|
|
mut query: Query<(Entity, &Dissolvable), Or<(With<BoardComponent>, With<Piece>)>>,
|
|
mut commands: Commands,
|
|
) {
|
|
players.iter_mut().for_each(|mut player| {
|
|
debug!("Playing intro camera animation");
|
|
player.resume()
|
|
});
|
|
query.iter_mut().for_each(|(e, d)| {
|
|
commands.entity(e).insert(Dissolving::In(d.duration));
|
|
});
|
|
}
|
|
|
|
fn set_animation_speed(
|
|
mut animation_speed: ResMut<AnimationSpeed>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
keys: Res<ButtonInput<KeyCode>>,
|
|
mouse: Res<ButtonInput<MouseButton>>,
|
|
) {
|
|
*animation_speed = if keys.just_pressed(KeyCode::Enter) || mouse.just_pressed(MouseButton::Left) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let movement = tweak.get::<f32>("animation_fast_movement_speed").unwrap();
|
|
let dissolve = tweak.get::<f32>("animation_fast_dissolve_speed").unwrap();
|
|
AnimationSpeed { movement, dissolve }
|
|
} else {
|
|
AnimationSpeed::default()
|
|
};
|
|
|
|
debug!("Set animation speeds {:?}", *animation_speed);
|
|
}
|
|
|
|
// When an animation starts, or the animation speed changes, update player speed
|
|
fn set_animation_player_speed(
|
|
mut players: Query<&mut AnimationPlayer, With<Animating>>,
|
|
animation_speed: Res<AnimationSpeed>,
|
|
) {
|
|
players.iter_mut().for_each(|mut p| {
|
|
p.set_speed(animation_speed.movement);
|
|
})
|
|
}
|
|
|
|
fn should_switch_sides(
|
|
query: Query<&Side, (With<Camera>, With<Display3d>)>,
|
|
state: Res<State<ai::PlayState>>,
|
|
) -> bool {
|
|
query.iter().all(|side| {
|
|
match state.get() {
|
|
ai::PlayState::AiBogo => match side {
|
|
Side::A => true,
|
|
Side::B => false,
|
|
}
|
|
ai::PlayState::Human => true,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn switch_sides(
|
|
mut players: Query<(&mut AnimationPlayer, &mut Side), (With<Camera>, With<Display3d>)>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
state: Res<State<game::TurnState>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
players.iter_mut().for_each(|(mut player, mut side)| {
|
|
let animation_key = match state.get() {
|
|
game::TurnState(game::Side::A) => "display3d_models_animations_turn_a",
|
|
game::TurnState(game::Side::B) => "display3d_models_animations_turn_b",
|
|
};
|
|
let animation_val = tweak.get::<String>(animation_key).unwrap();
|
|
let animation = gltf.named_animations.get(animation_val.as_str()).expect("Camera Transition Animation");
|
|
player.start_with_transition(
|
|
animation.clone(),
|
|
Duration::from_secs_f32(1.00),
|
|
);
|
|
*side = state.get().0;
|
|
});
|
|
}
|
|
|
|
fn vantage_point(
|
|
mut players: Query<&mut AnimationPlayer, (With<Camera>, With<Display3d>)>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
state: Res<State<game::TurnState>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
mut up: Local<bool>
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
players.iter_mut().for_each(|mut player| {
|
|
debug!("Getting a better view");
|
|
|
|
// play down events on state transitions
|
|
let animation_key = if !*up {
|
|
*up = true;
|
|
match state.get() {
|
|
game::TurnState(game::Side::A) => "display3d_models_animations_turn_up_a",
|
|
game::TurnState(game::Side::B) => "display3d_models_animations_turn_up_b",
|
|
}
|
|
} else {
|
|
""
|
|
};
|
|
|
|
if animation_key != "" {
|
|
let animation_val = tweak.get::<String>(animation_key).unwrap();
|
|
let animation = gltf.named_animations.get(animation_val.as_str()).expect("Camera Transition Animation");
|
|
player.start_with_transition(
|
|
animation.clone(),
|
|
Duration::from_secs_f32(1.00),
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Type expressing the extended material of standardMaterial + dissolveMaterial
|
|
type DissolveMaterial = ExtendedMaterial<StandardMaterial, DissolveExtension>;
|
|
|
|
/// Material extension for dissolving effect
|
|
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
|
|
struct DissolveExtension {
|
|
#[uniform(100)]
|
|
percentage: f32,
|
|
}
|
|
|
|
impl MaterialExtension for DissolveExtension {
|
|
fn fragment_shader() -> ShaderRef {
|
|
"shaders/dissolve.wgsl".into()
|
|
}
|
|
|
|
fn deferred_fragment_shader() -> ShaderRef {
|
|
"shaders/dissolve.wgsl".into()
|
|
}
|
|
}
|
|
|
|
/// Sets up all pieces to have an associated "dissolve" material ready for capture
|
|
fn setup_dissolve_materials(
|
|
// All entities with materials are candidates for this procedure
|
|
events: Query<(Entity, &Handle<StandardMaterial>, &Name), (With<Parent>, Added<Handle<StandardMaterial>>)>,
|
|
// Only process newly created pieces (we do not delete pieces at runtime)
|
|
query: Query<&Dissolvable>,
|
|
// Children of pieces are the actual meshes that need materials
|
|
parents: Query<&Parent>,
|
|
// Used to create DissolveMaterial
|
|
standard_materials: Res<Assets<StandardMaterial>>,
|
|
// Used to create Handle<DissolveMaterial>
|
|
mut dissolve_materials: ResMut<Assets<DissolveMaterial>>,
|
|
// Used to insert Handle<DissolveMaterial>;
|
|
mut commands: Commands,
|
|
) {
|
|
debug!("Setting up dissolve materials...");
|
|
events
|
|
.iter()
|
|
// Handle this entity (mesh)
|
|
.for_each(|(child, std_handle, name)| {
|
|
if let Some(dissolvable) = query
|
|
.iter_many(parents.iter_ancestors(child))
|
|
.next() {
|
|
|
|
debug!("Setting up dissolve material for {:?} {:?}", name, child);
|
|
|
|
// Extension we will add to existing gltf-sourced materials
|
|
let extension = DissolveExtension {
|
|
percentage: dissolvable.start,
|
|
};
|
|
// Base material we will extend for the duration of the dissolve effect
|
|
let mut base: StandardMaterial = standard_materials
|
|
.get(std_handle)
|
|
.expect("Resolve material data")
|
|
.clone();
|
|
|
|
base.alpha_mode = AlphaMode::Mask(1.0);
|
|
// base.base_color = base.base_color.clone().with_a(0.0);
|
|
|
|
let dis_handle = dissolve_materials.add(ExtendedMaterial { base, extension });
|
|
|
|
// Add the dissolve handle as a Backup(T)
|
|
commands
|
|
.entity(child)
|
|
.insert(dis_handle.clone())
|
|
.remove::<Handle<StandardMaterial>>();
|
|
}
|
|
});
|
|
}
|
|
|
|
/// When a piece is captured...
|
|
/// 1. Play a cool "captured" animation and a neat sound
|
|
fn capture_piece_start(
|
|
events: Query<(Entity, &Dissolvable), Added<game::BeingCaptured>>,
|
|
mut commands: Commands,
|
|
) {
|
|
events.iter().for_each(|(entity, dissolvable)| {
|
|
commands
|
|
.entity(entity)
|
|
.insert(Dissolving::Out(dissolvable.duration));
|
|
});
|
|
}
|
|
|
|
/// Once done fading out:
|
|
/// 1. Move piece to side of board
|
|
/// 2. Remove BeingCaptured component
|
|
/// 3. Add Captured marker
|
|
fn capture_piece_end(
|
|
mut events: RemovedComponents<Dissolving>,
|
|
mut query: Query<(Entity, &Dissolvable, &Side, &mut Transform), With<BeingCaptured>>,
|
|
mut commands: Commands,
|
|
board: Res<game::Board>,
|
|
score: Res<game::Score>,
|
|
) {
|
|
events.read().for_each(|e| {
|
|
if let Ok((entity, dissolvable, side, mut transform)) = query.get_mut(e) {
|
|
transform.translation = capture_translation(side, score.captures(!*side));
|
|
|
|
commands
|
|
.entity(entity)
|
|
.insert(Dissolving::In(dissolvable.duration))
|
|
.remove::<BeingCaptured>()
|
|
.insert(Captured { epoch: board.current_epoch() - 1 });
|
|
}
|
|
});
|
|
}
|
|
|
|
fn debug_selected(
|
|
query: Query<(Entity, &BoardIndex, &Piece, &Side), With<game::Selected>>,
|
|
mut debug_info: ResMut<debug::DebugInfo>,
|
|
) {
|
|
query.iter().for_each(|(e, bi, p, s)| {
|
|
debug_info.set(
|
|
"Active".into(),
|
|
format!(
|
|
"\n>>ID: {:?}\n>>Piece: {:?}\n>>Side: {:?}\n>>Index: {:?}",
|
|
e, p, s, bi
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
fn debug_textures(mut events: EventReader<AssetEvent<StandardMaterial>>) {
|
|
events.read().for_each(|event| {
|
|
debug!("Material Event: {:?}", event);
|
|
});
|
|
}
|
|
|
|
// Marker for entities which are currently animating
|
|
#[derive(Component, Debug)]
|
|
pub(crate) struct Animating;
|
|
|
|
fn monitor_animations(
|
|
active: Query<(Entity, &AnimationPlayer), (Changed<AnimationPlayer>, With<Animating>)>,
|
|
mut inactive: Query<
|
|
(Entity, &mut AnimationPlayer),
|
|
(Changed<AnimationPlayer>, Without<Animating>),
|
|
>,
|
|
mut commands: Commands,
|
|
) {
|
|
// Remove Animating component from players that are done
|
|
active.iter().for_each(|(entity, player)| {
|
|
if player.is_finished() {
|
|
debug!("Entity {:?} is done, removing animating marker", entity);
|
|
commands.entity(entity).remove::<Animating>();
|
|
}
|
|
});
|
|
// Set inactive entities to active
|
|
inactive.iter_mut().for_each(|(entity, mut player)| {
|
|
if !player.is_finished() && *player.animation_clip() != Handle::<AnimationClip>::default() {
|
|
debug!(
|
|
"Entity {:?} is playing {:?}, adding animating marker",
|
|
entity,
|
|
player.animation_clip()
|
|
);
|
|
commands.entity(entity).insert(Animating);
|
|
player.set_speed(1.0);
|
|
}
|
|
});
|
|
}
|
|
|
|
fn continue_title(mut next_state: ResMut<NextState<GameState>>) {
|
|
next_state.set(GameState::Play)
|
|
}
|
|
|
|
/// When a piece is tagged with `Dissolving` play the dissolve animation
|
|
/// This is done by updating the Material and referencing the `Dissolvable` tag
|
|
/// Calculating how far along the animation it should be update the material's percentage
|
|
/// Materials are on the children of the tagged entity
|
|
fn dissolve_animation(
|
|
mut query: Query<(Entity, &Dissolvable, &mut Dissolving, &mut Visibility)>,
|
|
children: Query<&Children>,
|
|
mut dissolve_materials: ResMut<Assets<DissolveMaterial>>,
|
|
object_materials: Query<(Entity, &Handle<DissolveMaterial>)>,
|
|
mut commands: Commands,
|
|
time: Res<Time>,
|
|
animation_speed: Res<AnimationSpeed>,
|
|
) {
|
|
query
|
|
.iter_mut()
|
|
.for_each(|(entity, dissolvable, mut dissolving, mut visibility)| {
|
|
debug!("Entity {:?} has Dissolving {:?}", entity, dissolving);
|
|
|
|
let percentage = match *dissolving {
|
|
Dissolving::In(mut sec) => {
|
|
// Check if seconds is below 0.0
|
|
sec = (sec - (time.delta_seconds() * animation_speed.dissolve)).max(0.0);
|
|
|
|
*dissolving = Dissolving::In(sec);
|
|
|
|
if *visibility != Visibility::Inherited {
|
|
*visibility = Visibility::Inherited
|
|
}
|
|
|
|
// Calculate the target percentage value
|
|
1.0 - (sec / dissolvable.duration)
|
|
}
|
|
Dissolving::Out(mut sec) => {
|
|
// Check if seconds is below 0.0
|
|
sec = (sec - (time.delta_seconds() * animation_speed.dissolve)).max(0.0);
|
|
|
|
*dissolving = Dissolving::Out(sec);
|
|
|
|
if sec <= 0.0 {
|
|
*visibility = Visibility::Hidden;
|
|
}
|
|
|
|
// Calculate the target percentage value
|
|
sec / dissolvable.duration
|
|
}
|
|
};
|
|
|
|
object_materials
|
|
.iter_many(children.iter_descendants(entity))
|
|
.for_each(|(_child, handle)| {
|
|
let dissolve_material = dissolve_materials
|
|
.get_mut(handle)
|
|
.expect("Get the dissolve material");
|
|
|
|
// Change the material's value to create animation
|
|
dissolve_material.extension.percentage = percentage;
|
|
});
|
|
|
|
// If animation is done, remove dissolving component
|
|
if percentage <= 0.0 || percentage >= 1.0 {
|
|
debug!(
|
|
"Removing dissolving from {:?} with percentage {:?}",
|
|
entity, percentage
|
|
);
|
|
|
|
commands.entity(entity).remove::<Dissolving>();
|
|
}
|
|
});
|
|
}
|
|
|
|
fn fixup_shadows(
|
|
mut spot_lights: Query<(&mut SpotLight, &Name)>,
|
|
mut point_lights: Query<&mut PointLight>,
|
|
mut directional_lights: Query<&mut DirectionalLight>,
|
|
) {
|
|
debug!("Fixing up shadows");
|
|
spot_lights.iter_mut().for_each(|(mut l, n)| {
|
|
if n.as_str().starts_with("Spot") {
|
|
l.shadows_enabled = true;
|
|
}
|
|
});
|
|
point_lights.iter_mut().for_each(|mut l| {
|
|
l.shadows_enabled = true;
|
|
});
|
|
directional_lights.iter_mut().for_each(|mut l| {
|
|
l.shadows_enabled = true;
|
|
});
|
|
}
|
|
|
|
fn color_grading_tweak(
|
|
mut query: Query<&mut ColorGrading>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let exposure = tweak.get::<f32>("color_grading_exposure").unwrap();
|
|
let gamma = tweak.get::<f32>("color_grading_gamma").unwrap();
|
|
let pre_saturation = tweak.get::<f32>("color_grading_pre_saturation").unwrap();
|
|
let post_saturation = tweak.get::<f32>("color_grading_post_saturation").unwrap();
|
|
|
|
query.iter_mut().for_each(|mut cg| {
|
|
*cg = ColorGrading {
|
|
exposure,
|
|
gamma,
|
|
pre_saturation,
|
|
post_saturation,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn fog_tweak(
|
|
mut query: Query<&mut FogSettings>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
query.iter_mut().for_each(|mut fog| {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
|
|
fog.falloff = FogFalloff::Exponential {
|
|
density: tweak.get::<f32>("color_fog_density").unwrap(),
|
|
};
|
|
})
|
|
}
|
|
|
|
fn bloom_tweak(
|
|
mut query: Query<&mut BloomSettings>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
query.iter_mut().for_each(|mut bloom| {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
|
|
bloom.intensity = tweak.get::<f32>("color_bloom_intensity").unwrap();
|
|
})
|
|
}
|
|
|
|
fn fade_title_in(
|
|
query: Query<Entity, With<TitleText>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
mut commands: Commands,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let seconds = tweak.get::<f32>("title_fade_in_seconds").unwrap();
|
|
|
|
query.iter().for_each(|e| {
|
|
commands.entity(e).insert(Fading::In(seconds));
|
|
})
|
|
}
|
|
|
|
fn fade_title_out(
|
|
query: Query<Entity, With<TitleText>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
mut commands: Commands,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let seconds = tweak.get::<f32>("title_fade_in_seconds").unwrap();
|
|
|
|
query.iter().for_each(|e| {
|
|
commands.entity(e).insert(Fading::Out(seconds));
|
|
})
|
|
}
|
|
|
|
fn fade_title(
|
|
mut query: Query<(Entity, &mut BackgroundColor, &mut Visibility, &Fading), With<TitleText>>,
|
|
time: Res<Time>,
|
|
mut commands: Commands,
|
|
) {
|
|
query.iter_mut().for_each(|(e, mut bgc, mut v, d)| {
|
|
if *v != Visibility::Inherited {
|
|
*v = Visibility::Inherited;
|
|
}
|
|
// Determine how much to change alpha
|
|
let step = match d {
|
|
Fading::In(duration) => {
|
|
// If we are fully saturated, remove dissolving
|
|
if bgc.0.a() >= 1.0 {
|
|
commands.entity(e).remove::<Fading>().insert(Visibility::Inherited);
|
|
}
|
|
|
|
// Delta is simple
|
|
time.delta_seconds() / duration
|
|
},
|
|
Fading::Out(duration) => {
|
|
// If we are fully saturated, remove dissolving
|
|
if bgc.0.a() <= 0.0 {
|
|
commands.entity(e).remove::<Fading>().insert(Visibility::Hidden);
|
|
}
|
|
|
|
// Negative delta because we are fading out
|
|
-(time.delta_seconds() / duration)
|
|
},
|
|
};
|
|
// Increment/decrement the alpha value
|
|
let new_a = (bgc.0.a() + step).min(1.0).max(0.0);
|
|
bgc.0.set_a(new_a);
|
|
})
|
|
}
|