- 1. What is Playdate?
- 2. Contents of the SDK
- 3. Writing a game
- 4. Developing in Lua
- 5. Developing in C
- 6. API reference
- 6.1. Playdate SDK Lua enhancements
- 6.2. System and Game Metadata
- 6.3. Game flow
- 6.4. Game lifecycle
- 6.5. Interacting with the System Menu
- 6.6. Localization
- 6.7. Accessibility
- 6.8. Accelerometer
- 6.9. Buttons
- 6.10. Crank
- 6.11. Input Handlers
- 6.12. Device Auto Lock
- 6.13. Date & Time
- 6.14. Debugging
- 6.15. Profiling
- 6.16. Display
- 6.17. Easing functions
- 6.18. Files
- 6.19. Geometry
- 6.20. Graphics
- 6.21. JSON
- 6.22. Keyboard
- 6.23. Math
- 6.24. Pathfinding
- 6.25. Power
- 6.26. Simulator-only functionality
- 6.27. Sound
- 6.28. Strings
- 6.29. Timers
- 6.30. Frame timers
- 6.31. UI components
- 6.32. Garbage collection
- 7. Hidden Gems
- 8. Getting Help
- 9. Legal information
1. What is Playdate?
Playdate is a curious handheld gaming console.
Playdate players collectively share the experience of a curated selection of video games made by independent developers, revealed one at a time on a fixed schedule. A collection of these games is known as a "season", analogous to a season of a television show.
Playdate developers write their games using the simple scripting language Lua, and asset creation tools they are already familiar with.
1.1. Playdate specifications
- Display
-
-
Monochrome (1-bit) memory LCD display
-
400 x 240 pixel resolution
-
Refreshed at 30 frames-per-second (fps) by default, maximum 50 fps
-
- Controls
-
-
Eight-way directional control (D-pad)
-
Two primary buttons
-
Menu button
-
Lock button
-
Collapsible crank
-
Accelerometer
-
- Sound
-
-
Internal speaker
-
Microphone
-
Headphone jack supporting mic input
-
- Connectivity
-
-
Wi-Fi
-
Bluetooth
-
- Memory & Storage
-
-
16MB RAM
-
4GB flash storage
-
1.2. Playdate hardware naming conventions
- Lock button
-
The top-edge metal button, which sleeps and wakes the system. Referred to as the capital-L "Lock button".
- Menu button
-
The top-right button on the face of the device, with a dot in its center. This presents the System Menu. Referred to as the capital-M "Menu button".
- D-pad
-
The D is capitalized if the term is at the beginning of the sentence; otherwise, it is "d-pad".
- A button/B button
-
"A" and "B" are capitalized; the "b" in "button" is not.
- Crank
-
The action of taking out the crank is called extending the crank. Putting it away is stowing the crank. If the crank is turned in the direction shown in the illustration below, it is said to be turning forward. The opposite direction is backward.
2. Contents of the SDK
This SDK contains:
-
Software tools to compile your game
-
A device simulator to test your game
-
A set of libraries for common functions you can use in your game
-
Some fonts and other assets you can use in your game
-
Some example code and games
-
The latest firmware image for the console hardware
-
Documentation
3. Writing a game
3.1. Choosing your development language
Most Playdate games are written in Lua for ease of development, but games with the strictest performance needs can be written partially or entirely in C. See the associated sections for information on which might be the right choice for you.
3.2. Structuring your project
Place all scripts and assets together in a single project directory.
Your source directory must, at minimum, contain one Lua script called main.lua. This script can source other scripts if necessary via the import statement. The Playdate runtime uses import instead of the standard Lua require function, and it behaves a little differently: All files imported from main.lua (and imported from files imported from main.lua, and so on) are compiled into a single pdz file, and import runs the code from the file only once. A second import call from anywhere in the pdz will do nothing.
a.lua:
return "hello"
b.lua:
print("b says " .. import "a" or "nil")
main.lua:
print(import "a" or "nil")
import "b"
prints the following:
hello
b says nil
Though Lua projects can be organized in many ways, here is a suggested structure:
[myProjectName]/
source/
main.lua
...and other .lua files
images/
[myImageFile1].png
[myImageFile2].png
...and so on
sounds/
[myAudioFile1].wav
[myAudioFile2].mp3
...and other ADPCM- or MP3-formatted files
support/
Project files including Photoshop assets, project outlines, etc.
With this structure, you can do the following:
-
Import a Lua file via an
import "myLuaFile"
at the start of your file. -
Load an image with
myImage = playdate.graphics.image.new("images/myImageFile")
-
Load a sound with
mySound = playdate.sound.sampleplayer.new("sounds/mySoundFile")
-
If your project will be object-oriented, create a subclass B of class A in file
B.lua
, like so:
import "A"
class("B").extends(A)
function B:init()
B.super.init(self) -- calls superclass initializer
-- initialization code goes here
end
3.3. Compiling a project
Playdate projects are compiled with the command line tool pdc
(for "Playdate Compiler").
Set PLAYDATE_SDK_PATH
Environment Variable
On macOS, it is recommended, but not required.
On Linux, it is required for CMake and Make files, and recommended for Lua projects.
On Windows, it is required for CMake files (see the Building on Windows section in the Inside Playdate for C docs for instructions), and recommended for Lua projects
Add the following line to your shell’s startup file (~/.bash_profile or ~/.bashrc for bash, or ~/.zprofile if you use zsh, etc.). Replace <path to SDK>
placeholder text with the SDK location:
export PLAYDATE_SDK_PATH=<path to SDK>
Note
|
The pdc compiler will use this value for the default location of the SDK if it is not specified using the -sdkpath flag.
|
pdc
requires two arguments: the input (source) directory, and an output directory.
$ pdc MyGameSource MyGame.pdx
The output directory, by convention, should end with the extension .pdx. This directory will appear as a single-icon bundle in Finder. It will contain the compiled source as well as any files that weren’t recognized as Lua source, such as images, sounds, or data files.
Passing the -s
option to pdc
will strip debugging information from the output files.
3.4. Using the Playdate Simulator
The Playdate Simulator is an application that mimics the Playdate device, and makes Playdate development quick and easy. The Simulator not only runs Playdate applications, but can also emulate the functionality of Playdate’s controls, including its crank and accelerometer.
Games running in the simulator can be controlled by the on-screen GUI, or keyboard equivalents. The simulator can also be controlled by a select number of a compatible USB game controllers or the Playdate console itself, if connected.
Running your game
To run your game, take one of these three approaches:
-
Launch the Playdate Simulator app.
-
Do one of the following to choose which game to run:
-
Choose Open from the File menu to select the .pdx folder you’d like to run.
-
Drag your .pdx folder onto the Simulator window.
-
-
-
Double-click on a .pdx folder.
-
If you’re using Nova as your development environment, press Command+R to launch the Simulator and start your game.
Caution
|
Game performance is considerably faster in the Simulator than on the Playdate hardware. Please take that into consideration when developing your game, and make sure to periodically test on Playdate hardware. |
Running your game on Playdate hardware
-
Attach your Playdate to your computer via USB cable.
-
Turn on your Playdate by pushing the Unlock button on top.
-
Run your game in the Playdate Simulator.
-
Choose Upload Game to Device from the Simulator’s Device menu. After the game is uploaded to your Playdate, it will start running automatically.
Note
|
If you do not see a Device menu in the Simulator’s menubar, check to ensure your Playdate is unlocked (via the metal button on top of Playdate), powered, and properly connected to your computer via USB cable. |
Using your Playdate to control the Simulator
If you enjoy the rapid development the Playdate Simulator offers, while also wanting the tactile feel of Playdate controls, you can put your Playdate device into controller mode to control the Simulator with your Playdate hardware.
-
Attach your Playdate to your computer via USB cable.
-
Unlock your Playdate by pushing the metal Lock button on Playdate’s top edge.
-
Press the button with the little Playdate on it that will appear in the lower right corner of the Simulator window.
-
Choose Use Device as Controller in the menu that appears. Your Playdate’s inputs will now control the Simulator.
Note
|
If you do not see a button with a Playdate icon on the lower edge of the Simulator window, check to ensure your Playdate is unlocked (via the metal button on top of Playdate), powered, and properly connected to your computer via USB cable. |
3.5. Using the Nova extension
Mac users with Nova installed can make use of the Playdate syntax and extension provided by the SDK. The syntax offers autocompletion for Playdate API, and the extension allows you to compile and run your project in the simulator with a single keypress.
To install the extension:
-
Verify that Nova is installed on your Mac.
-
Find the Playdate extension in the Nova extension repository.
-
Click the "Install" button on the web page.
The best way to develop for Playdate using Nova is to create a Project for each Playdate game.
-
After creating your project, click on the project name in the top left of the window tooolbar.
-
In the Build & Run section of the resulting dialog, click the plus (+) button.
-
Choose Playdate Simulator from the list of options to create a new configuration.
-
Specify our project’s Source folder. If it is the default ./Source or ./source, then you don’t need to do anything.
-
Click Done to finish.
-
Press the Run (▶️) button in the upper left corner of the window to invoke the Playdate Simulator and run your game. (Make sure you have a main.lua file in your project.)
3.6. Game metadata
If a file named pdxinfo is present at the root of your project’s source directory, it will be used by the system to gather information about your game.
Here is a sample pdxinfo file:
name=b360
author=Panic Inc.
description=When all you have is a ton of bricks, everything looks like a paddle.
bundleID=com.panic.b360
version=1.0
buildNumber=123
imagePath=path/to/launcher/assets
launchSoundPath=path/to/launch/sound/file
The compiler will automatically copy your game’s metadata from your project folder into the resulting game. The contents of the pdxinfo file are accessible via playdate.metadata
.
A unique identifier for your game, in reverse DNS notation.
A game version number, formatted any way you wish, that is displayed to players. It is not used to compute when updates should occur.
A monotonically-increasing integer value used to indicate a unique build of your game. This is typically set using some kind of automated build process like Continuous Integration. Please do not maintain this value by hand as a mistake may lead to a game update not being recognized. If in doubt, leave it out.
Should point to either to a single card image (350 x 155 pixels), or to a directory of images that will be used by the launcher.
If using a directory, images should be named as follows:
The game’s main card image. Must be 350 x 155 pixels.
A folder of images that will be played in in a loop when your game is the highlighted game in the Launcher. Images should be named 1.png
, 2.png
, etc. Each image must be 350 x 155 pixels. This folder can optionally contain a text file called animation.txt
with the format:
loopCount = 2
frames = 1, 2, 3x4, 4x2, 5, 5
introFrames = 1, 2x2, 3, 4x2
All three lines are optional. loopCount
indicates the number of times the animation will repeat (indefinitely by default). frames
is the sequence in which the frames will be shown. Add an x#
after the frame image number to repeat the image for multiple animation frames. introFrames
is a sequence of frames that will play once before the frames
sequence begins, when the card is first highlighted. If a frame sequence is not specified, images will play in order from 1 to the last sequentially numbered image found.
Displayed on button down. Must be 350 x 155 pixels.
An image that displays while your game is loading, before it is responsive. Must be fullscreen 400 x 240 pixels, and should not contain transparency. Not necessary if launchImages
are used, but will be added as the last frame in the game launch animation if they are.
A folder of images (named 1.png, 2.png, …) that will be played as a transition animation when your game is launched. Images can contain transparency, but should all be 400 x 240 pixels. See the provided sample game Level 1-1 for an example. Before the game launch animation your game’s card image (or card-highlighted, or card-pressed image, if available) is drawn by the launcher centered on the screen, drawn in the rect (25, 43, 350, 155) so your animation should assume that image with transparent surrounding space as a starting frame.
Optional, but if present, will be used as the pattern for the wrapping paper on newly-downloaded games that have yet to be unwrapped. The image dimensions should be 400 x 240 pixels.
At minimum, all games should include card.png and a launchImage.png which will allow the launcher to provide a default transition animation when the game is started.
Optional. Should point to the path of a short audio file to be played as the game launch animation is taking place.
3.7. Saving game state
In most games, your users will expect that if they exit your game and come back, they’ll find the game in the same — or similar — state as when they left it.
To implement basic state saving functionality, do the following:
-
Write a function that saves pertinent game data into a table.
-
Serialize your table to a playdate.datastore. (If you need greater flexibility, you can use any of Playdate’s File APIs.)
-
Implement the functions playdate.gameWillTerminate() and playdate.deviceWillSleep() and invoke your
saveGameData
function in each. -
Write code that executes near the beginning of your game that will load game state data from your datastore into a table. Populate your game structures with the saved data in the table.
3.8. Localization
Localization in Playdate is achieved through the use of string lookup files. Currently, English and Japanese are supported. The files should be called en.strings and jp.strings respectively and should be placed in the root of the game’s source folder.
The format of a .strings file is as follows:
"greeting" = "Howdy" "farewell" = "Goodbye" -- comments are allowed "video game" = "video game"
The corresponding jp.strings file would be:
"greeting" = "こんにちは" "farewell" = "さようなら" -- comments are allowed "video game" = "ビデオゲーム"
Refer to the API reference for how to retrieve or draw localized text.
3.9. Game size
Playdate has 4GB of flash storage. While that is a decent amount, it isn’t inexhaustible.
What’s a good size for a Playdate game? From what we’ve seen so far, a typical Playdate game might be in the 20-40MB range. Some — primarily those that use synthesized audio — are much smaller, even less than 100KB. Large games with a lot of audio can grow to be 100MB or more.
Out of respect for Playdate owners, we ask that you try to keep your games closer to that average size of 20-40MB. (Of course, you can make your game as big as you want — and maybe there is some spectacular 400MB game out there just waiting to be written. Shy of that, however, we — and the Playdate owners you’re targeting — would prefer it if you keep the size down.)
The biggest culprit in blowing up game size is audio. If your game is large due to the inclusion of a lot of audio, we recommend:
-
Ensuring your audio is compressed. See here for some tips.
-
If your audio is already compressed, consider synthesized audio, using the rich set of APIs provided. Or consider simply using less audio.
4. Developing in Lua
Lua is a great language for writing Playdate games. Its easy to use, and enables speedy development. Lua’s main drawback is performance, including sporadic hits due to garbage collection. For games with moderate performance requirements, these drawbacks should be manageable.
Your game can use any of Lua’s standard features. Please refer to the Lua 5.4 manual for detailed information on the language itself.
Our build of the Lua runtime is configured to use 32-bit numbers.
4.1. A Basic Playdate Game in Lua
To showcase basic Playdate API features, we’ll implement a little game in Lua. (You can code this in C if you want as well — the concepts are similar.) All this game does is display a sprite on a background. The sprite can be moved by pressing on the Playdate’s d-pad.
And that’s it! But there’s hopefully enough here to provide a good framework for your own game.
-- Name this file `main.lua`. Your game can use multiple source files if you wish
-- (use the `import "myFilename"` command), but the simplest games can be written
-- with just `main.lua`.
-- You'll want to import these in just about every project you'll work on.
import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"
-- Declaring this "gfx" shorthand will make your life easier. Instead of having
-- to preface all graphics calls with "playdate.graphics", just use "gfx."
-- Performance will be slightly enhanced, too.
-- NOTE: Because it's local, you'll have to do it in every .lua source file.
local gfx <const> = playdate.graphics
-- Here's our player sprite declaration. We'll scope it to this file because
-- several functions need to access it.
local playerSprite = nil
-- A function to set up our game environment.
function myGameSetUp()
-- Set up the player sprite.
-- The :setCenter() call specifies that the sprite will be anchored at its center.
-- The :moveTo() call moves our sprite to the center of the display.
local playerImage = gfx.image.new("Images/playerImage")
assert( playerImage ) -- make sure the image was where we thought
playerSprite = gfx.sprite.new( playerImage )
playerSprite:moveTo( 200, 120 ) -- this is where the center of the sprite is placed; (200,120) is the center of the Playdate screen
playerSprite:add() -- This is critical!
-- We want an environment displayed behind our sprite.
-- There are generally two ways to do this:
-- 1) Use setBackgroundDrawingCallback() to draw a background image. (This is what we're doing below.)
-- 2) Use a tilemap, assign it to a sprite with sprite:setTilemap(tilemap),
-- and call :setZIndex() with some low number so the background stays behind
-- your other sprites.
local backgroundImage = gfx.image.new( "Images/background" )
assert( backgroundImage )
gfx.sprite.setBackgroundDrawingCallback(
function( x, y, width, height )
gfx.setClipRect( x, y, width, height ) -- let's only draw the part of the screen that's dirty
backgroundImage:draw( 0, 0 )
gfx.clearClipRect() -- clear so we don't interfere with drawing that comes after this
end
)
end
-- Now we'll call the function above to configure our game.
-- After this runs (it just runs once), nearly everything will be
-- controlled by the OS calling `playdate.update()` 30 times a second.
myGameSetUp()
-- `playdate.update()` is the heart of every Playdate game.
-- This function is called right before every frame is drawn onscreen.
-- Use this function to poll input, run game logic, and move sprites.
function playdate.update()
-- Poll the d-pad and move our player accordingly.
-- (There are multiple ways to read the d-pad; this is the simplest.)
-- Note that it is possible for more than one of these directions
-- to be pressed at once, if the user is pressing diagonally.
if playdate.buttonIsPressed( playdate.kButtonUp ) then
playerSprite:moveBy( 0, -2 )
end
if playdate.buttonIsPressed( playdate.kButtonRight ) then
playerSprite:moveBy( 2, 0 )
end
if playdate.buttonIsPressed( playdate.kButtonDown ) then
playerSprite:moveBy( 0, 2 )
end
if playdate.buttonIsPressed( playdate.kButtonLeft ) then
playerSprite:moveBy( -2, 0 )
end
-- Call the functions below in playdate.update() to draw sprites and keep
-- timers updated. (We aren't using timers in this example, but in most
-- average-complexity games, you will.)
gfx.sprite.update()
playdate.timer.updateTimers()
end
Playdate’s API is exposed in a Lua namespace called playdate
. Our API is explained in detail later in this document.
4.2. Playdate Lua API conventions
Arrays
By convention, Lua arrays are 1-indexed. It is recommended that you follow this idiom to avoid confusion with other Lua code. (Arrays are in fact implemented as a specialized form of tables, which are the only container type in Lua. One of the most notable consequences of this is that arrays cannot contain nil values, since a nil value represents the end of the array.)
Calling functions
Class/table functions are invoked with a period, as in myTable.function(a, b, c)
.
Instance functions are invoked with a colon, as in myObject:function(a, b, c)
.
Why is this? The colon version passes the table itself as an implicit first argument to the function. This is generally used to simulate the object-oriented programming concept of "self" in Lua, which does not have "objects", "classes" or any other OOP affordances built-in. Again, we recommend you review the official Lua 5.4 reference manual to make sure you understand the difference. Playdate, through CoreLibs, provides a simple implementation of Lua "objects" which you can choose to use or not use as you see fit.
Caution
|
Confusing these two invocation methods can result in very-difficult-to-track-down bugs, so be careful! |
Return values
Some APIs return objects (really Lua tables), and others return tuples. Make sure you know what type of value is being returned!
-- returns a rect object
r1 = playdate.geometry.rect.new(5, 5, 10, 10)
r2 = playdate.geometry.rect.new(8, 8, 10, 10)
intersection = r1:intersection(r2)
print(intersection.x, intersection.y, intersection.width, intersection.height)
-- returns a rect tuple
x, y, w, h = playdate.geometry.rect.fast_intersection(5, 8, 5, 8, 10, 10, 10, 10)
print(x, y, w, h)
Tip
|
You can use the Lua function table.unpack(table) to turn any table’s values into a tuple.
|
4.3. Lua Tips
Initialize variables with local
You should almost always use local
in your variable initializers to narrow your variable’s scope to the current block. Not doing this will perhaps unnecessarily broaden your variable’s scope. Also, globals are slower to access during runtime than locals.
function MyClass:myFunction(a, b, c)
-- You probably want to do this:
local x, y, z = a*a, b*b, c*c
-- …and not this. Here, x, y, and z are defined as globals.
x, y, z = a*a, b*b, c*c
return x, y, z
end
Assign frequently-used objects to local variables
If you are frequently accessing playdate API objects like playdate.graphics
, performance will increase by assigning that object to a local variable at the beginning of your source file. (It’ll also make your code less verbose.) So instead of this:
playdate.graphics.setColor(playdate.graphics.kColorWhite)
playdate.graphics.drawRect(14, 14, 22, 22)
playdate.graphics.setColor(playdate.graphics.kColorBlack)
playdate.graphics.fillRect(15, 15, 20, 20)
Do this:
local gfx <const> = playdate.graphics -- do this at the top of your source file
-- (<const> is a Lua constant declaration that will improve performance slightly)
...
gfx.setColor(gfx.kColorWhite)
gfx.drawRect(14, 14, 22, 22)
gfx.setColor(gfx.kColorBlack)
gfx.fillRect(15, 15, 20, 20)
…and you’ll only be doing one look-up of the playdate
and graphics
objects in the global namespace, instead of six.
4.4. Object-oriented programming in Lua
Lua does not offer built-in support for object-oriented programming of any kind. Some developers like to use language extensions to provide an "OOP-like" environment, but you should have an understanding of what is happening behind the scenes.
This is purely a personal preference. There is no need for you to use object-oriented programming techniques, unless you want to.
CoreLibs provides a basic object-oriented class system. Object is the base class all new subclasses inherit from.
Important
|
You must import CoreLibs/object to use these functions. |
New Object subclasses can be created as follows:
class(ClassName, [properties], [namespace]).extends(ParentClass)
Where properties
is a table of default key/value pairs for the class. If a parent class is not provided, Object will be used.
So, to create a Tree
class:
class('Tree').extends()
or
class('Tree', {color = 'Brown'}).extends(Object)
And to create a subclass of Tree
:
class('Oak').extends(Tree)
Classes are provided with an init function. The subclass decides how many and what type of arguments its init function takes:
function Oak:init(age, height)
Oak.super.init(self, age)
self.height = height
end
The init function will normally want to call its superclass’s implementation of init and must use the syntax above. (Calling Oak.super:init(age)
would pass super as self, which will lead to incorrect behavior.)
Instances of a class are created by calling the class as a function:
oakInstance = Oak(age, height)
Class names can be accessed via the className property:
oakInstance.className -- equals 'Oak'
The base Object class defines an isa()
function:
oakInstance:isa(Tree) -- returns true
A debugging function Object:tableDump([indent], [table])
is provided to print all key/value pairs from the object and its superclasses.
oakInstance:tableDump()
4.5. CoreLibs
In addition to the default Playdate functions, a set of optional utility libraries named CoreLibs is available for you to use. CoreLibs provides functionality for such things as managing sprites, handling timers, animation curves, collision detection, and more.
CoreLibs is itself written in Lua and can be inspected in the SDK in the CoreLibs directory. Documentation for each of the CoreLibs is detailed later in this document. Use of each of the CoreLibs requires an import "CoreLibs/[nameOfLibrary]"
in your game source file, and will be noted when necessary.
5. Developing in C
If your Playdate game requires maximum performance, C is the best choice.
Parts of your game, or the entire game if desired, can be written in C using the Playdate C API. For details, see Inside Playdate with C. There are also a few examples in the C_API/Examples folder that should help get you started.
We are still in the process of adding more functions to the C API, and creating more examples.
6. API reference
6.1. Playdate SDK Lua enhancements
Additional assignment operators
Lua does not by default support assignment operators like +=
and -=
that are common in other languages. As a convenience for developers, the Playdate SDK adds the following:
|
Addition |
|
Subtraction |
|
Multiplication |
/= |
Division |
//= |
Integer division |
%= |
Modulo |
<<= |
Shift left |
>>= |
Shift right |
&= |
Bitwise AND |
|= |
Bitwise OR |
^= |
Exponent (not bitwise XOR) |
Table additions
The Playdate SDK offers some convenience functions for handling Lua tables, beyond what is available in Lua itself:
Returns the first index of element in the given array-style table. If the table does not contain element, the function returns nil.
Returns the size of the given table as a tuple (arrayCount, hashCount).
Returns a new Lua table with the array and hash parts preallocated to accommodate arrayCount and hashCount elements respectively.
Tip
|
If you can make a decent estimation of how big your table will need to be, table.create() can be much more efficient than the alternative, especially in loops. For example, if you know your array is always going to contain approximately ten elements, say myArray = table.create( 10, 0 ) instead of myArray = {} .
|
shallowCopy
returns a shallow copy of the source table. If a destination table is provided, it copies the contents of source into destination and returns destination. The copy will contain references to any nested tables.
deepCopy
returns a deep copy of the source table. The copy will contain copies of any nested tables.
6.2. System and Game Metadata
Returns two values, the current API version of the Playdate runtime and the minimum API version supported by the runtime.
The playdate.metadata
table contains the values in the current game’s pdxinfo file, keyed by variable name. To retrieve the version number of the game, for example, you would use playdate.metadata.version
.
Changing values in this table at run time has no effect.
6.3. Game flow
Callbacks
Implement this callback and Playdate OS will call it once per frame. This is the place to put the main update-and-draw code for your game. Playdate will attempt to call this function by default 30 times per second; that value can be changed by calling playdate.display.setRefreshRate().
Note
|
If your update() function takes too long to execute, Playdate OS may not be able to call it as often as specified by the current refresh rate. In this case, Playdate OS will simply try and call it as often as it can, with a not-to-exceed rate of playdate.display.getRefreshRate() frames per second.
|
Coroutines and playdate.update()
If you are familiar with Lua coroutines, it’s useful to know that playdate.update()
is invoked as a coroutine. This allows you to call coroutine.yield()
during execution of lengthy processes inside update()
, facilitating more frequent screen updates by Playdate OS.
For example, while loading assets at the beginning of execution, you could use coroutines to aid in displaying a progress bar:
local allImagesProcessed = false
-- our main update function, called every 0.033 seconds by Playdate OS.
function playdate.update()
if allImagesProcessed == false then
-- process images
for i = 1, #images do
-- some time-consuming process…
processImage( images[i] )
-- draw a progress bar
local progressPercentage = i / #images
playdate.graphics.fillRect( 100, 20, 200*progressPercentage, 40 )
-- yield to the OS, giving it a chance to update the screen
coroutine.yield()
-- execution will resume here when the OS calls coroutine.resume()
end
allImagesProcessed = true
else
-- main game update and drawing code
end
end
As an exercise, it’s worth removing the coroutine.yield()
call from the above code to see how its execution differs. (Spoiler: all images will be processed before there are any screen redraws, rendering the progress bar useless.) It’s also an interesting exercise to attempt code that performs the same as above without use of the .yield()
function. In this simple case it’s not terribly difficult — you need to save off your loop’s state after each iteration — but the code is messier, and it can quickly get unwieldy in more complex cases. .yield()
makes things much easier.
For more on coroutine usage in games, view this tutorial.
Functions
Suspends callbacks to playdate.update()
for the specified number of milliseconds.
Tip
|
playdate.wait() is ideal for pausing game execution to, for example, show a message to the player. Because .update() will not be called, the screen will freeze during .wait() . Audio will continue to play. Animation during this wait period is possible, but you will need to explicitly call playdate.display.flush() once per frame.
|
Caution
|
While timers should pause during playdate.wait() (assuming playdate.timer.updateTimers() and playdate.frameTimer.updateTimers() are invoked during playdate.update() ), animators will not pause during playdate.wait() . Be sure to account for this in your code.
|
Stops per-frame callbacks to playdate.update(). Useful in conjunction with playdate.display.flush() if your program only does things in response to button presses.
Resumes per-frame callbacks to playdate.update().
6.4. Game lifecycle
Called when the player chooses to exit the game via the System Menu or Menu button.
Called before the device goes to low-power sleep mode because of a low battery.
Important
|
If your game saves its state, playdate.gameWillTerminate() and playdate.deviceWillSleep() are good opportunities to do it.
|
If your game is running on the Playdate when the device is locked, this function will be called. Implementing this function allows your game to take special action when the Playdate is locked, e.g., saving state.
If your game is running on the Playdate when the device is unlocked, this function will be called.
Called before the system pauses the game. (In the current version of Playdate OS, this only happens when the device’s Menu button is pushed.) Implementing these functions allows your game to take special action when it is paused, e.g., updating the menu image.
Called before the system resumes the game.
6.5. Interacting with the System Menu
Your game can add up to three menu items to the System Menu. Three types of menu items are supported: normal action menu items, checkmark menu items, and options menu items.
Important
|
When titling your System Menu additions, try to give them names that demonstrate that they pertain to your game, and not Playdate overall. For example, the title "restart game" will be clearer to the player than just "restart" — the player might assume the latter will restart the hardware. |
local menu = playdate.getSystemMenu()
local menuItem, error = menu:addMenuItem("Item 1", function()
print("Item 1 selected")
end)
local checkmarkMenuItem, error = menu:addCheckmarkMenuItem("Item 2", true, function(value)
print("Checkmark menu item value changed to: ", value)
end)
When this menu item is selected, the OS will:
-
Hide the System Menu.
-
Invoke your
callback
function. -
Unpause your game and call playdate.gameWillResume.
If the returned
menuItem
is nil, a seconderrorMessage
return value will indicate the reason the operation failed.
While the game is paused it can optionally provide an image to be displayed alongside the System Menu. Use this function to set that image.
image should be a 400 x 240 pixel playdate.graphics.image. All important content should be in the left half of the image in an area 200 pixels wide, as the menu will obscure the rest. The right side of the image will be visible briefly as the menu animates in and out.
Optionally, xOffset can be provided which must be a number between 0 and 200 and will cause the menu image to animate to a position offset left by xOffset pixels as the menu is animated in.
To remove a previously-set menu image, pass nil
for the image argument.
6.6. Localization
Returns the current language of the system, which will be one of the constants playdate.graphics.font.kLanguageEnglish or playdate.graphics.font.kLanguageJapanese.
6.7. Accessibility
Returns true if the user has checked the "Reduce Flashing" option in Playdate Settings; false otherwise. Games should read this value and, if true, avoid visuals that could be problematic for people with sensitivities to flashing lights or patterns.
Returns true if the user has checked the "Upside Down" option in Playdate Settings; false otherwise. (Upside Down mode can be convenient for players wanting to hold Playdate upside-down so they can use their left hand to operate the crank.)
Typically your game doesn’t need to anything in regards to this setting. But it is available in case your game wants to take some special actions, display special instructions, etc.
Important
|
Reported d-pad directions are flipped when in Upside Down mode — RIGHT will be reported as LEFT, UP as DOWN, etc. — so that the d-pad will make sense to a user holding Playdate upside-down. However, the A and B buttons — since they are still labeled as "A" and "B" — retain their normal meanings and will be reported as usual. |
6.8. Accelerometer
The accelerometer is off by default, to save a bit of power. If you will be using the accelerometer in your game, you’ll first need to call playdate.startAccelerometer()
then wait for the next update cycle before reading its values. If you won’t be using the accelerometer again for a while, calling playdate.stopAccelerometer()
will put it back into a low-power idle state.
The accelerometer is off by default, to save a bit of power. If you will be using the accelerometer in your game, you’ll first need to call playdate.startAccelerometer()
then wait for the next update cycle before reading its values. If you won’t be using the accelerometer again for a while, calling playdate.stopAccelerometer()
will put it back into a low-power idle state.
If the accelerometer has been turned on with playdate.startAccelerometer(), returns the x, y, and z values from the accelerometer as a tuple. Positive x points right, positive y points to the bottom of the screen, and positive z points through the screen away from the viewer. For example, with the device held upright this function returns the tuple (0,1,0). With it flat on its back, it returns (0,0,1).
Returns true if the accelerometer is currently running.
6.9. Buttons
There are several different methods for determining button presses.
Querying buttons directly
Button callbacks
Playdate will attempt to call the following functions in your script when input events occur:
Called immediately after the player presses the A Button.
Called after the A Button is held down for one second. This can be used for secondary actions (e.g., displaying a game world map, changing weapons).
Called immediately after the player releases the A Button.
Called immediately after the player presses the B Button.
Called after the B Button is held down for one second. This can be used for secondary actions (e.g., displaying a game world map, changing weapons).
Called immediately after the player releases the B Button.
Called immediately after the player presses the down direction on the d-pad.
Called immediately after the player releases the down direction on the d-pad.
Called immediately after the player releases the left direction on the d-pad.
Called immediately after the player releases the left direction on the d-pad.
Called immediately after the player presses the right direction on the d-pad.
Called immediately after the player releases the right direction on the d-pad.
Called immediately after the player presses the up direction on the d-pad.
Called immediately after the player releases the up direction on the d-pad.
Input handlers
Button interactions can also be observed via input handlers.
6.10. Crank
Reading crank input
There are multiple ways to determine how the player is interacting with the crank control:
Querying crank status directly
Returns a boolean indicating whether or not the crank is folded into the unit.
Tip
|
If your game requires the crank and :isCrankDocked() is true, you can use a crank alert to notify the user that the crank should be extended.
|
Returns the absolute position of the crank (in degrees). Zero is pointing straight up parallel to the device. Turning the crank clockwise (when looking at the right edge of an upright device) increases the angle, up to a maximum value 359.9999. The value then resets back to zero as the crank continues its rotation.
Returns two values, change and acceleratedChange. change represents the angle change (in degrees) of the crank since the last time this function (or the playdate.cranked() callback) was called. Negative values are anti-clockwise. acceleratedChange is change multiplied by a value that increases as the crank moves faster, similar to the way mouse acceleration works.
Returns the number of "ticks" — whose frequency is defined by the value of ticksPerRevolution — the crank has turned through since the last time this function was called. Tick boundaries are set at absolute positions along the crank’s rotation. Ticks can be positive or negative, depending upon the direction of rotation.
For example, say you have a movie player and you want your movie to advance 6 frames for every one revolution of the crank. Calling playdate.getCrankTicks(6)
during each update will give you a return value of 1 as the crank turns past each 60 degree increment. (Since we passed in a 6, each tick represents 360 ÷ 6 = 60 degrees.) So getCrankTicks(6)
will return a 1 as the crank turns past the 0 degree absolute position, the 60 degree absolute position, and so on for the 120, 180, 240, and 300 degree positions. Otherwise, 0 will be returned. (-1 will be returned if the crank moves past one of these mentioned positions while going in a backward direction.)
Important
|
You must import CoreLibs/crank to use getCrankTicks() .
|
Crank callbacks
For playdate.cranked(), change is the angle change in degrees. acceleratedChange is change multiplied by a value that increases as the crank moves faster, similar to the way mouse acceleration works. Negative values are anti-clockwise.
This function, if defined, is called when the crank is docked.
This function, if defined, is called when the crank is undocked.
Input handlers
Crank interactions can also be observed via input handlers.
Crank sounds
True disables the default crank docking/undocking sound effects. False re-enables them. Useful if the crank sounds seem out-of-place in your game.
Note
|
When your game terminates, crank sounds will automatically be re-enabled. |
6.11. Input Handlers
The InputHandlers architecture allows you to push and pop a series of playdate.inputHandler
objects, each capable of handling any or all button and crank interactions. New input is propagated down the stack until it finds the first responder (or drops it altogether), which allows for switching out control schemes and temporarily stealing focus.
You can define an inputHandler as in the sample below, implementing just as few or as many handler functions as you want.
Note
|
An inputHandlers object is just an ordinary Lua table. No subclassing is required. |
local myInputHandlers = {
AButtonDown = function()
-- do stuff
end,
cranked = function(change, acceleratedChange)
-- do other stuff
end,
-- etc.
}
…and later, put them into effect by pushing them on the stack:
playdate.inputHandlers.push(myInputHandlers)
-- myInputHandlers are in effect
playdate.inputHandlers.pop()
-- original handlers are back now
The following functions can be defined in your custom inputHandlers table:
-
AButtonDown()
-
AButtonHeld()
-
AButtonUp()
-
BButtonDown()
-
BButtonHeld()
-
BButtonUp()
-
downButtonDown()
-
downButtonUp()
-
leftButtonDown()
-
leftButtonUp()
-
rightButtonDown()
-
rightButtonUp()
-
upButtonDown()
-
upButtonUp()
-
cranked(change, acceleratedChange)
For definitions of how each of these functions works, see Button Callbacks.
Note
|
Since the playdate table is always at the bottom of the stack, existing playdate.BButtonDown definitions will work out of the box. |
Pushes a new input handler onto the stack.
-
handler: A table containing one or more custom input functions.
-
masksPreviousHandlers: If true, input functions not defined in handler will not be called. If missing or false, the previously-pushed input handler tables will be searched for input functions missing from handler, cascading down to the default
playdate
table.
Pops the last input handler off of the stack.
6.12. Device Auto Lock
Playdate will automatically lock if the user doesn’t press any buttons or use the crank for more than 60 seconds. In order for games that expect longer periods without interaction to continue to function, it is possible to manually disable the auto lock feature.
True disables the 60-second auto-lock feature. False re-enables it and resets the timer back to 60 seconds.
Note
|
Auto-lock will automatically be re-enabled when your game terminates. |
Tip
|
If disabling auto-lock, developers should look for opportunities to re-enable auto-lock when appropriate. (For example, if your game is an MP3 audio player, auto-lock could be re-enabled when the user pauses the audio.) |
6.13. Date & Time
Returns the number of milliseconds the game has been active since launched.
Important
|
When the game is not active — say, when the System Menu is visible, or when the Playdate is locked — that time is not counted by playdate.getCurrentTimeMilliseconds() .
|
Resets the high-resolution timer.
Returns the number of seconds since playdate.resetElapsedTime()
was called. The value is a floating-point number with microsecond accuracy.
Returns the number of seconds and milliseconds elapsed since midnight (hour 0), January 1 2000 UTC, as a tuple: (seconds, milliseconds). This function is suitable for seeding the random number generator:
math.randomseed(playdate.getSecondsSinceEpoch())
Returns a table with values for the local time, accessible via the following keys:
-
year: 4-digit year (until 10,000 AD)
-
month: month of the year, where 1 is January and 12 is December
-
day: day of the month, 1 - 31
-
weekday: day of the week, where 1 is Monday and 7 is Sunday
-
hour: 0 - 23
-
minute: 0 - 59
-
second: 0 - 59 (or 60 on a leap second)
-
millisecond: 0 - 999
Returns a table in the same format as playdate.getTime(), but in GMT rather than local time.
Returns the number of seconds and milliseconds between midnight (hour 0), January 1 2000 UTC and time, specified in local time, as a tuple: (seconds, milliseconds).
time should be a table of the same format as the one returned by playdate.getTime().
Returns the number of seconds and milliseconds between midnight (hour 0), January 1 2000 UTC and time, specified in GMT time, as a tuple: (seconds, milliseconds).
time should be a table of the same format as the one returned by playdate.getTime().
Converts the epoch to a local date and time table, in the same format as the table returned by playdate.getTime().
Converts the epoch to a GMT date and time table, in the same format as the table returned by playdate.getTime().
6.14. Debugging
Note that some simulator-only functions may also provide assistance in debugging.
Text output from print()
will be displayed in the simulator’s console, in black if generated by a game running in the simulator or in blue if it’s coming from a plugged-in Playdate device. Printed text is also copied to stdout, which is helpful if you run the simulator from the command line.
Tip
|
You should ideally remove debugging print statements from your final games to improve performance. |
Text output from printTable()
will be displayed in the simulator’s console, in black if generated by a game running in the simulator or in blue if it’s coming from a plugged-in Playdate device. Printed text is also copied to stdout, which is helpful if you run the simulator from the command line.
Important
|
You must import CoreLibs/object to use printTable .
|
Tip
|
You should ideally remove debugging print statements from your final games to improve performance. |
If the simulator is launched from the command line, any extra arguments passed there are available in the playdate.argv
array.
flag determines whether or not the print() function adds a newline to the end of the printed text. Default is true.
Calculates the current frames per second and draws that value at x, y.
Caution
|
Make sure to invoke drawFPS() only once per frame, otherwise its displayed results will be incorrect.
|
Returns a single-line stack trace as a string. For example:
main.lua:10 foo() < main.lua:18 (from C)
Use print(where())
to see this trace written to the console.
Important
|
You must import CoreLibs/utilities/where to use this function. |
6.15. Profiling
Suspect some code is running hot? Wrap it in an anonymous function and pass it to sample()
like so:
sample("name of this sample", function()
-- nested for loops, lots of table creation, member access...
end)
By moving around where you start and end the anonymous function in your code, you can get a better idea of where the problem lies.
Multiple code paths can be sampled at once by using different names for each sample.
Important
|
You must import CoreLibs/utilities/sampler to use this function. |
Returns a table containing percentages for each system task, such as:
{ "kernel"=0.23, "game"=0.62, "audio"=0.15 }
Important
|
playdate.getStats() only functions on a Playdate device. In the Simulator, this function returns nil .
|
setStatsInterval()
sets the length of time for each sample frame of runtime stats. Set seconds to zero to disable stats collection.
Using the Simulator
Profiling performance
-
Press the Sampler button.
-
The Sampler window appears.
Choose whether you want to sample:
-
Simulator performance in Lua code
-
Device performance in Lua code
-
Device performance in C code
-
-
Press the
Sample
button in the upper right corner to start.
Profiling memory usage
-
Press the Memory button.
-
The Memory window appears:
NoteThe first item displayed, _G
, is the table where Lua stores global variables.
Profiling malloc calls in the Simulator
-
From the Simulator menubar, choose 16MB from the Playdate → Malloc Pool menu.
-
From the Simulator menubar, choose Malloc Log from the Window menu.
-
To make your life easier, click on the Autorefresh checkbox at the bottom of the window.
-
There’s also a Map mode. See below.
Figure 1. Gray denotes the total 16MB memory space; white is the total amount of heap allocated so far; purple — which overlaps the white region — is currently active or "in-use" memory.
Profiling malloc calls on the Device
Note
|
This functionality is currently available only in the MacOS Simulator, but will be made available for the Windows and Linux Simulators as well. |
-
From the Simulator menubar, choose Device Info from the Window menu.
-
In the Device Info window you can observe frames per second data, CPU usage data, and total memory usage.
NoteThe dark yellow region shows the how much time the Lua runtime is spending doing garbage collection (GC). Large amounts of time spent in GC does not necessarily mean your game has a problem: if your game doesn’t use all of its allotted CPU, the Lua runtime will try and grab as much time as it can for GC, causing the GC percentage to balloon. See Garbage Collection for details on how to modify the behavior of the garbage collector, including having it run for a shorter amount of time. -
Click "Map" to see the memory map:
6.16. Display
The playdate.display module contains functions pertaining to Playdate’s screen. Functions related to drawing can be found in playdate.graphics.
Display updating
Sets the nominal refresh rate in frames per second. The default is 30 fps, which is a recommended figure that balances animation smoothness with performance and power considerations. Maximum is 50 fps.
If rate is 0, playdate.update() is called as soon as a frame buffer is available. Since the display refreshes line-by-line, and unchanged lines aren’t sent to the display, the update cycle will be faster than 30 times a second but at an indeterminate rate. playdate.getCurrentTimeMilliseconds() should then be used as a steady time base.
Gets the nominal refresh rate in frames per second.
Sends the contents of the frame buffer to the display immediately. Useful if you have called playdate.stop() to disable update callbacks in, say, the case where your app updates the display only in reaction to button presses.
Other display properties
Returns the height the Playdate display, taking the current display scale into account; e.g., if the scale is 2, the values returned will be based off of a 200 x 120-pixel screen rather than the native 400 x 240. (See playdate.display.setScale().)
Returns the width the Playdate display, taking the current display scale into account; e.g., if the scale is 2, the values returned will be based off of a 200 x 120-pixel screen rather than the native 400 x 240. (See playdate.display.setScale().)
Returns a tuple of (width, height) about the Playdate display size. Takes the current display scale into account; e.g., if the scale is 2, the values returned will be based off of a 200 x 120-pixel screen rather than the native 400 x 240. (See playdate.display.setScale().)
Returns a tuple of (x, y, width, height) about the Playdate display size.Takes the current display scale into account; e.g., if the scale is 2, the values returned will be based off of a 200 x 120-pixel screen rather than the native 400 x 240. (See playdate.display.setScale().)
Sets the display scale factor. Valid values for scale are 1, 2, 4, and 8.
The top-left corner of the frame buffer is scaled up to fill the display; e.g., if the scale is set to 4, the pixels in rectangle [0,100] x [0,60] are drawn on the screen as 4 x 4 squares.
Gets the display scale factor. Valid values for scale are 1, 2, 4, and 8.
If the argument passed to setInverted()
is true, the frame buffer will be drawn inverted (everything onscreen that was black will now be white, etc.)
If getInverted()
returns true, the frame buffer will be drawn inverted (everything onscreen that was black will now be white, etc.)
Adds a mosaic effect to the display. Valid x and y values are between 0 and 3, inclusive.
Returns a tuple (x, y).
Offsets the entire display by x, y. Offset values can be negative. The "exposed" part of the display is black or white, according to the value set in playdate.graphics.setBackgroundColor(). This is an efficient way to make a "shake" effect without redrawing anything.
Caution
|
This function is different from playdate.graphics.setDrawOffset(). |
getOffset()
returns the current display offset as a tuple (x, y).
Flips the display on the x or y axis, or both.
Caution
|
Function arguments are booleans, and in Lua 0 evaluates to true .
|
Displaying an image
The simplest method for putting an image on the display. Copies the contents of the image at path directly to the frame buffer. The image must be 400x240 pixels with no transparency.
Tip
|
Loading an image via playdate.graphics.image.new() and drawing it at a desired coordinate with playdate.graphics.image:draw() offers more flexibility. |
6.17. Easing functions
A set of easing functions to aid with animation timing.
Important
|
You must import CoreLibs/easing to use these functions. |
playdate.easingFunctions.inQuad(t, b, c, d)
playdate.easingFunctions.outQuad(t, b, c, d)
playdate.easingFunctions.inOutQuad(t, b, c, d)
playdate.easingFunctions.outInQuad(t, b, c, d)
playdate.easingFunctions.inCubic(t, b, c, d)
playdate.easingFunctions.outCubic(t, b, c, d)
playdate.easingFunctions.inOutCubic(t, b, c, d)
playdate.easingFunctions.outInCubic(t, b, c, d)
playdate.easingFunctions.inQuart(t, b, c, d)
playdate.easingFunctions.outQuart(t, b, c, d)
playdate.easingFunctions.inOutQuart(t, b, c, d)
playdate.easingFunctions.outInQuart(t, b, c, d)
playdate.easingFunctions.inQuint(t, b, c, d)
playdate.easingFunctions.outQuint(t, b, c, d)
playdate.easingFunctions.inOutQuint(t, b, c, d)
playdate.easingFunctions.outInQuint(t, b, c, d)
playdate.easingFunctions.inSine(t, b, c, d)
playdate.easingFunctions.outSine(t, b, c, d)
playdate.easingFunctions.inOutSine(t, b, c, d)
playdate.easingFunctions.outInSine(t, b, c, d)
playdate.easingFunctions.inExpo(t, b, c, d)
playdate.easingFunctions.outExpo(t, b, c, d)
playdate.easingFunctions.inOutExpo(t, b, c, d)
playdate.easingFunctions.outInExpo(t, b, c, d)
playdate.easingFunctions.inCirc(t, b, c, d)
playdate.easingFunctions.outCirc(t, b, c, d)
playdate.easingFunctions.inOutCirc(t, b, c, d)
playdate.easingFunctions.outInCirc(t, b, c, d)
playdate.easingFunctions.inElastic(t, b, c, d, [a, p])
playdate.easingFunctions.outElastic(t, b, c, d, [a, p])
playdate.easingFunctions.inOutElastic(t, b, c, d, [a, p])
playdate.easingFunctions.outInElastic(t, b, c, d, [a, p])
playdate.easingFunctions.inBack(t, b, c, d, [s])
playdate.easingFunctions.outBack(t, b, c, d, [s])
playdate.easingFunctions.inOutBack(t, b, c, d, [s])
playdate.easingFunctions.outInBack(t, b, c, d, [s])
playdate.easingFunctions.outBounce(t, b, c, d)
playdate.easingFunctions.inBounce(t, b, c, d)
playdate.easingFunctions.inOutBounce(t, b, c, d)
playdate.easingFunctions.outInBounce(t, b, c, d)
-
t is elapsed time
-
b is the beginning value
-
c is the change (or end value - start value)
-
d is the duration
-
a - amplitude
-
p - period parameter
-
s - amount of "overshoot"
See playdate.graphics.animator, playdate.timer, or playdate.frameTimer for use-cases.
Tip
|
This page does a great job illustrating the shape of each easing function. (A mouseover will show an animation.) |
6.18. Files
The Playdate SDK offers a few different approaches to writing and reading data:
-
playdate.datastore is the simplest way to write or read tables and images.
-
Advanced file access functions are implemented in playdate.file.
-
To encode or decode a JSON file or string, see playdate.json.
Note
|
When running in the simulator, all Playdate file operations will happen in the SDK’s Disk/Data/(bundleID) folder. bundleID is as specified in your project’s metadata file. |
playdate.datastore
If you’re looking for a simple way to save data, the Datastore APIs allow easy serialization of Lua tables and images.
Encodes the given table into the named file. (The .json
extension should be omitted from the file name.) The default file name is "data". If pretty-print is true, the JSON will be nicely formatted.
Returns a table instantiated with the data in the JSON-encoded file you specify. (The .json
extension should be omitted.) The default file name is "data". If no file is found, this function returns nil.
Deletes the specified datastore file. The default file name is "data". Returns false
if the datastore file could not be deleted.
Saves a playdate.graphics.image to a file. If path doesn’t contain a folder name, the image is stored in a folder named "images".
By default, this method writes out a PDI file, a custom image format used by Playdate that can be read back in using readImage(). If you want to write out a GIF file, append a .gif
extension to your path.
Important
|
Because writeImage() doesn’t currently support GIF transparency, if you attempt to write a GIF from an image buffer you instantiated, you must call playdate.graphics.image.new( width, height, bgcolor ) with bgcolor set to playdate.graphics.kColorWhite or playdate.graphics.kColorBlack , otherwise your image will render improperly to the file.
|
Reads a playdate.graphics.image from a file in the data folder. If path doesn’t contain a folder name, the image is searched for in a folder named "images".
Important
|
readImage() can only load compiled pdi files. (writeImage() by default creates compiled pdi files.)
|
playdate.file
The playdate.file module contains functions which allow you to interact with files on Playdate’s filesystem. It contains the playdate.file.file submodule for interacting with an opened file.
File reading and writing
Returns a playdate.file.file corresponding to the opened file. mode should be one of the following:
-
playdate.file.kFileRead: the file is opened for reading; the system first looks in the /Data/<bundleid> folder for the given file, then in the game’s pdx folder if it isn’t found
-
playdate.file.kFileWrite: the file is created if it doesn’t exist, truncated to zero length if it does, then opened for writing
-
playdate.file.kFileAppend: the file is created if it doesn’t exist, opened for writing, with new data written to the end of the file
If mode is not specified, the default is playdate.file.kFileRead.
If the file couldn’t be opened, a second return value indicates the error.
Closes the file.
Writes the given string to the file and returns the number of bytes written if successful, or 0 and a second return value describing the error. If you wish to include line termination characters (\n
, \r
), please include them in the string.
Flushes any buffered data written to the file to the disk.
Returns the next line of the file, delimited by either \n or \r\n. The returned string does not include newline characters.
Returns a buffer containing up to numberOfBytes bytes from the file, and the number of bytes read. If the read failed, the function returns nil
and a second value describing the error.
Sets the file read/write position to the given byte offset.
Returns the current byte offset of the read/write position in the file.
Filesystem operations
Returns an array containing the file names in the given directory path as strings. Folders are indicated by a slash /
at the end of the filename.
Call with no argument to get a list of all files and folders your game has access to. (For a game with default access permissions, listFiles()
, listFiles("/")
, and listFiles(".")
should all return the same result.)
Returns true if a file exists at the given path.
Returns true if a directory exists at the given path.
Creates a directory at the given path, under the /Data/<bundleid> folder. See About the Playdate Filesystem for details.
playdate.file.mkdir()
will create all intermediate directories, if a succession of directories ("testdir/testdir/testdir/") is specified in path.
Deletes the file at the given path. Returns true if successful, else false.
If recursive is true
, this function will delete the directory at path and its contents, otherwise the directory must be empty to be deleted.
Returns the size of the file at the given path.
Returns the type of the file at the given path.
Returns the modification date/time of the file at the given path, as a table with keys:
-
year: 4-digit year (until 10,000 AD)
-
month: month of the year, where 1 is January and 12 is December
-
day: day of the month, 1 - 31
-
hour: 0 - 23
-
minute: 0 - 59
-
second: 0 - 59 (or 60 on a leap second)
Renames the file at path, if it exists, to the value of newPath. This can result in the file being moved to a new directory, but directories will not be created. Returns true if the operation was successful.
.pdz files
Loads the compiled .pdz file at the given location and returns the contents as a function. The .pdz extension on path is optional.
env, if specified, is a table to use as the function’s global namespace instead of _G.
Runs the pdz file at the given location. Equivalent to playdate.file.load(path, env)()
.
The .pdz extension on path is optional. Values returned from the pdz file are left on the stack.
env, if specified, is a table to use as the function’s global namespace instead of _G.
6.19. Geometry
The playdate.geometry library allows you to store and manipulate points, sizes, rectangles, line segments, 2D vectors, polygons, and affine transforms.
All new geometry objects are created with a new() function using syntax like:
r = playdate.geometry.rect.new(x, y, width, height)
They can be output to the simulator console:
print('rect', r)
And tested for equality:
b = r1 == r2
Fields on most geometry objects can be set directly:
r.x = 42.0
Functions for drawing playdate.geometry objects to screen are available in playdate.graphics.
Affine transform
Affine transforms can be used to modify the coordinates of points, rects (as axis aligned bounding boxes (AABBs)), line segments, and polygons. The underlying matrix is of the form:
[m11 m12 tx]
[m21 m22 ty]
[ 0 0 1 ]
Returns a new playdate.geometry.affineTransform. Use new() instead to get a new copy of the identity transform.
Returns a new playdate.geometry.affineTransform that is the identity transform.
Returns a new copy of the affine transform.
Mutates the caller so that it is an affine transformation matrix constructed by inverting itself.
Inversion is generally used to provide reverse transformation of points within transformed objects. Given the coordinates (x, y), which have been transformed by a given matrix to new coordinates (x’, y’), transforming the coordinates (x’, y’) by the inverse matrix produces the original coordinates (x, y).
Mutates the the caller, changing it to an identity transform matrix.
Mutates the the caller. The affine transform af is concatenated to the caller.
Concatenation combines two affine transformation matrices by multiplying them together. You might perform several concatenations in order to create a single affine transform that contains the cumulative effects of several transformations.
Note that matrix operations are not commutative — the order in which you concatenate matrices is important. That is, the result of multiplying matrix t1 by matrix t2 does not necessarily equal the result of multiplying matrix t2 by matrix t1.
Mutates the caller by applying a translate transformation. x values are moved by dx, y values by dy.
Returns a copy of the calling affine transform with a translate transformation appended.
Mutates the caller by applying a scaling transformation.
If both parameters are passed, sx is used to scale the x values of the transform, sy is used to scale the y values.
If only one parameter is passed, it is used to scale both x and y values.
Returns a copy of the calling affine transform with a scaling transformation appended.
If both parameters are passed, sx is used to scale the x values of the transform, sy is used to scale the y values.
If only one parameter is passed, it is used to scale both x and y values.
Mutates the caller by applying a rotation transformation.
angle is the value, in degrees, by which to rotate the affine transform. A positive value specifies clockwise rotation and a negative value specifies counterclockwise rotation. If the optional x and y arguments or point point are given, the transform rotates around (x,y) or point instead of (0,0).
Mutates the caller by applying a rotation transformation.
angle is the value, in degrees, by which to rotate the affine transform. A positive value specifies clockwise rotation and a negative value specifies counterclockwise rotation. If the optional x and y arguments or point point are given, the transform rotates around (x,y) or point instead of (0,0).
Returns a copy of the calling affine transform with a rotate transformation appended.
angle is the value, in degrees, by which to rotate the affine transform. A positive value specifies clockwise rotation and a negative value specifies counterclockwise rotation. If the optional x and y arguments or point point are given, the transform rotates around (x,y) or point instead of (0,0).
Returns a copy of the calling affine transform with a rotate transformation appended.
angle is the value, in degrees, by which to rotate the affine transform. A positive value specifies clockwise rotation and a negative value specifies counterclockwise rotation. If the optional x and y arguments or point point are given, the transform rotates around (x,y) or point instead of (0,0).
Mutates the caller, appending a skew transformation. sx is the value by which to skew the x axis, and sy the value for the y axis. Values are in degrees.
Returns the given transform with a skew transformation appended. sx is the value by which to skew the x axis, and sy the value for the y axis. Values are in degrees.
Modifies the point p by applying the affine transform.
As above, but returns a new point rather than modifying p.
Returns two values calculated by applying the affine transform to the point (x, y)
Modifies the line segment ls by applying the affine transform.
As above, but returns a new line segment rather than modifying ls.
Modifies the axis aligned bounding box r (a rect) by applying the affine transform.
As above, but returns a new rect rather than modifying r.
Modifies the polygon p by applying the affine transform.
As above, but returns a new polygon rather than modifying p.
Returns the transform created by multiplying transform t1 by transform t2
Returns the vector2D created by applying the transform t to the vector2D
v
Returns the point created by applying the transform t to the point
p
Arc
playdate.geometry.arc implements an arc.
You can directly read or write the x, y, radius, startAngle, endAngle and clockwise values of an arc
.
Returns a new playdate.geometry.arc. Angles should be specified in degrees. Zero degrees represents the top of the circle.
If specified, direction should be true for clockwise, false for counterclockwise. If not specified, the direction is inferred from the start and end angles.
Returns a new copy of the arc.
Returns the length of the arc.
Returns true if the direction of the arc is clockwise.
Sets the direction of the arc.
Returns a new point on the arc, distance
pixels from the arc’s start angle.
Line segment
playdate.geometry.lineSegment implements a line segment between two points in two-dimensional space.
You can directly read or write x1, y1, x2, or y2 values to a lineSegment.
Returns a new playdate.geometry.lineSegment.
Returns a new copy of the line segment.
Returns the values x1, y1, x2, y2.
Returns the length of the line segment.
Modifies the line segment, offsetting its values by dx, dy.
Returns a new line segment, the given segment offset by dx, dy.
Returns a playdate.geometry.point representing the mid point of the line segment.
Returns a playdate.geometry.point on the line segment, distance
pixels from the start of the line.
Returns a playdate.geometry.vector2D representation of the line segment.
Returns a playdate.geometry.point that is the closest point to point p that is on the line segment.
Returns true if there is an intersection between the caller and the line segment ls.
If there is an intersection, a playdate.geometry.point representing that point is also returned.
For use in inner loops where speed is the priority.
Returns true if there is an intersection between the line segments defined by (x1, y1), (x2, y2) and (x3, y3), (x4, y4). If there is an intersection, x, y values representing the intersection point are also returned.
Returns the tuple (intersects, intersectionPoints).
intersects is true if there is at least one intersection between the caller and poly.
intersectionPoints is an array of playdate.geometry.points containing all intersection points between the caller and poly.
Returns the tuple (intersects, intersectionPoints).
intersects is true if there is at least one intersection between the caller and rect.
intersectionPoints is an array of playdate.geometry.points containing all intersection points between the caller and rect.
Point
playdate.geometry.point implements a two-dimensional point.
You can directly read or write the x and y values of a point
.
Returns a new playdate.geometry.point.
Returns a new copy of the point.
Returns the values x, y.
Modifies the point, offsetting its values by dx, dy.
Returns a new point object, the given point offset by dx, dy.
Returns the square of the distance to point p.
Returns the distance to point p.
Returns a new point by adding the vector v to point p.
Returns the vector constructed by subtracting p2 from p1. By this construction, p2 + (p1 - p2) == p1.
Returns a new point by applying the transform t to point p.
Returns a new lineSegment connecting points p1 and p2.
Polygon
playdate.geometry.polygon implements two-dimensional open or closed polygons.
playdate.geometry.polygon.new(p1, p2, …, pn)
playdate.geometry.polygon.new(numberOfVertices)
new(x1, y1, x2, y2, …, xn, yn)
returns a new playdate.geometry.polygon with vertices (x1, y1) through (xn, yn). The Lua function table.unpack()
can be used to turn an array into function arguments.
new(p1, p2, …, pn)
does the same, except the points are expressed via point objects.
new(numberOfVertices)
returns a new playdate.geometry.polygon with space allocated for numberOfVertices vertices. All vertices are initially (0, 0). Vertex coordinates can be set with playdate.geometry.polygon:setPointAt().
Note
|
If the polygon’s first and last points are coincident, the polygon will be considered closed. Alternatively, you may call :close() to automatically close the polygon. |
Tip
|
To draw a polygon, use playdate.graphics.drawPolygon() .
|
Returns a copy of a polygon.
:close()
closes a polygon. If the polygon’s first and last point aren’t coincident, a line segment will be generated to connect them.
Returns true if the polygon is closed, false if not.
playdate.geometry.polygon:containsPoint(x, y, [fillRule])
Returns a boolean value, true if the point p or the point at (x, y) is contained within the caller polygon.
fillrule
is an optional argument that can be one of the values defined in playdate.graphics.setPolygonFillRule. By default playdate.graphics.kPolygonFillEvenOdd
is used.
Returns the axis-aligned bounding box for the given polygon as the tuple (x, y, width, height).
Returns the axis-aligned bounding box for the given polygon as a playdate.geometry.rect
object.
Returns the number of points in the polygon.
Returns the total length of all line segments in the polygon.
Sets the polygon’s n-th point to (x, y).
Returns the polygon’s n-th point.
Returns true if the given polygon intersects the polygon p.
Returns a playdate.geometry.point on one of the polygon’s line segments, distance
pixels from the start of the polygon.
Translates each point on the polygon by dx, dy pixels.
Returns a new polygon formed by applying the transform t to polygon p.
Rect
playdate.geometry.rect implements a rectangle.
You can directly read or write x, y, width, or height values to a rect.
The values of top, bottom, right, left, origin, and size are read-only.
Returns a new playdate.geometry.rect.
Returns a new copy of the rect.
Returns a new playdate.geometry.polygon version of the rect.
Returns x, y, width and height as individual values.
Returns true if a rectangle has zero width or height.
Returns true if the x, y, width, and height values of the caller and r2 are all equal.
Returns true if r2 intersects the caller.
Returns a rect representing the overlapping portion of the caller and r2.
For use in inner loops where speed is the priority. About 3x faster than intersection.
Returns a tuple (x, y, width, height) representing the overlapping portion of the two rects defined by x1, y1, w1, h1 and x2, y2, w2, h2. If there is no intersection, (0, 0, 0, 0) is returned.
Returns the smallest possible rect that contains both the source rect and r2.
For use in inner loops where speed is the priority. About 3x faster than union.
Returns a tuple (x, y, width, height) representing the smallest possible rect that contains the two rects defined by x1, y1, w1, h1 and x2, y2, w2, h2.
Insets the rect by the given dx and dy.
Returns a rect that is inset by the given dx and dy, with the same center point.
Offsets the rect by the given dx and dy.
Returns a rect with its origin point offset by dx, dy.
Returns true if the rect defined by (x, y, width, height) is contained within the caller rect.
Returns true if the point at (x, y) is contained within the caller rect.
Returns a point at the center of the caller.
Flips the caller about the center of rect r2.
flip should be one of the following constants:
-
playdate.geometry.kUnflipped
-
playdate.geometry.kFlippedX
-
playdate.geometry.kFlippedY
-
playdate.geometry.kFlippedXY
Size
You can directly read or write the width and height values of a size
.
Returns a new playdate.geometry.size.
Returns a new copy of the size.
Returns the values width, height.
Utility functions
Returns the square of the distance from point (x1, y1) to point (x2, y2).
Compared to geometry.point:squaredDistanceToPoint(), this version will be slightly faster.
Returns the the distance from point (x1, y1) to point (x2, y2).
Compared to geometry.point:distanceToPoint(), this version will be slightly faster.
Vector
playdate.geometry.vector2D implements a two-dimensional vector.
You can directly read or write dx, or dy values to a vector2D.
Returns a new playdate.geometry.vector2D.
Returns a new copy of the vector2D.
Returns the values dx, dy.
Modifies the caller by adding vector v.
Modifies the caller, scaling it by amount s.
Returns the given vector scaled by s.
Modifies the caller by normalizing it so that its length is 1. If the vector is (0,0), the vector is unchanged.
Returns a new vector by normalizing the given vector.
Returns the dot product of the caller and the vector v.
Returns the magnitude of the caller.
Returns the square of the magnitude of the caller.
Modifies the caller by projecting it along the vector v.
Returns a new vector created by projecting the given vector along the vector v.
Returns the angle between the caller and the vector v.
Returns a vector that is the left normal of the caller.
Returns a vector that is the right normal of the caller.
Returns the vector formed by negating the components of vector v.
Returns the vector formed by adding vector v2 to vector v1.
Returns the vector formed by subtracting vector v2 from vector v1.
Returns the vector v1 scaled by s.
Returns the dot product of the two vectors.
Returns the vector transformed by transform t.
Returns the vector divided by scalar s.
6.20. Graphics
The playdate.graphics module contains functions related to displaying information on the device screen.
Conventions
-
The Playdate coordinate system has its origin point (0, 0) at the upper left. The x-axis increases to the right, and the y-axis increases downward.
-
(0, 0) represents the upper-left corner of the first pixel onscreen. The center of that pixel is (0.5, 0.5).
-
In the Playdate SDK, angle values should always be provided in degrees, and angle values returned will be in degrees. Not radians. (This is in contrast to Lua’s built-in math libraries, which use radians.)
Contexts
Pushes the current graphics state to the context stack and creates a new context. If a playdate.graphics.image is given, drawing functions are applied to the image instead of the screen buffer.
Important
|
If you draw into an image context with color set to playdate.graphics.kColorClear, those drawn pixels will be set to transparent. When you later draw the image into the framebuffer, those pixels will not be rendered, i.e., will act as transparent pixels in the image. |
Note
|
playdate.graphics.lockFocus(image) will reroute drawing into an image, without saving the overall graphics context. |
Pops a graphics context off the context stack and restores its state.
Clearing the Screen
Clears the entire display, setting the color to either the given color argument, or the current background color set in setBackgroundColor(color) if no argument is given.
Image
PNG and GIF images in the source folder are compiled into a Playdate-specific format by pdc
, and can be loaded into Lua with
playdate.graphics.image.new(path). Playdate images are 1 bit per pixel, with an optional alpha channel.
Image basics
Creates a new blank image of the given width and height. The image can be drawn on using playdate.graphics.pushContext() or playdate.graphics.lockFocus(). The optional bgcolor argument is one of the color constants as used in playdate.graphics.setColor(), defaulting to kColorClear.
Returns a playdate.graphics.image object from the data at path. If there is no file at path, the function returns nil and a second value describing the error.
Loads a new image from the data at path into an already-existing image, without allocating additional memory. The image at path must be of the same dimensions as the original.
Returns (success, [error]). If the boolean success is false, error is also returned.
Returns a new playdate.graphics.image
that is an exact copy of the original.
Returns the pair (width, height)
Returns the pair (width, height) for the image at path without actually loading the image.
playdate.graphics.image:draw(p, [flip, [sourceRect]])
Draws the image with its upper-left corner at location (x, y) or playdate.geometry.point p.
The optional flip argument can be one of the following:
Alternately, one of the strings "flipX", "flipY", or "flipXY" can be used for the flip argument.
sourceRect, if specified, will cause only the part of the image within sourceRect to be drawn. sourceRect should be relative to the image’s bounds and can be a playdate.geometry.rect or four integers, (x, y, w, h), representing the rect.
Draws the image at location (x, y) centered at the point within the image represented by (ax, ay) in unit coordinate space. For example, values of ax = 0.0, ay = 0.0 represent the image’s top-left corner, ax = 1.0, ay = 1.0 represent the bottom-right, and ax = 0.5, ay = 0.5 represent the center of the image.
The flip argument is optional; see playdate.graphics.image:draw()
for valid values.
Important
|
You must import CoreLibs/graphics to use this method. |
Draws the image centered at location (x, y).
The flip argument is optional; see playdate.graphics.image:draw()
for valid values.
Important
|
You must import CoreLibs/graphics to use this method. |
Draws the image ignoring the currently-set drawOffset
.
Draws the image ignoring the currently-set drawOffset
.
Erases the contents of the image, setting all pixels to white if color is playdate.graphics.kColorWhite, black if it’s playdate.graphics.kColorBlack, or clear if it’s playdate.graphics.kColorClear. If the image is cleared to black or white, the mask (if it exists) is set to fully opaque. If the image is cleared to kColorClear and the image doesn’t have a mask, a mask is added to it.
Returns playdate.graphics.kColorWhite if the image is white at (x, y), playdate.graphics.kColorBlack if it’s black, or playdate.graphics.kColorClear if it’s transparent.
Note
|
The upper-left pixel of the image is at coordinate (0, 0). |
Image transformations
Important
|
The following functions can be quite slow, especially when rotating images off-axis. Transforming a large image can take many milliseconds on the device. Be sure to test performance on the hardware when using these functions. |
Draws this image centered at point (x,y) at (clockwise) angle degrees, scaled by optional argument scale, with an optional separate scaling for the y axis.
Returns a new image containing this image rotated by (clockwise) angle degrees, scaled by optional argument scale, with an optional separate scaling for the y axis.
Caution
|
Unless rotating by a multiple of 180 degrees, the new image will have different dimensions than the original. |
Draws this image with its upper-left corner at point (x,y), scaled by amount scale, with an optional separate scaling for the y axis.
Returns a new image containing this image scaled by amount scale, with an optional separate scaling for the y axis.
Draws this image centered at point (x,y) with the transform xform applied.
Returns a new image containing the image with the transform xform applied.
Draws the image as if it’s mapped onto a tilted plane, transforming the target coordinates to image coordinates using an affine transform:
x' = dxx * x + dyx * y + dx y' = dxy * x + dyy * y + dy
-
x, y, width, height: The rectangle to fill
-
centerx, centery: The point in the above rectangle [in (0,1)x(0,1) coordinates] for the center of the transform
-
dxx, dyx, dxy, dyy, dx, dy: Defines an affine transform from geometry coordinates to image coordinates
-
z: The distance from the viewer to the target plane — lower z means more exaggerated perspective
-
tiltAngle: The tilt of the target plane about the x axis, in degrees
-
tile: A boolean, indicating whether the image is tiled on the target plane
The Mode7Driver demo in the /Examples folder of the SDK demonstrates the usage of this function.
Image masks
set
sets the image’s mask to a copy of maskImage.
If the image has a mask, get
returns the mask as an image. Otherwise, returns nil.
Important
|
The returned image references the original’s data, so drawing into this image alters the original image’s mask. |
set
sets the image’s mask to a copy of maskImage.
If the image has a mask, get
returns the mask as an image. Otherwise, returns nil.
Important
|
The returned image references the original’s data, so drawing into this image alters the original image’s mask. |
Adds a mask to the image if it doesn’t already have one. If opaque is true
, the image will be set to entirely opaque. Otherwise, or if not specified, the image will be completely transparent.
Removes the mask from the image if it has one.
Returns true if the image has a mask.
Erases the contents of the image’s mask, so that the image is entirely opaque if opaque is 1, transparent otherwise. This function has no effect if the image doesn’t have a mask.
Image effects
playdate.graphics.image:drawTiled(rect, [flip])
Tiles the image into the given rectangle, using either listed dimensions or a playdate.geometry.rect
object, and the optional flip style.
Draws a blurred version of the image at (x, y).
-
radius: A bigger radius means a more blurred result. Processing time is independent of the radius.
-
numPasses: A box blur is used to blur the image. The more passes, the more closely the blur approximates a gaussian blur. However, higher values will take more time to process.
-
ditherType: The algorithm to use when blurring the image, must be one of the values listed in
playdate.graphics.image:blurredImage()
-
flip: optional; see
playdate.graphics.image:draw()
for valid values. -
xPhase, yPhase: optional; integer values that affect the appearance of playdate.graphics.image.kDitherTypeDiagonalLine, playdate.graphics.image.kDitherTypeVerticalLine, playdate.graphics.image.kDitherTypeHorizontalLine, playdate.graphics.image.kDitherTypeScreen, playdate.graphics.image.kDitherTypeBayer2x2, playdate.graphics.image.kDitherTypeBayer4x4, and playdate.graphics.image.kDitherTypeBayer8x8.
Draws a partially transparent image with its upper-left corner at location (x, y)
-
alpha: The alpha value used to draw the image, with 1 being fully opaque, and 0 being completely transparent.
-
ditherType: The caller is faded using one of the dithering algorithms listed in
playdate.graphics.image:blurredImage()
If flag is true, the image will be drawn with its colors inverted. If the image is being used as a stencil, its behavior is reversed: pixels are drawn where the stencil is black, nothing is drawn where the stencil is white.
Returns a color-inverted copy of the caller.
Returns an image that is a blend between the caller and image.
-
image: the playdate.graphics.image to be blended with the caller.
-
alpha: The alpha value assigned to the caller. image will have an alpha of (1 - alpha).
-
ditherType: The caller and image are blended into a greyscale image and dithered with one of the dithering algorithms listed in
playdate.graphics.image:blurredImage()
Returns a blurred copy of the caller.
-
radius: A bigger radius means a more blurred result. Processing time is independent of the radius.
-
numPasses: A box blur is used to blur the image. The more passes, the more closely the blur approximates a gaussian blur. However, higher values will take more time to process.
-
ditherType: The original image is blurred into a greyscale image then dithered back to 1-bit using one of the following dithering algorithms:
-
padEdges: Boolean indicating whether the edges of the images should be padded to accommodate the blur radius. Defaults to false.
-
xPhase, yPhase: optional; integer values that affect the appearance of playdate.graphics.image.kDitherTypeDiagonalLine, playdate.graphics.image.kDitherTypeVerticalLine, playdate.graphics.image.kDitherTypeHorizontalLine, playdate.graphics.image.kDitherTypeScreen, playdate.graphics.image.kDitherTypeBayer2x2, playdate.graphics.image.kDitherTypeBayer4x4, and playdate.graphics.image.kDitherTypeBayer8x8.
Returns a faded version of the caller.
-
alpha: The alpha value assigned to the caller, in the range 0.0 - 1.0. If an image mask already exists it is multiplied by alpha.
-
ditherType: The caller is faded into a greyscale image and dithered with one of the dithering algorithms listed in playdate.graphics.image:blurredImage()
Returns an image created by applying a VCR pause effect to the calling image.
Other image stuff
Returns true if the non-alpha-masked portions of image1 and image2 overlap if they were drawn at positions (x1, y1) and (x2, y2) and flipped according to flip1 and flip2, which should each be one of the values listed in playdate.graphics.image:draw()
.
Color & Pattern
Sets and gets the current drawing color for primitives.
color should be one of the constants:
-
playdate.graphics.kColorBlack
-
playdate.graphics.kColorWhite
-
playdate.graphics.kColorClear
-
playdate.graphics.kColorXOR
This color applies to drawing primitive shapes such as lines and rectangles, not bitmap images.
Gets the current drawing color for primitives.
Sets the color used for drawing the background, if necessary, before playdate.graphics.sprites are drawn on top.
color should be one of the constants:
-
playdate.graphics.kColorBlack
-
playdate.graphics.kColorWhite
-
playdate.graphics.kColorClear
Use kColorClear if you intend to draw behind sprites.
Gets the color used for drawing the background, if necessary, before playdate.graphics.sprites are drawn on top.
Sets the 8x8 pattern used for drawing. The pattern argument is an array of 8 numbers describing the bitmap for each row; for example, { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 } specifies a checkerboard pattern. An additional 8 numbers can be specified for an alpha mask bitmap.
Important
|
To "un-set" a pattern, call setColor() .
|
playdate.graphics.setPattern(image, [x, y])
Uses the given playdate.graphics.image to set the 8 x 8 pattern used for drawing. The optional x, y offset (default 0, 0) indicates the top left corner of the 8 x 8 pattern.
Sets the pattern used for drawing to a dithered pattern. If the current drawing color is white, the pattern is white pixels on a transparent background and (due to a bug) the alpha value is inverted: 1.0 is transparent and 0 is opaque. Otherwise, the pattern is black pixels on a transparent background and alpha 0 is transparent while 1.0 is opaque.
The optional ditherType argument is a dither type as used in playdate.graphics.image:blurredImage()
, and should be an ordered dither type; i.e., line, screen, or Bayer.
Caution
|
The error-diffusing dither types Floyd-Steinberg (kDitherTypeFloydSteinberg ), Burkes (kDitherTypeBurkes ), and Atkinson (kDitherTypeAtkinson ) are allowed but produce very unpredictable results here.
|
Drawing
Line
playdate.graphics.drawLine(ls)
Draws a line from (x1, y1) to (x2, y2), or draws the playdate.geometry.lineSegment ls.
Line width is specified by setLineWidth(). End cap style is specified by setLineCapStyle().
Specifies the shape of the endpoints drawn by drawLine.
style should be one of these constants:
-
playdate.graphics.kLineCapStyleButt
-
playdate.graphics.kLineCapStyleRound
-
playdate.graphics.kLineCapStyleSquare
Pixel
Draw a single pixel in the current color at (x, y).
playdate.graphics.drawPixel(p)
Draw a single pixel in the current color at playdate.geometry.point p.
Rect
playdate.graphics.drawRect(r)
Draws the rect r or the rect with origin (x, y) with a size of (w, h).
Line width is specified by setLineWidth(). Stroke location is specified by setStrokeLocation().
playdate.graphics.fillRect(r)
Draws the filled rectangle r or the rect at (x, y) of the given width and height.
Round rect
playdate.graphics.drawRoundRect(r, radius)
Draws a rectangle with rounded corners in the rect r or the rect with origin (x, y) and size (w, h).
radius defines the radius of the corners.
playdate.graphics.fillRoundRect(r, radius)
Draws a filled rectangle with rounded corners in the rect r or the rect with origin (x, y) and size (w, h).
radius defines the radius of the corners.
Arc
Important
|
You must import CoreLibs/graphics to use the arc drawing functions. |
playdate.graphics.drawArc(x, y, radius, startAngle, endAngle)
Draws an arc using the current color.
Angles are specified in degrees, not radians.
Circle
Important
|
You must import CoreLibs/graphics to use the circle drawing functions. |
playdate.graphics.drawCircleAtPoint(p, radius)
Draws a circle at the point (x, y) (or p) with radius radius.
playdate.graphics.drawCircleInRect(r)
Draws a circle in the rect r or the rect with origin (x, y) and size (width, height).
If the rect is not a square, the circle will be drawn centered in the rect.
playdate.graphics.fillCircleAtPoint(p, radius)
Draws a filled circle at the point (x, y) (or p) with radius radius.
playdate.graphics.fillCircleInRect(r)
Draws a filled circle in the rect r or the rect with origin (x, y) and size (width, height).
If the rect is not a square, the circle will be drawn centered in the rect.
Ellipse
playdate.graphics.drawEllipseInRect(rect, [startAngle, endAngle])
Draws an ellipse in the rect r or the rect with origin (x, y) and size (width, height).
startAngle and endAngle, if provided, should be in degrees (not radians), and will cause only the segment of the ellipse between startAngle and endAngle to be drawn.
playdate.graphics.fillEllipseInRect(rect, [startAngle, endAngle])
Draws a filled ellipse in the rect r or the rect with origin (x, y) and size (width, height).
startAngle and endAngle, if provided, should be in degrees (not radians), and will cause only the segment of the ellipse between startAngle and endAngle to be drawn.
Polygon
Draw the playdate.geometry.polygon p.
Line width is specified by setLineWidth().
Draw the polygon specified by the given sequence of x,y coordinates. The Lua function table.unpack()
can be used to turn an array into function arguments.
Line width is specified by setLineWidth().
Fills the polygon specified by a list of x,y coordinates.
Fills the polygon specified by the playdate.geometry.polygon p with the currently selected color or pattern.
Tip
|
The Lua function table.unpack() can be used to turn an array into function arguments.
|
Sets the winding rule for filling polygons, one of:
-
playdate.graphics.kPolygonFillNonZero
-
playdate.graphics.kPolygonFillEvenOdd
See https://en.wikipedia.org/wiki/Nonzero-rule for an explanation of the winding rule.
Triangle
Draws a triangle with vertices (x1, y1), (x2, y2), and (x3, y3).
Draws a filled triangle with vertices (x1, y1), (x2, y2), and (x3, y3).
Nine slice
A "9 slice" is a rectangular image that is made "stretchable" by being sliced into nine pieces — the four corners, the four edges, and the center.
Important
|
You must import CoreLibs/nineslice to use these functions. |
Returns a new 9 slice image from the image at imagePath with the stretchable region defined by other parameters. The arguments represent the origin and dimensions of the innermost ("center") slice.
Returns the size of the 9 slice image as a pair (width, height).
Returns the minimum size of the 9 slice image as a pair (width, height).
playdate.graphics.nineSlice:drawInRect(rect)
Draws the 9 slice image at the desired coordinates by stretching the defined region to achieve the width and height inputs.
Perlin noise
Perlin noise is an algorithm useful for generating "organic" looking things procedurally, such as terrain, visual effects, and more. For a good introduction to Perlin noise, see: http://flafla2.github.io/2014/08/09/perlinnoise.html
Returns the Perlin value (from 0.0 to 1.0) at position (x, y, z).
If repeat is greater than 0, the pattern of noise will repeat at that point on all 3 axes.
octaves is the number of octaves of noise to apply. Compute time increases linearly with each additional octave, but the results are a bit more organic, consisting of a combination of larger and smaller variations.
When using more than one octave, persistence is a value from 0.0 - 1.0 describing the amount the amplitude is scaled each octave. The lower the value of persistence, the less influence each successive octave has on the final value.
Returns an array of Perlin values at once, avoiding the performance penalty of calling perlin() multiple times in a loop.
The parameters are the same as perlin() except:
count is the number of values to be returned.
dx, dy, and dz are how far to step along the x, y, and z axes in each iteration.
QRCode
Important
|
You must import CoreLibs/qrcode to use this function. |
Caution
|
This function uses playdate.timer internally, so be sure to call playdate.timer.updateTimers() in your main playdate.update() function, otherwise the callback will never be invoked.
|
Asynchronously returns an image representing a QR code for the passed-in string to the function callback
. The arguments passed to the callback are image, errorMessage. (If an errorMessage string is returned, image will be nil.)
desiredEdgeDimension
lets you specify an approximate edge dimension in pixels for the desired QR code, though the function has limited flexibility in sizing QR codes, based on the amount of information to be encoded, and the restrictions of a 1-bit screen. The function will attempt to generate a QR code smaller than desiredEdgeDimension
if possible. (Note that QR codes always have the same width and height.)
If you specify nil for desiredEdgeDimension
, the returned image will balance small size with easy readability. If you specify 0, the returned image will be the smallest possible QR code for the specified string.
generateQRCode()
will return a reference to the timer it uses to run asynchronously. If you wish to stop execution of the background process generating the QR code, call :remove()
on that returned timer.
Tip
|
If you know ahead of time what data you plan to encode, it is much faster to pre-generate the QR code, store it as a .png file in your game, and draw the .png at runtime. You can use playdate.simulator.writeToFile() to create this .png file.
|
Sine wave
Important
|
You must import CoreLibs/graphics to use this function. |
Draws an approximation of a sine wave between the points startX, startY and endX, endY.
-
startAmplitude: The number of pixels above and below the line from startX, startY and endX, endY the peaks and valleys of the wave will be drawn at the start of the wave.
-
endAmplitude: The number of pixels above and below the line from startX, startY and endX, endY the peaks and valleys of the wave will be drawn at the end of the wave.
-
period: The distance between peaks, in pixels.
-
phaseShift: If provided, specifies the wave’s offset, in pixels.
Drawing Modifiers
Clipping
setClipRect()
sets the clipping rectangle for all subsequent graphics drawing, including bitmaps. The argument can either be separate dimensions or a playdate.geometry.rect object. The clip rect is automatically cleared at the beginning of the playdate.update()
callback. The function uses world coordinates; that is, the given rectangle will be translated by the current drawing offset. To use screen coordinates instead, use setScreenClipRect()
setClipRect()
sets the clipping rectangle for all subsequent graphics drawing, including bitmaps. The argument can either be separate dimensions or a playdate.geometry.rect object. The clip rect is automatically cleared at the beginning of the playdate.update()
callback. The function uses world coordinates; that is, the given rectangle will be translated by the current drawing offset. To use screen coordinates instead, use setScreenClipRect()
getClipRect()
returns the current clipping rectangle as the tuple (x, y, width, height).
Sets the clip rectangle as above, but uses screen coordinates instead of world coordinates—that is, it ignores the current drawing offset.
Sets the clip rectangle as above, but uses screen coordinates instead of world coordinates—that is, it ignores the current drawing offset.
Gets the clip rectangle as above, but uses screen coordinates instead of world coordinates—that is, it ignores the current drawing offset.
Clears the current clipping rectangle, set with setClipRect()
.
Stencil
Sets the current stencil to the given image. If the image is smaller than full screen, its width should be a multiple of 32 pixels. Stencils smaller than full screen will be tiled.
Sets a pattern to use for stenciled drawing, as an alternative to creating an image, drawing a pattern into the image, then using that in setStencilImage()
.
Sets a pattern to use for stenciled drawing, as an alternative to creating an image, drawing a pattern into the image, then using that in setStencilImage()
.
Sets the stencil to a dither pattern specified by level and optional ditherType (defaults to playdate.graphics.image.kDitherTypeBayer8x8
).
Clears the stencil buffer.
Caution
|
Deprecated. |
Clears the stencil buffer.
Drawing mode
Sets the current drawing mode for images.
Important
|
The draw mode applies to images and fonts (which are technically images). The draw mode does not apply to primitive shapes such as lines or rectangles. |
The available options for mode (demonstrated by drawing a two-color background image first, setting the specified draw mode, then drawing the Crankin' character on top) are:
-
playdate.graphics.kDrawModeCopy
-
playdate.graphics.kDrawModeWhiteTransparent
-
playdate.graphics.kDrawModeBlackTransparent
-
playdate.graphics.kDrawModeFillWhite
-
playdate.graphics.kDrawModeFillBlack
-
playdate.graphics.kDrawModeXOR
-
playdate.graphics.kDrawModeNXOR
-
playdate.graphics.kDrawModeInverted
Instead of the above-specified constants, you can also use one of the following strings: "copy", "inverted", "XOR", "NXOR", "whiteTransparent", "blackTransparent", "fillWhite", or "fillBlack".
Gets the current drawing mode for images.
Lines & Strokes
Sets the width of the line for drawLine, drawRect, drawPolygon, and drawArc when a playdate.geometry.arc is passed as the argument.
Gets the width of the line for drawLine, drawRect, drawPolygon, and drawArc when a playdate.geometry.arc is passed as the argument.
Specifies where the stroke is placed relative to the rectangle passed into drawRect.
location is one of these constants:
-
playdate.graphics.kStrokeCentered
-
playdate.graphics.kStrokeOutside
-
playdate.graphics.kStrokeInside
Gets stroke position relative to the rectangle passed into drawRect.
location is one of these constants:
-
playdate.graphics.kStrokeCentered
-
playdate.graphics.kStrokeOutside
-
playdate.graphics.kStrokeInside
Offscreen Drawing
lockFocus()
routes all drawing to the given playdate.graphics.image. playdate.graphics.unlockFocus() returns drawing to the frame buffer.
Important
|
If you draw into an image with color set to playdate.graphics.kColorClear, those drawn pixels will be set to transparent. When you later draw the image into the framebuffer, those pixels will not be rendered, i.e., will act as transparent pixels in the image. |
Note
|
playdate.graphics.pushContext(image) will also allow offscreen drawing into an image, with the additional benefit of being able to save and restore the graphics state. |
After calling unlockFocus()
, drawing is routed to the frame buffer.
Animation
Animation loop
playdate.graphics.animation.loop helps keep track of animation frames, especially for frames in an playdate.graphics.imagetable
. For a more general timer see playdate.timer or playdate.frameTimer.
Important
|
You must import CoreLibs/animation to use these functions. |
Creates a new animation object.
-
imageTable should be a
playdate.graphics.imagetable
, or nil.
The following properties can be read or set directly, and have these defaults:
-
delay : the value of delay, if passed, or 100ms (the delay before moving to the next frame)
-
startFrame : 1 (the value the object resets to when the loop completes)
-
endFrame : the number of images in imageTable if passed, or 1 (the last frame value in the loop)
-
frame : 1 (the current frame counter)
-
step : 1 (the value by which frame increments)
-
shouldLoop : the value of shouldLoop, if passed, or true. (whether the object loops when it completes)
-
paused : false (paused loops don’t change their frame value)
Draw’s the loop’s current image at x, y.
The flip argument is optional; see playdate.graphics.image:draw()
for valid values.
Returns a playdate.graphics.image
from the caller’s imageTable if it exists. The image returned will be at the imageTable’s index that matches the caller’s frame.
Returns false if the loop has passed its last frame and does not loop.
Sets the playdate.graphics.imagetable
to be used for this animation loop, and sets the loop’s endFrame property to #imageTable.
Animator
Animators are lightweight objects that keep track of animation progress. They can animate between two numbers, two points, along a line segment, arc, or polygon, or along a compound path made up of all three.
Usage is simple: create a new Animator, query for its current value when you need to update your animation, and optionally call animator:ended()
to see if the animation is complete.
Example Code in Single File Examples |
---|
Important
|
You must import CoreLibs/animator to use these functions. |
Animates between two number or playdate.geometry.point values.
duration is the total time of the animation in milliseconds.
startValue and endValue should be either numbers or playdate.geometry.point
easingFunction, if supplied, should be a value from playdate.easingFunctions. If your easing function requires additional variables s, a, or p, set them on the animator directly after creation. For example:
local a = playdate.graphics.animator.new(1000, 0, 100, playdate.easingFunctions.inBack)
a.s = 1.9
startTimeOffset, if supplied, will delay the start time of the animation by the specified number of milliseconds.
Creates a new Animator that will animate along the provided playdate.geometry.lineSegment
Creates a new Animator that will animate along the provided playdate.geometry.arc
Creates a new Animator that will animate along the provided playdate.geometry.polygon
Creates a new Animator that will animate along each of the items in the parts array in order, which should be comprised of playdate.geometry.lineSegment, playdate.geometry.arc, or playdate.geometry.polygon objects.
durations should be an array of durations, one for each item in parts.
easingFunctions should be an array of playdate.easingFunctionss, one for each item in parts.
Note
|
By default, animators do not repeat. If you would like them to, set a value for repeatCount representing the number of times the animation should repeat. It can be set to any positive number or -1 to indicate the animation should repeat forever. A repeat count of 1 means the animation will play twice - once for the initial animation plus one repeat. |
Returns the current value of the animation, which will be either a number or a playdate.geometry.point, depending on the type of animator.
Returns true if the animation is completed. Only returns true if this function or currentValue()
has been called since the animation ended in order to allow animations to fully finish before true is returned.
For easing functions that take additional amplitude (such as inOutElastic), set these values on animator instances to the desired values.
For easing functions that take additional period arguments (such as inOutElastic), set these values on animator instances to the desired values.
Blinker
playdate.graphics.animation.blinker keeps track of a boolean that changes on a timer.
Important
|
You must import CoreLibs/animation to use these functions. |
Creates a new blinker object. The default properties are
-
cycles: 6 (the number of changes the blinker goes through before it’s complete)
-
counter: Read this property to see which cycle the blinker is on (counts from n down to zero)
-
onDuration: 200 (the number of milliseconds the blinker is "on")
-
offDuration: 200 (the number of milliseconds the blinker is "off")
-
default: true (the default state of the blinker — true is "on", false is "off")
-
loop: false (should the blinker restart after completing)
-
on: Read this property to determine the current state of the blinker
-
running: Read this property to see if the blinker is actively running
Updates the state of all valid blinkers
Updates the caller’s state.
Starts a blinker if it’s not running.
Starts a blinker if it’s not running and sets its loop
property to true.
Stops a blinker if it’s running. Its state will reset to its default
value.
Stops all blinkers.
Flags the caller for removal from the global list of blinkers
Scrolling
setDrawOffset(x, y)
offsets the origin point for all drawing calls to x, y (can be negative). So, for example, if the offset is set to -20, -20, an image drawn at 20, 20 will appear at the origin (in the upper left corner.)
This is useful, for example, for centering a "camera" on a sprite that is moving around a world larger than the screen.
Tip
|
It can be useful to have operations sometimes ignore the draw offsets. For example, you may want to have the score or some other heads-up display appear onscreen apart from scrolling content. A sprite can be set to ignore offsets by calling playdate.graphics.sprite:setIgnoresDrawOffset(true). playdate.graphics.image:drawIgnoringOffsets() lets you render an image using screen coordinates. |
getDrawOffset()
returns the current draw offset as a tuple (x, y).
Caution
|
These functions are different from playdate.display.setOffset() and playdate.display.getOffset(). |
Frame buffer
Returns a copy the contents of the last completed frame, i.e., a "screenshot", as a playdate.graphics.image.
Note
|
Display functions like setMosaic(), setInverted(), setScale(), and setOffset() do not affect the returned image. |
Returns a copy the contents of the working frame buffer — the current frame, in-progress — as a playdate.graphics.image.
Note
|
Display functions like setMosaic(), setInverted(), setScale(), and setOffset() do not affect the returned image. |
Image table
There are two kinds of image tables: matrix and sequential.
Matrix image tables are great as sources of imagery for tilemap. They are loaded from a single file in your game’s source folder with the suffix -table-<w>-<h>
before the file extension. The compiler splits the image into separate bitmaps of dimension w by h pixels that are accessible via imagetable:getImage(x,y).
Sequential image tables are useful as a way to load up sequential frames of animation. They are loaded from a sequence of files in your game’s source folder at compile time from filenames with the suffix -table-<sequenceNumber>
before the file extension. Individual images in the sequence are accessible via imagetable:getImage(n). The images employed by a sequential image table are not required to be the same size, unlike the images used in a matrix image table.
Returns a playdate.graphics.imagetable object from the data at path. If there is no file at path, the function returns nil and a second value describing the error. If the file at path is an animated GIF, successive frames of the GIF will be loaded as consecutive bitmaps in the imagetable. Any timing data in the animated GIF will be ignored.
Important
|
To load a matrix image table defined in frames-table-16-16.png , you call playdate.graphics.imagetable.new("frames") .
|
Important
|
To load a sequential image table defined with the files frames-table-1.png , frames-table-2.png , etc., you call playdate.graphics.imagetable.new("frames") .
|
Returns an empty image table for loading images into via imagetable:load() or setting already-loaded images into with imagetable:setImage(). If set, cellsWide is used to locate images by x,y position. The optional cellSize argument gives the allocation size for the images, if load() will be used. (This is a weird technical detail, so ask us if you need guidance here.)
Returns the n-th playdate.graphics.image in the table (ordering left-to-right, top-to-bottom). The first image is at index 1. If .n_ or (x,y) is out of bounds, the function returns nil. See also imagetable[n].
Returns the image in cell (x,y) in the original bitmap. The first image is at index 1. If n or (x,y) is out of bounds, the function returns nil. See also imagetable[n].
Sets the image at slot n in the image table by creating a reference to the data in image.
Loads a new image from the data at path into an already-existing image table, without allocating additional memory. The image table at path must contain images of the same dimensions as the previous.
Returns (success, [error]). If the boolean success is false, error is also returned.
Returns the number of images in the table. See also #imagetable.
Returns the pair (cellsWide, cellsHigh).
Equivalent to graphics.imagetable:getImage(n):draw(x,y,[flip])
.
Equivalent to imagetable:getImage(n).
Equivalent to imagetable:getLength()
Tip
|
In Lua, you can get the length of a string or table using the length operator. For a playdate.graphics.imagetable called myImageTable , both #myImageTable and myImageTable:getLength() would return the same result.
|
Tilemap
Tilemaps are often used to represent the game environment. Tiles are a very efficient way to create levels and scenery. (Alternatively, sprites are the best way to create objects that move about your playfield, like the character that represents the player, enemies, etc.)
At its most fundamental, a tilemap is a table of indexes into an playdate.graphics.imagetable. The images in the imagetable represent small chunks of your scenery; the tilemap is what organizes them into a specific arrangement.
Creates a new tilemap object.
Sets the tilemap’s playdate.graphics.imagetable to .table_, a playdate.graphics.imagetable.
Sets the tilemap’s width to width, then populates the tilemap with data, which should be a flat, one-dimensional array-like table containing index values to the tilemap’s imagetable.
Returns data, width
data is a flat, one-dimensional array-like table containing index values to the tilemap’s imagetable.
width is the width of the tile map, in number of tiles.
Draws the tile map at screen coordinate (x, y).
sourceRect, if specified, will cause only the part of the tilemap within sourceRect to be drawn.
Sets the index of the tile at tilemap position (x, y). index is the (1-based) index of the image in the tilemap’s playdate.graphics.imagetable.
Note
|
Tilemaps and imagetables, like Lua arrays, are 1-based, not 0-based. tilemap:getTileAtPosition(1, 1) will get the index of the top-leftmost tile.
|
Returns the image index of the tile at the given x and y coordinate. If x or y is out of bounds, returns nil.
Note
|
Tilemaps and imagetables, like Lua arrays, are 1-based, not 0-based. tilemap:getTileAtPosition(1, 1) will return the index of the top-leftmost tile.
|
Sets the tilemap’s width and height, in number of tiles.
Returns the size of the tile map, in tiles, as a pair, (width, height).
Returns the size of the tilemap in pixels; that is, the size of the image multiplied by the number of rows and columns in the map. Returns the tuple (width, height).
Returns the tuple (width, height), the pixel width and height of an individual tile.
This function returns an array of playdate.geometry.rect objects that describe the areas of the tilemap that should trigger collisions. You can also think of them as the "impassable" rects of your tilemap. These rects will be in pixel coordinates, not tilemap coordinates.
emptyIDs is an array that contains the tile IDs of "empty" (or "passable") tiles in the tilemap — in other words, tile IDs that should not trigger a collision.
For example, if you have a tilemap describing terrain, where tile ID 1 represents grass the player can walk over, and tile ID 2 represents mountains that the player can’t cross, you’d pass an array containing just the value 1. You’ll get a back an array of a minimal number of rects describing the areas where there are mountain tiles.
You can then pass each of those rects into playdate.graphics.sprite.addEmptyCollisionSprite() to add an empty (invisible) sprite into the scene for the built-in collision detection methods. In this example, collide rects would be added around mountain tiles but not grass tiles.
Alternatively, instead of calling getCollisionRects() at all, you can use the convenience function playdate.graphics.sprite.addWallSprites(), which is effectively a shortcut for calling getCollisionRects() and passing all the resulting rects to addEmptyCollisionSprite().
Sprite
Sprites are graphic objects that can be used to represent moving entities in your games, like the player, or the enemies that chase after your player. Sprites animate efficiently, and offer collision detection and a host of other built-in functionality. (If you want to create an environment for your sprites to move around in, consider using tilemaps or drawing a background image.)
Note
|
To have access to all the sprite functionality described below, be sure to import "CoreLibs/sprites" at the top of your source file.
|
The simplest way to create a sprite is using sprite.new(image)
:
import "CoreLibs/sprites"
local image = playdate.graphics.image.new("coin")
local sprite = playdate.graphics.sprite.new(image)
sprite:moveTo(100, 100)
sprite:add()
If you want to use an object-oriented approach, you can also subclass sprites and create instance of those subclasses.
import "CoreLibs/sprites"
class('MySprite').extends(playdate.graphics.sprite)
local sprite = MySprite()
local image = playdate.graphics.image.new("coin")
sprite:setImage(image)
sprite:moveTo(100, 100)
sprite:add()
Or with a custom initializer:
import "CoreLibs/sprites"
class('MySprite').extends(playdate.graphics.sprite)
local image = playdate.graphics.image.new("coin")
function MySprite:init(x, y)
MySprite.super.init(self) -- this is critical
self:setImage(image)
self:moveTo(x, y)
end
local sprite = MySprite(100, 100)
sprite:add()
Sprite Basics
This class method (note the "." syntax rather than ":") returns a new sprite object. A previously-loaded image object can be optionally passed-in (to do this, be sure to import "CoreLibs/sprites"
at the beginning of your source file.)
Important
|
To see your sprite onscreeen, you will need to call :add() on your sprite to add it to the display list.
|
This class method (note the "." syntax rather than ":") calls the update() function on every sprite in the global sprite list and redraws all of the dirty rects.
Important
|
You will generally want to call playdate.graphics.sprite.update() once in your playdate.update() method, to ensure that your sprites are updated and drawn during every frame. Failure to do so may mean your sprites will not appear onscreen.
|
Caution
|
Be careful not confuse sprite.update() with sprite:update() : the former updates all sprites; the latter updates just the sprite being invoked.
|
Sets the sprite’s image to image
, which should be an instance of playdate.graphics.image. The .flip_ argument is optional; see playdate.graphics.image:draw() for valid values. Optional scale arguments are also accepted.
Returns the playdate.graphics.image object that was set with setImage().
Adds the given sprite to the display list, so that it is drawn in the current scene.
Adds the given sprite to the display list, so that it is drawn in the current scene.
Removes the given sprite from the display list.
Removes the given sprite from the display list.
Can be used to directly read your sprite’s x position.
Can be used to directly read your sprite’s y position.
Can be used to directly read your sprite’s width.
Can be used to directly read your sprite’s height.
Warning
|
Do not set these properties directly. Use :moveTo() or :setBounds() instead.
|
Moves the sprite by x, y pixels relative to its current position.
Sets the Z-index of the given sprite. Sprites with higher Z-indexes are drawn on top of those with lower Z-indexes. Valid values for z are in the range (-32768, 32767).
Returns the Z-index of the given sprite.
Sprites that aren’t visible don’t get their draw() method called.
Sprites that aren’t visible don’t get their draw() method called.
Sets the sprite’s drawing center as a fraction (ranging from 0.0 to 1.0) of the height and width. Default is 0.5, 0.5 (the center of the sprite). This means that when you call :moveTo(x, y), the center of your sprite will be positioned at x, y. If you want x and y to represent the upper left corner of your sprite, specify the center as 0, 0.
Returns the tuple (x, y
) representing the sprite’s drawing center as a fraction (ranging from 0.0 to 1.0) of the height and width.
Returns a playdate.geometry.point representing the sprite’s drawing center as a fraction (ranging from 0.0 to 1.0) of the height and width.
Sets the sprite’s size. The method has no effect if the sprite has an image set.
Returns the tuple (width, height), the current size of the sprite.
Sets the scaling factor for the sprite, with an optional separate scaling for the y axis. If setImage() is called after this, the scale factor is applied to the new image. Only affects sprites that have an image set.
Returns the tuple (xScale, yScale), the current scaling of the sprite.
Sets the rotation for the sprite, in degrees clockwise, with an optional scaling factor. If setImage() is called after this, the rotation and scale is applied to the new image. Only affects sprites that have an image set. This function should be used with discretion, as it’s likely to be slow on the hardware. Consider pre-rendering rotated images for your sprites instead.
Returns the current rotation of the sprite.
Returns a copy of the caller.
The sprite’s updatesEnabled flag (defaults to true) determines whether a sprite’s update() method will be called. By default, a sprite’s update
method does nothing; however, you may choose to have your sprite do something on every frame by implementing an update method on your sprite instance, or implementing it in your sprite subclass.
The sprite’s updatesEnabled flag (defaults to true) determines whether a sprite’s update() method will be called. By default, a sprite’s update
method does nothing; however, you may choose to have your sprite do something on every frame by implementing an update method on your sprite instance, or implementing it in your sprite subclass.
Sets the sprite’s tag, an integer value useful for identifying sprites later, particularly when working with collisions.
Returns the sprite’s tag, an integer value.
Sets the mode for drawing the bitmap. See playdate.graphics.setImageDrawMode(mode) for valid modes.
Flips the bitmap. See playdate.graphics.image:draw() for valid flip
values.
If true
is passed for the optional flipCollideRect argument, the sprite’s collideRect will be flipped as well.
Calling setImage() will reset the sprite to its default, non-flipped orientation. So, if you call both setImage() and setImageFlip(), call setImage() first.
Returns one of the values listed at playdate.graphics.image:draw().
When set to true, the sprite will draw in screen coordinates, ignoring the currently-set drawOffset.
This only affects drawing, and should not be used on sprites being used for collisions, which will still happen in world-space.
setBounds()
positions and sizes the sprite, used for drawing and for calculating dirty rects. upper-left-x and upper-left-y are relative to the overall display coordinate system. (If an image is attached to the sprite, the size will be defined by that image, and not by the width and height parameters passed in to setBounds()
.)
Note
|
In setBounds() , x and y always correspond to the upper left corner of the sprite, regardless of how a sprite’s center is defined. This makes it different from sprite:moveTo(), where x and y honor the sprite’s defined center (by default, at a point 50% along the sprite’s width and height.)
|
setBounds(rect)
sets the bounds of the sprite with a playdate.geometry.rect
object.
getBounds()
returns the 4-tuple (x, y, width, height).
getBoundsRect()
returns the sprite bounds as a playdate.geometry.rect
object.
Marking a sprite opaque tells the sprite system that it doesn’t need to draw anything underneath the sprite, since it will be overdrawn anyway. If you set an image without a mask/alpha channel on the sprite, it automatically sets the opaque flag.
Setting a sprite to opaque can have performance benefits.
Marking a sprite opaque tells the sprite system that it doesn’t need to draw anything underneath the sprite, since it will be overdrawn anyway. If you set an image without a mask/alpha channel on the sprite, it automatically sets the opaque flag.
Setting a sprite to opaque can have performance benefits.
Drawing images alongside sprites
Important
|
You must import CoreLibs/sprites to use this function. |
A convenience function that creates a screen-sized sprite with a z-index set to the lowest possible value so it will draw behind other sprites, and adds the sprite to the display list so that it is drawn in the current scene. The background sprite ignores the drawOffset, and will not be automatically redrawn when the draw offset changes; use playdate.graphics.sprite.redrawBackground() if necessary in this case.
drawCallback will be called from the newly-created background sprite’s playdate.graphics.sprite:draw() callback function and is where you should do your background drawing. The callback should be a function of the form draw(x, y, width, height)
, where x, y, width, height specify the region (in screen coordinates, not world coordinates) of the background to be drawn.
Returns the newly created playdate.graphics.sprite.
setBackgroundDrawingCallback(drawCallback)
function playdate.graphics.sprite.setBackgroundDrawingCallback(drawCallback)
bgsprite = gfx.sprite.new()
bgsprite:setSize(playdate.display.getSize())
bgsprite:setCenter(0, 0)
bgsprite:moveTo(0, 0)
bgsprite:setZIndex(-32768)
bgsprite:setIgnoresDrawOffset(true)
bgsprite:setUpdatesEnabled(false)
bgsprite.draw = function(s, x, y, w, h)
drawCallback(x, y, w, h)
end
bgsprite:add()
return s
end
Important
|
You must import CoreLibs/sprites to use this function. |
Marks the background sprite dirty, forcing the drawing callback to be run when playdate.graphics.sprite.update() is called.
Sets the sprite’s contents to the given tilemap. Useful if you want to automate drawing of your tilemap, especially if interleaved by depth with other sprites being drawn.
Automatically animating sprites
While it is customary to move sprites around onscreen by calling sprite:moveTo(x, y)
on successive playdate.update()
calls, it is possible to automate animation behavior with the use of animators.
Important
|
You must import CoreLibs/sprites to use the setAnimator method.
|
setAnimator
assigns an playdate.graphics.animator to the sprite, which will cause the sprite to automatically update its position each frame while the animator is active.
movesWithCollisions, if provided and true will cause the sprite to move with collisions. A collision rect must be set on the sprite prior to passing true for this argument.
removeOnCollision, if provided and true will cause the animator to be removed from the sprite when a collision occurs.
Note
|
setAnimator should be called only after any custom update method has been set on the sprite.
|
Removes a playdate.graphics.animator assigned to the sprite
Clipping
Sets the clipping rectangle for the sprite, using separate parameters or a playdate.geometry.rect
object. Only areas within the rect will be drawn.
Sets the clipping rectangle for the sprite, using separate parameters or a playdate.geometry.rect
object. Only areas within the rect will be drawn.
Clears the sprite’s current clipping rectangle.
Sets the clip rect for sprites in the given z-index range.
Sets the clip rect for sprites in the given z-index range.
Clears sprite clip rects in the given z-index range.
Specifies a stencil image to be set on the frame buffer before the sprite is drawn. If the image is smaller than full screen, its width should be a multiple of 32 pixels. Stencils smaller than full screen will be tiled.
Sets the sprite’s stencil to a dither pattern specified by level and optional ditherType (defaults to playdate.graphics.image.kDitherTypeBayer8x8
).
Clears the sprite’s stencil.
Drawing
If set to true, causes all sprites to draw each frame, whether or not they have been marked dirty. This may speed up the performance of your game if the system’s dirty rect tracking is taking up too much time - for example if there are many sprites moving around on screen at once.
If set to true, causes all sprites to draw each frame, whether or not they have been marked dirty. This may speed up the performance of your game if the system’s dirty rect tracking is taking up too much time - for example if there are many sprites moving around on screen at once.
Marks the rect defined by the sprite’s current bounds as needing a redraw.
Marks the given rectangle (in screen coordinates) as needing a redraw. playdate.graphics drawing functions now call this automatically, adding their drawn areas to the sprite’s dirty list, so there’s likely no need to call this manually any more. This behavior may change in the future, though.
Group operations
Returns an array of all sprites in the display list.
Important
|
You must import CoreLibs/sprites to use this function. |
Performs the function f on all sprites in the display list. f should take one argument, which will be a sprite.
Returns the number of sprites in the display list.
Removes all sprites from the global sprite list.
Removes all sprites in spriteArray
from the global sprite list.
Sprite callbacks
If the sprite doesn’t have an image, the sprite’s draw function is called as needed to update the display. The rect passed in is the current dirty rect being updated by the display list.
Called by playdate.graphics.sprite.update() (note the syntactic difference between the period and the colon) before sprites are drawn. Implementing :update()
gives you the opportunity to perform some code upon every frame.
Note
|
The update method will only be called on sprites that have had add() called on them, and have their updates enabled. |
Caution
|
Be careful not confuse sprite:update() with sprite.update() : the latter updates all sprites; the former updates just the sprite being invoked.
|
Sprite collision detection
The following functions are based on the bump.lua collision detection library. Some things to note:
-
To participate in collisions, a sprite must have its collideRect set.
-
Only handles axis-aligned bounding box (AABB) collisions.
-
Handles tunneling — all items are treated as "bullets". The fact that we only use AABBs makes this fast.
-
Centered on detection, but also offers some (minimal & basic) collision response.
Ideal for:
-
Tile-based games, and games where most entities can be represented as axis-aligned rectangles.
-
Games which require some physics but not a full realistic simulation, like a platformer.
-
Examples of appropriate genres: top-down games (Zelda), shoot 'em ups, fighting games (Street Fighter), platformers (Super Mario).
Not a good match for:
-
Games that require polygons for collision detection.
-
Games that require highly realistic simulations of physics - things stacking up, rolling over slides, etc.
-
Games that require very fast objects colliding realistically against each other (sprites here are moved and collided one at a time).
-
Simulations where the order in which the collisions are resolved isn’t known.
Basic collision checking
playdate.graphics.sprite:setCollideRect(rect)
setCollideRect()
marks the area of the sprite, relative to its own internal coordinate system, to be checked for collisions with other sprites' collide rects. Note that the coordinate space is relative to the top-left corner of the bounds, regardless of where the sprite’s center/anchor is located.
Tip
|
If you want to set the sprite’s collide rect to be the same size as the sprite itself, you can write sprite:setCollideRect( 0, 0, sprite:getSize() ) .
|
Important
|
setCollideRect() must be invoked on a sprite in order to get it to participate in collisions.
|
Returns the sprite’s collide rect set with setCollideRect()
. Return value is a playdate.geometry.rect
.
Important
|
This function return coordinates relative to the sprite itself; the sprite’s position has no bearing on these values. |
Returns the sprite’s collide rect as a 4-tuple, (x, y, width, height).
Important
|
This function return coordinates relative to the sprite itself; the sprite’s position has no bearing on these values. |
Clears the sprite’s collide rect set with setCollideRect()
.
Returns an array of sprites that have collide rects that are currently overlapping the calling sprite’s collide rect, taking the sprites' groups and collides-with masks into consideration.
Returns an array of array-style tables, each containing two sprites that have overlapping collide rects. All sprite pairs that are have overlapping collide rects (taking the sprites' group and collides-with masks into consideration) are returned.
local collisions = gfx.sprite.allOverlappingSprites()
for i = 1, #collisions do
local collisionPair = collisions[i]
local sprite1 = collisionPair[1]
local sprite2 = collisionPair[2]
-- do something with the colliding sprites
end
Returns a boolean value set to true if a pixel-by-pixel comparison of the sprite images shows that non-transparent pixels are overlapping, based on the current bounds of the sprites.
This method may be used in conjunction with the standard collision architecture. Say, if overlappingSprites()
or moveWithCollisions()
report a collision of two sprite’s bounding rects, alphaCollision() could then be used to discern if a pixel-level collision occurred.
The sprite’s collisionsEnabled flag (defaults to true) can be set to false
in order to temporarily keep a sprite from colliding with any other sprite.
The sprite’s collisionsEnabled flag (defaults to true) can be set to false
in order to temporarily keep a sprite from colliding with any other sprite.
Restricting collisions
Collisions can be restricted using one of two methods: setting collision groups, or setting group masks. Groups are in fact just a simplified API for configuring group masks; they both operate on the same underlying architecture.
Collision groups
Adds the sprite to one or more collision groups. A group is a collection of sprites that exhibit similar collision behavior. (An example: in Atari’s Asteroids, asteroid sprites would all be added to the same group, while the player’s spaceship might be in a different group.) Use setCollidesWithGroups()
to define which groups a sprite should collide with.
There are 32 groups, each defined by the integer 1 through 32. To add a sprite to only groups 1 and 3, for example, call mySprite:setGroups({1, 3})
.
Alternatively, use setGroupMask()
to set group membership via a bitmask.
Pass in a group number or an array of group numbers to specify which groups this sprite can collide with. Groups are numbered 1 through 32. Use setGroups()
to specify which groups a sprite belongs to.
Alternatively, you can specify group collision behavior with a bitmask by using setCollidesWithGroupsMask()
.
Group masks
Sprites may be assigned to groups and define which groups they collide with as a method of filtering collisions. These groups are represented by two bitmasks on the sprites: a group bitmask, and a collides-with-groups bitmask. If sprite A’s collides-with-groups bitmask overlaps sprite B’s groups (a bitwise AND of the masks is not zero), or if no groups have been set (both masks are set to 0x00000000), a collision will happen when moving sprite A through sprite B. Convenience functions setGroups()
and setCollidesWithGroups()
exist to avoid the need to deal with bitmasks directly.
setGroupMask()
sets the sprite’s group bitmask, which is 32 bits. In conjunction with the setCollidesWithGroupsMask()
method, this controls which sprites can collide with each other.
For large group mask numbers, pass the number as a hex value, eg. 0xFFFFFFFF
to work around limitations in Lua’s integer sizes.
getGroupMask()
returns the integer value of the sprite’s group bitmask.
Sets the sprite’s collides-with-groups bitmask, which is 32 bits. The mask specifies which other sprite groups this sprite can collide with. Sprites only collide if the moving sprite’s collidesWithGroupsMask matches at least one group of a potential collision sprite (i.e. a bitwise AND (&) between the moving sprite’s collidesWithGroupsMask and a potential collision sprite’s groupMask != zero) or if the moving sprite’s collidesWithGroupsMask and the other sprite’s groupMask are both set to 0x00000000 (the default values).
For large mask numbers, pass the number as a hex value, eg. 0xFFFFFFFF
to work around limitations in Lua’s integer sizes.
Returns the integer value of the sprite’s collision bitmask.
Resets the sprite’s group mask to 0x00000000
.
Resets the sprite’s collides-with-groups mask to 0x00000000
.
Advanced Collisions
Moves the sprite towards goalX, goalY taking collisions into account, which means the sprite’s final position may not be the same as goalX, goalY.
Returns actualX, actualY, collisions, length.
actualX, actualY |
the final position of the sprite. If no collisions occurred, this will be the same as goalX, goalY. |
collisions |
an array of userdata objects containing information about all collisions that occurred. Each item in the array contains values for the following indices: - sprite: The sprite being moved. - other: The sprite colliding with the sprite being moved. - type: The result of collisionResponse. - overlaps: Boolean. True if the sprite was overlapping other when the collision started. False if it didn’t overlap but tunneled through other. - ti: A number between 0 and 1 indicating how far along the movement to the goal the collision occurred. - move: playdate.geometry.vector2D. The difference between the original coordinates and the actual ones when the collision happened. - normal: playdate.geometry.vector2D. The collision normal; usually -1, 0, or 1 in x and y. Use this value to determine things like if your character is touching the ground. - touch: playdate.geometry.point. The coordinates where the sprite started touching other. - spriteRect: playdate.geometry.rect. The rectangle the sprite occupied when the touch happened. - otherRect: playdate.geometry.rect. The rectangle If the collision type was playdate.graphics.sprite.kCollisionTypeBounce the table also contains bounce, a playdate.geometry.point indicating the coordinates to which the sprite attempted to bounce (could be different than actualX, actualY if further collisions occurred). If the collision type was playdate.graphics.sprite.kCollisionTypeSlide the table also contains slide, a playdate.geometry.point indicating the coordinates to which the sprite attempted to slide. |
length |
the length of the collisions array, equal to #collisions |
Note that the collision info items are only valid until the next call of moveWithCollisions or checkCollisions. To save collision information for later, the data should be copied out of the collision info userdata object.
See also checkCollisions()
to check for collisions without actually moving the sprite.
Moves the sprite towards goalPoint taking collisions into account, which means the sprite’s final position may not be the same as goalPoint.
Returns actualX, actualY, collisions, length.
actualX, actualY |
the final position of the sprite. If no collisions occurred, this will be the same as goalX, goalY. |
collisions |
an array of userdata objects containing information about all collisions that occurred. Each item in the array contains values for the following indices: - sprite: The sprite being moved.
- other: The sprite colliding with the sprite being moved.
- type: The result of collisionResponse.
- overlaps: Boolean. True if the sprite was overlapping other when the collision started. False if it didn’t overlap but tunneled through other.
- ti: A number between 0 and 1 indicating how far along the movement to the goal the collision occurred.
- move: playdate.geometry.vector2D. The difference between the original coordinates and the actual ones when the collision happened.
- normal: playdate.geometry.vector2D. The collision normal; usually -1, 0, or 1 in x and y. Use this value to determine things like if your character is touching the ground.
- touch: playdate.geometry.point. The coordinates where the sprite started touching other.
- spriteRect: playdate.geometry.rect. The rectangle the sprite occupied when the touch happened.
- otherRect: playdate.geometry.rect. The rectangle If the collision type was playdate.graphics.sprite.kCollisionTypeBounce the table also contains bounce, a playdate.geometry.point indicating the coordinates to which the sprite attempted to bounce (could be different than actualX, actualY if further collisions occurred). If the collision type was playdate.graphics.sprite.kCollisionTypeSlide the table also contains slide, a playdate.geometry.point indicating the coordinates to which the sprite attempted to slide. |
length |
the length of the collisions array, equal to #collisions |
Note that the collision info items are only valid until the next call of moveWithCollisions or checkCollisions. To save collision information for later, the data should be copied out of the collision info userdata object.
See also checkCollisions()
to check for collisions without actually moving the sprite.
playdate.graphics.sprite:checkCollisions(point)
Returns the same values as moveWithCollisions()
but does not actually move the sprite.
A callback that can be defined on a sprite to control the type of collision response that should happen when a collision with other occurs. This callback should return one of the following four values:
-
playdate.graphics.sprite.kCollisionTypeSlide: Use for collisions that should slide over other objects, like Super Mario does over a platform or the ground.
-
playdate.graphics.sprite.kCollisionTypeFreeze: Use for collisions where the sprite should stop moving as soon as it collides with other, such as an arrow hitting a wall.
-
playdate.graphics.sprite.kCollisionTypeOverlap: Use for collisions in which you want to know about the collision but it should not impact the movement of the sprite, such as when collecting a coin.
-
playdate.graphics.sprite.kCollisionTypeBounce: Use when the sprite should move away from other, like the ball in Pong or Arkanoid.
The strings "slide", "freeze", "overlap", and "bounce" can be used instead of the constants.
Feel free to return different values based on the value of other. For example, if other is a wall sprite, you may want to return "slide" or "bounce", but if it’s a coin you might return "overlap".
If the callback is not present, or returns nil, kCollisionTypeFreeze is used.
Tip
|
Instead of defining a callback, the collisionResponse property of a sprite can be set directly to one of the four collision response types. This will be faster, as the lua function will not need to be called, but does not allow for dynamic behavior. |
This method should not attempt to modify the sprites in any way. While it might be tempting to deal with collisions here, doing so will have unexpected and undesirable results. Instead, this function should return one of the collision response values as quickly as possible. If sprites need to be modified as the result of a collision, do so elsewhere, such as by inspecting the list of collisions returned by moveWithCollisions()
.
playdate.graphics.sprite.querySpritesAtPoint(p)
Returns all sprites with collision rects containing the point.
playdate.graphics.sprite.querySpritesInRect(rect)
Returns all sprites with collision rects overlapping the rect.
playdate.graphics.sprite.querySpritesAlongLine(lineSegment)
Returns all sprites with collision rects intersecting the line segment.
playdate.graphics.sprite.querySpriteInfoAlongLine(lineSegment)
Similar to querySpritesAlongLine(), but instead of sprites returns an array of collisionInfo tables containing information about sprites intersecting the line segment, and len, which is the number of collisions found. If you don’t need this information, use querySpritesAlongLine() as it will be faster.
Each collisionInfo table contains:
-
sprite: the sprite being intersected by the segment.
-
entryPoint: a
point
representing the coordinates of the first intersection betweensprite
and the line segment. -
exitPoint: a
point
representing the coordinates of the second intersection betweensprite
and the line segment. -
ti1 & ti2: numbers between 0 and 1 which indicate how far from the starting point of the line segment the collision happened; t1 for the entry point, t2 for the exit point. This can be useful for things like having a laser cause more damage if the impact is close.
Sprites in tilemap-based games
For tile-based games, the built-in tilemap library has a convenience function called getCollisionRects(), which will generate from the tilemap an array of rectangles suitable for use with the collision system to define walls and other impassable regions.
playdate.graphics.sprite.addEmptyCollisionSprite(x, y, w, h)
Important
|
You must import CoreLibs/sprites to use this function. |
This convenience function adds an invisible sprite defined by the rectangle x, y, w, h (or the playdate.geometry.rect r) for the purpose of triggering collisions. This is useful for making areas impassable, triggering an event when a sprite enters a certain area, and so on.
Important
|
You must import CoreLibs/sprites to use this function. |
This convenience function automatically adds empty collision sprites necessary to restrict movement within a tilemap.
tilemap is a playdate.graphics.tilemap.
emptyIDs is an array of tile IDs that should be considered "passable" — in other words, not walls.
xOffset, yOffset optionally indicate the distance the new sprites should be offset from (0,0).
Returns an array-style table of the newly created sprites.
Calling this function is effectively a shortcut for calling playdate.graphics.tilemap:getCollisionRects() and passing the resulting rects to addEmptyCollisionSprite().
Text
Fonts
Playdate fonts are playdate.graphics.font objects, loaded into Lua with the playdate.graphics.font.new(path) function and drawn on screen using playdate.graphics.drawText(text, x, y).
The compiler can create a font from a standalone .fnt file with embedded image data or by combining a dependent .fnt file with a related image table. For example, if a dependent .fnt file is named awesomefont.fnt then the related image table would be named awesomefont-table-9-12.png
Standalone .fnt files can be created with the Playdate Caps web app from scratch or from a dependent .fnt file and image table pair. Caps can also create .fnt files from common font file formats like .ttf, .otf, and .woff files.
At its simplest, a dependent .fnt file contains one line per glyph. Each line contains the glyph (the space character is indicted with the text "space"), in the order the glyph appears in the image table, and the width of the glpyh, separated by any amount of whitespace.
space 6 ! 2 " 4 # 7
Blank lines are ignored. Comments begin with two dashes.
$ 6 % 8 -- this comment will be ignored, as will any blank lines & 7
An optional, default tracking value can be specified on its own line like so:
tracking = 2
The tracking value is the number of pixels of whitespace between each character drawn in a string.
Kerning pairs are supported, one line per pair. Each line contains the two character pair, and the offset, separated by any amount of whitespace.
To -2 ll 3
A standalone .fnt file must contain these additional properties to compile correctly. (While a standalone .fnt file can be authored manually, most will be created with Playdate Caps. This informataion is included here for thoroughness.)
Embedding a font’s pixel data requires 4 additional properties: the string length of the base64-encoded image table data as datalen
, a base64-encoded image table as data
, and the pixel dimensions of each uniform cell in the image table as width
, and height
.
datalen=8984 data=iVBO...YII= width=8 height=12
Playdate Caps will also embed some metrics used for authoring as a JSON object in a comment.
--metrics={"baseline":17,"xHeight":6,"capHeight":2}
Supported characters
ASCII characters in the range 0x20 (space) to 0x7E (~
) are supported in Playdate fonts. Katakana characters in the unicode range 30A0–30FF and Hiragana characters in the range 3040–309F are allowed with the exception of 3040, 3097, 3098, 3099, and 309A.
Additionally, characters not in those ranges that are allowed are:
-
Ellipsis (…)
-
Trademark sign (™)
-
Double exclamation mark (‼︎)
-
Copyright sign (©)
-
Registered sign (®)
-
Yen symbol (¥)
-
Yen symbol (円)
-
Full stop (。)
-
Comma (、)
-
Left corner bracket (「)
-
Right corner bracket (」)
-
Replacement character (�)
If a replacement character is specified it will be drawn in place of any missing characters in your font. If it is not, characters missing from the font will be drawn using the system font.
Variants
In order to support formatting and localization, Playdate allows you to set up to three font files as variants: normal, bold, and italic.
Font class functions
Returns a playdate.graphics.font object from the data at path. If there is no file at path, the function returns nil.
Returns a font family table from the font files specified in fontPaths. fontPaths should be a table with the following format:
local fontPaths = { [playdate.graphics.font.kVariantNormal] = "path/to/normalFont", [playdate.graphics.font.kVariantBold] = "path/to/boldFont", [playdate.graphics.font.kVariantItalic] = "path/to/italicFont" }
The table returned is of the same format with font objects in place of the paths, and is appropriate to pass to the functions setFontFamily() and getTextSize().
Sets the current font, a playdate.graphics.font.
variant should be one of the strings "normal", "bold", or "italic", or one of the constants:
-
playdate.graphics.font.kVariantNormal
-
playdate.graphics.font.kVariantBold
-
playdate.graphics.font.kVariantItalic
If no variant is specified, kFontVariantNormal is used.
Returns the current font, a playdate.graphics.font.
Sets multiple font variants at once. fontFamily
should be a table using the following format:
local fontFamily = { [playdate.graphics.font.kVariantNormal] = normal_font, [playdate.graphics.font.kVariantBold] = bold_font, [playdate.graphics.font.kVariantItalic] = italic_font }
All fonts and font variants need not be present in the table.
Sets the global font tracking (spacing between letters) in pixels. This value is added to the font’s own tracking value as specified in its .fnt file.
See playdate.graphics.font:setTracking to adjust tracking on a specific font.
Gets the global font tracking (spacing between letters) in pixels.
See playdate.graphics.font:setTracking to adjust tracking on a specific font.
Like getFont() but returns the system font rather than the currently set font.
variant should be one of the strings "normal", "bold", or "italic", or one of the constants:
-
playdate.graphics.font.kVariantNormal
-
playdate.graphics.font.kVariantBold
-
playdate.graphics.font.kVariantItalic
Font instance functions
Draws a string at the specified x, y coordinate using this particular font instance. (Compare to playdate.graphics.drawText(text, x, y), which draws the string with whatever the "current font", as defined by playdate.graphics.setFont(font)).
The optional leadingAdjustment may be used to modify the spacing between lines of text. Pass nil to use the default leading for the font.
Important
|
You must import CoreLibs/graphics to use this function. |
Draws the string text aligned to the left, right, or centered on the x coordinate. Pass one of kTextAlignment.left, kTextAlignment.center, kTextAlignment.right for the alignment parameter. (Compare to playdate.graphics.drawTextAligned(text, x, y, alignment), which draws the string with the "current font", as defined by playdate.graphics.setFont(font)).
Returns the pixel height of this font.
Returns the pixel width of the text when rendered with this font.
Sets the tracking of this font (spacing between letters), in pixels.
Returns the tracking of this font (spacing between letters), in pixels.
Sets the leading (spacing between lines) of this font, in pixels.
Returns the leading (spacing between lines) of this font, in pixels.
Drawing Text
Draws the text using the current font and font advance at location (x, y).
To draw bold text, surround the bold portion of text with asterisks. To draw italic text, surround the italic portion of text with underscores. For example:
playdate.graphics.drawText("normal *bold* _italic_", x, y)
which will output: "normal bold italic". Bold and italic font variations must be set using setFont() with the appropriate variant argument, otherwise the default Playdate fonts will be used.
To draw an asterisk or underscore, use a double-asterisk or double-underscore. Styles may not be nested, but double-characters can be used inside of a styled portion of text.
For a complete set of characters allowed in text, see playdate.graphics.font. In addition, the newline character \n is allowed and works as expected.
To draw white-on-black text (assuming the font you are using is defined in the standard black-on-transparent manner), first call playdate.graphics.setImageDrawMode(playdate.graphics.kDrawModeFillWhite), followed by the appropriate drawText() call. setImageDrawMode() affects how text is rendered because characters are technically images.
If fontFamily is provided, the text is draw using the given fonts instead of the currently set font. fontFamily should be a table of fonts using keys as specified in setFontFamily(fontFamily).
The optional leadingAdjustment may be used to modify the spacing between lines of text. Pass nil to use the default leading for the font.
Draws the text found by doing a lookup of key in the .strings file corresponding to the current system language, or language, if specified.
The optional language argument can be one of the strings "en", "jp", or one of the constants:
-
playdate.graphics.font.kLanguageEnglish
-
playdate.graphics.font.kLanguageJapanese
For more information about localization and strings files, see the Localization section.
Returns a string found by doing a lookup of key in the .strings file corresponding to the current system language, or language, if specified.
The optional language argument can be one of the strings "en", "jp", or one of the constants:
-
playdate.graphics.font.kLanguageEnglish
-
playdate.graphics.font.kLanguageJapanese
For more information about localization and strings files, see the Localization section.
Returns the tuple (width, height) giving the dimensions required to draw the text str using drawText(). Newline characters (\n) are respected.
fontFamily should be a table of fonts using keys as specified in setFontFamily(fontFamily). If provided, fonts from fontFamily will be used for calculating the size of str instead of the currently set font.
Important
|
You must import CoreLibs/graphics to use this function. |
Draws the string text aligned to the left, right, or centered on the x coordinate. Pass one of kTextAlignment.left, kTextAlignment.center, kTextAlignment.right for the alignment parameter.
For text formatting options, see drawText()
To draw unstyled text using a single font, see playdate.graphics.font:drawTextAligned()
playdate.graphics.drawTextInRect(text, rect, [leadingAdjustment, [truncationString, [alignment, [font]]]])
Important
|
You must import CoreLibs/graphics to use these functions. |
Draws the text using the current font and font advance into the rect defined by (x
, y
, width
, height
) (or rect
).
If truncationString
is provided and the text cannot fit in the rect, truncationString
will be appended to the last line.
alignment
, if provided, should be one of one of kTextAlignment.left
, kTextAlignment.center
, kTextAlignment.right
. Pass nil
for leadingAdjustment
and truncationString
if those parameters are not required.
font
, if provided, will cause the text to be drawn unstyled using font:drawText() rather than playdate.graphics.drawText() using the currently-set system fonts.
For text formatting options, see drawText()
Returns (width
, height
, textWasTruncated
)
width
and height
indicate the size in pixels of the drawn text. These values may be smaller than the width and height specified when calling the function.
textWasTruncated
indicates if the text was truncated to fit within the specified rect.
Important
|
You must import CoreLibs/graphics to use this function. |
Same as drawTextAligned() except localized text is drawn.
playdate.graphics.drawLocalizedTextInRect(text, rect, [leadingAdjustment, [truncationString, [alignment, [language]]]])
Important
|
You must import CoreLibs/graphics to use these functions. |
Same as drawTextInRect() except localized text is drawn.
Important
|
You must import CoreLibs/graphics to use this function. |
Returns width
, height
which indicate the minimum size required for the text str to be drawn using drawTextInRect(). The width returned will be less than or equal to maxWidth
.
font
, if provided, will cause the text size to be calculated without bold or italic styling using the specified font.
Video
The video player renders frames from a pdv file into an image or directly to the screen. Note that the renderer expects to have ownership of the data in its drawing context, whether it’s the screen or a separate image. Drawing over the video frames in the render context can cause the image to become garbled. If you want to use drawing functions on top of the video, create a context image for the video to render to (calling video:getContext() will create the image), call video:renderFrame(), then draw the context image to the screen, then draw on top of that. The pdv file does not (currently) contain audio, so typically you’d play the audio in a fileplayer or sampleplayer and use the current audio offset to determine which video frame to display.
A minimal video player:
local disp = playdate.display
local gfx = playdate.graphics
local snd = playdate.sound
disp.setRefreshRate(0)
local video = gfx.video.new('movie')
video:useScreenContext()
video:renderFrame(0)
local lastframe = 0
local audio, loaderr = snd.sampleplayer.new('movie')
if audio ~= nil then
audio:play(0)
else
print(loaderr)
end
function playdate.update()
local frame = math.floor(audio:getOffset() * video:getFrameRate())
if frame ~= lastframe then
video:renderFrame(frame)
lastframe = frame
end
end
Returns a playdate.graphics.video object from the pdv file at path. If the file at path can’t be opened, the function returns nil.
Returns the width and height of the video as a tuple.
Returns the number of frames in the video.
Returns the number of frames per second of the video source. This number is simply for record-keeping, it is not used internally—the game code is responsible for figuring out which frame to show when.
Sets the given image to the video render context. Future video:renderFrame()
calls will draw into this image.
Returns the image into which the video will be rendered, creating it if needed.
Sets the display framebuffer as the video’s render context.
Draws the given frame into the video’s render context.
6.21. JSON
Provides encoding and decoding of JSON files and strings.
Takes the JSON encoded string and converts it to a Lua table.
Reads the given playdate.file.file object and converts it to a Lua table.
Reads the file at the given path
and converts it to a Lua table.
Returns a string containing the JSON representation of the passed-in Lua table.
Returns a string containing the JSON representation of a Lua table, with human-readable formatting.
Encodes the Lua table table
to JSON and writes it to the given playdate.file.file object. If pretty
is true, the output is formatted to make it human-readable. Otherwise, no additional whitespace is added.
Tip
|
For a very simple way to serialize a table to a file, see playdate.datastore. |
Encodes the Lua table table
to JSON and writes it to the file at path
. If pretty
is true, the output is formatted to make it human-readable. Otherwise, no additional whitespace is added.
Tip
|
For a very simple way to serialize a table to a file, see playdate.datastore. |
6.22. Keyboard
An on-screen keyboard that can be used for text entry.
Important
|
You must import CoreLibs/keyboard to use these functions. |
Opens the keyboard, taking over input focus.
text, if provided, will be used to set the initial text value of the keyboard.
Hides the keyboard.
Access or set the text value of the keyboard.
behavior should be one of the constants playdate.keyboard.kCapitalizationNormal, playdate.keyboard.kCapitalizationWords, or playdate.keyboard.kCapitalizationSentences.
In the case of playdate.keyboard.kCapitalizationWords, the keyboard selection will automatically move to the upper case column after a space is entered. For playdate.keyboard.kCapitalizationSentences the selection will automatically move to the upper case column after a period and a space have been entered.
Returns the current x location of the left edge of the keyboard.
Returns the pixel width of the keyboard.
Returns true if the keyboard is currently being shown.
If set, this function will be called when the keyboard is finished the opening animation.
If set, this function will be called when the keyboard has finished the hide animation.
If set, this function will be called when the keyboard starts to close. A Boolean argument will be passed to the callback, true
if the user selected "OK" close the keyboard, false
otherwise.
If set, this function is called as the keyboard animates open or closed. Provided as a way to sync animations with the keyboard movement.
If set, this function will be called every time a character is entered or deleted.
6.23. Math
Returns a number that is the linear interpolation between min and max based on t, where t = 0.0 will return min and t = 1.0 will return max.
Important
|
You must import CoreLibs/math to use this function. |
6.24. Pathfinding
An implementation of the popular A* pathfinding algorithm. To find a path first create a playdate.pathfinder.graph containing connected playdate.pathfinder.nodes then call findPath on the graph. A heuristic function callback can be specified for determining an estimate of the distance between two nodes, otherwise the manhattan distance between nodes will be used. In that case it is important to set appropriate x and y values on the nodes.
Example Code |
---|
Graph
Returns a new empty playdate.pathfinder.graph object.
If nodeCount
is supplied, that number of nodes will be allocated and added to the graph. Their IDs will be set from 1 to nodeCount
.
coordinates
, if supplied, should be a table containing tables of x, y values, indexed by node IDs. For example, {{10, 10}, {50, 30}, {20, 100}, {100, 120}, {160, 130}}
.
Convenience function that returns a new playdate.pathfinder.graph object containing nodes for for each grid position, even if not connected to any other nodes. This allows for easier graph modification once the graph is generated. Weights for connections between nodes are set to 10 for horizontal and vertical connections and 14 for diagonal connections (if included), as this tends to produce nicer paths than using uniform weights. Nodes have their indexes set from 1 to width * height, and have their x, y values set appropriately for the node’s position.
-
width: The width of the grid to be created.
-
height: The height of the grid to be created.
-
allowDiagonals: If true, diagonal connections will also be created.
-
includedNodes: A one-dimensional array of length width * height. Each entry should be a 1 or a 0 to indicate nodes that should be connected to their neighbors and nodes that should not have any connections added. If not provided, all nodes will be connected to their neighbors.
Creates a new playdate.pathfinder.node and adds it to the graph.
-
id: id value for the new node.
-
x: Optional x value for the node.
-
y: Optional y value for the node.
-
connectedNodes: Array of existing nodes to create connections to from the new node.
-
weights: Array of weights for the new connections. Array must be the same length as connectedNodes. Weights affect the path the A* algorithm will solve for. A longer, lighter-weighted path will be chosen over a shorter heavier path, if available.
-
addReciprocalConnections: If true, connections will also be added in the reverse direction for each node.
Creates count new nodes, adding them to the graph, and returns them in an array-style table. The new node’s id_s will be assigned values 1 through _count-1.
This method is useful to improve performance if many nodes need to be allocated at once rather than one at a time, for example when creating a new graph.
Adds an already-existing node to the graph. The node must have originally belonged to the same graph.
-
node: Node to be added to the graph.
-
connectedNodes: Array of existing nodes to create connections to from the new node.
-
weights: Array of weights for the new connections. Array must be the same length as connectedNodes. Weights affect the path the A* algorithm will solve for. A longer, lighter-weighted path will be chosen over a shorter heavier path, if available.
-
addReciprocalConnections: If true, connections will also be added in the reverse direction for each connection added.
Adds an array of already-existing nodes to the graph.
Returns an array containing all nodes in the graph.
Removes node from the graph. Also removes all connections to and from the node.
Returns the first node found with coordinates matching x, y, after removing it from the graph and removing all connections to and from the node.
Returns the first node found with a matching id, after removing it from the graph and removing all connections to and from the node.
Returns the first node found in the graph with a matching id, or nil if no such node is found.
Returns the first node found in the graph with matching x and y values, or nil if no such node is found.
connections
should be a table of array-style tables. The keys of the outer table should correspond to node IDs, while the inner array should be a series if connecting node ID and weight combinations that will be assigned to that node. For example, {[1]={2, 10, 3, 12}, [2]={1, 20}, [3]={1, 20, 2, 10}}
will create a connection from node ID 1 to node ID 2 with a weight of 10, and a connection to node ID 3 with a weight of 12, and so on for the other entries.
Adds a connection from the node with id
fromNodeID
to the node with id
toNodeID
with a weight value of weight
. Weights affect the path the A* algorithm will solve for. A longer, lighter-weighted path will be chosen over a shorter heavier path, if available. If addReciprocalConnection
is true, the reverse connection will also be added.
Removes all connections from all nodes in the graph.
Removes all connections from the matching node.
If removeIncoming
is true, all connections from other nodes to the calling node are also removed. False by default. Please note: this can signficantly increase the time this function takes as it requires a full search of the graph - O(1) vs O(n)).
Returns an array of nodes representing the path from startNode to goalNode, or nil if no path can be found.
-
heuristicFunction: If provided, this function should be of the form function(startNode, goalNode) and should return an integer value estimate or underestimate of the distance from startNode to goalNode. If not provided, a manhattan distance function will be used to calculate the estimate. This requires that the x, y values of the nodes in the graph have been set properly.
-
findPathToGoalAdjacentNodes: If true, a path will be found to any node adjacent to the goal node, based on the x, y values of those nodes and the goal node. This does not rely on connections between adjacent nodes and the goal node, which can be entirely disconnected from the rest of the graph.
Works the same as findPath, but looks up nodes to find a path between using startNodeID and goalNodeID and returns a list of nodeIDs rather than the nodes themselves.
Sets the matching node’s x
and y
values.
Node
You can directly read or write x, y and id values on a playdate.pathfinder.node.
Adds a new connection between nodes.
-
node: The node the new connection will point to.
-
weight: Weight for the new connection. Weights affect the path the A* algorithm will solve for. A longer, lighter-weighted path will be chosen over a shorter heavier path, if available.
-
addReciprocalConnection: If true, a second connection will be created with the same weight in the opposite direction.
Adds a new connection to each node in the nodes array.
-
nodes: An array of nodes which the new connections will point to.
-
weights: An array of weights for the new connections. Must be of the same length as the nodes array. Weights affect the path the A* algorithm will solve for. A longer, lighter-weighted path will be chosen over a shorter heavier path, if available.
-
addReciprocalConnections: If true, connections will also be added in the reverse direction for each node.
Adds a connection to the first node found with matching x and y values, if it exists.
-
weight: The weight for the new connection. Weights affect the path the A* algorithm will solve for. A longer, lighter-weighted path will be chosen over a shorter heavier path, if available.
-
addReciprocalConnections: If true, a connection will also be added in the reverse direction, from the node at x, y to the caller.
Returns an array of nodes that have been added as connections to this node.
Removes a connection to node, if it exists. If removeReciprocal is true the reverse connection will also be removed, if it exists.
Removes all connections from the calling node.
If removeIncoming
is true, all connections from other nodes to the calling node are also removed. False by default. Please note: this can signficantly increase the time this function takes as it requires a full search of the graph - O(1) vs O(n)).
Sets the x and y values for the node.
6.25. Power
Returns a table holding booleans with the following keys: charging (for any sort of charging), and USB (for USB-specific charging). Test these values for true
to see if the device is being charged, and by what means.
Returns a value from 0-100 denoting the current level of battery charge. 0 = empty; 100 = full.
Returns the battery’s current voltage level.
6.26. Simulator-only functionality
This variable—not a function, so don’t invoke with ()—it is set to 1 when running inside of the simulator and is nil otherwise.
Writes an image to a PNG file at the path specified. Only available on the simulator.
Note
|
path represents a path on your development computer, not the Playdate filesystem. It’s recommended you prefix your path with ~/ to ensure you are writing to a writeable directory, for example, ~/myImageFile.png . Please include the .png file extension in your path name. Any directories in your path must already exist on your development computer in order for the file to be written.
|
Quits the Playdate Simulator app.
Returns the contents of the URL url as a string.
Clears the simulator console.
Sets the color of the playdate.debugDraw() overlay image.
Simulator debug callbacks
These callbacks are only invoked when your game is running in the Simulator.
Lets you act on keyboard keypresses when running in the Simulator ONLY. These can be useful for adding debugging functions that can be enabled via your keyboard.
Note
|
It is possible test a game on Playdate hardware and trap computer keyboard keypresses if you are using the Simulator’s Control Device with Simulator option.
|
key
is a string containing the character pressed or released on the keyboard. Note that:
-
The key in question needs to have a textual representation or these functions will not be called. For instance, alphanumeric keys will call these functions; keyboard directional arrows will not.
-
If the keypress in question is already in use by the Simulator for another purpose (say, to control the d-pad or A/B buttons), these functions will not be called.
Lets you act on keyboard key releases when running in the Simulator ONLY. These can be useful for adding debugging functions that can be enabled via your keyboard.
Called immediately after playdate.update(), any drawing performed during this callback is overlaid on the display in 50% transparent red (or another color selected with playdate.setDebugDrawColor()).
White pixels are drawn in the debugDrawColor. Black pixels are transparent.
6.27. Sound
The Playdate audio engine provides sample playback from memory for short on-demand samples, file streaming for playing longer files (uncompressed, MP3, and ADPCM formats), and a synthesis library for generating "computer-y" sounds. Sound sources are grouped into channels, which can be panned separately, and various effects may be applied to the channels. Additionally, signals can automate various parameters of the sound objects.
Returns the sample rate of the audio system (44100). The sample rate is determined by the hardware, and is not currently mutable.
Sampleplayer
The sampleplayer class is used for playing short samples like sound effects. Audio data is loaded into memory at instantiation, so it plays with little overhead. For longer audio like background music, the fileplayer class may be more appropriate; there, audio data is streamed from disk as it’s played and only a small portion of the data is in memory at any given time.
Note
|
Unlike fileplayer, sampleplayer cannot play MP3 files. For a balance of good performance and small file size, we recommend encoding audio into ADPCM .wav files. |
Returns a new playdate.sound.sampleplayer object, with the sound data loaded in memory. If the sample can’t be loaded, the function returns nil and a second value containing the error.
Returns a new playdate.sound.sampleplayer object for playing the given sample.
Returns a new playdate.sound.sampleplayer with the same sample, volume, and rate as the given sampleplayer.
Starts playing the sample. If repeatCount is greater than one, it loops the given number of times. If zero, it loops endlessly until it is stopped with playdate.sound.sampleplayer:stop(). If rate is set, the sample will be played at the given rate instead of the rate previous set with playdate.sound.sampleplayer.setRate().
Schedules the sound for playing at device time when. If vol is specified, the sample will be played at level vol (with optional separate right channel volume rightvol). If when is less than the current device time, the sample is played immediately. If rate is set, the sample will be played at the given rate instead of the rate previous set with playdate.sound.sampleplayer.setRate().
The function returns true if the sample was successfully added to the sound channel, otherwise false (i.e., if the channel is full).
Sets the playback volume (0.0 - 1.0) for left and right channels. If the optional right argument is omitted, it is the same as left.
Returns the playback volume for the sampleplayer, a single value for mono sources or a pair of values (left, right) for stereo sources.
Sets a function to be called every time the sample loops. The sample object is passed to this function as the first argument, and the optional arg argument is passed as the second.
Sets the range of the sample to play. start and end are frame offsets from the beginning of the sample.
Pauses or resumes playback.
Returns a boolean indicating whether the sample is playing.
Stops playing the sample.
Sets a function to be called when playback has completed. The sample object is passed to this function as the first argument, and the optional arg argument is passed as the second.
Sets the sample to be played.
Gets the sample to be played.
Returns the length of the sampleplayer’s sample, in seconds. Length is not scaled by playback rate.
Sets the playback rate for the sample. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. Sampleplayers can also play samples backwards, by setting a negative rate.
Gets the playback rate for the sample. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. Sampleplayers can also play samples backwards, by setting a negative rate.
Sets the signal to use as a rate modulator, added to the rate set with playdate.sound.sampleplayer:setRate().
Sets the current offset of the sampleplayer, in seconds. This value is not adjusted for rate.
Gets the current offset of the sampleplayer, in seconds. This value is not adjusted for rate.
Fileplayer
The fileplayer class is used for streaming audio from a file on disk. This requires less memory than keeping all of the file’s data in memory (as with the sampleplayer), but can increase overhead at run time.
Note
|
Fileplayer can play MP3 files, but MP3 decoding is CPU-intensive. For a balance of good performance and small file size, we recommend encoding audio into ADPCM .wav files. |
Returns a fileplayer object, which can stream samples from disk. If path is given but the file can’t be loaded, the function returns nil and a second value containing the error.
If given, buffersize specifies the size in seconds of the fileplayer’s data buffer, and causes the buffer to be filled from source data (if path was provided). A shorter value reduces the latency of a playdate.sound.fileplayer:setOffset() call, but increases the chance of a buffer underrun. If buffersize is not set in new(), the buffer is later created, and data loaded, by a call to playdate.sound.fileplayer:setBufferSize(). Or, if playdate.sound.fileplayer:play() is called without first setting the buffer size, a default 0.25 s buffer is created.
Returns a fileplayer object, which can stream samples from disk. If path is given but the file can’t be loaded, the function returns nil and a second value containing the error.
If given, buffersize specifies the size in seconds of the fileplayer’s data buffer, and causes the buffer to be filled from source data (if path was provided). A shorter value reduces the latency of a playdate.sound.fileplayer:setOffset() call, but increases the chance of a buffer underrun. If buffersize is not set in new(), the buffer is later created, and data loaded, by a call to playdate.sound.fileplayer:setBufferSize(). Or, if playdate.sound.fileplayer:play() is called without first setting the buffer size, a default 0.25 s buffer is created.
Instructs the fileplayer to load the file at path. The fileplayer must not be playing when this function is called. The fileplayer’s play offset is reset to the beginning of the file, and its loop range is cleared.
If the file is loaded successfully, the function returns true; otherwise it returns false plus a second value containing the error.
Starts playing the file, first creating and filling a 1/4 second playback buffer if a buffer size hasn’t been set yet.
If repeatCount is set, playback repeats when it reaches the end of the file or the end of the loop range if one is set. After the loop has run repeatCount times, it continues playing to the end of the file. A repeatCount of zero loops endlessly. If repeatCount is not set, the file plays once.
The function returns true if the fileplayer was successfully added to the sound channel, otherwise false (i.e., if the channel is full).
Stops playing the file, resets the playback offset to zero, and calls the finish callback.
Stops playing the file. A subsequent play() call resumes playback from where it was paused.
Returns a boolean indicating whether the fileplayer is playing.
Returns the length, in seconds, of the audio file.
Sets a function to be called when playback has completed. The fileplayer is passed as the first argument to func. The optional argument arg is passed as the second.
Returns the fileplayer’s underrun flag, indicating that the player ran out of data. This can be checked in the finish callback function to check for an underrun error.
By default, the fileplayer stops playback if it can’t provide data fast enough. Setting the flag to false tells the fileplayer to restart playback (after an audible stutter) as soon as data is available.
Provides a way to loop a portion of an audio file. In the following code:
local fp = playdate.sound.fileplayer.new( "myaudiofile" )
fp:setLoopRange( 10, 20 )
fp:play( 3 )
…the fileplayer will start playing from the beginning of the audio file, loop the 10-20 second range three times, and then stop playing.
start and end are specified in seconds. If end is omitted, the end of the file is used. If the function loopCallback is provided, it is called every time the player loops, with the fileplayer as the first argument and the optional arg argument as the second.
Important
|
The fileplayer:play([repeatCount]) call needs to be invoked with a repeatCount value of 0 (infinite looping), or 2 or greater in order for the looping action to happen. |
Sets a function to be called every time the fileplayer loops. The fileplayer object is passed to this function as the first argument, and arg as the second.
Important
|
The fileplayer:play([repeatCount]) call needs to be invoked with a repeatCount value of 0 (infinite looping), or 2 or greater in order for the loop callback to be invoked. |
Sets the buffer size for the fileplayer, in seconds. Larger buffers protect against buffer underruns, but consume more memory. Calling this function also fills the output buffer, if a source file has been set.
Sets the playback rate for the file. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. Unlike sampleplayers, fileplayers can’t play in reverse (i.e., rate < 0).
Gets the playback rate for the file. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. Unlike sampleplayers, fileplayers can’t play in reverse (i.e., rate < 0).
Sets the signal to use as a rate modulator, added to the rate set with playdate.sound.fileplayer:setRate().
Sets the playback volume (0.0 - 1.0). If a single value is passed in, both left side and right side volume are set to the given value. If two values are given, volumes are set separately. The optional fadeSeconds specifies the time it takes to fade from the current volume to the specified volume, in seconds. If the function fadeCallback is given, it is called when the volume fade has completed. The fileplayer object is passed as the first argument to the callback, and the optional arg argument is passed as the second.
Returns the current volume for the fileplayer, a single value for mono sources or a pair of values (left, right) for stereo sources.
Sets the current offset of the fileplayer, in seconds. This value is not adjusted for rate.
Sets or gets the current offset of the fileplayer, in seconds. This value is not adjusted for rate.
Sample
playdate.sound.sample is an abstraction of an individual sound sample. If all you want to do is play a single sound sample, you may wish to use playdate.sound.sampleplayer instead. However, playdate.sound.sample exists so you can preload sounds and swap them in and out without fragmenting device memory.
Returns a new playdate.sound.sample object, with the sound data loaded in memory. If the sample can’t be loaded, the function returns nil and a second value containing the error.
playdate.sound.sample.new(seconds, [format])
Returns a new playdate.sound.sample object, with a buffer size of seconds in the given format. If format is not specified, it defaults to playdate.sound.kFormat16bitStereo. When used with playdate.sound.sample:load(), this allows you to swap in a different sample without re-allocating the buffer, which could lead to memory fragmentation.
Returns a new subsample containing a subrange of the given sample. Offset values are in frames, not bytes.
Loads the sound data from the file at path into an existing sample buffer. If there is no file at path, the function returns nil.
Returns the sample rate as an integer, such as 44100 or 22050.
Returns the format of the sample, one of
-
playdate.sound.kFormat8bitMono
-
playdate.sound.kFormat8bitStereo
-
playdate.sound.kFormat16bitMono
-
playdate.sound.kFormat16bitStereo
Returns two values, the length of the available sample data and the size of the allocated buffer. Both values are measured in seconds. For a sample loaded from disk, these will be the same; for a sample used for recording, the available data may be less than the allocated size.
Convenience function: Creates a new sampleplayer for the sample and passes the function arguments to its play function.
Convenience function: Creates a new sampleplayer for the sample and passes the function arguments to its playAt function.
Saves the sample to the given file. If filename
has a .wav
extension it will be saved in WAV format (and be unreadable by the Playdate sound functions), otherwise it will be saved in the Playdate pda format.
Channel
Channels are collections of sources (synths, sampleplayers, and fileplayers) with a list of effects to apply to the sounds, and pan and volume parameters.
Returns a new channel object and adds it to the global list.
Removes the channel from the global list.
Adds an effect to the channel.
Removes an effect from the channel.
Adds a source to the channel. If a source is not assigned to a channel, it plays on the default global channel.
Removes a source from the channel.
Sets the volume (0.0 - 1.0) for the channel.
Gets the volume (0.0 - 1.0) for the channel.
Sets the pan parameter for the channel. -1 is left, 0 is center, and 1 is right.
Sets a signal to automate the pan parameter.
Sets a signal to automate the volume parameter.
Source
playdate.sound.source is the parent class of our sound sources, playdate.sound.fileplayer, playdate.sound.sampleplayer, playdate.sound.synth, and playdate.sound.instrument.
Returns a list of all sources currently playing.
Synth
Returns a new synth object to play a waveform. See playdate.sound.synth:setWaveform for waveform types.
Returns a new synth object to play a Sample. An optional sustain region defines a loop to play while the note is on. Sample data must be uncompressed PCM, not ADPCM.
Returns a copy of the given synth.
Plays a note with the current waveform or sample.
-
pitch: In the first variant, the pitch value is in Hertz. If a sample is playing, pitch=261.63 (C4) plays at normal speed
-
note: In the second variant, note is a MIDI note number: 60=C4, 61=C#4, etc. Fractional values are allowed.
-
in either function, a string like
Db3
can be used instead of a number
-
-
volume: 0 to 1, defaults to 1
-
length: in seconds. If omitted, note will play until you call noteOff()
-
when: seconds since the sound engine started (see playdate.sound.getCurrentTime). Defaults to the current time.
The function returns true if the synth was successfully added to the sound channel, otherwise false (i.e., if the channel is full).
Plays a note with the current waveform or sample.
-
pitch: In the first variant, the pitch value is in Hertz. If a sample is playing, pitch=261.63 (C4) plays at normal speed
-
note: In the second variant, note is a MIDI note number: 60=C4, 61=C#4, etc. Fractional values are allowed.
-
in either function, a string like
Db3
can be used instead of a number
-
-
volume: 0 to 1, defaults to 1
-
length: in seconds. If omitted, note will play until you call noteOff()
-
when: seconds since the sound engine started (see playdate.sound.getCurrentTime). Defaults to the current time.
The function returns true if the synth was successfully added to the sound channel, otherwise false (i.e., if the channel is full).
Releases the note, if one is playing. The note will continue to be voiced through the release section of the synth’s envelope.
Stops the synth immediately, without playing the release part of the envelope.
Returns true if the synth is still playing, including the release phase of the envelope.
Sets the attack time, decay time, sustain level, and release time for the sound envelope.
Sets the signal to use as the amplitude modulator.
Sets the attack time, in seconds.
Sets the decay time, in seconds.
Sets a function to be called when the synth stops playing.
Sets the signal to use as the frequency modulator.
Sets whether to use legato phrasing for the synth. If the legato flag is set and a new note starts while a previous note is still playing, the synth’s envelope remains in the sustain phase instead of starting a new attack.
Some synth types have extra parameters: The square wave’s one parameter is its duty cycle; the TE synths each have two parameters that change some quality of the sound. Parameter numbers start at 1. value ranges from 0 to 1.
Sets the signal to modulate the parameter.
Sets the release time, in seconds.
Sets the sustain level, as a proportion of the total level (0.0 to 1.0).
Sets the synth volume. If a single value is passed in, sets both left side and right side volume to the given value. If two values are given, volumes are set separately.
Volume values are between 0.0 and 1.0.
Returns the current volume for the synth, a single value for mono sources or a pair of values (left, right) for stereo sources.
Volume values are between 0.0 and 1.0.
Sets the waveform or Sample the synth plays. If a sample is given, its data must be uncompressed PCM, not ADPCM. Otherwise waveform should be one of the following constants:
Signal
playdate.sound.signal is the parent class of our low-frequency signals, playdate.sound.lfo, playdate.sound.envelope, and playdate.sound.controlsignal. These can be used to automate certain parameters in the audio engine.
LFO
Returns a new LFO object, which can be used to modulate sounds. See playdate.sound.lfo:setType() for LFO types.
Sets the LFO type to arpeggio, where the given values are in half-steps from the center note. For example, the sequence (0, 4, 7, 12) plays the notes of a major chord.
Sets the center value of the LFO.
Sets the depth of the LFO’s modulation.
Sets the rate of the LFO, in cycles per second.
Sets the current phase of the LFO, from 0 to 1.
If an LFO is marked global, it is continuously updated whether or not it’s attached to any source.
If retrigger is on, the LFO’s phase is reset to 0 when a synth using the LFO starts playing a note.
Sets an initial holdoff time for the LFO where the LFO remains at its center value, and a ramp time where the value increases linearly to its maximum depth. Values are in seconds.
Envelope
Creates a new envelope with the given (optional) parameters.
Sets the envelope attack time to attack, in seconds.
Sets the envelope decay time to decay, in seconds.
Sets the envelope sustain level to sustain, as a proportion of the maximum. For example, if the sustain level is 0.5, the signal value rises to its full value over the attack phase of the envelope, then drops to half its maximum over the decay phase, and remains there while the envelope is active.
Sets the envelope release time to attack, in seconds.
Sets scale values to the envelope. The transformed envelope has an initial value of offset and a maximum (minimum if scale is negative) of offset + scale.
Sets scale and offset values to the envelope. The transformed envelope has an initial value of offset and a maximum (minimum if scale is negative) of offset + scale.
Sets whether to use legato phrasing for the envelope. If the legato flag is set, when the envelope is re-triggered before it’s released, it remains in the sustain phase instead of jumping back to the attack phase.
If retrigger is on, the envelope always starts from 0 when a note starts playing, instead of the current value if it’s active.
Triggers the envelope at the given velocity. If a length parameter is given, the envelope moves to the release phase after the given time. Otherwise, the envelope is held in the sustain phase until the trigger function is called again with velocity equal to zero.
If an envelope is marked global, it is continuously updated whether or not it’s attached to any source.
Effects
playdate.sound.effect is the parent class of our sound effects, playdate.sound.bitcrusher, playdate.sound.twopolefilter, playdate.sound.onepolefilter, playdate.sound.ringmod, playdate.sound.overdrive, and playdate.sound.delayline
Adds the given playdate.sound.effect to the default sound channel.
Removes the given effect from the default sound channel.
Bitcrusher
Creates a new bitcrusher filter.
Sets the wet/dry mix for the effect. A level of 1 (full wet) replaces the input with the effect output; 0 leaves the effect out of the mix.
Sets a signal to modulate the mix level.
Sets the amount of crushing to amt. Valid values are 0 (no effect) to 1 (quantizing output to 1-bit).
Sets a signal to modulate the filter level.
Sets the number of samples to repeat; 0 is no undersampling, 1 effectively halves the sample rate.
Sets a signal to modulate the filter level.
Ring Modulator
Creates a new ring modulator filter.
Sets the wet/dry mix for the effect. A level of 1 (full wet) replaces the input with the effect output; 0 leaves the effect out of the mix.
Sets a signal to modulate the mix level.
Sets the ringmod frequency to f.
Sets a signal to modulate the ringmod frequency.
One pole filter
The one pole filter is a simple low/high pass filter, with a single parameter describing the cutoff frequency: values above 0 (up to 1) are high-pass, values below 0 (down to -1) are low-pass.
Returns a new one pole filter.
Sets the wet/dry mix for the effect. A level of 1 (full wet) replaces the input with the effect output; 0 leaves the effect out of the mix.
Sets a signal to modulate the mix level.
Sets the filter’s single parameter (cutoff frequency) to p.
Sets a modulator for the filter’s parameter.
Two pole filter
Sets the wet/dry mix for the effect. A level of 1 (full wet) replaces the input with the effect output; 0 leaves the effect out of the mix.
Sets a signal to modulate the mix level.
Sets the center frequency (in Hz) of the filter to f.
Sets a signal to modulate the filter frequency.
Sets the resonance of the filter to r. Valid values are in the range 0-1. This parameter has no effect on shelf type filters.
Sets a signal to modulate the filter resonance.
Sets the gain of the filter to g. Gain is only used in PEQ and shelf type filters.
Sets the type of the filter to type.
Overdrive
Creates a new overdrive effect.
Sets the wet/dry mix for the effect. A level of 1 (full wet) replaces the input with the effect output; 0 leaves the effect out of the mix.
Sets a signal to modulate the mix level.
Sets the gain of the filter.
Sets the level where the amplified input clips.
Sets a signal to modulate the limit level.
Adds an offset to the upper and lower limits to create an asymmetric clipping.
Sets a signal to modulate the offset value.
Delay line
Creates a new delay line effect, with the given length (in seconds).
Sets the wet/dry mix for the effect. A level of 1 (full wet) replaces the input with the effect output; 0 leaves the effect out of the mix, which is useful if you’re using taps for varying delays.
Sets a signal to modulate the mix level.
Returns a new playdate.sound.delaylinetap on the delay line, at the given delay (which must be less than or equal to the delay line’s length).
Sets the feedback level of the delay line.
Delay line tap
playdate.sound.delaylinetap is a subclass of playdate.sound.source. Note that a tap can be added to any channel, not just the channel the tap’s delay line is on.
Sets the position of the tap on the delay line, up to the delay line’s length.
Sets a signal to modulate the tap delay. If the signal is continuous (e.g. an envelope or a triangle LFO, but not a square LFO) playback is sped up or slowed down to compress or expand time.
Sets the tap’s volume.
Returns the tap’s volume.
If set and the delay line is stereo, the tap outputs the delay line’s left channel to its right output and vice versa.
Sequence
Creates a new sound sequence. If path.mid
is given, it attempts to load data from the midi file into the sequence.
Starts playing the sequence. finishCallback
is an optional function to be called when the sequence finishes playing or is stopped. The sequence is passed to the callback as its single argument.
Stops playing the sequence.
Returns true if the sequence is currently playing.
Returns the length of the longest track in the sequence. See also playdate.sound.track.getLength().
Moves the play position for the sequence to step number step
. If play
is set, triggers the notes at that step.
Returns the step number the sequence is currently at.
Sets the tempo of the sequence, in steps per second.
Gets the tempo of the sequence, in steps per second.
Sets the looping range of the sequence. If loops is 0 or unset, the loop repeats endlessly.
Returns the number of tracks in the sequence.
Adds the given playdate.sound.track to the sequence.
Sets the given playdate.sound.track object at position n
in the sequence.
Gets the given playdate.sound.track object at position n
in the sequence.
Sends an allNotesOff() message to each track’s instrument.
Track
Creates a new playdate.sound.track
object.
Adds a single note event to the track, letting you specify step
, note
, length
, and velocity
directly. The second format allows you to pack them into a table, using the format returned by getNotes(). The note
argument can be a MIDI note number or a note name like "Db3". length
is the length of the note in steps, not time—that is, it follows the sequence’s tempo. The default velocity is 1.0.
See setNotes() for the ability to add more than one note at a time.
Adds a single note event to the track. Specify step
, note
, length
, and velocity
in a table, using the format returned by getNotes(). The note
argument can be a MIDI note number or a note name like "Db3". length
is the length of the note in steps, not time—that is, it follows the sequence’s tempo. The default velocity is 1.0.
See setNotes() for the ability to add more than one note at a time.
Set multiple notes at once, each array element should be a table containing values for the keys The tables contain values for keys step
, note
, length
, and velocity
.
The tables contain values for keys step
, note
, length
, and velocity
. If step
is given, the function returns only the notes at that step; if both step
and endstep
are set, it returns the notes between the two steps (including notes at endstep). n.b. The note
field in the event tables is always a MIDI note number value, even if the note was added using the string notation.
Returns an array of tables representing the note events in the track.
The tables contain values for keys step
, note
, length
, and velocity
. If step
is given, the function returns only the notes at that step; if both step
and endstep
are set, it returns the notes between the two steps (including notes at endstep). n.b. The note
field in the event tables is always a MIDI note number value, even if the note was added using the string notation.
Returns the length, in steps, of the track—that is, the step where the last note in the track ends.
Returns the current number of notes active in the track.
Returns the maximum number of notes simultaneously active in the track. (Known bug: this currently only works for midi files)
Sets the playdate.sound.instrument that this track plays. If inst
is a playdate.sound.synth, the function creates an instrument for the synth.
Gets the playdate.sound.instrument that this track plays.
Mutes or unmutes the track.
Adds a playdate.sound.controlsignal object to the track. Note that the signal must be assigned to a modulation input for it to have any audible effect. The input can be anywhere in the sound engine—it’s not required to belong to the track in any way.
Returns an array of playdate.sound.controlsignal objects assigned to this track.
Instrument
Creates a new playdate.sound.instrument
object. If synth
is given, adds it as a voice for the instrument.
Adds the given playdate.sound.synth to the instrument. If only the note argument is given, the voice is only used for that note, and is transposed to play at normal speed (i.e. rate=1.0 for samples, or C4 for synths). If rangeend is given, the voice is assigned to the range note to rangeend, inclusive, with the first note in the range transposed to rate=1.0/C4. The note
and rangeend
arguments can be MIDI note numbers or note names like "Db3". The final transpose argument transposes the note played, in half-tone units.
Transposes all voices in the instrument. halfsteps can be a fractional value.
Plays the given note on the instrument. A string like Db3
can be used instead of a pitch/note number. Fractional values are allowed. vel defaults to 1.0, fully on. If length isn’t specified, the note stays on until instrument.noteOff(note) is called. when is the number of seconds in the future to start playing the note, default is immediately.
Plays the given note on the instrument, where note is a MIDI note number: 60=C4, 61=C#4, etc. A string like Db3
can be used instead of a pitch/note number. Fractional values are allowed. vel defaults to 1.0, fully on. If length isn’t specified, the note stays on until instrument.noteOff(note) is called. when is the number of seconds in the future to start playing the note, default is immediately.
Stops the instrument voice playing note note. If when is given, the note is stopped when seconds in the future, otherwise it’s stopped immediately.
Sends a stop signal to all playing notes.
Sets the instrument volume. If a single value is passed in, sets both left side and right side volume to the given value. If two values are given, volumes are set separately.
Volume values are between 0.0 and 1.0.
Returns the current volume for the synth, a single value for mono sources or a pair of values (left, right) for stereo sources.
Volume values are between 0.0 and 1.0.
Control Signal
Creates a new control signal object, for automating effect parameters, channel pan and level, etc.
The signal’s event list is modified by getting and setting the events
property of the object. This is an array of tables, each containing values for keys step
and value
, and optionally interpolate
.
addEvent
is a simpler way of adding events one at a time than setting the entire events table. Arguments are either the values themselves in the given order, or a table containing values for step
, value
, and optionally interpolate
.
addEvent
is a simpler way of adding events one at a time than setting the entire events table. Arguments are either the values themselves in the given order, or a table containing values for step
, value
, and optionally interpolate
.
Clears all events from the control signal.
Sets the midi controller number for the control signal, if that’s something you want to do. The value has no effect on playback.
Control signals in midi files are assigned a controller number, which describes the intent of the control. This function returns the controller number.
Mic Input
buffer
should be a Sample created with the following code, with secondsToRecord replaced by a number specifying the record duration:
local buffer = playdate.sound.sample.new(_secondsToRecord_, playdate.sound.kFormat16bitMono)
completionCallback
is a function called at the end of recording, when the buffer is full.
Stops a sample recording started with recordToSample, if it hasn’t already reached the end of the buffer. The recording’s completion callback is called immediately.
Starts monitoring the microphone input level.
Stops monitoring the microphone input level.
Returns the current microphone input level, a value from 0.0 (quietest) to 1.0 (loudest).
Returns the current microphone input source, either "headset" or "device".
Audio Output
Returns a pair of booleans (headphone, mic) indicating whether headphones are plugged in, and if so whether they have a microphone attached. If changeCallback is a function, it will be called every time the headphone state changes, until it is cleared by calling playdate.sound.getHeadphoneState(nil)
. If a change callback is set, the audio does not automatically switch from speaker to headphones when headphones are plugged in (and vice versa), so the callback should use playdate.sound.setOutputsActive()
to change the output if needed.
Forces sound to be played on the headphones or on the speaker, regardless of whether headphones are plugged in or not. (With the caveat that it is not actually possible to play on the headphones if they’re not plugged in.) This function has no effect in the simulator.
Audio Device Time
Returns the current time, in seconds, as measured by the audio device. The audio device uses its own time base in order to provide accurate timing.
Resets the audio output device time counter.
6.28. Strings
Important
|
You must import CoreLibs/string to use these functions. |
Generates a random string of uppercase letters
Returns a string with the whitespace removed from the beginning and ending of string.
Returns a string with the whitespace removed from the beginning of string.
Returns a string with the whitespace removed from the ending of string.
6.29. Timers
playdate.timer provides a time-based timer useful for handling animation timings, countdowns, or performing tasks after a delay. For a frame-based timer see playdate.frameTimer.
Important
|
You must import CoreLibs/timer to use these functions. It is also to critical to call playdate.timer.updateTimers() in your playdate.update() function to ensure that all timers are updated every frame. |
This should be called from the main playdate.update() loop to drive the timers.
Standard timers
Returns a new playdate.timer that will run for duration milliseconds. callback is a function closure that will be called when the timer is complete.
Accepts a variable number of arguments that will be passed to the callback function when it is called. If arguments are not provided, the timer itself will be passed to the callback instead.
By default, timers start upon instantiation. To modify the behavior of a timer, see common timer methods and properties.
Delay timers
Performs the function callback after delay milliseconds. Accepts a variable number of arguments that will be passed to the callback function when it is called. If arguments are not provided, the timer itself will be passed to the callback instead.
Value timers
Returns a new playdate.timer that will run for duration milliseconds. If not specified, startValue and endValue will be 0, and a linear easing function will be used.
By default, timers start upon instantiation. To modify the behavior of a timer, see common timer methods and properties.
Current value calculated from the start and end values, the time elapsed, and the easing function.
The function used to calculate value. The function should be of the form function(t, b, c, d), where t is elapsed time, b is the beginning value, c is the change (or end value - start value), and d is the duration. Many such functions are available in playdate.easingFunctions.
For easing functions that take additional amplitude and period arguments (such as inOutElastic), set these to the desired values.
For easing functions that take additional amplitude and period arguments (such as inOutElastic), set these to the desired values.
Set to provide an easing function to be used for the reverse portion of the timer. The function should be of the form function(t, b, c, d), where t is elapsed time, b is the beginning value, c is the change (or end value - start value), and d is the duration. Many such functions are available in playdate.easingFunctions.
Start value used when calculating value.
End values used when calculating value.
Key repeat timers
keyRepeatTimer()
returns a timer that fires at key-repeat intervals. The function callback will be called immediately, then again after 300 milliseconds, then repeatedly at 100 millisecond intervals. If you wish to customize these millisecond intervals, use keyRepeatTimerWithDelay()
.
keyRepeatTimer()
returns a timer that fires at key-repeat intervals. The function callback will be called immediately, then again after 300 milliseconds, then repeatedly at 100 millisecond intervals. If you wish to customize these millisecond intervals, use keyRepeatTimerWithDelay()
.
Both functions accept any number of arguments; those arguments will be passed to the callback function when it is called. If arguments are not provided, the timer itself will be passed instead.
import "CoreLibs/timer"
local keyTimer = nil
function playdate.BButtonDown()
local function timerCallback()
print("key repeat timer fired!")
end
keyTimer = playdate.timer.keyRepeatTimer(timerCallback)
end
function playdate.BButtonUp()
keyTimer:remove()
end
function playdate.update()
playdate.timer.updateTimers()
end
Common timer methods
Pauses a timer. (There is no need to call :start() on a newly-instantiated timer: timers start automatically.)
Resumes a previously paused timer. (There is no need to call :start() on a newly-instantiated timer: timers start automatically.)
Removes this timer from the list of timers. This happens automatically when a non-repeating timer reaches its end, but you can use this method to dispose of timers manually.
Note that timers do not actually get removed until the next invocation of playdate.timer.updateTimers().
Resets a timer to its initial values.
Returns an array listing all running timers.
Note
|
Note the "." syntax rather than ":". This is a class method, not an instance method. |
Common timer properties
The number of milliseconds the timer has been running. Read-only.
Number of milliseconds to wait before starting the timer.
If true, the timer is discarded once it is complete. Defaults to true.
The number of milliseconds for which the timer will run.
The number of milliseconds remaining in the timer. Read-only.
If true, the timer starts over from the beginning when it completes. Defaults to false.
If true, the timer plays in reverse once it has completed. The time to complete both the forward and reverse will be duration x 2. Defaults to false.
Please note that currentTime will restart at 0 and count up to duration again when the reverse timer starts, but value will be calculated in reverse, from endValue to startValue. The same easing function (as opposed to the inverse of the easing function) will be used for the reverse timer unless an alternate is provided by setting reverseEasingFunction.
A Function of the form function(timer) or function(…) where "…" corresponds to the values in the table assigned to timerEndedArgs. Called when the timer has completed.
For repeating timers, this function will be called each time the timer completes, before it starts again.
An array-style table of values that will be passed to the timerEndedCallback function.
A callback function that will be called on every frame (every time timer.updateAll() is called). If the timer was created with arguments, those will be passed as arguments to the function provided. Otherwise, the timer is passed as the single argument.
Timer sample code
To count milliseconds, a simple timer can be created as follows:
t = playdate.timer.new(1000)
The timer will begin running immediately. The current time can be read by looking at t.currentTime.
To transition between two values, set up a timer like:
t = timer(500, 0, 100)
If no easing function is provided as a fourth argument linear easing will be used. As the timer runs, you can access the current value by looking at t.value.
In both of these examples, the timer will be automatically discarded once it is finished. Set discardOnCompletion to false to keep the timer around for later reuse.
An example of setting up a bouncing ball animation (assuming the ball would be drawn elsewhere based on the rectangle r):
local r = playdate.geometry.rect.new(100, 10, 40, 40)
local t = playdate.timer.new(1000, 10, 150, easingFunctions.inCubic) t.reverses = true t.repeats = true t.reverseEasingFunction = easingFunctions.outQuad t.updateCallback = function(timer) r.y = timer.value end
6.30. Frame timers
A frame-based timer useful for handling frame-precise animation timings. For a time-based timer see playdate.timer or playdate.graphics.animation.loop
Important
|
You must import CoreLibs/frameTimer to use these functions. It is also to critical to call playdate.frameTimer.updateTimers() in your playdate.update() function to ensure that all timers are updated every frame. |
This should be called from the main playdate.update() loop to drive the frame timers.
Standard frame timers
Returns a new playdate.frameTimer that will run for duration frames. callback is a function closure that will be called when the timer is complete.
Accepts a variable number of arguments that will be passed to the callback function when it is called. If arguments are not provided, the timer itself will be passed to the callback instead.
By default, frame timers start upon instantiation. To modify the behavior of a frame timer, see common frame timer methods and properties.
Delay frame timers
Performs the function callback after the delay number of frames. Accepts a variable number of arguments that will be passed to the callback function when it is called. If arguments are not provided, the timer itself will be passed to the callback instead.
Value frame timers
Returns a new playdate.frameTimer that will run for duration number of frames. If not specified, startValue and endValue will be 0, and a linear easing function will be used.
By default, frame timers start upon instantiation. To modify the behavior of a frame timer, see common frame timer methods and properties.
Current value calculated from the start and end values, the current frame, and the easing function.
Start value used when calculating value.
End value used when calculating value.
The function used to calculate value. The function should be of the form function(t, b, c, d), where t is elapsed time, b is the beginning value, c is the change (or endValue - startValue), and d is the duration.
For easing functions in CoreLibs/easing that take additional amplitude and period arguments (such as inOutElastic), set these to desired values.
For easing functions in CoreLibs/easing that take additional amplitude and period arguments (such as inOutElastic), set these to desired values.
Set to provide an easing function to be used for the reverse portion of the timer. The function should be of the form function(t, b, c, d), where t is elapsed time, b is the beginning value, c is the change (or endValue - startValue), and d is the duration.
Common frame timer methods
Pauses a timer. (There is no need to call :start() on a newly-instantiated frame timer: frame timers start automatically.)
Resumes a timer. (There is no need to call :start() on a newly-instantiated frame timer: frame timers start automatically.)
Removes this timer from the list of timers. This happens automatically when a non-repeating timer reaches it’s end, but you can use this method to dispose of timers manually.
Resets a timer to its initial values.
Returns an array listing all running frameTimers.
Note
|
Note the "." syntax rather than ":". This is a class method, not an instance method. |
Common frame timer properties
Number of frames to wait before starting the timer.
If true, the timer is discarded once it is complete. Defaults to true.
The number of frames for which the timer will run.
The current frame.
If true, the timer starts over from the beginning when it completes. Defaults to false.
If true, the timer plays in reverse once it has completed. The number of frames to complete both the forward and reverse will be duration x 2. Defaults to false.
Please note that the frame counter will restart at 0 and count up to duration again when the reverse timer starts, but value will be calculated in reverse, from endValue to startValue. The same easing function (as opposed to the inverse of the easing function) will be used for the reverse timer unless an alternate is provided by setting reverseEasingFunction.
A Function of the form function(timer) or function(…) where "…" corresponds to the values in the table assigned to timerEndedArgs. Called when the timer has completed.
For repeating timers, this function will be called each time the timer completes, before it starts again.
An array-style table of values that will be passed to the timerEndedCallback function.
A function to be called on every frame update. If the frame timer was created with arguments, those will be passed as arguments to the function provided. Otherwise, the timer is passed as the single argument.
Frame timer sample code
To count frames a simple timer can be created as follows:
t = playdate.frameTimer.new(200)
The timer will begin running immediately, and the current frame can be read by looking at t.frame.
To transition between two values, set up a timer like:
t = FrameTimer(50, 0, 100)
If no easing function is provided as a fourth argument linear easing will be used. As the timer runs, you can access the current value by looking at t.value.
In both of these examples, the timer will be automatically discarded once it is finished. Set discardOnCompletion to false to keep the timer around for later reuse.
An example of setting up a bouncing ball animation (assuming the ball would be drawn elsewhere based on the rectangle r):
local r = playdate.geometry.rect.new(100, 10, 40, 40)
local t = playdate.frameTimer.new(20, 10, 150, playdate.easingFunctions.inCubic)
t.reverses = true
t.repeats = true
t.reverseEasingFunction = playdate.easingFunctions.outQuad
t.updateCallback = function(timer)
r.y = timer.value
end
6.31. UI components
playdate.ui provides common UI elements for playdate games.
Crank alert
playdate.ui.crankIndicator
provides a small system-styled indicator, alerting the player that this game will use the crank. This is often used in conjunction with playdate.isCrankDocked()
, to determine if the player has the crank extended.
Important
|
You must import CoreLibs/ui to use crankIndicator. There is no need to instantiate a crankIndicator object: simply call playdate.ui.crankIndicator:start() to begin using it, and playdate.ui.crankIndicator:update() once each frame to display it and make it animate. The crank alert will disappear once you stop calling :update() .
|
Initializes or resets the crankIndicator. Should be called before showing the alert.
Draws the alert.
Important
|
playdate.crankIndicator uses playdate.timer internally, so be sure to call playdate.timer.updateTimers() in your main playdate.update() function.
|
Boolean property specifying which direction to animate the crank. Defaults to true.
Grid view
playdate.ui.gridview provides a means for drawing a grid view composed of cells, and optionally sections with section headers.
Important
|
You must import CoreLibs/ui to use gridview. |
Some notes:
-
playdate.ui.gridview uses playdate.timer internally, so playdate.timer.updateTimers() must be called in the main playdate.update() function.
-
If the gridview’s cell width is set to 0, cells will be drawn the same width as the table (minus any padding).
-
Section headers always draw the full width of the grid (minus padding), and do not scroll horizontally along with the rest of the content.
Returns a new playdate.ui.gridview with cells sized cellWidth, cellHeight. (Sizes are in pixels.) If cells should span the entire width of the grid (as in a list view), pass zero (0) for cellWidth.
Drawing
Override this method to draw the cells in the gridview. selected is a boolean, true if the cell being drawn is the currently-selected cell.
Override this method to draw section headers. This function will only be called if the header height has been set to a value greater than zero (0).
Override this method to customize the drawing of horizontal dividers. This function will only be called if the horizontal divider height is greater than zero (0) and at least one divider has been added.
Draws the gridview in the specified rect. Ideally this should be called on every playdate.update() to accommodate scrolling.
This read-only variable returns true if the gridview needs to be redrawn. This can be used to help optimize drawing in your app. Keep in mind that a gridview cannot know all reasons it may need to be redrawn, such as changes in your drawing callback functions, coordinate or size changes, or overlapping drawing, so you may need to additionally redraw at other times.
if myGridView.needsDisplay == true then
myGridView:drawInRect(x, y, w, h)
end
Configuration
Sets the number of sections in the grid view. Each section contains at least one row, and row numbering starts at 1 in each section.
Gets the number of sections in the grid view.
Sets the number of rows in section.
Gets the number of rows in section.
Sets the number of columns in the gridview. 1 by default.
Gets the number of columns in the gridview. 1 by default.
Convenience method for list-style gridviews, or for setting the number of rows for multiple sections at a time. Pass in a list of numbers of rows for sections starting from section 1.
Sets the size of the cells in the gridview. If cells should span the entire width of the grid (as in a list view), pass zero (0) for cellWidth.
Sets the amount of padding around cells.
Sets the amount of space the content is inset from the edges of the gridview. Useful if a background image is being used as a border.
Returns the tuple (x, y, width, height) representing the bounds of the cell, not including padding, relative to the top-right corner of the grid view.
If the grid view is configured with zero width cells (see playdate.ui.gridview:new), gridWidth is required, and should be the same value you would pass to playdate.ui.gridview:drawInRect.
Sets the height of the section headers. 0 by default, which causes section headers not to be drawn.
Gets the height of the section headers. 0 by default, which causes section headers not to be drawn.
Sets the amount of padding around section headers.
Sets the height of the horizontal dividers. The default height is half the cell height specified when creating the grid view.
Gets the height of the horizontal dividers. The default height is half the cell height specified when creating the grid view.
Causes a horizontal divider to be drawn above the specified row. Drawing can be customized by overriding playdate.ui.gridview:drawHorizontalDivider.
Removes all horizontal dividers from the grid view.
Scrolling
Controls the duration of scroll animations. 250ms by default.
'set' scrolls to the coordinate x, y.
If animated is true (or not provided) the new scroll position is animated to using playdate.ui.gridview.scrollEasingFunction and the value set in playdate.ui.gridview:setScrollDuration().
Returns the current scroll location x, y.
Scrolls to the specified cell, just enough so the cell is visible.
Scrolls to the specified cell, so the cell is centered in the gridview, if possible.
Convenience function for list-style gridviews. Scrolls to the specified row in the list.
Scrolls to the top of the gridview.
Selection
Changing the selection can also change the scroll position. By default cells are scrolled so that they are centered in the gridview, if possible. To change that behavior so the grid is just scrolled enough to make the cell visible, set scrollCellsToCenter to false.
Selects the cell at the given position.
Returns the currently-selected cell as section, row, column
Convenience method for list-style gridviews. Selects the cell at row in section 1.
Convenience method for list-style gridviews. Returns the selected cell at row in section 1.
Selects the cell directly below the currently-selected cell.
If wrapSelection is true, the selection will wrap around to the opposite end of the grid. If scrollToSelection is true (or not provided), the newly-selected cell will be scrolled to. If animate is true (or not provided), the scroll will be animated.
Selects the cell directly above the currently-selected cell.
If wrapSelection is true, the selection will wrap around to the opposite end of the grid. If scrollToSelection is true (or not provided), the newly-selected cell will be scrolled to. If animate is true (or not provided), the scroll will be animated.
Selects the cell directly to the right of the currently-selected cell.
If the last column is currently selected and wrapSelection is true, the selection will wrap around to the opposite side of the grid. If a wrap occurs and the gridview’s changeRowOnColumnWrap
is true
the row will also be advanced or moved back.
If scrollToSelection is true (or not provided), the newly-selected cell will be scrolled to. If animate is true (or not provided), the scroll will be animated.
Selects the cell directly to the left of the currently-selected cell.
If the first column is currently selected and wrapSelection is true, the selection will wrap around to the opposite side of the grid. If a wrap occurs and the gridview’s changeRowOnColumnWrap
is true
the row will also be advanced or moved back.
If scrollToSelection is true (or not provided), the newly-selected cell will be scrolled to. If animate is true (or not provided), the scroll will be animated.
Properties
A background image that draws behind the gridview’s cells. This image can be either a playdate.graphics.image
which will be tiled or a playdate.nineSlice
.
Read-only. True if the gridview is currently performing a scroll animation.
The easing function used when performing scroll animations. The function should be of the form function(t, b, c, d), where t is elapsed time, b is the beginning value, c is the change, or end value - start value, and d is the duration. Many such functions are available in playdate.easingFunctions
. playdate.easingFunctions.outCubic
is the default.
For easing functions that take additional amplitude and period arguments (such as inOutElastic), set these to the desired values.
For easing functions that take additional amplitude and period arguments (such as inOutElastic), set these to the desired values.
Controls the behavior of playdate.ui.gridview:selectPreviousColumn() and playdate.ui.gridview:selectNextColumn() if the current selection is at the first or last column, respectively. If set to true, the selection switch to a new row to allow the selection to change. If false, the call will have no effect on the selection. True by default.
If true, the gridview will attempt to center cells when scrolling. If false, the gridview will be scrolled just as much as necessary to make the cell visible.
Grid view sample code
To set up a grid view, specify the dimensions and override the necessary drawing methods:
local gfx = playdate.graphics
local gridview = playdate.ui.gridview.new(44, 44)
gridview.backgroundImage = playdate.graphics.nineSlice.new('shadowbox', 4, 4, 45, 45)
gridview:setNumberOfColumns(8)
gridview:setNumberOfRows(2, 4, 3, 5) -- number of sections is set automatically
gridview:setSectionHeaderHeight(24)
gridview:setContentInset(1, 4, 1, 4)
gridview:setCellPadding(4, 4, 4, 4)
gridview.changeRowOnColumnWrap = false
function gridview:drawCell(section, row, column, selected, x, y, width, height)
if selected then
gfx.drawCircleInRect(x-2, y-2, width+4, height+4, 3)
else
gfx.drawCircleInRect(x+4, y+4, width-8, height-8, 0)
end
local cellText = ""..row.."-"..column
gfx.drawTextInRect(cellText, x, y+14, width, 20, nil, nil, kTextAlignment.center)
end
function gridview:drawSectionHeader(section, x, y, width, height)
gfx.drawText("*SECTION ".. section .. "*", x + 10, y + 8)
end
For the simple case of a simple list-style grid:
local menuOptions = {"Sword", "Shield", "Arrow", "Sling", "Stone", "Longbow", "MorningStar", "Armour", "Dagger", "Rapier", "Skeggox", "War Hammer", "Battering Ram", "Catapult"}
local listview = playdate.ui.gridview.new(0, 10)
listview.backgroundImage = playdate.graphics.nineSlice.new('scrollbg', 20, 23, 92, 28)
listview:setNumberOfRows(#menuOptions)
listview:setCellPadding(0, 0, 13, 10)
listview:setContentInset(24, 24, 13, 11)
function listview:drawCell(section, row, column, selected, x, y, width, height)
if selected then
gfx.fillRoundRect(x, y, width, 20, 4)
gfx.setImageDrawMode(gfx.kDrawModeFillWhite)
else
gfx.setImageDrawMode(gfx.kDrawModeCopy)
end
gfx.drawTextInRect(menuOptions[row], x, y+2, width, height, nil, "...", kTextAlignment.center)
end
Then, to draw the grid view:
function playdate.update()
gridview:drawInRect(20, 20, 180, 200)
listview:drawInRect(220, 20, 160, 210)
playdate.timer:updateTimers()
end
6.32. Garbage collection
If flag is false, automatic garbage collection is disabled and the game should manually collect garbage with Lua’s collectgarbage()
function.
Force the Lua garbage collector to run for at least ms milliseconds every frame, so that garbage doesn’t pile up and cause the game to run out of memory and stall in emergency garbage collection. The default value is 5 milliseconds.
Tip
|
If your game isn’t generating a lot of garbage, it might be advantageous to set a smaller minimum GC time, granting more CPU bandwidth to your game. |
When the amount of used memory is less than min
(scaled from 0-1, as a percentage of total system memory), the system will only run the collector for the minimum GC time, as set by playdate.setGCScaling(), every frame. If the used memory is more than max
, the system will spend all free time running the collector. Between the two, the time used by the garbage collector is scaled proportionally.
For example, if the scaling is set to a min of 0.4 and max of 0.7, and memory is half full, the collector will run for the minimum GC time plus 1/3 of whatever time is left before the next frame (because (0.5 - 0.4) / (0.7 - 0.4) = 1/3).
The default behavior is a scaling of (0.0, 1.0)
. If set to (0.0, 0.0)
, the system will use all available extra time each frame running GC.
7. Hidden Gems
The Playdate APIs include a lot of functionality you might expect:
There are also some unexpected APIs, some unique to the Playdate platform, that you may not be aware of. Be sure to take a look at these:
7.1. Lua enhancements
The Playdate SDK offers some enhancements to standard Lua, including additional assignment operators (+=
, -=
) and convenience functions for handling Lua tables.
7.2. Debugging
-
playdate.drawFPS(): Displays the current framerate onscreen.
-
playdate.debugDraw(): Highlight regions on the Simulator screen in a different color, to aid in debugging.
-
printTable(): Outputs the contents of a table to the console.
-
playdate.keyPressed(): Captures computer keyboard keypresses as an aid in debugging. For example, typing a number might advance the game to a higher level.
7.3. Enhancing your game’s user experience
-
playdate.ui.crankIndicator(): Inform the player that your game uses the crank.
-
playdate.menu:addOptionsMenuItem(): Add a special menu item for your game into the System Menu.
-
playdate.wait(): Pause your game’s execution for a specified period of time. Useful for, say, suspending gameplay while displaying a message to the player.
-
playdate.setMenuImage(): Set a custom image that displays on the left-side of the screen while your game is paused.
-
playdate.keyboard: Display a special Playdate keyboard onscreen and collect text input from the player.
-
playdate.timer.keyRepeatTimer(): Useful, keyboard-style repeating.
7.4. System callbacks
-
playdate.gameWillTerminate(): Notifies your game it’s about to end its execution.
-
playdate.deviceWillLock(), playdate.deviceDidUnlock(): Notifies your game the Playdate is about to be locked, or woken up.
-
playdate.gameWillPause()/Resume(): Notifies your game it’s about to be paused or resumed.
7.5. Drawing
-
playdate.graphics.setDrawOffset(): Force all drawing calls to render with an offset; ideal for games with scrolling content.
-
playdate.ui.gridview: Render one- or two-dimensional grids of content.
-
playdate.graphics.nineslice: Create resizable rectangular assets.
7.6. Accessibility
-
playdate.getReduceFlashing(): Check this at the beginning of your game. If true, your game should avoid visuals that could be problematic for people with sensitivities to flashing lights or patterns.
7.7. File I/O
-
playdate.datastore: Easy writing and reading of data.
-
json: Read and write JSON.
7.8. Game logic
-
playdate.pathfinder: An implementation of the A* pathfinding algorithm.
7.9. Odds & ends
-
playdate.graphics.perlin: Generate natural-looking patterns.
-
playdate.graphics.generateQRCode: Display a QR code onscreen.
8. Getting Help
8.1. Where can I download the SDK?
Head to the Playdate Developer page to download the latest SDK.
8.2. Where do I go if I have questions about the SDK?
You can find the SDK documentation for Lua here and C here . If you’re interested in seeing the Playdate SDK in action, check out our Twitch stream . For tips on making Playdate games, click here.
Searching in the Get Help and Development Discussion on our Developer Forum to find solutions will also be a good place to look at. If you still need help, the best way to get help from either the community or Panic is to post in that same Get Help category.
8.3. Where do I report bugs or issues relating to the SDK?
Head to the Bug Reports category and check the Bug Report category info for information on how to post a bug report. One of us at Panic will take a look at it! And what if I have feature requests?
To share your ideas, suggestions, and requests relating to Playdate, head to the Feature Request category and check the Feature Request category info before posting your feature request.
8.4. List of Helpful Libraries and Code
This thread includes some helpful tips from the community. Check it out here. For more resources, head to the Development Discussion category.
9. Legal information
Playdate fonts are licensed to you under the Creative Commons Attribution 4.0 International (CC BY 4.0) license.