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.
689 lines
22 KiB
Rust
689 lines
22 KiB
Rust
#![allow(dead_code)]
|
|
#![allow(clippy::type_complexity)]
|
|
#![allow(clippy::too_many_arguments)]
|
|
|
|
mod mono;
|
|
|
|
use games::*;
|
|
use mono::*;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(BaseGamePlugin)
|
|
.add_plugins(MonologueAssetsPlugin)
|
|
.add_event::<DialogEvent>()
|
|
.init_state::<DialogState>()
|
|
.insert_resource(ClearColor(WHITE.into()))
|
|
.add_systems(
|
|
Startup,
|
|
(
|
|
init_trees,
|
|
init_ui,
|
|
init_debug_ui,
|
|
load_monologues,
|
|
position_camera.after(setup_camera),
|
|
),
|
|
)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
// Start a dialog if none are running
|
|
start_dialog
|
|
.run_if(in_state(DialogState::None))
|
|
.run_if(on_event::<Pointer<Click>>),
|
|
// Close the dialog box if it is idle (not choosing)
|
|
end_dialog
|
|
.run_if(in_state(DialogState::Idle))
|
|
.run_if(on_event::<Pointer<Click>>),
|
|
spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),
|
|
dialog_engine.run_if(on_event::<DialogEvent>),
|
|
mouse_wheel_scroll.run_if(on_event::<MouseWheel>),
|
|
auto_scroll.run_if(any_component_added::<DialogOption>),
|
|
dialog_box_visibility.run_if(state_changed::<DialogState>),
|
|
monologue_asset_tooltip
|
|
.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
|
|
scale_window.run_if(on_event::<WindowResized>),
|
|
),
|
|
)
|
|
.add_observer(add_dialog_option)
|
|
.add_observer(add_tree_monologue)
|
|
.add_observer(remove_tree_monologue)
|
|
.add_observer(hide_monologue_preview)
|
|
.add_observer(populate_tree)
|
|
.run();
|
|
}
|
|
|
|
/// Tree marker component
|
|
#[derive(Component)]
|
|
struct Tree;
|
|
|
|
#[derive(Component, PartialEq, Clone)]
|
|
struct TreeMonologue(Handle<Monologue>);
|
|
|
|
/// Initialize the trees, currently placeholders
|
|
/// Trees are 2d cards in a 3d world for flexibility
|
|
/// Might move fully 2d if the art style allows it
|
|
fn init_trees(mut ambient_light: ResMut<AmbientLight>) {
|
|
// Global light
|
|
ambient_light.brightness = 500.0;
|
|
}
|
|
|
|
/// Dialog box marker component
|
|
#[derive(Component)]
|
|
struct DialogBox;
|
|
|
|
/// Initialize the UI which consists soley of a dialog box (for now?)
|
|
fn init_ui(mut commands: Commands) {
|
|
commands
|
|
.spawn((
|
|
DialogBox,
|
|
BackgroundColor(BLACK.with_alpha(0.9).into()),
|
|
DialogState::Ongoing,
|
|
Node {
|
|
align_self: AlignSelf::Start,
|
|
justify_self: JustifySelf::Center,
|
|
width: Val::Percent(98.0),
|
|
max_height: Val::Percent(50.0),
|
|
align_items: AlignItems::Center,
|
|
margin: UiRect::all(Val::Percent(1.0)),
|
|
padding: UiRect::all(Val::Percent(1.0)),
|
|
flex_direction: FlexDirection::Column,
|
|
// Scroll on the Y axis
|
|
overflow: Overflow::scroll_y(),
|
|
..default()
|
|
},
|
|
))
|
|
.observe(hover_dialog_box_over)
|
|
.observe(hover_dialog_box_out);
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct MonologuesContainer;
|
|
|
|
#[derive(Component)]
|
|
struct MonologuesList;
|
|
|
|
#[derive(Component)]
|
|
struct MonologuePreview;
|
|
|
|
/// Panel for selecting which monologue tree to spawn
|
|
/// TODO: When monologue is loaded, add a button for it in this
|
|
/// TODO: When mouse over button, preview it in the "MonologuePreview" box
|
|
fn init_debug_ui(mut commands: Commands) {
|
|
commands
|
|
.spawn((
|
|
Node {
|
|
height: Val::Percent(90.0),
|
|
align_self: AlignSelf::Center,
|
|
justify_self: JustifySelf::Start,
|
|
..default()
|
|
},
|
|
MonologuesContainer,
|
|
GlobalZIndex(i32::MAX - 1),
|
|
DebuggingState::On,
|
|
))
|
|
.with_children(|parent| {
|
|
parent.spawn((
|
|
Node {
|
|
height: Val::Percent(100.0),
|
|
flex_direction: FlexDirection::Column,
|
|
padding: UiRect::all(Val::Px(10.0)),
|
|
..default()
|
|
},
|
|
BackgroundColor(PINK.with_alpha(0.9).into()),
|
|
MonologuesList,
|
|
));
|
|
parent.spawn((
|
|
Node {
|
|
height: Val::Percent(100.0),
|
|
flex_direction: FlexDirection::Column,
|
|
padding: UiRect::all(Val::Px(10.0)),
|
|
..default()
|
|
},
|
|
BackgroundColor(ORANGE.with_alpha(0.9).into()),
|
|
MonologuePreview,
|
|
));
|
|
});
|
|
}
|
|
|
|
fn hover_dialog_box_over(
|
|
trigger: Trigger<Pointer<Over>>,
|
|
mut query: Query<&mut BackgroundColor, With<DialogBox>>,
|
|
) {
|
|
if let Ok(mut bg) = query.get_mut(trigger.target()) {
|
|
bg.0.set_alpha(0.95);
|
|
}
|
|
}
|
|
|
|
fn hover_dialog_box_out(
|
|
trigger: Trigger<Pointer<Out>>,
|
|
mut query: Query<&mut BackgroundColor, With<DialogBox>>,
|
|
) {
|
|
if let Ok(mut bg) = query.get_mut(trigger.target()) {
|
|
bg.0.set_alpha(0.9);
|
|
}
|
|
}
|
|
|
|
/// On startup move the camera to a suitable position
|
|
/// This should be mostly static for the entire game
|
|
fn position_camera(mut query: Query<&mut Transform, (With<Camera>, With<Camera3d>)>) {
|
|
use std::f32::consts::PI;
|
|
|
|
query.iter_mut().for_each(|mut t| {
|
|
*t = Transform::from_xyz(0.0, 100.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y);
|
|
t.rotate_y(-PI * 0.5)
|
|
})
|
|
}
|
|
|
|
/// Allows user to scroll (with mouse whell) through old dialog
|
|
fn mouse_wheel_scroll(
|
|
mut events: EventReader<MouseWheel>,
|
|
mut scroll_position: Query<&mut ScrollPosition>,
|
|
) {
|
|
events.read().for_each(|MouseWheel { unit, y, .. }| {
|
|
let offset_y = match unit {
|
|
MouseScrollUnit::Line => 10.0 * y,
|
|
MouseScrollUnit::Pixel => 1.0 * y,
|
|
};
|
|
scroll_position
|
|
.iter_mut()
|
|
.for_each(|mut pos| pos.offset_y += offset_y);
|
|
});
|
|
}
|
|
|
|
/// Automatically scrolls dialog when a new batch of options are added
|
|
fn auto_scroll(
|
|
added: Query<Entity, Added<DialogOption>>,
|
|
mut scroll_positions: Query<&mut ScrollPosition>,
|
|
) {
|
|
debug_assert!(
|
|
!added.is_empty(),
|
|
"Should only scroll when dialog options are added"
|
|
);
|
|
|
|
scroll_positions.iter_mut().for_each(|mut sp| {
|
|
sp.offset_y = f32::MAX;
|
|
});
|
|
}
|
|
|
|
/// A possible line of dialog the user can choose
|
|
#[derive(Component)]
|
|
struct DialogOption;
|
|
|
|
/// A line of dialog which has been chosen, so is permanent
|
|
#[derive(Component)]
|
|
struct DialogLine;
|
|
|
|
/// Events that drive the dialog engine
|
|
#[derive(Event, PartialEq)]
|
|
enum DialogEvent {
|
|
Start(Entity, Handle<Monologue>),
|
|
NextBatch,
|
|
End,
|
|
}
|
|
|
|
/// State tracking if we are actively "doing a monologue"
|
|
#[derive(States, Debug, Hash, Eq, PartialEq, Clone, Component, Default)]
|
|
enum DialogState {
|
|
// Dialog is running and being interacted with
|
|
Ongoing,
|
|
// There is no more dialog so it's just sitting there
|
|
Idle,
|
|
// Dialog box is not visible
|
|
#[default]
|
|
None,
|
|
}
|
|
|
|
/// Start dialog, will expand later with handle to monologue asset
|
|
fn start_dialog(
|
|
mut click_events: EventReader<Pointer<Click>>,
|
|
mut dialog_events: EventWriter<DialogEvent>,
|
|
query: Query<&TreeMonologue, With<Tree>>,
|
|
) {
|
|
click_events.read().for_each(|event| {
|
|
debug!("Click event detected");
|
|
if let Ok(TreeMonologue(handle)) = query.get(event.target) {
|
|
debug!("Tree Monologue received, sending start dialog event");
|
|
dialog_events.write(DialogEvent::Start(event.target, handle.clone()));
|
|
dialog_events.write(DialogEvent::NextBatch);
|
|
}
|
|
})
|
|
}
|
|
|
|
/// When dialog is complete and you click away from the dialog box, close it out
|
|
fn end_dialog(
|
|
mut click_events: EventReader<Pointer<Click>>,
|
|
mut dialog_events: EventWriter<DialogEvent>,
|
|
query: Query<Entity, Or<(With<TreeMonologue>, With<DialogBox>, With<DialogOption>)>>,
|
|
) {
|
|
click_events.read().for_each(|event| {
|
|
debug!("Click even triggered end of dialog: {:?}", event.target);
|
|
if !query.contains(event.target) {
|
|
dialog_events.write(DialogEvent::End);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// System which puts DialogOptions into the DialogBox
|
|
fn dialog_engine(
|
|
// React to dialog events
|
|
mut events: EventReader<DialogEvent>,
|
|
// Reference to DialogBox
|
|
dialog_box: Single<Entity, With<DialogBox>>,
|
|
// EntityCommands for Dialog Box
|
|
mut commands: Commands,
|
|
// Handle to "active" monologue
|
|
mut handle: Local<Handle<Monologue>>,
|
|
// Track active entity as well as the monologue
|
|
mut tree_entity: Local<Option<Entity>>,
|
|
// Index into "active" monologue
|
|
mut idx: Local<usize>,
|
|
// Inform the rest of the game what state of dialog we are in
|
|
mut next_state: ResMut<NextState<DialogState>>,
|
|
// Monologue assets for obvious reasons
|
|
monologues: Res<Assets<Monologue>>,
|
|
// Dialog lines to despawn them at the end/start of a dialog
|
|
lines: Query<Entity, With<DialogLine>>,
|
|
) {
|
|
debug_assert!(
|
|
!events.is_empty(),
|
|
"Dialog engine is triggered by Dialog Events"
|
|
);
|
|
|
|
events.read().for_each(|event| {
|
|
match event {
|
|
DialogEvent::Start(e, h) => {
|
|
debug!("Dialog start: {:?}", h);
|
|
|
|
// Set state to "Active"
|
|
next_state.set(DialogState::Ongoing);
|
|
|
|
// Copy monologue asset into local
|
|
*handle = h.clone();
|
|
*tree_entity = Some(*e);
|
|
}
|
|
DialogEvent::NextBatch => {
|
|
debug!("Dialog batch");
|
|
|
|
commands.entity(*dialog_box).with_children(|parent| {
|
|
// Fetch this monologue from the assets
|
|
if let Some(monologue) = monologues.get(handle.clone().id()) {
|
|
// Fetch this batch of options
|
|
if let Some(options) = monologue.get(*idx) {
|
|
// Spawn the dialog options in the dialog box
|
|
options.iter().for_each(|option| {
|
|
parent.spawn((
|
|
Text::new(option.clone()),
|
|
DialogOption,
|
|
TextLayout::new(JustifyText::Left, LineBreak::NoWrap),
|
|
));
|
|
});
|
|
*idx += 1;
|
|
} else {
|
|
// Set the dialog state to "Idle"
|
|
next_state.set(DialogState::Idle);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
DialogEvent::End => {
|
|
debug!("Dialog ended");
|
|
|
|
// Remove the TreeMonologue component from the tree we just "spoke to"
|
|
commands
|
|
.entity(tree_entity.unwrap())
|
|
.remove::<TreeMonologue>();
|
|
|
|
// Remove lines from dialog box
|
|
lines.iter().for_each(|e| {
|
|
commands.entity(e).despawn();
|
|
});
|
|
|
|
// Reset index into the dialog options array
|
|
*idx = 0;
|
|
|
|
// Wipe the current handle from context
|
|
*handle = Handle::default();
|
|
|
|
// Set state to "Active"
|
|
next_state.set(DialogState::None);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// When a dialog option is chosen (clicked on) we do the following:
|
|
fn choose_dialog_option(
|
|
trigger: Trigger<Pointer<Click>>,
|
|
mut dialog_events: EventWriter<DialogEvent>,
|
|
mut commands: Commands,
|
|
texts: Query<&Text>,
|
|
options: Query<Entity, With<DialogOption>>,
|
|
dialog_box: Single<Entity, With<DialogBox>>,
|
|
) {
|
|
debug!("Choosing dialog {:?}", trigger.target());
|
|
|
|
debug!("Despawning dialog options");
|
|
options.iter().for_each(|e| {
|
|
commands.entity(e).despawn();
|
|
});
|
|
|
|
debug!("Inserting dialog line");
|
|
if let Ok(t) = texts.get(trigger.target()) {
|
|
commands.entity(*dialog_box).with_children(|parent| {
|
|
parent.spawn((t.clone(), DialogLine));
|
|
});
|
|
}
|
|
|
|
// trigger the next dialog line
|
|
dialog_events.write(DialogEvent::NextBatch);
|
|
}
|
|
|
|
fn hover_dialog_option_over(
|
|
trigger: Trigger<Pointer<Over>>,
|
|
mut query: Query<(&mut TextColor, &mut BackgroundColor)>,
|
|
) {
|
|
if let Ok((mut tc, mut bg)) = query.get_mut(trigger.target()) {
|
|
*tc = TextColor(DARK_ORANGE.into());
|
|
bg.0.set_alpha(1.0);
|
|
}
|
|
}
|
|
|
|
fn hover_dialog_option_out(
|
|
trigger: Trigger<Pointer<Out>>,
|
|
mut query: Query<(&mut TextColor, &mut BackgroundColor)>,
|
|
) {
|
|
if let Ok((mut tc, mut bg)) = query.get_mut(trigger.target()) {
|
|
*tc = TextColor(ORANGE.into());
|
|
bg.0.set_alpha(0.0);
|
|
}
|
|
}
|
|
|
|
/// When a (Text) dialog option is added:
|
|
/// 1. Add the Button component
|
|
/// 2. Change the color to Orange
|
|
/// 3. Add observers for click (select) and hover (change color)
|
|
fn add_dialog_option(trigger: Trigger<OnAdd, DialogOption>, mut commands: Commands) {
|
|
commands
|
|
.entity(trigger.target())
|
|
.insert(Button)
|
|
.insert(Node {
|
|
width: Val::Percent(100.0),
|
|
..default()
|
|
})
|
|
.insert(TextLayout::new_with_justify(JustifyText::Center))
|
|
.insert(TextColor(ORANGE.into()))
|
|
.observe(choose_dialog_option)
|
|
.observe(hover_dialog_option_over)
|
|
.observe(hover_dialog_option_out);
|
|
}
|
|
|
|
fn dialog_box_visibility(
|
|
state: Res<State<DialogState>>,
|
|
mut dialog_box: Single<&mut Visibility, With<DialogBox>>,
|
|
) {
|
|
**dialog_box = if *state.get() == DialogState::None {
|
|
Visibility::Hidden
|
|
} else {
|
|
Visibility::Inherited
|
|
};
|
|
}
|
|
|
|
fn monologue_asset_tooltip(
|
|
mut over_events: EventReader<Pointer<Over>>,
|
|
mut out_events: EventReader<Pointer<Out>>,
|
|
mut tooltip: ResMut<ToolTip>,
|
|
scripts: Query<&TreeMonologue, With<Button>>,
|
|
) {
|
|
out_events
|
|
.read()
|
|
.filter_map(|Pointer { target, .. }| scripts.contains(*target).then_some(*target))
|
|
.for_each(|_| {
|
|
tooltip.remove("Script");
|
|
});
|
|
|
|
over_events
|
|
.read()
|
|
.filter_map(|Pointer { target, .. }| scripts.contains(*target).then_some(*target))
|
|
.for_each(|e| {
|
|
if let Ok(TreeMonologue(handle)) = scripts.get(e) {
|
|
match handle.path() {
|
|
Some(p) => tooltip.insert("Script", format!("{p}")),
|
|
None => tooltip.insert("Script", "???".into()),
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn add_tree_monologue(
|
|
trigger: Trigger<OnAdd, TreeMonologue>,
|
|
query: Query<&MeshMaterial3d<StandardMaterial>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
) {
|
|
// Get the affected entity's MeshMaterial3d
|
|
if let Ok(handle) = query.get(trigger.target()) {
|
|
// Get the concrete StandardMaterial
|
|
if let Some(material) = materials.get_mut(handle) {
|
|
material.base_color = WHITE.with_alpha(1.0).into();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn remove_tree_monologue(
|
|
trigger: Trigger<OnRemove, TreeMonologue>,
|
|
query: Query<&MeshMaterial3d<StandardMaterial>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
) {
|
|
// Get the affected entity's MeshMaterial3d
|
|
if let Ok(handle) = query.get(trigger.target()) {
|
|
// Get the concrete StandardMaterial
|
|
if let Some(material) = materials.get_mut(handle) {
|
|
// Make it dull
|
|
material.base_color = WHITE.with_alpha(0.9).into();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn scale_window(events: EventReader<WindowResized>, mut window: Single<&mut Window>) {
|
|
debug_assert!(!events.is_empty(), "Only scale window when resized");
|
|
|
|
let r = &mut window.resolution;
|
|
|
|
let a: f32 = match r.physical_width() as usize {
|
|
0..=640 => 0.6,
|
|
641..=1280 => 1.0,
|
|
1281..=1920 => 1.6,
|
|
1921.. => 2.0,
|
|
};
|
|
|
|
let b: f32 = match r.physical_height() as usize {
|
|
0..=360 => 0.6,
|
|
361..=720 => 1.0,
|
|
721..=1080 => 1.6,
|
|
1081.. => 2.0,
|
|
};
|
|
|
|
let n = a.min(b);
|
|
|
|
r.set_scale_factor(n);
|
|
debug!(
|
|
"Proposed scale factor: ({} -> {a} / {} -> {b}) {n}",
|
|
r.width(),
|
|
r.height(),
|
|
);
|
|
}
|
|
|
|
fn delete_tree(trigger: Trigger<Pointer<Click>>, mut commands: Commands) {
|
|
if matches!(trigger.event.button, PointerButton::Secondary) {
|
|
info!("Right Click -> Despawning {}", trigger.target());
|
|
commands.entity(trigger.target()).despawn();
|
|
}
|
|
}
|
|
|
|
fn load_monologues(server: ResMut<AssetServer>, mut loaded_folder: Local<Handle<LoadedFolder>>) {
|
|
*loaded_folder = server.load_folder("trees");
|
|
}
|
|
|
|
fn spawn_debug_buttons(
|
|
mut events: EventReader<AssetEvent<Monologue>>,
|
|
mut commands: Commands,
|
|
container: Single<Entity, (With<MonologuesList>, Without<Button>)>,
|
|
server: Res<AssetServer>,
|
|
) {
|
|
debug_assert!(
|
|
!events.is_empty(),
|
|
"Should only spawn buttons when monologues are loaded"
|
|
);
|
|
|
|
events.read().for_each(|event| {
|
|
// If this is an "Asset was loaded" event
|
|
// These are just asset events for monologues so no need to check the asset type
|
|
if let AssetEvent::LoadedWithDependencies { id } = event {
|
|
// Get the handle from the asset ID
|
|
let handle = server.get_id_handle(*id).unwrap();
|
|
// Spawn a button in the box containing buttons
|
|
commands.entity(*container).with_children(|parent| {
|
|
parent
|
|
.spawn((
|
|
Button,
|
|
Text::new(handle.path().unwrap().to_string()),
|
|
TextLayout::new(JustifyText::Left, LineBreak::WordBoundary),
|
|
TreeMonologue(handle.clone()),
|
|
MonologuesList,
|
|
BackgroundColor(PINK.into()),
|
|
))
|
|
.observe(preview_monologue)
|
|
.observe(spawn_monologue_tree)
|
|
.observe(toggle_debug_button_color_over)
|
|
.observe(toggle_debug_button_color_out);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
fn toggle_debug_button_color_over(
|
|
trigger: Trigger<Pointer<Over>>,
|
|
mut buttons: Query<&mut BackgroundColor, With<Button>>,
|
|
) {
|
|
if let Ok(mut bg) = buttons.get_mut(trigger.target()) {
|
|
bg.0 = RED.into();
|
|
}
|
|
}
|
|
|
|
fn toggle_debug_button_color_out(
|
|
trigger: Trigger<Pointer<Out>>,
|
|
mut buttons: Query<&mut BackgroundColor, With<Button>>,
|
|
) {
|
|
if let Ok(mut bg) = buttons.get_mut(trigger.target()) {
|
|
bg.0 = PINK.into();
|
|
}
|
|
}
|
|
|
|
fn preview_monologue(
|
|
trigger: Trigger<Pointer<Over>>,
|
|
container: Single<Entity, (With<MonologuePreview>, Without<Button>, Without<Text>)>,
|
|
tree_monologue: Query<&TreeMonologue, With<Button>>,
|
|
monologues: Res<Assets<Monologue>>,
|
|
mut commands: Commands,
|
|
) {
|
|
// Get the handle for this button's monologuie
|
|
if let Ok(TreeMonologue(handle)) = tree_monologue.get(trigger.target()) {
|
|
// Get the monologue data
|
|
if let Some(monologue) = monologues.get(handle) {
|
|
commands.entity(*container).despawn_related::<Children>();
|
|
|
|
// Spawn the monologue
|
|
let mut i = 0;
|
|
commands.entity(*container).with_children(|parent| {
|
|
while let Some(options) = monologue.get(i) {
|
|
parent.spawn((Text::new("---"), MonologuePreview));
|
|
for (n, item) in options.iter().enumerate() {
|
|
parent.spawn((Text::new(format!("{i}.{n}: {item}")), MonologuePreview));
|
|
}
|
|
// TODO: Just implement iter_batches or something
|
|
i += 1;
|
|
}
|
|
parent.spawn((Text::new("---"), MonologuePreview));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn spawn_monologue_tree(
|
|
trigger: Trigger<Pointer<Click>>,
|
|
tree_monologues: Query<&TreeMonologue, With<Button>>,
|
|
mut commands: Commands,
|
|
) {
|
|
let tree_monologue = tree_monologues.get(trigger.target()).unwrap();
|
|
info!("Spawning monologuing tree");
|
|
commands.spawn((Tree, tree_monologue.clone()));
|
|
}
|
|
|
|
/// When a monologuing tree is added, give it a mesh, a material, and a position in space
|
|
///
|
|
/// TODO: This can be an `on_add` hook intead, just a little more clunky
|
|
fn populate_tree(
|
|
trigger: Trigger<OnAdd, TreeMonologue>,
|
|
time: Res<Time>,
|
|
trees: Query<Entity, With<Tree>>,
|
|
server: Res<AssetServer>,
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
) {
|
|
if !trees.contains(trigger.target()) {
|
|
return
|
|
}
|
|
|
|
// Generate "random" X and Y Coordinates for this tree
|
|
// 1. Take the top 2 bytes
|
|
// 2. Interpet as u8s
|
|
// 3. Re-interpret as i8s
|
|
// 4. Cast to f32
|
|
let transform = {
|
|
let [a, b, c, d] = time.elapsed().as_secs_f32().to_be_bytes();
|
|
info!("Time bits: {a} {b} {c} {d}");
|
|
let x = c as i8 / 4;
|
|
let y = d as i8 / 4;
|
|
// Avoid mesh clipping by offsetting each on the z axis
|
|
let z = trees.iter().len() as f32;
|
|
info!("Coordiantes: {x} {y}");
|
|
Transform::from_xyz(x.into(), z, y.into()).with_scale(Vec3::splat(10.0))
|
|
};
|
|
|
|
let material = MeshMaterial3d(materials.add(StandardMaterial {
|
|
base_color_texture: Some(server.load("placeholder/tree.png")),
|
|
base_color: WHITE.into(),
|
|
alpha_mode: AlphaMode::Blend,
|
|
..default()
|
|
}));
|
|
|
|
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
|
|
|
|
info!("Fleshing out monologuing tree");
|
|
|
|
commands
|
|
.entity(trigger.target())
|
|
.insert((mesh, material, transform))
|
|
.observe(delete_tree);
|
|
}
|
|
|
|
fn hide_monologue_preview(
|
|
trigger: Trigger<Pointer<Over>>,
|
|
preview: Single<Entity, (With<MonologuePreview>, Without<Button>, Without<Text>)>,
|
|
query: Query<
|
|
Entity,
|
|
Or<(
|
|
With<MonologuePreview>,
|
|
With<MonologuesList>,
|
|
With<MonologuesContainer>,
|
|
With<Window>,
|
|
)>,
|
|
>,
|
|
mut commands: Commands,
|
|
) {
|
|
if !query.contains(trigger.target()) {
|
|
commands.entity(*preview).despawn_related::<Children>();
|
|
}
|
|
}
|