在尝试 Bevy 时,我需要拖放精灵。不幸的是,这似乎不是现成的,或者我没有在文档中找到它。
实现这一目标的最惯用的方法是什么?
到目前为止,我已经尝试过在我的答案中,但我很乐意接受另一种更好/更快/更惯用的解决方案。
不幸的是,我没有足够的经验知道什么是惯用的,但是,这里概述了我如何在我的应用程序中实现精灵拖动,这对我来说是一个好方法:
Cursor
用于识别的组件),我在系统中将每帧更新到光标的位置。Hoverable
和Draggable
组件。我在一个系统中迭代这些对象,在每个系统中我向实体添加/删除一个Hovered
和Dragged
组件,以指示它们是悬停还是拖动。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_cursor_state: Query<&CursorState>,
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, 1.0, 0.5)
} else {
(1.0, 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, &GlobalTransform), With<Cursor>>,
) {
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, &mut Transform, &GlobalTransform), Added<Dropped>>,
) {
for (entity, mut transform, 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。
这是我想出的解决方案。完整示例
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,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
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, 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>,
windows: Res<Windows>,
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,
0.0,
)
}
Cargo.toml
[package]
name = "bevy-drag-sprite"
version = "0.1.0"
authors = ["Me"]
edition = "2018"
[dependencies]
bevy = "0.4"