-
Notifications
You must be signed in to change notification settings - Fork 0
CottonScript
CottonScript is a friendly scripting library that allows you to add Pulp-like interactivity to your Lua games. Its syntax is terse but powerful. A good rule of thumb when writing CottonScript is do less, less often.
You can comment your Lua with two dashes:
-- this is a comment
Comments can appear on their own line or after a valid expression.
Event handlers are written like so:
function eventName()
-- something
end
In addition to a handful of built-in events, you can also declare your own custom functions like so:
function self:aCustomFunctionName()
-- something
end
and trigger them from the current entity with:
self:eventName()
or for all tiles that implement a particular event with:
-- emitting is currently not supported yet
These are the built-in events:
-
load
: called once on the game, each room, and each tile when all assets are first loaded -
start
: called once on the game after all rooms and tiles have handled their load event -
enter
: called on the game, the current room, and each tile in the current room every time the Player enters a new room -
exit
: called on the game, the current room, and each tile in the current room every time the Player exits the current room -
finish
: called once on the game when the game is completed -
loop
: called on the game, 30 frames per second, before anything else is done that frame (except when a say text box or menu is on screen) -
update
: called on the Player every time they move or interact with something -
bump
: called on the Player every time they bump into a solid world tile -
confirmPressed
: called on the Player when the A button is pressed -
confirmReleased
: called on the Player when the A button is released -
cancelPressed
: called on the Player when the B button is pressed -
cancelReleased
: called on the Player when the B button is released -
crank
: called on the Player when the crank is turned -
dock
: called on the Player when the crank is docked -
undock
: called on the Player when the crank is undocked -
draw
: called on the Player 30 frames per second, before drawing -
interact
: called on an entity when the Player bumps into or acts upon it -
collect
: called on an entity when the Player steps onto or acts upon it -
change
: NOT-YET-SUPPORTED called on the game when the cursor moves to a different option in a menu or ask menu and when the menu first appears -
select
: NOT-YET-SUPPORTED called on the game when the player selects an option in a menu or ask menu -
dismiss
: NOT-YET-SUPPORTED called on the game when the player dismisses a menu submenu -
invalid
: NOT-YET-SUPPORTED called on the game when the player attempts to back out of an ask menu or selects an empty option
There is one more special catch-all event:
-
any
: NOT-YET-SUPPORTED called before any event the game or a tile may receive This is useful with themimic
NOT-YET-SUPPORTED function to allow one tile to behave exactly like another without having to copy/paste behavior.
All uninitialized variables have a default value of 0. Variables can hold a number or a string.
Variables are initialized like so:
local varName = nil
And assigned like so:
varName = 0
Strings can be wrapped in double or single quotes. But for consistency I recommend using only one of the two:
varName = "some string"
or
varName = 'some string'
Lua supports simple arithmetic operations.
Variables can be incremented or decremented like so:
varName = 0
varName = varName + 1 -- now equals 1
varName = varName - 1 -- equals 0 again
You can add to or subtract from a variable like so:
varName = 0
varName = varName + 4 -- now equals 4
varName = varName - 3 -- now equals 1
You can also multiply or divide a variable like so:
varName = 0
varName = varName + 1 * 6 -- now equals 6
varName = varName / 3 -- now equals 2
Compound expressions are also supported like so:
varName = 0
varName = varName + 1 * 2 -- now equals 2
varName = varName - 1 + 1 - 2 -- equals 0 again
Conditionals in PulpScript take the following form:
if varName == 0 then
-- handle 0
elseif varName == 1 then
-- handle 1
else
-- handle all other values
end
Both the elseif [condition] then
and else
are optional. The following boolean comparisons are available:
-
==
: equal to -
~=
: not equal to -
>
: greater than -
<
: less than -
>=
: greater than or equal to -
<=
: less than or equal to
String comparisons are case-sensitive. This also applies to functions, room, and tile names when used as arguments for functions.
Compound expressions are supported like so:
if varName1 == 0 and varName2 == 0 then
-- do something when varName1 and varName2 are both 0
end
CottonScript also includes a while loop which can be thought of as a repeating if:
while varName==0 do
-- repeat until varName is set to another value
end
User-defined functions and event handlers can return values. They can be exited early with return
like so.
while varName==0 do
ticks = ticks + 1
if ticks<interval then
return -- return from the "while" loop
end
ticks = ticks - interval
-- do something at a regular interval
end
You can also skip only 1 cycle of the while loop and keep going instead of completely returning from the loop like so:
local redFound = 0
while varName==0 do
if color == "blue" then
goto continue -- skips loop while the color is blue
end
redFound = redFound + 1
::continue::
end
Variables can be persisted across launches using the store
and restore
functions. A game’s persistent storage file can be found in the following locations, for the Playdate Simulator:
<SDKRoot>/Disk/Data/com.<author>.<name>/store.json
and on the hardware:
/Disk/Data/com.<author>.<name>/store.json
NOT-YET-SUPPORTED
Since the config variables have grown a bit , we moved this section to a separate page.
datetime()
The datetime
function has the following members reflecting the current time:
year
: 1900-10,000
month
: 1-12
day
: 1-31
weekday
: 0-6
hour
: 0-23
minute
: 0-59
second
: 0-59
millisecond
: 0-600
and
timestamp()
return seconds since midnight (hour 0), January 1 2000 UTC
You can include variables in a string by adding ..
between the string and the variable:
log("count is now set to " .. count)
log("message")
Logs message to the console. You can include variable values in log.
dump()
Logs the current values contained in the config
, and persistent storage.
say("message", at())
and
say("message", at(), function()
-- do something after the text box is dismissed
end)
Displays message
in a text box.
The text box can optionally be manually positioned and sized:
say("message", at( 10, 10 ))
or
say("message", at( 10, 10, 200, 100 ))
ask("stringValue", at(), {
{
name = "optionOne",
callback = function()
-- do one thing
end
},
{
name = "optionTwo",
callback = function()
-- do another
end
}
})
Displays stringValue
in a text box. The player can then select from the provided options. The options are presented as a vertical list in another box overlaying the bottom right corner of the question box. ask
requires at least one option, six or more options will be paginated.
Its text box can optionally be manually positioned and sized:
ask("stringValue", at( x, y ), ...
or
ask("stringValue", at( x, y, w, h ), ...
menu(at( 10, 10, 200, 100 ), {
{
name = "optionOne",
callback = (function()
-- do one thing
end)
},
{
name = "optionTwo",
callback = function()
-- do another
end
}
})
Presents a paginated menu with one or more options at x
,y
. w
and h
are optional and specify the width and height of the menu minus chrome tiles and cursor. Menu options are selected by pressing the A button. Submenus can be dismissed by pressing the B button. By default the root menu cannot be dismissed. Set config.allowDismissRootMenu
to true
to allow the root menu to be dismissed by pressing B.
Here's an example for a menu with a submenu:
menu( at( 10, 10, 200, 100 ), {
{ name = "mainOptionOne", type = "submenu", callback = function()
menu( at( 20, 20 ), {
{ name = "subOptionOne", callback = function()
-- do sub things
end
},
{ name = "subOptionTwo", callback = function()
-- do sub things
end
}
})
end
},
{ name = "mainOptionTwo", callback = function()
-- do another
end
}
})
fin("message")
Displays message in a text and marks the game as finished. Also triggers the finish event.
wait(duration, function()
-- do something after duration seconds
end)
Note that this does not stop the run loop. If you do not want the player to be able to move during the pause you can call ignore
before the wait
call and listen
at the end of its body.
local otherTile = tell("id-of-the-entity")
-- do something with the entity with id: id-of-the-entity
Allows one entity to interact with other entity. You can for instance; call functions on it like so:
local otherTile = tell("id-of-the-entity")
otherTile:functionName()
ignore()
and
listen()
Toggle whether the game should accept user input or not. Does not affect advancing or dismissing text.
act()
Make the player interact with the sprite or collect the item one tile in front of them based on their last movement direction. Note that this function is only really useful if you’ve set config.autoAct to false
.
hidePlayer()
Prevents the Player from being drawn. Can only be called from the Player
entity.
showPlayer()
Makes sure the Player is being drawn. Can only be called from the Player
entity.
local window = window(at(x, y, w, h))
Draws a window frame at x
,y
with dimensions w
,h
. (Coordinates and dimensions are in pixels, not tiles.) Unlike in Pulp, window
can be used everywhere. Just remember that you need to call:
window:add()
to add it to the screen, and
window:remove()
to remove it afterwards.
local sprite = sprite(imagePath, at(x, y))
Draws the requested image at x,y. Unlike draw
in Pulp, sprite
can be used everywhere. Just remember that you need to call:
sprite:add()
to add it to the screen, and
sprite:remove()
to remove it afterwards.
This function is an alternative for the draw
function in Pulp, but less performance heavy.
label("stringValue", at(x, y, w, h))
Draws text stringValue
at x
,y
. len is an optional maximum width to draw. lines is an optional maximum height to draw. Does not soft-wrap but does support the \n
newline character for hard-wrapping. Can only be called from the Player’s draw event.
fillRect(colorName, at(x, y, w, h))
Fills a rect with colorName (either white
or black
) at x
,y
with dimensions w
,h
. (Coordinates and dimensions are in pixels.) Can only be called from the Player’s draw event.
fillCircle(colorName, at(x, y, r))
Fills a circle with colorName (either white
or black
) at x
,y
with dimensions w
. (Coordinates and dimensions are in pixels.) Can only be called from the Player’s draw event.
sound("audioFileName")
Plays the sound matching audioFileName.<extension>
located in Source/sounds
.
Supports:
wav
mp3
You can also decrease the volume of the sound. Do:
sound("audioFileName", 0.5)
once("audioFileName")
or
once("songName", function()
-- do something when the song ends
end)
Plays the song matching audioFileName.<extension>
located in Source/sounds
then stops. Does nothing if the requested song is already playing.
WIP, callback not yet working
loop("audioFileName")
Plays the song matching audioFileName.<extension>
located in Source/sounds
, looping back to the beginning (or the song’s loopFrom point if set in the editor) each time it completes. Does nothing if the requested song is already playing.
stop()
Stops the currently playing song.
store()
Copies the current value of the global save
variable into persistent storage.
restore()
Sets the global save
variable to the save
values from persistent storage.
toss()
Deletes the storage file.
local varName = math.random (max)
or
local varName = math.random (min, max)
Returns an integer between the positive integers min
(or 0) and max
inclusive.
local varName = math.floor(num)
Returns the largest integer less than or equal to the given number.
local varName = math.ceil(num)
Returns the next largest integer greater than or equal to the given number.
Not supported
Round is not supported by the math library. Though you could use ceil
and floor
as alternatives.
local varName = math.sine(num)
Returns the sine of the given number (in radians).
local varName = math.cos(num)
Returns the cosine of the given number (in radians).
local varName = math.tan(num)
Returns the tangent of the given number (in radians).
local varName = math.rad(num)
Returns the given number (in degrees) converted to radians.
local varName = math.deg(num)
Returns the given number (in radians) converted to degrees.
invert()
or
local varName = invert()
Causes all drawing to be inverted (white displays as black and vice versa) until called again. Returns true
when drawing is inverted and false
when drawing is not inverted.
local varName = isSolid(entity)
Returns true
if the entity is solid, otherwise returns false
. Just to clarify: All entities with collisionTypes other than overlap
are solid.
at(x,y,w,h)
Returns an object { x = x, y = y, w = w, h = h }
. This makes it easier and more readable to position dialogs and windows. This function is used in the docs above, though if you prefer you can also write position and size without this function.
Example with at
function:
say("Message", at( 20, 20 ))
Example without at
function:
say("Message", { x = 20, y = 20 })