I've been learning Bevy and I can't figure out how to simply rotate a sprite.
Here is a minimal example illustrating what appears to be a correctly rotated square but an incorrectly rotated rectangle with one dependency bevy = "0.6.1" and main.rs:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.run();
}
fn setup(mut commands: Commands) {
// cameras
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
// square
commands.spawn_bundle(SpriteBundle {
transform: Transform {
translation: Vec3::from((0f32, 0f32, 0f32)),
rotation: Quat::from_rotation_z(2f32),
scale: Vec3::new(100f32, 100f32, 0f32),
},
sprite: Sprite {
color: Color::rgb(1., 1., 1.),
..Default::default()
},
..Default::default()
});
// line
commands.spawn_bundle(SpriteBundle {
transform: Transform {
translation: Vec3::from((0f32, 200f32, 0f32)),
rotation: Quat::from_rotation_z(2f32),
scale: Vec3::new(100f32, 10f32, 0f32),
},
sprite: Sprite {
color: Color::rgb(1., 1., 1.),
..Default::default()
},
..Default::default()
});
}
How could I properly rotate the rectangle?
(I've read How to rotate and move object in bevy and I cannot see how it might answer my problem)
You should have better described what you mean by "incorrectly rotated rectangle". In fact your code does exactly what one would expect.
However I think the issue that you are describing is that because you scaled your sprite using the Transform along the coordinate axes and then rotate the sprite, the scaling axes stay fixed which leads to the effect that the rectangle is constantly scaled differently while rotating. You can see this easily when using the rotate_system below with your original code.
So how do you set the size of the sprite in its local frame? One would use the custom_size for this as shown below and then set the scale of the Transform to Vec3::new(1f32, 1f32, 1f32).
I think you want to rotate the rectangle like so based on your code:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(rotate_system.system())
.run();
}
#[derive(Component)]
struct RotationEntity;
fn setup(mut commands: Commands) {
// cameras
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
// square
commands.spawn_bundle(SpriteBundle {
transform: Transform {
translation: Vec3::from((0f32, 0f32, 0f32)),
rotation: Quat::from_rotation_z(0f32),
scale: Vec3::new(1f32, 1f32, 1f32),
},
sprite: Sprite {
color: Color::rgb(1., 1., 1.),
custom_size: Some(Vec2::new(100f32, 100f32)),
..Default::default()
},
..Default::default()
}).insert(RotationEntity);
// rectangle
commands.spawn_bundle(SpriteBundle {
transform: Transform {
translation: Vec3::from((0f32, 200f32, 0f32)),
rotation: Quat::from_rotation_z(0f32),
scale: Vec3::new(1f32, 1f32, 1f32),
},
sprite: Sprite {
color: Color::rgb(1., 0., 0.),
custom_size: Some(Vec2::new(100f32, 10f32)),
..Default::default()
},
..Default::default()
}).insert(RotationEntity);
}
fn rotate_system(
time: Res<Time>,
mut query: Query<(
&mut Transform,
With<RotationEntity>
)>,
) {
for (mut transform, is_rotation_entity) in query.iter_mut() {
if is_rotation_entity {
transform.rotation = Quat::from_rotation_z(time.seconds_since_startup() as f32);
}
}
}
which gives you this
Related
I want to calculate per pixel color when drawing text.
draw_text doesn't take a closure ( lambda) for calculating the color so i thought to make my own canvas and make a custom draw_pixel.
sounds good but when for the life of me i cannot get a simple color to pixel conversion to compile here.
for example below, the c cannot be turned into a Pixel. but when i am writing code outside this, such as code external to imageproc, rgb to pixel works just fine.
pub struct CustomBlend<I>(pub I );
impl<I: GenericImage> Canvas for CustomBlend<I> {
type Pixel = I::Pixel;
fn dimensions(&self) -> (u32, u32) {
self.0.dimensions()
}
fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel {
self.0.get_pixel(x, y)
}
fn draw_pixel(&mut self, x: u32, y: u32, color: Self::Pixel) {
let mut pix = self.0.get_pixel(x, y);
let c = Rgb([1u8,1u8,1u8]);
self.0.put_pixel(x, y,c );
}
}
I'm trying to render a cube with a texture on all sides in bevy. The texture is 16x16 and the cube is 1 bevy coordinate large.
This is my code so far:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
title: "Definitely Minecraft".to_string(),
..Default::default()
},
..Default::default()
}))
.add_startup_system(setup_system)
.add_startup_system_to_stage(StartupStage::PostStartup, generate_world_system)
.run();
}
#[derive(Resource)]
struct GameMaterials {
dirt: Handle<StandardMaterial>
}
fn setup_system (
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Light
commands.spawn(DirectionalLightBundle {
transform: Transform::from_xyz(4., 80., 4.),
..Default::default()
});
commands.insert_resource(GameMaterials {
dirt: materials.add(StandardMaterial {
base_color_texture: Some(asset_server.load("dirt.png")),
alpha_mode: AlphaMode::Blend,
unlit: false,
..Default::default()
})
});
// Camera
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(3., 5., 8.).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
}
fn generate_world_system(
mut commands: Commands,
game_materials: Res<GameMaterials>,
mut meshes: ResMut<Assets<Mesh>>,
) {
let block_handle = meshes.add(Mesh::from(shape::Cube::new(1.)));
commands.spawn(PbrBundle {
mesh: block_handle.clone(),
material: game_materials.dirt.clone(),
transform: Transform::from_xyz(0., 0., 1.5),
..Default::default()
});
}
When I compile I get a 1x1 cube with a blurry (the actual texture is sharp) texture on a single side. Why does this happen and how can I fix it?
By default Bevy uses linear sampling which makes the low resolution textures blurry. You can change the sampler so that the textures look pixelated:
DefaultPlugins.set(ImagePlugin::default_nearest())
Bevy v0.8 has several "displayable-object" types, Sprite/SpriteBundle, SpriteSheetBundle, and MaterialMeshBundle. I would like to change the transparency (Alpha-channel, stored on Color Components) of, well, all of them - if possible.
The Answer Changing Sprite Color, addresses changing the Color of an object (where Color has the Alpha-channel) , but it only works for things that have a material associated. In Bevy v0.8, SpriteBundles don't have a material (they did in 0.5).
So is it possible to change the transparency of other objects, that don't have materials? (And if so, how?)
Fortunately, it is doable (and thanks to people in the Bevy-Discord channel for suggestions). There are two elements to the solution - first, all of the items I named, do have a Color component (although finding it can be tricky). Even a SpriteSheet, which is an Atlas of bitmaps, has one - presumably just for this usage. Second, the AnyOf filter, which gets any of the requested components, as a tuple of Options, each of which is either an object, or None.
// Create some disparate objects
pub fn do_add_sprites(mut commands: Commands, asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
) {
commands.spawn_bundle(SpriteSheetBundle {
transform: Transform::from_xyz(-300.0, -200.0, 1.0),
texture_atlas: texture_atlas_handle2, ..default()
})
.insert(IsMousing { is_dragging: false, is_hovering: false });
commands.spawn_bundle(MaterialMesh2dBundle {
transform: Transform::from_translation(Vec3::new(500.0, -200.0, 1.0)),
mesh: meshes.add(shape::RegularPolygon::new(50., 6).into()).into(),
material: materials.add(ColorMaterial::from(Color::BLUE)),
..default()})
.insert(IsMousing { is_dragging: false, is_hovering: false });
}
// Code elsewhere that says: if cursor over object, set is_hovering flag
pub fn apply_any_hovers(mut materials: ResMut<Assets<ColorMaterial>>,
mut any_hovercraft: Query<(Entity, &IsMousing,
AnyOf<(&mut TextureAtlasSprite, &Handle<ColorMaterial>)>,)>,
) {
for (_entity_id, mouser_data, some_object) in any_hovercraft.iter_mut() {
if mouser_data.is_hovering {
if some_object.0.is_some() {
let some_color = &mut some_object.0.unwrap().color;
some_color.set_a(0.5);
}
if some_object.1.is_some() {
let some_handle = some_object.1.unwrap();
let some_color = &mut materials.get_mut(some_handle).unwrap().color;
some_color.set_a(0.5);
}
} else {
if some_object.0.is_some() {
let some_color = &mut some_object.0.unwrap().color;
some_color.set_a(1.0);
}
if some_object.1.is_some() {
let some_handle = some_object.1.unwrap();
let some_color = &mut materials.get_mut(some_handle).unwrap().color;
some_color.set_a(1.0);
}
}
}
}
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))
Piston's graphics library provides a function for drawing a line between two points, but nothing for more than two. How do I efficiently draw a path through many points without having to draw a line for every segment?
Let's say I have the following code:
extern crate piston_window;
use piston_window::*;
fn main() {
let mut window: PistonWindow = WindowSettings::new("Hello Piston!", [640, 480])
.exit_on_esc(true).build().unwrap();
while let Some(e) = window.next() {
window.draw_2d(&e, |c, g| {
clear([1.0; 4], g);
let points = [
[100., 100.],
[200., 200.],
[150., 350.],
//...
];
let mut prev = points[0];
for pt in points[1..].iter() {
line([0., 0., 0., 255.], 1., [
prev[0], prev[1], pt[0], pt[1]
], c.transform, g);
prev = *pt;
}
});
}
}
Is there a way to turn it into something like this?
extern crate piston_window;
use piston_window::*;
fn main() {
let mut window: PistonWindow = WindowSettings::new("Hello Piston!", [640, 480])
.exit_on_esc(true).build().unwrap();
while let Some(e) = window.next() {
window.draw_2d(&e, |c, g| {
clear([1.0; 4], g);
let points = [
[100., 100.],
[200., 200.],
[150., 350.],
//...
];
path([0., 0., 0., 255.], 1., &points, c.transform, g);
});
}
}
I was referred to the lyon library but I don't know how to use it with piston.
How do I efficiently draw a path through many points without having to draw a line for every segment?
I'm not hugely familiar with Piston, but I would question your assumption that line() has some overhead that makes it inefficient to call repeatedly. In the end, whether you draw lots of lines or a library function draws them they will get drawn and there shouldn't be much difference in performance.
It doesn't look like there is currently a method to draw a sequence of lines between points, but it should just be a matter of looping through them as you were already doing. Using Vec::windows is a bit nicer though, as you don't need temporary mutable variables:
pub fn path<G>(color: Color, radius: Radius, points: &[[Scalar; 2]], transform: Matrix2d, g: &mut G)
where
G: Graphics,
{
for w in points.windows(2) {
line(
color,
radius,
[w[0][0], w[0][1], w[1][0], w[1][1]],
transform,
g,
);
}
}
You might consider making a feature request or a PR to the Piston project.