idanarye / bevy-yoleck

Your Own Level Editor Creation Kit
Other
172 stars 11 forks source link

Allow user to click on the level where they want to place a new entity instead of just placing it at the origin #20

Closed idanarye closed 1 year ago

idanarye commented 1 year ago

Currently, when adding a new entity in the editor, all the YoleckComponents are initialized to their default value. For the position component, this usually means the origin. This can get a bit cumbersome:

  1. You can't put anything at the level origin, because then when you add new entities they'll appear in the same place.
  2. If you edit the level too far away from the origin, you have to scroll to the origin every time you want to add something.

It could be nice if instead of just spawning the thing at the origin, the user would click in the world space where they want to place the new entity.

Note that because there is no position in Yoleck's core (it's part of Vpeol), this will have to be user-customizable. On the plus size this could mean that the same mechanism can be used for other initializations.

idanarye commented 1 year ago

The idea - just like we have edit systems and populate systems, we should also have initialize systems for initializing entities.

Initialize systems are similar to edit systems: they use YoleckEdit to edit the new entity and potentially YoleckUi to use egui, and they are stored in a vector that maintains a deterministic order.

Unlike edit systems, only one initialize system is active at any given time. This allows the initialize system to take control over the entire input. The initialize system must return a value - probably an enum, but maybe an Option will work better because it'd allow easily exit with ? instead of the usual let...else (then again, maybe it's better to force a let...else for conformity?). Once the initialize system returns a value that indicates it is done, the next initialize system will be active. Once all the initialize systems finish, the editor/entity goes back to edit mode and once again runs edit systems.

idanarye commented 1 year ago

Something like this:

fn vpeol_2d_initialize_position(
    mut edit: YoleckEdit<&mut Vpeol2dPosition>,
    mut cameras_query: Query<&VpeolCameraState>,
    buttons: Res<Input<MouseButton>>,
) -> Option<YoleckInitialize> {
    let mut position = edit.get_single_mut().ok()?;

    // I think `cursor_ray` is `None` if the cursor is inside the egui window, but if it isn't I
    // should probably make it so it does. That would mean clicking inside the egui window should
    // not be considered as clicking to set the position.
    let cursor_position = camera_state
        .iter()
        .find_map(|camera_state| camera_state.cursor_ray)?;
    position.0 = cursor_position.origin.truncate();

    if buttons.just_released(MouseButton::Left) {
        Some(YoleckInitialize::Done)
    } else {
        None
    }
}
idanarye commented 1 year ago

Note that there are still default values - we just immediately start to edit them.

idanarye commented 1 year ago

Not sure if the next initialize system should start in the exact same frame. I worry that buttons.just_released will also be true in the next initialize system if I do that, but if each initialize system has its own frame it'll cause a delay when some of them are skipped.

Maybe a good heuristic is that if an initialize system returns None on the first frame it runs, the next one can run in the same frame.

idanarye commented 1 year ago

Actually, maybe returning an Option is not a good idea? In the example I've naturally written I used None both for "no editable entity with a position" and for "the button was not just released", but one of them would mean "just skip this system" while the other would mean "keep running this system".

I should probably just use an explicit enum with all three directives (keep running this, continue to the next, abort)

Actually, do I need abort? Maybe abort should be done externally (e.g. - always done when the user hits Escape) instead of controlled by these systems?