Compare commits
No commits in common. '9c4b4a79b99ec53120c90e1a50f9acd17fefcb94' and 'dd73f923dff1ff4cc9138e7987a684d9c5c1266c' have entirely different histories.
9c4b4a79b9
...
dd73f923df
Binary file not shown.
@ -1,55 +0,0 @@
|
|||||||
use std::f32::consts::PI;
|
|
||||||
|
|
||||||
use games::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(BaseGamePlugin::default())
|
|
||||||
.add_systems(Startup, init_image)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_image(
|
|
||||||
mut commands: Commands,
|
|
||||||
server: Res<AssetServer>,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
||||||
mut std_materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
) {
|
|
||||||
// 2d
|
|
||||||
{
|
|
||||||
let texture = MeshMaterial2d(materials.add(ColorMaterial {
|
|
||||||
color: WHITE.into(),
|
|
||||||
alpha_mode: AlphaMode2d::Opaque,
|
|
||||||
texture: Some(server.load("flappy/bevy.png")),
|
|
||||||
..default()
|
|
||||||
}));
|
|
||||||
let mesh = Mesh2d(meshes.add(Rectangle::from_size(Vec2::splat(10.0))));
|
|
||||||
|
|
||||||
|
|
||||||
// opaque
|
|
||||||
// Each sprite should be square with the transparent parts being completely black
|
|
||||||
// The blue sprite should be on top with the white and green one behind it
|
|
||||||
commands.spawn((Name::new("2D Example"),mesh, texture));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3d
|
|
||||||
{
|
|
||||||
let material = MeshMaterial3d(std_materials.add(StandardMaterial {
|
|
||||||
base_color_texture: Some(server.load("flappy/bevy.png")),
|
|
||||||
base_color: WHITE.with_alpha(0.9).into(),
|
|
||||||
alpha_mode: AlphaMode::Blend,
|
|
||||||
..default()
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
|
|
||||||
|
|
||||||
let name = Name::new("3D example");
|
|
||||||
|
|
||||||
let t = Transform::from_xyz(0.0, 0.0, -10.0).with_rotation(Quat::from_rotation_x(PI / 2.0));
|
|
||||||
|
|
||||||
commands.spawn((name, mesh, material, t));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
use games::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(BaseGamePlugin { name: "flappy bird (with rewind)".into() })
|
|
||||||
.add_systems(Startup, init_bird)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_bird(
|
|
||||||
mut commands: Commands,
|
|
||||||
server: Res<AssetServer>,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
) {
|
|
||||||
let material = MeshMaterial3d(materials.add(StandardMaterial {
|
|
||||||
base_color_texture: Some(server.load("flappy/bevy.png")),
|
|
||||||
base_color: WHITE.with_alpha(0.9).into(),
|
|
||||||
alpha_mode: AlphaMode::Blend,
|
|
||||||
..default()
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
|
|
||||||
|
|
||||||
let name = Name::new("bird");
|
|
||||||
|
|
||||||
let t = Transform::from_xyz(0.0, 0.0, -10.0).with_rotation(Quat::from_rotation_x(PI / 2.0));
|
|
||||||
|
|
||||||
let mass = (
|
|
||||||
RigidBody::Dynamic,
|
|
||||||
Collider::capsule(1.0, 1.0),
|
|
||||||
Mass(5.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
commands.spawn((name, mesh, material, mass, t));
|
|
||||||
}
|
|
||||||
@ -1,415 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
|
|
||||||
pub(crate) struct TreesDebugPlugin;
|
|
||||||
|
|
||||||
impl Plugin for TreesDebugPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.add_systems(
|
|
||||||
Startup,
|
|
||||||
init_debug_ui,
|
|
||||||
).add_systems(
|
|
||||||
Update,
|
|
||||||
(
|
|
||||||
(
|
|
||||||
spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
monologue_asset_tooltip
|
|
||||||
.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
|
|
||||||
hide_menu.run_if(any_component_changed::<NavState>),
|
|
||||||
clear_monologue.run_if(any_component_changed::<NavState>),
|
|
||||||
control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
|
|
||||||
delete_tree.run_if(on_event::<Pointer<Click>>),
|
|
||||||
drag_tree.run_if(on_event::<Pointer<Drag>>),
|
|
||||||
).run_if(in_state(DebuggingState::On)),
|
|
||||||
)
|
|
||||||
).add_observer(add_dialog_option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
struct MonologuesContainer;
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
struct MonologuesList;
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
struct MonologuePreview;
|
|
||||||
|
|
||||||
|
|
||||||
fn drag_tree(
|
|
||||||
mut events: EventReader<Pointer<Drag>>,
|
|
||||||
state: Res<State<DebuggingState>>,
|
|
||||||
mut query: Query<&mut Transform, With<Tree>>,
|
|
||||||
camera: Single<(&Camera, &GlobalTransform), With<Camera>>,
|
|
||||||
window: Single<&Window>,
|
|
||||||
) {
|
|
||||||
debug_assert_eq!(*state.get(), DebuggingState::On);
|
|
||||||
|
|
||||||
events.read().for_each(|event| {
|
|
||||||
if let Ok(mut t) = query.get_mut(event.target)
|
|
||||||
{
|
|
||||||
let world_position = window
|
|
||||||
.cursor_position()
|
|
||||||
.and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok())
|
|
||||||
.map(|ray| {
|
|
||||||
// Compute ray's distance to entity
|
|
||||||
let distance = ray
|
|
||||||
.intersect_plane(t.translation, InfinitePlane3d::new(t.up()))
|
|
||||||
.unwrap();
|
|
||||||
ray.get_point(distance)
|
|
||||||
});
|
|
||||||
t.translation = world_position.unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Panel for selecting which monologue tree to spawn
|
|
||||||
fn init_debug_ui(mut commands: Commands) {
|
|
||||||
let mut monologue_button = None;
|
|
||||||
commands
|
|
||||||
.spawn((
|
|
||||||
Node {
|
|
||||||
top: Val::Px(0.0),
|
|
||||||
left: Val::Px(0.0),
|
|
||||||
flex_direction: FlexDirection::Row,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
DebuggingState::On,
|
|
||||||
))
|
|
||||||
.with_children(|parent| {
|
|
||||||
monologue_button = Some(
|
|
||||||
parent
|
|
||||||
.spawn((
|
|
||||||
Name::new("Monologue Assignment Menu"),
|
|
||||||
children![Text::new("+Monologue"),],
|
|
||||||
Button,
|
|
||||||
Node {
|
|
||||||
min_width: Val::Px(25.0),
|
|
||||||
min_height: Val::Px(25.0),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
DebuggingState::On,
|
|
||||||
MonologuesContainer,
|
|
||||||
))
|
|
||||||
.id(),
|
|
||||||
);
|
|
||||||
parent
|
|
||||||
.spawn((
|
|
||||||
Name::new("Tree Planter"),
|
|
||||||
children![Text::new("+Tree"),],
|
|
||||||
Node {
|
|
||||||
min_width: Val::Px(25.0),
|
|
||||||
min_height: Val::Px(25.0),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
DebuggingState::On,
|
|
||||||
MonologuesContainer,
|
|
||||||
Button,
|
|
||||||
))
|
|
||||||
.observe(spawn_tree);
|
|
||||||
});
|
|
||||||
|
|
||||||
commands
|
|
||||||
.spawn((
|
|
||||||
NavParent(monologue_button.unwrap()),
|
|
||||||
NavState::default(),
|
|
||||||
DebuggingState::On,
|
|
||||||
Name::new("Container"),
|
|
||||||
Node {
|
|
||||||
flex_direction: FlexDirection::Row,
|
|
||||||
top: Val::Px(25.0),
|
|
||||||
height: Val::Percent(90.0),
|
|
||||||
width: Val::Percent(80.0),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.with_children(|parent| {
|
|
||||||
parent
|
|
||||||
.spawn((
|
|
||||||
Name::new("Buttons"),
|
|
||||||
Node {
|
|
||||||
flex_direction: FlexDirection::Column,
|
|
||||||
height: Val::Percent(100.0),
|
|
||||||
width: Val::Percent(40.0),
|
|
||||||
overflow: Overflow::scroll_y(),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
ScrollPosition::default(),
|
|
||||||
MonologuesList,
|
|
||||||
))
|
|
||||||
.observe(scroll);
|
|
||||||
|
|
||||||
parent.spawn((
|
|
||||||
Name::new("Preview"),
|
|
||||||
MonologuePreview,
|
|
||||||
NavParent(monologue_button.unwrap()),
|
|
||||||
NavState::default(),
|
|
||||||
Node {
|
|
||||||
flex_direction: FlexDirection::Column,
|
|
||||||
padding: UiRect::all(Val::Px(10.0)),
|
|
||||||
overflow: Overflow::scroll_y(),
|
|
||||||
width: Val::Percent(60.0),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// When you pointer goes off of the '+' or any of it's children make the entire menu invisible
|
|
||||||
fn control_menu(
|
|
||||||
mut over_events: EventReader<Pointer<Over>>,
|
|
||||||
mut out_events: EventReader<Pointer<Out>>,
|
|
||||||
nav_children: Query<&NavParent>,
|
|
||||||
children: Query<&ChildOf>,
|
|
||||||
nav_parents: Query<&NavChildren>,
|
|
||||||
parents: Query<&Children>,
|
|
||||||
mut nav: Query<&mut NavState>,
|
|
||||||
hover_map: Res<HoverMap>,
|
|
||||||
) {
|
|
||||||
over_events.read().for_each(|over| {
|
|
||||||
let root = nav_children.root_ancestor(over.target);
|
|
||||||
|
|
||||||
nav_parents.iter_descendants(root).for_each(|child| {
|
|
||||||
if let Ok(mut n) = nav.get_mut(child) {
|
|
||||||
*n = NavState::Open;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Gives us the enities covered by the HoverMap
|
|
||||||
let is_hovered: Vec<&Entity> = hover_map
|
|
||||||
.iter()
|
|
||||||
.flat_map(|(_, submap)| {
|
|
||||||
let x: Vec<&Entity> = submap.iter().map(|(node, _)| node).collect();
|
|
||||||
x
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// For all pointer out events
|
|
||||||
out_events.read().for_each(|out| {
|
|
||||||
// If a relative of out.target is hovered, do nothing
|
|
||||||
// Otherwise set to closed
|
|
||||||
let root = children.root_ancestor(out.target);
|
|
||||||
let tree_still_hovered = parents
|
|
||||||
.iter_descendants(root)
|
|
||||||
.any(|child| is_hovered.contains(&&child));
|
|
||||||
if !tree_still_hovered {
|
|
||||||
if let Ok(mut n) = nav.get_mut(root) {
|
|
||||||
*n = NavState::Closed;
|
|
||||||
}
|
|
||||||
parents.iter_descendants(root).for_each(|child| {
|
|
||||||
if let Ok(mut n) = nav.get_mut(child) {
|
|
||||||
*n = NavState::Closed;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 assign_monologue_event(
|
|
||||||
trigger: Trigger<Pointer<Click>>,
|
|
||||||
mut events: EventWriter<AssignMonologue>,
|
|
||||||
monologues: Query<&TreeMonologue>,
|
|
||||||
) {
|
|
||||||
let TreeMonologue(handle) = monologues.get(trigger.target()).unwrap();
|
|
||||||
events.write(AssignMonologue(handle.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Observer for the "Plant a new tree" button in the debug UI
|
|
||||||
fn spawn_tree(_trigger: Trigger<Pointer<Click>>, mut events: EventWriter<PlantTree>) {
|
|
||||||
events.write(PlantTree(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn clear_monologue(
|
|
||||||
mut nodes: Query<
|
|
||||||
(Entity, &NavState),
|
|
||||||
(Changed<NavState>, With<MonologuePreview>, Without<Text>),
|
|
||||||
>,
|
|
||||||
mut commands: Commands,
|
|
||||||
) {
|
|
||||||
nodes.iter_mut().for_each(|(e, n)| {
|
|
||||||
if matches!(n, NavState::Closed) {
|
|
||||||
commands.entity(e).despawn_related::<Children>();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// When you pointer over the '+' make the entire menu visible
|
|
||||||
fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>) {
|
|
||||||
nodes.iter_mut().for_each(|(mut v, n)| {
|
|
||||||
*v = match n {
|
|
||||||
NavState::Open => Visibility::Inherited,
|
|
||||||
NavState::Closed => Visibility::Hidden,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_tree(mut events: EventReader<Pointer<Click>>, mut commands: Commands, query: Query<Entity, With<Tree>>) {
|
|
||||||
events.read().for_each(|event| {
|
|
||||||
if matches!(event.event.button, PointerButton::Middle) && query.contains(event.target) {
|
|
||||||
debug!("Middle Click -> Despawning {}", event.target);
|
|
||||||
commands.entity(event.target).despawn();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add the "script: path/to/file.mono" tooltip info
|
|
||||||
fn monologue_asset_tooltip(
|
|
||||||
mut over_events: EventReader<Pointer<Over>>,
|
|
||||||
mut out_events: EventReader<Pointer<Out>>,
|
|
||||||
mut tooltip: ResMut<ToolTip>,
|
|
||||||
trees: Query<(&Tree, Option<&TreeMonologue>)>,
|
|
||||||
) {
|
|
||||||
out_events
|
|
||||||
.read()
|
|
||||||
.filter_map(|Pointer { target, .. }| trees.contains(*target).then_some(*target))
|
|
||||||
.for_each(|_| {
|
|
||||||
tooltip.remove("Script");
|
|
||||||
});
|
|
||||||
|
|
||||||
over_events
|
|
||||||
.read()
|
|
||||||
.filter_map(|Pointer { target, .. }| trees.contains(*target).then_some(*target))
|
|
||||||
.for_each(|e| match trees.get(e) {
|
|
||||||
Ok((_tree, Some(TreeMonologue(handle)))) => match handle.path() {
|
|
||||||
Some(p) => tooltip.insert("Script", format!("{p}")),
|
|
||||||
None => tooltip.insert("Script", "A".into()),
|
|
||||||
},
|
|
||||||
Ok((_tree, None)) => {
|
|
||||||
tooltip.insert("Script", "N/A".into());
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 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
|
|
||||||
commands.entity(*container).with_children(|parent| {
|
|
||||||
let mut i = 0;
|
|
||||||
while let Some(batch) = monologue.get(i) {
|
|
||||||
parent.spawn((Text::new("---"), MonologuePreview));
|
|
||||||
debug!("---");
|
|
||||||
|
|
||||||
for (n, item) in batch.lines.iter().enumerate() {
|
|
||||||
parent.spawn((Text::new(format!("{i}.{n}: {item}")), MonologuePreview));
|
|
||||||
debug!("{i}.{n}: {item}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Just implement iter_batches or something
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.spawn((Text::new("---"), MonologuePreview));
|
|
||||||
debug!("---");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
children![
|
|
||||||
Text::new(
|
|
||||||
handle
|
|
||||||
.path()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
.trim_prefix("trees/")
|
|
||||||
.trim_suffix(".mono"),
|
|
||||||
),
|
|
||||||
TextLayout::new(JustifyText::Left, LineBreak::WordBoundary),
|
|
||||||
],
|
|
||||||
TreeMonologue(handle.clone()),
|
|
||||||
MonologuesList,
|
|
||||||
))
|
|
||||||
.observe(assign_monologue_event)
|
|
||||||
.observe(preview_monologue);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue