Skip to content

PlaydateSquad/pd-achievements

Repository files navigation

pd-achievements

Experimental cross-game achievement standard for the Playdate console.

This is essentially a reference implementation in the form of a functional library. It is not necessarily guaranteed to be robust against edge cases, but by the time of a 1.0 release it should be development ready for standard uses.

WARNING: This standard is still in development. Forward-compatibility is not yet guaranteed.

The specification draft can be seen here.

Usage

TODO: Better formalize this section once everything's been settled.

Primary Module

To import the main library, include achievements.lua in your project and import it. This will create a global variable named achievements containing the module.

achievements.initialize(table: config_data, bool: silent)

This function is responsible for configuring your game's metadata and valid achievements, loading local saved data, and exporting the necessary files to /Shared for other games to read your game's achievements.

This function logs relevant initialization information to the console unless the silent flag is set to True.

To configure your game, define a table containing the following format and pass it to this function:

local achievementData = {
    -- These four fields are equivalent to the matching fields in your game's pdxinfo metadata.
    -- They will be automatically filled in from the metadata if not defined here.
    gameID = "com.example.yourgame",
    name = "My Awesome Game",
    author = "You, Inc",
    description = "The next (r)evolution in cranking technology.",
    achievements = {
        -- This table should be an array of achievement tables.
        -- Each of these tables stores the data for a single achievement.
        {
            -- A unique string ID for your achievement.
            id = "my_achievement",
            -- Display information for achievement viewers.
            name = "Achievement Name",
            description = "Achievement Description",
            -- Should this achievement appear in viewers if not yet earned?
            is_secret = false,
            -- The filepath to a .pdi icon for the achievement once granted.
            icon = "my_icon",
            -- The filepath to a .pdi icon for the achievement before it's granted.
            icon_locked = "my_icon_locked",
        },
        -- Continue configuring additional achievements as needed.
    }
}

achievements.initialize(achievementData)

This table makes up the top-level data structure being saved to the shared json file. The gameID field determines the name of the folder it will be written to, rather than bundleID, to keep things consistent.

achievements.grant(string: achievement_id)

Grants the achievement achievement_id to the player. Attempting to grant a previously earned achievement does nothing.

Returns true if the achievement was successfully granted, otherwise false.

achievements.revoke(string: achievement_id)

Revokes the achievement achievement_id from the player. Attempting to revoke an unearned achievement does nothing.

Returns true if the achievement was successfully revoked, otherwise false.

achievements.save()

Saves the player's earned achievements as "Achievements.json" in your game's data, and exports the updated achievement information to /Shared.

achievements.isGranted(string: achievement_id)

Returns true if the achievement has been earned by the player, returns false otherwise.

bool: achievements.forceSaveOnGrantOrRevoke

If this flag is set to true then achievements.save() will be run every time an achievement is newly granted or revoked. Defaults to false.

Toasts Module

To use toasts when achievements are granted, add toast_graphics.lua to your project and import it. This will create a global variable named toast_graphics containing the module.

There are several values which can be configured (this may break some things):

int: toast_graphics.displayGrantedMilliseconds

How long should a toast animation last? Default: 2000

int: toast_graphics.displayGrantedDefaultX

What X coordinate should a toast animation draw at? Default: 20

int: toast_graphics.displayGrantedDefaultY

What Y coordinate should a toast animation draw at? Default: 0

int: toast_graphics.displayGrantedDelayNext

How many milliseconds should pass after a toast begins before another one can begin playing? Default: 400

int: iconWidth

How wide are the achievement icons? Default: 32

int: iconHeight

How tall are the achievement icons? Default: 32

[TODO]: The size of icons should really be set in the standard rather than configured here.

Wrappers:

toast_graphics provides wrappers around the following basic functions:

  • achievements.initialize -> toast_graphics.initialize
  • achievements.grant -> toast_graphics.grant
  • achievements.revoke -> toast_graphics.revoke
  • achievments.isGranted -> toast_graphics.isGranted
  • achievements.save -> toast_graphics.save

When using this module, these wrappers should be used instead of the originals.

toast_graphics.grant(string: achievement_id, function: draw_card_func)

Like achievements.grant, this function grants the achievement achievement_id to the player. It also queues up a toast for drawing.

The new argument draw_card_func optionally takes a function which will be called each frame in order to draw the achievement toast to the screen. The function must take the following arguments:

  • ach: The achievement's internal data as configured during initialization.
  • x: The base x coordinate the card should be drawn at.
  • y: The base y coordinate the toast should be drawn at.
  • elapsed_miliseconds: The amount of time which has passed since the toast began.

The function should return true while the animation is ongoing and false once it has ended.

toast_graphics.drawCard(achievement_or_id, x, y, elapsed_msec)

Draws a card using the default toast animation at x and y, at elapsed_msec into the animation. achievement_or_id can be either a string achievement ID or an achievement's internal data. x and y default to the module's configured x/y locations. elapsed_msec defaults to 0.

toast_graphics.updateVisuals()

Updates currently drawing toasts. Should be called once per frame as part of the global update loop.