使用 Bevy 0.4 拖动精灵的可接受方法是什么?

问题描述

在试用 Bevy 时,我需要拖放精灵。 不幸的是,这似乎不是现成的,或者我没有在文档中找到它。

实现这一目标最惯用的方式是什么?

到目前为止我尝试过的是 in my answer,但我很乐意接受另一种更好/更快/更惯用的解决方案。

解决方法

不幸的是,我没有足够的经验来了解什么是惯用的,但是,这里概述了我如何在我的应用程序中实现精灵拖动,这对我来说是一种很好的方法:

  • 我有一个“光标位置”实体,其中包含一个转换组件(以及一个用于识别的 Cursor 组件),我在系统中将每帧更新到光标位置。
  • 每个可拖动对象都有一个 HoverableDraggable 组件。我在一个系统中迭代这些对象,在每个系统中我向实体添加/删除 HoveredDragged 组件以指示它们是悬停还是拖动。
  • 我有一个系统可以检查对象是否被丢弃,如果是,则为其提供一个 Dropped 组件。
  • 我有一个系统,当实体获得“拖动”组件(使用 Added<C> 过滤器)时会运行,它将对象父级设置为“光标位置”实体。
  • 还有另一个系统,用于当实体获取“已删除”组件时,该组件会清除父级。

对我来说,拥有多个负责小范围的系统感觉很好。由于我缺乏经验,我很想听听反对意见。

当然,我在这个概述中遗漏了很多东西,所以这里是我的代码以供参考。一个最小的例子有一些奇怪和不必要的代码,因为这是根据我的实际代码改编的:

#![allow(clippy::type_complexity)]

use bevy::{prelude::*,render::camera::Camera};

fn main() {
    App::build()
        .init_resource::<State>()
        .add_resource(WindowDescriptor {
            title: "Bevy".to_string(),width: 1024.0,height: 768.0,vsync: true,..Default::default()
        })
        .add_plugins(DefaultPlugins)
        .add_plugin(MyPlugin)
        .run();
}

pub struct MyPlugin;

impl Plugin for MyPlugin {
    fn build(&self,app: &mut AppBuilder) {
        app.add_startup_system(setup.system())
            .add_system_to_stage(stage::PRE_UPDATE,cursor_state.system())
            .add_system_to_stage(stage::UPDATE,cursor_transform.system())
            .add_system_to_stage(stage::UPDATE,draggable.system())
            .add_system_to_stage(stage::UPDATE,hoverable.system())
            .add_system_to_stage(stage::POST_UPDATE,drag.system())
            .add_system_to_stage(stage::POST_UPDATE,drop.system())
            .add_system_to_stage(stage::POST_UPDATE,material.system());
    }
}

const SPRITE_SIZE: f32 = 55.0;

fn setup(
    commands: &mut Commands,asset_server: Res<AssetServer>,mut materials: ResMut<Assets<ColorMaterial>>,) {
    let bevy_texture = asset_server.load("sprites/bevy-icon.png");

    commands
        .spawn(Camera2dBundle::default())
        .spawn(())
        .with(CursorState::default())
        .spawn((Transform::default(),GlobalTransform::default(),Cursor));

    for _ in 0..4 {
        commands
            .spawn(SpriteBundle {
                material: materials.add(bevy_texture.clone().into()),sprite: Sprite::new(Vec2::new(SPRITE_SIZE,SPRITE_SIZE)),..Default::default()
            })
            .with(Hoverable)
            .with(Draggable);
    }
}

#[derive(Default)]
struct CursorState {
    cursor_world: Vec2,cursor_moved: bool,}

struct Cursor;

struct Draggable;
struct Dragged;
struct Dropped;

struct Hoverable;
struct Hovered;

fn cursor_state(
    mut state: ResMut<State>,e_cursor_moved: Res<Events<CursorMoved>>,windows: Res<Windows>,mut q_cursor_state: Query<&mut CursorState>,q_camera: Query<&Transform,With<Camera>>,) {
    let event_cursor_screen = state.er_cursor_moved.latest(&e_cursor_moved);

    for mut cursor_state in q_cursor_state.iter_mut() {
        if let Some(event_cursor_screen) = event_cursor_screen {
            let window = windows.get_primary().unwrap();
            let cam_transform = q_camera.iter().last().unwrap();
            cursor_state.cursor_world =
                cursor_to_world(window,cam_transform,event_cursor_screen.position);

            cursor_state.cursor_moved = true;
        } else {
            cursor_state.cursor_moved = false;
        }
    }
}

