Export Playdate sprite sheets from procedurally drawn graphics.
Roto is a utility for use with the Playdate Simulator.
Use it to generate tiled sprite sheets or image sequences from animated graphics drawn procedurally using
the PlayDate drawing APIs. These files may
then be loaded into imagetable
s
for playback in place of the procedural calls.
Consider using Roto when:
- You want to optimize graphics performance, and…
- You have already created procedurally drawn sprite graphics, or…
- Generating your graphics procedurally would be more efficient than achieving the same results by hand.
Rendering pre-drawn image sequences is significantly faster on device than rendering graphics procedurally using the Playdate APIs. However, if generating the graphics procedurally is easier (or if you've already implemented your sprites using the drawing APIs), Roto can make the process of transitioning to pre-rendered sprite sheets easy.
Roto gets its name from Rotoscoping, a 2D animation process in which animators trace over individual frames of video to produce lifelike animation. In effect, the Roto instance "traces" the graphics of your sprite each frame.
-
Download the Roto.lua file.
-
Place the file in your project directory (e.g. in the
source
directory next tomain.lua
). -
Import it in your project (e.g. from
main.lua
).import "Roto"
Using toybox.py
-
If you haven't already, download and install
toybox.py
. -
Navigate to your project folder in a Terminal window.
cd "/path/to/myProject"
-
Add Roto to your project
toybox add ebeneliason/roto toybox update
-
Then, if your code is in the
source
directory, import it as follows:import '../toyboxes/toyboxes.lua'
- Sprite Capture. Capture procedurally drawn sprite animations individually as pre-rendered sprite images.
- Start & Stop. Dynamically start and stop capture so you get only the frames you need.
- Save to File. Save captured animations as image sequences or tiled sprite sheets.
- Live Previews. Preview your captures live in the simulator. This even works with randomized drawing, so you can repeat the capture process until you see one you'd like to save.
Roto has a few known limitations:
- Dither Patterns. Any dither patterns wind up drawn with phase zero, as they begin relative to the top left corner of the captured frames. As such, any effects achieved by moving the sprite "through" a dither pattern in world space will not have any effect in the captured images.
- Transformations. Rotations (e.g.
playdate.graphics.sprite:setRotation
) or other "external" transformations or modifications applied to the sprite itself will not be reflected in the captured frames. (Any transformations or effects applied on images or objects drawn directly withindraw
are preserved.) - Performance. Using Roto may result in a slight performance impact while actively tracing, though it should be minimal. Saving may also cause a momentary hang depending on the number of captured frames.
NOTE: Roto only works within the Playdate Simulator. Attempts to utilize Roto on device will cause an assertion failure.
import "Roto"
-- Initialize a Roto instance for the sprite you wish to generate assets from
local mySprite = MySprite()
local roto = Roto(mySprite)
-- Start capturing frames, perhaps in response to a button press or state change event
roto:startTracing()
-- Let some number of frames pass…
-- Stop capturing frames, perhaps in response to a button press, event or timer
roto:stopTracing()
-- Save a sprite sheet containing the captured images
roto:saveAsMatrix("~/Desktop")
Roto makes it extremely easy to capture the rendered content of your sprites. You won't need to make any
modifications to your sprite or its draw
function. You can create a Roto instance for each sprite you
wish to capture by passing it a reference to your sprite.
local mySprite = MySprite()
local roto = Roto(mySprite)
You'll likely only want to capture a particular sequence of frames. Roto gives you full control, letting you start and stop the tracing in response to events (game state, buttons presses, keys, etc.) or timers.
roto:startTracing()
roto:stopTracing()
You can also call these functions directly on the sprite which you've initialized with Roto. This makes it easier to control from anywhere you have a reference to the sprite.
mySprite:startTracing()
mySprite:stopTracing()
Finally, if you know the number of frames you wish to capture in advance:
roto:startTracing(12) -- only capture 12 frames
mySprite:startTracing(12) -- works here, too
Save the traced frames as a matrix image table, or "sprite sheet", providing a directory in which to save the resulting image.
roto:saveAsMatrix("~/Desktop/")
The file will be named MySprite-table-<w>-<h>.png
where w and h are the width and height of each
frame cell. Note that if the sizes of the individual captured frames are not all equal to the cell size
of the matrix image, the smaller frames will be centered within their respective cells.
If you'd like to name the file something other than the sprite class name, provide a second argument with the desired prefix.
roto:saveAsMatrix("~/Desktop/", "AnotherName") -- "AnotherName-table-<w>-<h>.png"
By default this function will produce a matrix that is approximately square, according to the total number of frames. If you wish to specify the desired number of frames per row, provide a third argument.
roto:saveAsMatrix("~/Desktop/", "MySprite", 10) -- 10 frames per row
If you prefer, you can also save your captured frames as a numbered sequence of images. All resulting
image files will have names of the form MySprite-table-<N>.png
, where N is the frame number.
Frame numbers will be padded with leading zeros according to the total number of frames.
roto:saveAsSequence("~/Desktop/MySequence")
Note that all images will be saved directly into the specified directory. It's recommended that you create a new empty directory to save them into. If you specify a directory path with a trailing path segment which doesn't yet exist, it will be created for you.
If you'd like to name the files something other than the sprite class name, provide a second argument with the desired prefix.
roto:saveAsSequence("~/Desktop/", "AnotherName") -- "AnotherName-table-<n>.png"
You can load both the matrix and sequence image tables for use within your sprites. You can use the
playdate.graphics.imagetable
API to load them:
playdate.graphics.imagetable.new("Images/MySprite") -- note omission of -table-* suffix
See the Playdate SDK docs for additional details.