While trying out Bevy, I had the need for dragging and dropping sprites.
Unfortunately, this does not seem to come ready made or I did not find it in the documentation.
What would be the most idiomatic way to achieve this goal?
What I have tried so far is in my answer, but I'll gladly accept another solution that is better/faster/more idiomatic.
I'm not experienced enough to know what's idiomatic unfortunately, however, here's an overview of how I've implemented sprite dragging in my application, and it feels like a good way to me:
I have a "cursor location" entity with a transform component (and a Cursor component for identification) that I update in a system each frame to the location of the cursor.
Every draggable object has a Hoverable and Draggable component. I iterate over those objects in one system each where I add/remove a Hovered and Dragged component to the entities to indicate if they are hovered or dragged.
I have a system that checks if an object is getting dropped, and if so gives it a Dropped component.
I have a system that runs when an entity gets the 'Dragged' component (using the Added<C> filter), which sets the objects parent to the "cursor location" entity.
And another system for when an entity gets the 'Dropped' component, which clears the parent.
To me, having many systems with small areas of responsibility feels good. I would be interested to hear opposing views as I lack experience.
There are of course many things I've left out in this overview, so here's my code for reference. There are some oddities and unnecessary code for a minimal example since this is adapted from my actual code:
#![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>,
}
This code is for bevy 0.4.
This is the solution I came up with. Complete example
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"
Related
I'm trying to load a shader into an amd video card. After all the buffers are created, I try to create a new Compute pipeline. As i started to debug it printing messages i found out that the "Finished Creating the compute pipeline" is never printed. When i run it with `cargo run --release` it prints the: "Creating pipeline with shader" but after some seconds it freezes my whole computer and i have to turn it off and back on again...
My Vulkano version is: 0.32.1;
My vulkano-shaders version is: 0.32.1;
My Video Card is: AMD RX570 4GB
Vulkano Physical device properties:
buffer_image_granularity: 64,
compute_units_per_shader_array: Some(
8,
),
conformance_version: Some(
1.2.0,
),
Cargo.toml:
[package]
name = "vulkano_matrix"
version = "0.1.0"
edition = "2021"
[dependencies]
vulkano = "0.32.1"
vulkano-shaders = "0.32.0"
rand = "0.8.4"
nalgebra="0.31.4"
colored = "2.0.0"
bytemuck = "1.12.3"
// main.rs
extern crate nalgebra as na;
use bytemuck::{Pod, Zeroable};
use colored::Colorize;
use na::{dmatrix, DMatrix};
use std::{
io::{stdin, stdout, Write},
sync::Arc,
time::Instant,
};
use vulkano::{
buffer::{BufferUsage, CpuAccessibleBuffer, DeviceLocalBuffer},
command_buffer::{
allocator::{CommandBufferAllocator, StandardCommandBufferAllocator},
AutoCommandBufferBuilder, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract,
},
descriptor_set::{
allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet,
},
device::{
physical::PhysicalDevice, Device, DeviceCreateInfo, DeviceExtensions, Features,
QueueCreateInfo, QueueFlags,
},
instance::{Instance, InstanceCreateInfo},
memory::allocator::{MemoryAllocator, StandardMemoryAllocator},
pipeline::Pipeline,
pipeline::{ComputePipeline, PipelineBindPoint},
sync::GpuFuture,
VulkanLibrary,
};
#[derive(Clone, Copy)]
pub enum Padding {
None,
Fixed(usize, usize),
Same,
}
#[repr(C)]
#[derive(Default, Copy, Clone, Debug, Zeroable, Pod)]
struct Dimension {
pub rows: usize,
pub columns: usize,
pub channels: usize,
}
impl Dimension {
pub fn from_matrix<T>(mat: &DMatrix<T>) -> Self {
let shape = mat.shape();
Self {
rows: shape.0,
columns: shape.1,
channels: 1,
}
}
}
#[repr(C)]
#[derive(Default, Copy, Clone, Debug, Zeroable, Pod)]
struct BufferDimensions {
pub input_matrix: Dimension,
pub kernel: Dimension,
pub output_matrix: Dimension,
}
#[repr(C)]
#[derive(Default, Copy, Clone, Debug, Zeroable, Pod)]
struct ConvolutionOptions {
pub padding: [i32; 2],
pub stride: u32,
}
fn input(question: impl Into<String>) -> String {
let mut result = "".to_string();
print!("{} ", question.into().bold().cyan());
stdout().flush().expect("Could not flush stdout");
stdin()
.read_line(&mut result)
.expect("Could not read stdin");
result
}
fn main() {
let library = VulkanLibrary::new().expect("Could not find vulkan.dll");
let instance =
Instance::new(library, InstanceCreateInfo::default()).expect("Failed to Create Instance");
println!("Available GPUs:");
let physical_devices = instance
.enumerate_physical_devices()
.expect("Could not enumerate the physical devices")
.enumerate()
.map(|(i, physical)| {
println!(
"[{}]: \"{}\"; TYPE: \"{:?}\"; API_VERSION: \"{}\"",
i.to_string().bold().bright_magenta(),
physical.properties().device_name.to_string().bold().green(),
physical.properties().device_type,
physical.api_version()
);
physical
})
.collect::<Vec<Arc<PhysicalDevice>>>();
let physical_index = input(format!("Type the chosen [{}]:", "index".bright_magenta()))
.replace("\n", "")
.parse::<usize>()
.expect("Please type a number.");
let physical = physical_devices[physical_index].clone();
println!(
"Using {}; TYPE: \"{:?}\"; \n\n {:?} \n\n {:#?}",
physical.properties().device_name.to_string().bold().green(),
physical.properties().device_type,
physical.api_version(),
physical.properties()
);
return;
let queue_family_index = physical
.queue_family_properties()
.iter()
.position(|q| {
q.queue_flags.intersects(&QueueFlags {
compute: true,
..QueueFlags::empty()
})
})
.unwrap() as u32;
let (device, mut queues) = Device::new(
physical,
DeviceCreateInfo {
enabled_features: Features::empty(),
queue_create_infos: vec![QueueCreateInfo {
queue_family_index,
..Default::default()
}],
..Default::default()
},
)
.expect("Failed to create device");
let queue = queues.next().unwrap();
let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
let descriptor_set_allocator = StandardDescriptorSetAllocator::new(device.clone());
let command_buffer_allocator =
StandardCommandBufferAllocator::new(device.clone(), Default::default());
let mut builder = AutoCommandBufferBuilder::primary(
&command_buffer_allocator,
queue.queue_family_index(),
vulkano::command_buffer::CommandBufferUsage::OneTimeSubmit,
)
.unwrap();
let stride = 1;
let get_result_shape = |input_shape: usize, padding: usize, ker_shape: usize| {
(input_shape + 2 * padding - ker_shape) / stride + 1
};
let padding = Padding::Same;
let input_data = dmatrix![1.0f32, 2., 3.; 4., 5., 6.; 7., 8., 9.];
let kernel_data = dmatrix![11.0f32, 19.; 31., 55.];
let input_shape = Dimension::from_matrix(&input_data);
let kernel_shape = Dimension::from_matrix(&kernel_data);
let padding = match padding {
Padding::None => (0, 0),
Padding::Fixed(x_p, y_p) => (x_p, y_p),
Padding::Same => {
let get_padding = |input_shape: usize, ker_shape: usize| {
(((stride - 1) as i64 * input_shape as i64 - stride as i64 + ker_shape as i64)
as f64
/ 2.0)
.ceil() as usize
};
(
/* rows */
get_padding(input_shape.rows, kernel_shape.rows),
/* columns */
get_padding(input_shape.columns, kernel_shape.columns),
)
}
};
let dimensions = BufferDimensions {
input_matrix: input_shape,
kernel: kernel_shape,
output_matrix: Dimension {
rows: get_result_shape(input_shape.rows, padding.0, kernel_shape.rows),
columns: get_result_shape(input_shape.columns, padding.1, kernel_shape.columns),
channels: 1,
},
};
let options = ConvolutionOptions {
padding: [padding.0 as i32, padding.1 as i32],
stride: stride as u32,
};
let dimensions_buffer = DeviceLocalBuffer::from_data(
&memory_allocator,
dimensions,
BufferUsage {
uniform_buffer: true,
..BufferUsage::empty()
},
&mut builder,
)
.expect("Failed to create uniform buffer.");
let options_buffer = DeviceLocalBuffer::from_data(
&memory_allocator,
options,
BufferUsage {
uniform_buffer: true,
..BufferUsage::empty()
},
&mut builder,
)
.expect("Failed to create uniform buffer.");
println!(
"{:?} {:?} {:?} {:?}",
input_data, dimensions, options, kernel_data
);
let input_buffer = DeviceLocalBuffer::from_iter(
&memory_allocator,
input_data.data.as_vec().to_owned(),
BufferUsage {
uniform_buffer: true,
..BufferUsage::empty()
},
&mut builder,
)
.expect("Failed to create uniform buffer.");
let kernel_buffer = DeviceLocalBuffer::from_iter(
&memory_allocator,
kernel_data.data.as_vec().to_owned(),
BufferUsage {
uniform_buffer: true,
..BufferUsage::empty()
},
&mut builder,
)
.expect("Failed to create uniform buffer.");
let output_buffer = CpuAccessibleBuffer::from_iter(
&memory_allocator,
BufferUsage {
storage_buffer: true,
..BufferUsage::empty()
},
false,
[0..(dimensions.output_matrix.channels
* dimensions.output_matrix.rows
* dimensions.output_matrix.columns)]
.map(|__| 0.0f32)
.to_owned(),
)
.expect("Failed to create storage buffer.");
println!("Loading shader");
let cs = cs::load(device.clone()).unwrap();
println!("Creating pipeline with shader"); // This line prints just fine
let compute_pipeline = ComputePipeline::new(
device.clone(),
cs.entry_point("main").unwrap(),
&(),
None,
|_| {},
)
.expect("Failed to create compute shader");
println!("Finished Creating the compute pipeline"); // THIS LINE NEVER GETS RUN
}
pub mod cs {
use vulkano_shaders::shader;
shader! {
ty: "compute",
path: "./matrix_convolution.glsl"
}
}
The shader is:
#version 450
#pragma shader_stage(compute)
layout(local_size_x=32, local_size_y=32, local_size_z=16) in;
struct Dimension {
uint rows;
uint columns;
uint channels;
};
layout(set=0, binding=0) buffer Dimensions {
Dimension input_matrix;
Dimension kernel;
Dimension output_matrix;
} dims_buf;
layout(set=0, binding=1) buffer readonly InputMatrix {
float[] input_matrix;
};
layout(set=0, binding=2) buffer readonly Kernel {
float[] kernel;
};
layout(set=0, binding=3) buffer writeonly OutputMatrix {
float[] output_matrix;
};
layout(set=0, binding=4) buffer Options {
ivec2 padding;
uint stride;
} options_buf;
void main() {
const uint raw_row = gl_GlobalInvocationID.x;
const uint raw_column = gl_GlobalInvocationID.y;
const uint raw_channel = gl_GlobalInvocationID.z;
}
I tried to run similar programs with different shaders and it worked just fine.
It turns out that the work groups sizes must be fewer less than the
Therefore: local_size_x * local_size_y * local_size_z must be less than max_compute_work_group_invocations
physical.properties().max_compute_work_group_invocations ```
I've been playing around with Rust by following along with Roguelike Tutorial, and have started to branch out a bit in hopes of creating some kind of nature simulation.
In my simple POC, I'm trying to have multiple "Creatures" wandering the map looking for entities with the ProvidesHealth component (so like plants or bushes or something that get eaten).
In the Roguelike tutorial, monsters can easily locate the player at all times because the player is shared throughout the world as a resource, but in my case, I can't figure out the best way to simulate this behavior in my Specs system.
The Creature entities have a Viewshed component to act as their vision. I originally thought I'd be able to iterate thru the Viewshed's visible_tiles and check if an entity with the ProvidesHealth entity was there, but I wasn't able to get that work.
Any thoughts on this would be greatly appreciated! I'm not sure if my approach is totally off, or I'm missing something simple.
Thanks!
// [dependencies]
// bracket-lib = { git = "https://github.com/thebracket/bracket-lib.git", rev = "927d229" }
// specs = "0.16.1"
// specs-derive = "0.4.1"
use bracket_lib::prelude::*;
use specs::prelude::*;
use specs_derive::*;
use std::{thread, time};
#[derive(Component)]
struct Position {
x: i32,
y: i32,
}
#[derive(Component)]
struct Renderable {
glyph: FontCharType,
fg: RGB,
bg: RGB,
}
#[derive(Component)]
struct Creature {}
#[derive(Component)]
struct ProvidesHealth {
pub hp_gain: i32
}
#[derive(Component)]
pub struct Viewshed {
pub visible_tiles: Vec<Point>,
pub range: i32,
pub dirty: bool
}
struct State {
ecs: World
}
impl State {
fn run_systems(&mut self) {
let mut vis = VisSystem {};
vis.run_now(&self.ecs);
let mut ai = CreatureAI {};
ai.run_now(&self.ecs);
self.ecs.maintain();
}
}
impl GameState for State {
fn tick(&mut self, ctx: &mut BTerm) {
ctx.cls();
self.run_systems();
// map defined in separate file, but isn't really
// important for this question
// draw_map(&self.ecs, ctx);
let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
for (pos, ren) in (&positions, &renderables).join() {
ctx.set(pos.x, pos.y, ren.fg, ren.bg, ren.glyph);
}
let sleep = time::Duration::from_millis(200);
thread::sleep(sleep);
}
}
fn main() -> BError {
let mut context = BTermBuilder::simple80x50()
.build()?;
let mut gs = State {
ecs: World::new(),
};
gs.ecs.register::<Position>();
gs.ecs.register::<Renderable>();
gs.ecs.register::<Creature>();
gs.ecs.register::<ProvidesHealth>();
gs.ecs.register::<Viewshed>();
// add one Creature
gs.ecs
.create_entity()
.with(Position {x: 10, y: 20})
.with(Renderable {
glyph: to_cp437('#'),
fg: RGB::named(WHITE),
bg: RGB::named(BLACK)
})
.with(Creature {})
.with(Viewshed { visible_tiles : Vec::new(), range: 6, dirty: true })
.with(HealthStats { max_hp: 100, hp: 100 })
.build();
// add one "food" item
gs.ecs
.create_entity()
.with(Position {x: 35, y: 35})
.with(Renderable {
glyph: to_cp437('*'),
fg: RGB::named(WHITE),
bg: RGB::named(BLACK),
})
.with(ProvidesHealth { hp_gain: 10 })
.build();
// map defined in separate file, but isn't really
// important for this question
let mut map = Map::new_map();
gs.ecs.insert(map);
main_loop(context, gs)
}
struct VisSystem {}
impl<'a> System<'a> for VisSystem {
type SystemData = (
WriteExpect<'a, Map>,
Entities<'a>,
WriteStorage<'a, Viewshed>,
ReadStorage<'a, Position>,
);
fn run(&mut self, data: Self::SystemData) {
let (map, entities, mut viewshed, pos) = data;
for (_ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() {
if viewshed.dirty {
viewshed.visible_tiles = field_of_view(
Point::new(pos.x, pos.y),
viewshed.range,
&*map,
)
}
}
}
}
struct CreatureAI {}
impl<'a> System<'a> for CreatureAI {
#[allow(clippy::type_complexity)]
type SystemData = (
// ...
);
fn run(&mut self, data: Self::SystemData) {
// ... not sure what to do here\
//
// by doing a join on (viewshed, position),
// i'd be able to iterate thru viewshed.visible_tiles,
// but i can't figure out how I could check if a given
// entity located at the Point has the "ProvidesHealth"
// component or not
}
}
I have a query system that finds an object in which the mouse is over. This is not a button, but, I wish to change the color. I'm not sure where to start. What property would I query and how would I change it? Currently, I have the following:
fn mouse_move(mut commands: Commands, cursor: Res<Cursor>, mut query: Query<(&Translation,&mut Sprite,&Box,&Name)>)
{
for (translation,mut sprite,_box,name) in &mut query.iter() {
let cursor_tup = translate_cursor ((cursor.0,cursor.1));
let cursor_vec = Vec3::new(cursor_tup.0,cursor_tup.1,0.0);
if collides(cursor_vec,Vec2::new(1.0,1.0),translation.0,sprite.size) {
println!("{}",name.0);
}
}
}
fn mouse_move(mut commands: Commands, cursor: Res<Cursor>, mut materials: ResMut<Assets<ColorMaterial>>, mut query: Query<(&Translation,&mut Sprite,&Box,&Name, &mut Handle<ColorMaterial>)>)
{
for (translation,mut sprite,_box,name, color) in &mut query.iter() {
let cursor_tup = translate_cursor ((cursor.0,cursor.1));
let cursor_vec = Vec3::new(cursor_tup.0,cursor_tup.1,0.0);
if collides(cursor_vec,Vec2::new(1.0,1.0),translation.0,sprite.size) {
println!("{}",name.0);
let mut color_mat = materials.get_mut(&color).unwrap();
color_mat.color = Color::rgb(1.0,1.0,1.0);
}
}
}
So you have to take the material handle associated with entity, and then have to get the
ColorMaterial from Assets.
Currently the color should just change to white.
I'm making a game using ggez, using a camera from ggez_goodies.
This camera only has rotation from the top left of an image, but I want it from the center. Is there a good way to position the image so that it rotates around the center instead?
I'd assume you'd just change the position to fix this, what I have currently is just the position
self.image
.draw_camera(
camera,
ctx,
graphics::Point2::new(self.position.0, self.position.1),
self.rotation,
)
.unwrap();
I'm guessing a fix would look sort of like this:
self.image
.draw_camera(
camera,
ctx,
graphics::Point2::new(
self.position.0 + self.rotation_offset.0,
self.position.1 + self.rotation_offset.1,
),
self.rotation,
)
.unwrap();
I think it would be possible with an offset, but I can't figure out how to set the offset based off of the rotation angle.
What offset/change could I make to the position to get the image to rotate based around the center instead of around the top left corner?
In following example:
use ggez;
use ggez::event;
use ggez::graphics;
use ggez::graphics::{DrawMode, MeshBuilder};
use ggez::nalgebra as na;
use ggez::timer::check_update_time;
use ggez::{Context, GameResult};
use std::env;
use std::path;
struct MainState {
rotate: f32,
sprite_batch: graphics::spritebatch::SpriteBatch,
}
impl MainState {
fn new(ctx: &mut Context) -> GameResult<MainState> {
let image = graphics::Image::new(ctx, "/sprite_sheet.png").unwrap();
let batch = graphics::spritebatch::SpriteBatch::new(image);
Ok(MainState {rotate: 0.0, sprite_batch: batch})
}
}
impl event::EventHandler for MainState {
fn update(&mut self, ctx: &mut Context) -> GameResult {
while check_update_time(ctx, 30) {
self.rotate += 0.1;
}
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, graphics::BLACK);
let mesh = MeshBuilder::new().circle(
DrawMode::fill(),
na::Point2::new(400.0, 300.0),
2.0,
2.0,
graphics::WHITE,
).build(ctx)?;
self.sprite_batch.add(
graphics::DrawParam::new()
.src(graphics::Rect::new(0.0, 0.0, 1.0, 1.0))
.rotation(self.rotate)
.dest(na::Point2::new(400.0, 300.0))
.offset(na::Point2::new(0.5, 0.5)),
);
graphics::draw(
ctx,
&self.sprite_batch,
graphics::DrawParam::new().dest(na::Point2::new(0.0, 0.0)),
)?;
graphics::draw(
ctx,
&mesh,
graphics::DrawParam::new().dest(na::Point2::new(0.0, 0.0)),
)?;
self.sprite_batch.clear();
graphics::present(ctx)?;
Ok(())
}
}
pub fn main() -> GameResult {
let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let mut path = path::PathBuf::from(manifest_dir);
path.push("resources");
path
} else {
path::PathBuf::from("./resources")
};
let cb = ggez::ContextBuilder::new("oc", "bux")
.add_resource_path(resource_dir)
.window_mode(ggez::conf::WindowMode::default().dimensions(800.0, 600.0));
let (ctx, event_loop) = &mut cb.build()?;
let state = &mut MainState::new(ctx)?;
event::run(ctx, event_loop, state)
}
Where resources/sprite_sheet.png is:
The which do rotation from the center of the image is:
.offset(na::Point2::new(0.5, 0.5))
I'm trying to do some game programming with Piston, but i'm struggling with opengl_graphics::Texture, since it does not derive Copy or Clone.
extern crate piston_window;
extern crate piston;
extern crate graphics;
extern crate opengl_graphics;
use opengl_graphics::Texture as Tex;
use piston_window::*;
use std::path::Path;
use opengl_graphics::GlGraphics;
#[derive(PartialEq)]
enum ObjectType {
Blocking,
Passing,
}
struct Object {
sprite: Tex,
obj_type: ObjectType,
position: Position,
}
struct Game {
gl: GlGraphics,
images: Vec<Object>,
player: Player,
}
struct Player {
sprite: Tex,
position: Position,
}
struct Position {
x: i32,
y: i32,
}
impl Game {
fn render(&mut self, args: &RenderArgs) {
let iter = self.images.iter();
let player = &self.player;
self.gl.draw(args.viewport(), |c, g| {
clear([1.0, 1.0, 1.0, 1.0], g);
for img in iter {
let pos = img.get_position();
let transform = c.transform.trans(((pos.x * 64)) as f64, ((pos.y * 64)) as f64);
image(img.get_sprite(), transform, g);
}
image(player.get_sprite(),
c.transform.trans((player.get_position().x * 64) as f64,
(player.get_position().y * 64) as f64),
g);
});
}
fn update(&mut self, args: &UpdateArgs) {}
}
The main game loop:
fn main() {
let (width, height) = (64*10, 64*10);
let opengl = OpenGL::V3_2;
let mut window: PistonWindow =
WindowSettings::new("piston", (width, height))
.exit_on_esc(true)
.opengl(opengl)
.build()
.unwrap();
window.hide();
println!("Loading...");
let mut player = Player { sprite: Tex::from_path(&Path::new(
"./assets/player_n.png")).unwrap(),
position: Position { x: 3, y: 3 },
};
let mut game = Game {
gl: GlGraphics::new(opengl),
images: Vec::new(),
player: player,
};
for i in 0..10 {
for j in 0..10 {
if i == 0 || i == 9 || j == 0 || j == 9 {
let obj = Object { sprite: Tex::from_path(&Path::new(
"./assets/wall.png")).unwrap(),
obj_type: ObjectType::Blocking,
position: Position { x: i, y: j },
};
game.images.push(obj);
} else {
let obj = Object { sprite: Tex::from_path(&Path::new(
"./assets/floor.png")).unwrap(),
obj_type: ObjectType::Passing,
position: Position { x: i, y: j },
};
game.images.push(obj);
}
}
}
window.show();
while let Some(e) = window.next() {
if let Some(Button::Keyboard(key)) = e.press_args() {
let mut pos = game.player.position.clone();
let mut spr: Option<Tex> = None;
match key {
Key::Up => { pos.y -= 1; spr = Some(Tex::from_path(&Path::new(
"./assets/player_n.png")).unwrap()); },
Key::Down => { pos.y += 1; spr = Some(Tex::from_path(&Path::new(
"./assets/player_s.png")).unwrap()); },
Key::Left => { pos.x -= 1; spr = Some(Tex::from_path(&Path::new(
"./assets/player_w.png")).unwrap()); },
Key::Right => { pos.x += 1; spr = Some(Tex::from_path(&Path::new(
"./assets/player_e.png")).unwrap()); },
_ => (),
}
for elem in game.images.iter() {
if pos.x == elem.position.x && pos.y == elem.position.y && elem.obj_type == ObjectType::Passing {
game.player.position = pos;
game.player.sprite = spr.clone().unwrap();
}
}
}
if let Some(r) = e.render_args() {
game.render(&r);
}
if let Some(u) = e.update_args() {
game.update(&u);
}
}
}
Produces the error:
error: no method named `clone` found for type `std::option::Option<opengl_graphics::Texture>` in the current scope
--> src/main.rs:159:46
159 | game.player.sprite = spr.clone().unwrap();
| ^^^^^
|
= note: the method `clone` exists but the following trait bounds were not satisfied: `opengl_graphics::Texture : std::clone::Clone`
I understand why I get this error, since opengl_graphics::Texture doesn't derive Copy I can't clone Option<opengl_texture>. What workaround is there for this?
I tried messing around with references but that didn't work.
How do I copy/clone a struct that derives neither?
You don't. The only thing you can do is take some kind of reference to it.
In this case, it is a very good thing that the library has chosen to not implement Clone or Copy. If you were able to clone the structure, you'd be allocating a lot of memory frequently and needlessly. Instead, the library has forced you to think about when you allocate that memory. One of the solutions is to load all the textures at application startup and reference them:
Change your structures to hold references:
#[derive(PartialEq)]
enum ObjectType {
Blocking,
Passing,
}
struct Object<'a> {
sprite: &'a Tex,
obj_type: ObjectType,
position: Position,
}
struct Game<'a> {
gl: GlGraphics,
images: Vec<Object<'a>>,
player: Player<'a>,
}
struct Player<'a> {
sprite: &'a Tex,
position: Position,
}
#[derive(Copy, Clone, PartialEq)]
struct Position {
x: i32,
y: i32,
}
struct Textures {
player_n: Tex,
player_s: Tex,
player_e: Tex,
player_w: Tex,
wall: Tex,
floor: Tex,
}
Load the textures early on in main. Note that there's no need to use Path explicitly, as it takes AsRef<Path>:
let textures = Textures {
player_n: Tex::from_path("./assets/player_n.png").unwrap(),
player_s: Tex::from_path("./assets/player_s.png").unwrap(),
player_e: Tex::from_path("./assets/player_e.png").unwrap(),
player_w: Tex::from_path("./assets/player_w.png").unwrap(),
wall: Tex::from_path("./assets/wall.png").unwrap(),
floor: Tex::from_path("./assets/floor.png").unwrap()
};
Then pass references to those textures:
match key {
Key::Up => {
pos.y -= 1;
spr = Some(&textures.player_n)
}
Key::Down => {
pos.y += 1;
spr = Some(&textures.player_s)
}
Key::Left => {
pos.x -= 1;
spr = Some(&textures.player_w)
}
Key::Right => {
pos.x += 1;
spr = Some(&textures.player_e)
}
_ => (),
}
for elem in game.images.iter() {
if pos == elem.position && elem.obj_type == ObjectType::Passing {
game.player.position = pos;
if let Some(spr) = spr {
game.player.sprite = spr;
}
}
}
Note that this also consolidates the places that errors can occur. There's no longer an unwrap inside the guts of the loop.
I was unable to get your code to finish compiling as the code is not complete but this should help get started:
error: no method named `render` found for type `Game<'_>` in the current scope
--> src/main.rs:122:18
|
122 | game.render(&r);
| ^^^^^^
|
= help: items from traits can only be used if the trait is implemented and in scope; the following trait defines an item `render`, perhaps you need to implement it:
= help: candidate #1: `piston_window::RenderEvent`
error: no method named `update` found for type `Game<'_>` in the current scope
--> src/main.rs:125:18
|
125 | game.update(&u);
| ^^^^^^
|
= help: items from traits can only be used if the trait is implemented and in scope; the following traits define an item `update`, perhaps you need to implement one of them:
= help: candidate #1: `piston_window::UpdateEvent`
= help: candidate #2: `piston_window::<unnamed>::UpdateTexture`
= help: candidate #3: `deflate::checksum::RollingChecksum`
= help: candidate #4: `cocoa::appkit::NSOpenGLContext`
= help: candidate #5: `cocoa::appkit::NSOpenGLContext`
In some cases, you may be able to wrap your types in an Rc or Arc and clone that. Cloning a Rc/Arc only increments a reference counter, regardless of the implementation of Clone for the underlying type (or the absence of such an implementation).