fn cursor_transform(
    commands: &mut Commands,q_cursor_state: Query<&CursorState>,mut q_cursor: Query<(Entity,&mut Transform),With<Cursor>>,) {
    let cursor_state = q_cursor_state.iter().next().unwrap();

    for (cursor_e,mut transform) in q_cursor.iter_mut() {
        transform.translation.x = cursor_state.cursor_world.x;
        transform.translation.y = cursor_state.cursor_world.y;
        commands.remove_one::<Parent>(cursor_e);
    }
}

fn hoverable(
    commands: &mut Commands,q_hoverable: Query<(Entity,&Transform,&Sprite),(With<Hoverable>,Without<Dragged>)>,) {
    let cursor_state = q_cursor_state.iter().next().unwrap();

    if cursor_state.cursor_moved {
        for (entity,transform,sprite) in q_hoverable.iter() {
            let half_width = sprite.size.x / 2.0;
            let half_height = sprite.size.y / 2.0;

            if transform.translation.x - half_width < cursor_state.cursor_world.x
                && transform.translation.x + half_width > cursor_state.cursor_world.x
                && transform.translation.y - half_height < cursor_state.cursor_world.y
                && transform.translation.y + half_height > cursor_state.cursor_world.y
            {
                commands.insert_one(entity,Hovered);
            } else {
                commands.remove_one::<Hovered>(entity);
            }
        }
    }
}

fn material(
    mut materials: ResMut<Assets<ColorMaterial>>,q_hoverable: Query<
        (&Handle<ColorMaterial>,Option<&Hovered>,Option<&Dragged>),With<Hoverable>,>,) {
    let mut first = true;

    for (material,hovered,dragged) in q_hoverable.iter() {
        let (red,green,alpha) = if dragged.is_some() {
            (0.0,1.0,1.0)
        } else if first && hovered.is_some() {
            first = false;
            (1.0,0.0,1.0)
        } else if hovered.is_some() {
            (1.0,0.5)
        } else {
            (1.0,1.0)
        };

        materials.get_mut(material).unwrap().color.set_r(red);
        materials.get_mut(material).unwrap().color.set_g(green);
        materials.get_mut(material).unwrap().color.set_a(alpha);
    }
}

fn cursor_to_world(window: &Window,cam_transform: &Transform,cursor_pos: Vec2) -> Vec2 {
    // get the size of the window
    let size = Vec2::new(window.width() as f32,window.height() as f32);

    // the default orthographic projection is in pixels from the center;
    // just undo the translation
    let screen_pos = cursor_pos - size / 2.0;

    // apply the camera transform
    let out = cam_transform.compute_matrix() * screen_pos.extend(0.0).extend(1.0);
    Vec2::new(out.x,out.y)
}

fn draggable(
    commands: &mut Commands,i_mouse_button: Res<Input<MouseButton>>,q_pressed: Query<Entity,(With<Hovered>,With<Draggable>)>,q_released: Query<Entity,With<Dragged>>,) {
    if i_mouse_button.just_pressed(MouseButton::Left) {
        if let Some(entity) = q_pressed.iter().next() {
            commands.insert_one(entity,Dragged);
        }
    } else if i_mouse_button.just_released(MouseButton::Left) {
        for entity in q_released.iter() {
            commands.remove_one::<Dragged>(entity);

            commands.insert_one(entity,Dropped);
        }
    }
}

