coffee
coffee copied to clipboard
Direct spritesheet support
I currently have the following really basic function:
fn rect_from_tilemap_coord(x: u16, y: u16, tile_size: Option<u16>) -> Rectangle<u16>{
let tile_size = tile_size.unwrap_or(16);
let x = max(0, x - 1);
let y = max(0, y - 1);
return Rectangle {
x: tile_size * x + x,
y: tile_size * y + y,
width: tile_size,
height: tile_size
}
}
Which is basically a really hacky way to get a Rectangle that represents an area of a spritesheet. I'm likely to create an enum of all the sprites in the spritesheet, but I was wondering if there was some "better" way to do this in the actual engine. A SpriteSheet that wraps an Image and takes grid coords rather than a quad maybe? Or some way to load a spritesheet from which you can get the source rectangle for a given sprite?
It may be that the best way to do it is indeed for me to create myself a tilemap class, that the renderer owns and have an enum that the renderer knows how to convert into a rectangle, but I thought I'd bring this up.
A SpriteSheet
type is definitely something to consider adding to the engine.
It's not there yet because there are many different approaches we could take and I had a hard time choosing one. Thus, I preferred to leave it up to users to implement one and wait until we see a pattern or a common approach, and design based on that.
Ideally, I would like something as type-safe as possible (i.e. something that avoids referencing sprites out of bounds, like the texture_array
module). However, I am not sure if the amount of type-safety we can get is worth it in this case.
For instance, enums work well for item sprites, but once you add animation into the mix they become a hassle.
Overall, I am unsure if there is a generic approach to satisfy most use cases here without promoting bad practice. We may be missing additional concepts, maybe we need different types of sprite sheets... I will keep thinking about it!
If anyone has built something similar or has any ideas, feel free to share your thoughts here. It may help us find a good solution.
@RantingBob Meanwhile, for your use case, if you need to refer individually to each sprite, I would use an enum and have a struct wrapping an Image
or a Batch
taking only values of this enum to perform draw operations.
I don't have any ideas at this time. I'll implement something and come back to you. As an aside, Batch vs Image for spritesheets?
@RantingBob Batch
will allow you to draw many sprites at once efficiently. If you are drawing a tilemap, it's definitely the recommended approach.
I just started messing around with Coffee, but this is what I made to handle sprite sheets. Functionality is very basic, but seems to work ok. Still needs some sanity checking like making sure a requested sprite is actually within bounds or that a given image can fit the stated number of rows and columns.
use coffee::graphics::{Point, Rectangle, Image, Sprite};
//size of a single sprite in the Sprite Sheet
struct SpriteSize { pub width: u16, pub height: u16}
//An array of sprites packed into a single image, also called an Atlas.
pub struct SpriteSheet {
atlas: Image,
sprite_size: SpriteSize,
}
impl SpriteSheet {
pub fn new(image: Image, rows: u16, columns: u16) -> SpriteSheet {
let sprite_size = SpriteSize {
width: image.width() / columns,
height: image.height() / rows,
};
SpriteSheet {
atlas: image,
sprite_size,
}
}
//Extract a specific sprite from the sprite sheet
// @position: provided position of the sprite on the target screen/frame/etc.
// @row: Which row of the atlas we are requesting. Note row starts at 1
// @column: Which column of the atlas we are requesting. Note column starts at 1
// returns: Image object of full atlas, used in drawing
// Sprite object depicting a single sprite in the atlas
pub fn get_sprite(&self, position: Point, mut row: u16, mut column: u16)
-> (Image, Sprite) {
//adjust row/column for calculating sprite position in atlas
row = row - 1;
column = column - 1;
//define these for brevity
let sprite_width = self.sprite_size.width;
let sprite_height = self.sprite_size.height;
//return full atlas image and requested sprite's location in the atlas.
(self.atlas.clone(), //note coffee docs says cloning Image is very cheap
Sprite {
source: Rectangle{
x: column * sprite_width, y: row * sprite_height,
width: sprite_width, height: sprite_height,
},
position: position,
scale: (1.0,1.0) //assume normal scale, lets other code change it as needed
})
}
}
Usage in Game::draw is like this:
let position = Point::new(100.0, 100.0);
let (image, sprite) = self.player_spritesheet.get_sprite(position, 5, 1);
image.draw(sprite, &mut frame.as_target());
Feel free to use it. Might be an ok starting point.