Spawning two sprites in Bevy will spawn at the center of the transform. So if we have a black 3x3 block and a white 1x1 block, the smaller block will spawn at the center of the larger block, which spawned at the global (0,0)
point.
use bevy::prelude::*;const BLOCK_SIZE: f32 = 25.0;fn main() {App::build().add_plugins(DefaultPlugins).add_startup_system(setup.system()).run();}fn setup(commands: &mut Commands,mut materials: ResMut<Assets<ColorMaterial>>,) {commands.spawn(Camera2dBundle::default()).spawn(SpriteBundle {material: materials.add(Color::BLACK.into()),sprite: Sprite::new(Vec2::new(3.0 * BLOCK_SIZE,3.0 * BLOCK_SIZE,)),..Default::default()}).spawn(SpriteBundle {material: materials.add(Color::WHITE.into()),sprite: Sprite::new(Vec2::new(BLOCK_SIZE, BLOCK_SIZE,)),..Default::default()});}
This is more clearly apparent if we make the 3x3 block a 2x2 block.
If we're making a game like Tetris, this is a problem because we want a set grid with 1 block offsets, not half-block offsets.
So if the offset is set by the center of the transform, we can do some calculations to set the offset to make it look like the origin is in a different location.
We'll spawn another square, the same size this time with a transform, which will offset the sprite relative to it's origin.
.spawn(SpriteBundle {material: materials.add(Color::BLUE.into()),sprite: Sprite::new(Vec2::new(2.0 * BLOCK_SIZE,2.0 * BLOCK_SIZE,)),transform: Transform::from_translation(Vec3::new(0.5 * BLOCK_SIZE,0.5 * BLOCK_SIZE,0.0,),),..Default::default()})
The reason the we have to do math to calculate half the width and offset is that the default meshes in bevy spawn at the (0,0)
point of the transform, which places .5 of the sprite to the left and .5 to the right of (0,0)
. To fix this and place the bottom left of the mesh at (0,0)
we can use a custom mesh.
fn make_mesh() -> Mesh {let mut mesh: Mesh =shape::Box::new(1.0, 1.0, 0.0).into();let vs =if let VertexAttributeValues::Float3(vertices) =mesh.attribute(Mesh::ATTRIBUTE_POSITION).expect("expected vertices from stdlib shape::Box",){vertices.iter().map(|v| {let mut points = v.clone();for i in 0..v.len() {points[i] = if points[i] != 0.0 {points[i] + 0.5} else {0.0};}points}).collect::<Vec<[f32; 3]>>()} else {panic!("should have worked")};mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vs);mesh}
Bevy gives us the ability to create a default Box
, which we can turn into a Mesh
for use with the SpriteBundle
. This Mesh
has an attribute called POSITION
that indicates the vertices on a unit box. The default box has vertices at places like (-0.5, -0.5, 0.0)
(it's specified in 3 dimensions even though z is always 0), which indicates that half of the box will be on one side of the x or y axis and half will be on the other side. To fix this we can map over the vertices and add 0.5
to each value that is 0.5
or -0.5
, which will put the original point (-0.5, -0.5, 0.0)
at (0.0, 0.0, 0.0)
and the farthest point, originally (0.5, 0.5, 0.0)
, at (1.0, 1.0, 0.0)
. This is what offsets the mesh against the transform.
The interior of the Vec
needs to be a 3 element array, so we clone and mutate locally
The type signature of setup()
needs to change. If you're unfamiliar this will likely feel a bit magical, as we get the meshes
argument by declaring that we want it in the setup
function arguments as ResMut<Assets<Mesh>>
. Diving into why this happens is a topic for another post.
fn setup(commands: &mut Commands,mut materials: ResMut<Assets<ColorMaterial>>,mut meshes: ResMut<Assets<Mesh>>,) {...}
We can now use our new mesh to spawn the same sized block as the black and blue blocks. This one will be pink.
We include the transform
for clarity, but the default transform is the same as this one, so we could omit it if we wanted to
.spawn(SpriteBundle {mesh: meshes.add(make_mesh()),material: materials.add(Color::PINK.into()),sprite: Sprite::new(Vec2::new(2.0 * BLOCK_SIZE,2.0 * BLOCK_SIZE,)),transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0),),..Default::default()})
We can also now confirm that a new purple 1x1 block at (0,0)
will spawn squarely in the first quadrant of the pink 2x2 block.
.spawn(SpriteBundle {mesh: meshes.add(make_mesh()),material: materials.add(Color::PURPLE.into()),sprite: Sprite::new(Vec2::new(BLOCK_SIZE, BLOCK_SIZE,)),..Default::default()})