fn drag(
    commands: &mut Commands,mut q_dragged: Query<(Entity,&mut Transform,&GlobalTransform),Added<Dragged>>,q_cursor: Query<(Entity,) {
    if let Some((cursor_e,cursor_transform)) = q_cursor.iter().next() {
        for (entity,mut transform,global_transform) in q_dragged.iter_mut() {
            let global_pos = global_transform.translation - cursor_transform.translation;

            commands.insert_one(entity,Parent(cursor_e));

            transform.translation.x = global_pos.x;
            transform.translation.y = global_pos.y;
        }
    }
}

fn drop(
    commands: &mut Commands,mut q_dropped: Query<(Entity,Added<Dropped>>,) {
    for (entity,global_transform) in q_dropped.iter_mut() {
        let global_pos = global_transform.translation;

        transform.translation.x = global_pos.x;
        transform.translation.y = global_pos.y;

        commands.remove_one::<Parent>(entity);
        commands.remove_one::<Dropped>(entity);
    }
}

#[derive(Default)]
struct State {
    er_cursor_moved: EventReader<CursorMoved>,}

此代码适用于 bevy 0.4。

,

这是我想出的解决方案。 Complete example

<script> jQuery(document).ready(function(){ jQuery(".popup").hide(); /* hide all popups */ jQuery(".trigger").click(function(){ /* when button is clicked */ jQuery(this).next(".popup").slideToggle(); /* toggle the closest popup */ }); }); </script>

(this)

main.rs

use bevy::prelude::*;
use bevy::render::pass::ClearColor;
use bevy::window::CursorMoved;

const SPRITE_SIZE: f32 = 55.0;

fn main() {
    App::build()
        .add_resource(WindowDescriptor {
            width: 1000.0,height: 1000.0,resizable: false,title: "Bevy: drag sprite".to_string(),..Default::default()
        })
        .add_resource(Msaa { samples: 4 })
        .add_resource(ClearColor(Color::rgb(0.9,0.9,0.9)))
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup.system())
        .add_system(sprite_system.system())
        .add_system(bevy::input::system::exit_on_esc_system.system())
        .run();
}

fn setup(
    commands: &mut Commands,) {
    commands.spawn(Camera2dBundle::default());

    // show sprite in the middle of the screen
    let bevy_texture = asset_server.load("sprites/bevy-icon.png");
    commands.spawn(SpriteBundle {
        sprite: Sprite::new(Vec2::new(SPRITE_SIZE,material: materials.add(bevy_texture.clone().into()),..Default::default()
    });
}

#[derive(Default)]
struct State {
    cursor_moved_event_reader: EventReader<CursorMoved>,// store current cursor/mouse position
    cursor_pos: Vec2,// store entity ID and the difference between sprite center and mouse click location
    sprite: Option<(Entity,Vec3)>,}

fn sprite_system(
    mut state: Local<State>,mouse_button_input: Res<Input<MouseButton>>,cursor_moved_events: Res<Events<CursorMoved>>,mut sprites: Query<(Entity,&Sprite)>,mut transforms: Query<&mut Transform>,) {
    let window = windows.get_primary().unwrap();
    let half_window = Vec2::new(window.width() / 2.0,window.height() / 2.0);

    // if cursor has moved,transform to graphics coordinates and store in state.curser_pos
    if let Some(cursor_event) = state.cursor_moved_event_reader.latest(&cursor_moved_events) {
        state.cursor_pos = cursor_event.position - half_window;
        state.cursor_pos.x = state.cursor_pos.x;
    };

    // stop dragging if mouse button was released
    if mouse_button_input.just_released(MouseButton::Left) {
        state.sprite = None;
        return;
    }

    // set new sprite position,if mouse button is pressed and a sprite was clicked on
    // take previous click difference into account,to avoid sprite jumps on first move
    if mouse_button_input.pressed(MouseButton::Left) && state.sprite.is_some() {
        let sprite = state.sprite.unwrap();

        let mut sprite_pos = transforms.get_mut(sprite.0).unwrap();

        trace!("Sprite position old: {:?}",sprite_pos.translation);
        sprite_pos.translation.x = state.cursor_pos.x + sprite.1.x;
        sprite_pos.translation.y = state.cursor_pos.y + sprite.1.y;
        trace!("Sprite position new: {:?}",sprite_pos.translation);
        // position clamping was left out intentionally
    }

    // store sprite ID and mouse distance from sprite center,if sprite was clicked
    if mouse_button_input.just_pressed(MouseButton::Left) {
        for (entity,sprite) in sprites.iter_mut() {
            let sprite_pos = transforms.get_mut(entity).unwrap().translation;
            let diff = cursor_to_sprite_diff(&state.cursor_pos,&sprite_pos);
            // sprite is a circle,so check distance from center < sprite radius
            if diff.length() < (sprite.size.x / 2.0) {
                state.sprite = Some((entity,diff));
            }
        }
    }
}

fn cursor_to_sprite_diff(cursor_pos: &Vec2,sprite_pos: &Vec3) -> Vec3 {
    Vec3::new(
        sprite_pos.x - cursor_pos.x,sprite_pos.y - cursor_pos.y,)
}