playdate-dev について
Playdate Dev は、公式 Playdate SDK を使って Lua でゲームを開発するためのスキルです。画面、入力、メモリ、性能の制約を踏まえ、プロジェクト作成からシミュレーターと実機でのテストまでを案内します。
主な機能
- 01Lua プロジェクトの作成とビルド
- 02ボタン、クランク、加速度センサー
- 03グラフィック、スプライト、衝突判定
- 04オーディオ、タイマー、セーブデータ
- 05性能とアクセシビリティへの配慮
活用シーン
- 01Playdate Lua ゲームの開発開始
- 02クランク操作の仕組みの実装
- 03実機での性能計測
- 04ランチャー素材とメタデータの準備
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ ckorhonen/ claude-skills --skill playdate-devインストール後、Claude Code で/playdate-devを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/lua-starter/Source/main.luapdxinforeferences/designing-for-playdate.mdinside-playdate-lua.md詳細
Playdate 開発
概要
公式の Playdate SDK を使用して、Lua で Playdate ゲームを開発します。Playdate は、400×240 の 1 ビットディスプレイ、物理クランク、A/B ボタン、十字キーを備えた小型の黄色い携帯ゲーム機です。その制約と独自の入力方式を理解することが重要です。
ハードウェア仕様:
- ディスプレイ:400×240 ピクセル、1 ビット(白黒のみ)
- メモリ:約 16 MB RAM(ピーク使用量を 8 MB 未満に抑えることを目指します)
- CPU:180 MHz Cortex-M7(実機はシミュレーターより低速です。必ず実機でプロファイリングしてください)
- 入力:A ボタン、B ボタン、十字キー(上/下/左/右)、クランク、メニューボタン
- オーディオ:44.1kHz ステレオ、Synth + SamplePlayer API
- 加速度センサー:バッテリーを節約するため、必要な場合のみ有効にします
クイックスタートのワークフロー
- 依頼の範囲を確認します(ゲームプレイの目標、対象が実機かシミュレーターか、SDK のバージョン、リリースかプロトタイプか)。
- 入力方法とアクセシビリティを選択します(ボタン、クランク、加速度センサー。クランクを使わない代替方法も用意し、点滅を軽減する設定に配慮します)。
- 描画方法を選択します(スプライトか即時描画か、画像サイズ、リフレッシュレート、1x か 2x スケールか)。
- コアループを実装します(
playdate.update()を定義し、ゲーム状態を更新します。使用する場合はplaydate.graphics.sprite.update()とplaydate.timer.updateTimers()を呼び出します)。 - メタデータとランチャー用アセットを追加します(
pdxinfo、buildNumber、ランチャーカードとアイコンのサイズ)。 - シミュレーターと実機でテストします(画面の見やすさ、クランクの操作感、オーディオバランス、パフォーマンス)。
スタータープロジェクト
assets/lua-starterを新しいプロジェクトフォルダーにコピーします。Source/main.luaとSource/pdxinfoはソースルートに配置したままにします。pdxinfoのプレースホルダー値を置き換え、更新ループを拡張します。
次のコマンドでビルドします:
pdc Source GameName.pdx # compile to .pdx bundle
# Then open GameName.pdx in the Simulator, or drag to device
コアゲームループ
-- main.lua
import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"
local gfx <const> = playdate.graphics
-- Game state
local playerX, playerY = 200, 120
function playdate.update()
-- 1. Handle input
if playdate.buttonIsPressed(playdate.kButtonLeft) then
playerX -= 2
elseif playdate.buttonIsPressed(playdate.kButtonRight) then
playerX += 2
end
-- 2. Handle crank
local crankDelta = playdate.getCrankChange() -- degrees since last update
playerY += crankDelta * 0.1 -- map crank to movement
-- 3. Update sprites and timers (required each frame if used)
gfx.sprite.update()
playdate.timer.updateTimers()
-- 4. Draw (if not using sprites)
gfx.clear()
gfx.fillCircleAtPoint(playerX, playerY, 10)
end
入力 API
ボタン
-- Check if button is held this frame
playdate.buttonIsPressed(playdate.kButtonA)
playdate.buttonIsPressed(playdate.kButtonB)
playdate.buttonIsPressed(playdate.kButtonUp)
playdate.buttonIsPressed(playdate.kButtonDown)
playdate.buttonIsPressed(playdate.kButtonLeft)
playdate.buttonIsPressed(playdate.kButtonRight)
-- Single-frame press/release events
local pressed, released = playdate.getButtonState()
if pressed & playdate.kButtonA ~= 0 then
-- A was pressed this frame
end
-- Callbacks (simpler for menu-style code)
function playdate.AButtonDown()
-- A pressed
end
function playdate.AButtonUp()
-- A released
end
クランク
-- Angle in degrees (0-359.99, clockwise = positive)
local angle = playdate.getCrankPosition()
-- Delta since last frame (positive = clockwise)
local delta = playdate.getCrankChange()
-- Detect if crank is docked (folded away)
if playdate.isCrankDocked() then
-- Show "pull out crank" hint or use button fallback
end
-- Request dock/undock alerts
playdate.setCrankSoundsDisabled(true) -- suppress built-in clicks
加速度センサー
-- Must opt-in (saves battery when off)
playdate.startAccelerometer()
-- Read in update()
local x, y, z = playdate.readAccelerometer()
-- x,y,z each in range ~[-1, 1] (1G = 1.0)
-- x: tilt left/right, y: tilt front/back, z: up/down
-- Remember to stop when not needed
playdate.stopAccelerometer()
グラフィックス
描画モード
local gfx <const> = playdate.graphics
-- Screen is black (0) and white (1) only
gfx.setColor(gfx.kColorBlack) -- or kColorWhite, kColorClear, kColorXOR
gfx.setImageDrawMode(gfx.kDrawModeFillBlack) -- for rendering images
-- Immediate drawing (clears each frame)
function playdate.update()
gfx.clear(gfx.kColorWhite) -- clear to white
gfx.drawRect(10, 10, 100, 50)
gfx.fillRect(20, 20, 80, 30)
gfx.drawLine(0, 0, 400, 240)
gfx.drawCircleAtPoint(200, 120, 40)
gfx.fillCircleAtPoint(200, 120, 40)
gfx.drawText("Hello Playdate!", 10, 10)
end
画像
-- Load from .png file (must be in Source/)
local img = gfx.image.new("images/player") -- no extension needed
-- Draw image
img:draw(x, y)
img:drawCentered(x, y)
-- Flip/transform
img:draw(x, y, gfx.kImageFlippedX)
-- Image tables (sprite sheets)
local table = gfx.imagetable.new("images/walk") -- walk-table-32-32.png format
local frame = table:getImage(frameIndex) -- 1-indexed
フォントとテキスト
-- System fonts
local font = gfx.font.new("fonts/Roobert-10-Bold") -- SDK includes several
gfx.setFont(font)
gfx.drawText("Score: " .. score, 10, 10)
-- Centered text
gfx.drawTextInRect("Hello!", 0, 100, 400, 30, nil, nil, kTextAlignment.center)
スプライトシステム
-- Create sprite
local playerSprite = gfx.sprite.new()
playerSprite:setImage(gfx.image.new("images/player"))
playerSprite:moveTo(200, 120)
playerSprite:setZIndex(10)
playerSprite:add() -- add to sprite list
-- Custom sprite class (OOP pattern)
class('Player').extends(gfx.sprite)
function Player:init()
Player.super.init(self)
self:setImage(gfx.image.new("images/player"))
self:add()
end
function Player:update()
if playdate.buttonIsPressed(playdate.kButtonRight) then
self:moveBy(2, 0)
end
end
-- In playdate.update():
gfx.sprite.update() -- required every frame
衝突検出(スプライト経由)
-- Set collision rect
playerSprite:setCollideRect(0, 0, playerSprite:getSize())
-- Query collisions after move
local actualX, actualY, cols, len = playerSprite:moveWithCollisions(newX, newY)
-- Collision response
for i = 1, len do
local col = cols[i]
print("Hit:", col.other:getTag()) -- other sprite's tag
end
オーディオ
-- Sample playback
local sfx = playdate.sound.sampleplayer.new("sounds/jump") -- .wav or .aif
sfx:play()
-- Background music (loops by default)
local music = playdate.sound.fileplayer.new("sounds/bgm")
music:play(0) -- 0 = loop forever
music:setVolume(0.7)
-- Synthesizer (procedural audio)
local synth = playdate.sound.synth.new(playdate.sound.kWaveformSquare)
synth:setFrequency(440)
synth:setVolume(0.5)
synth:playNote("A4", 0.5, 0.25) -- note, volume, duration
タイマー
import "CoreLibs/timer"
-- One-shot timer (fires after 2000ms)
playdate.timer.performAfterDelay(2000, function()
print("Two seconds passed!")
end)
-- Repeating timer
local t = playdate.timer.new(500, function()
-- fires every 500ms
end)
t.repeats = true
-- Value timer (lerp a value over time)
local vt = playdate.timer.new(1000, 0, 100) -- 1000ms, from 0 to 100
-- In update: use vt.value
-- IMPORTANT: Must call every frame
playdate.timer.updateTimers()
pdxinfo メタデータ
# Source/pdxinfo (required)
name=My Game
author=Your Name
description=A short game description
bundleID=com.yourname.mygame
version=1.0.0
buildNumber=1
imagePath=images/
launchSoundPath=sounds/launch
contentWarning=Contains flashing lights
必須のランチャー用アセット(images/ に配置するか、imagePath で設定します):
launcher/card.png— 350×155 ピクセルlauncher/card~highlight.png— 350×155 ピクセル(ハイライト表示時)launcher/icon.png— 32×32 ピクセルlauncher/icon~highlight.png— 32×32 ピクセル
パフォーマンスのヒント
- 実機では 30fps を目標にします(最大 50fps ですが、標準は 30fps です)
gfx.clear()の使用を控えます — 代わりにgfx.sprite.update()のダーティ矩形描画を使用します- 動くオブジェクトにはスプライトシステムを優先します。画面全体の再描画を避けられます
- オブジェクトをプールします — 毎フレーム新しいテーブルやオブジェクトを作成しないようにします
- 50fps が不要な場合は
playdate.display.setRefreshRate(30)を使用します - 実機でプロファイリングします — シミュレーターは実機より 2~3 倍高速です
-- Check frame time
playdate.display.setRefreshRate(30)
-- Draw FPS overlay (for profiling)
playdate.drawFPS(0, 0)
アクセシビリティ
-- Respect "Reduce Flashing" system setting
if playdate.getReduceFlashing() then
-- Avoid strobing effects, use slower transitions
end
-- Always provide button fallback for crank actions
if playdate.isCrankDocked() then
showCrankHint = false -- hide crank UI hints
-- Let D-pad substitute for crank
end
クランク UI インジケーター
import "CoreLibs/ui"
-- Show built-in crank indicator (hint to pull out crank)
local crankIndicator = playdate.ui.crankIndicator
function playdate.update()
if playdate.isCrankDocked() then
crankIndicator:draw() -- draws in corner
end
end
メニュー連携
-- Add items to the system pause menu
local menu = playdate.getSystemMenu()
-- Checkmark toggle
local soundItem, err = menu:addCheckmarkMenuItem("Sound", true, function(value)
soundEnabled = value
end)
-- Options list
local diffItem, err = menu:addOptionsMenuItem("Difficulty", {"Easy","Normal","Hard"}, "Normal", function(value)
difficulty = value
end)
データの保存と読み込み
-- Simple key-value store (persists between sessions)
-- Save
local data = {score = 1234, level = 5}
playdate.datastore.write(data)
-- Load
local data = playdate.datastore.read()
if data then
score = data.score or 0
end
-- Delete save
playdate.datastore.delete()
よく使われるパターン
シーン管理
-- Simple scene switcher
local currentScene = nil
function switchScene(newScene)
if currentScene and currentScene.leave then
currentScene:leave()
end
gfx.sprite.removeAll()
currentScene = newScene
if currentScene.enter then
currentScene:enter()
end
end
-- Define scenes as tables
local titleScene = {}
function titleScene:enter() ... end
function titleScene:update() ... end
-- In playdate.update():
function playdate.update()
if currentScene and currentScene.update then
currentScene:update()
end
gfx.sprite.update()
playdate.timer.updateTimers()
end
クランク操作のメカニクス
-- Accumulate crank ticks for grid-based movement
local TICKS_PER_STEP = 12 -- degrees per step
local crankAccumulator = 0
function playdate.update()
local delta = playdate.getCrankChange()
crankAccumulator += delta
while crankAccumulator >= TICKS_PER_STEP do
crankAccumulator -= TICKS_PER_STEP
moveRight()
end
while crankAccumulator <= -TICKS_PER_STEP do
crankAccumulator += TICKS_PER_STEP
moveLeft()
end
-- Button fallback (for docked crank)
if playdate.buttonJustPressed(playdate.kButtonLeft) then moveLeft() end
if playdate.buttonJustPressed(playdate.kButtonRight) then moveRight() end
end
リソース
references/designing-for-playdate.md— 画面、テキスト、入力、オーディオ、UI、ランチャーに関するガイダンスreferences/inside-playdate-lua.md— Lua API の全名称、ファイル構成、ワークフローの詳細assets/lua-starter/— スタータープロジェクトのテンプレート- 公式 SDK ドキュメント — 信頼できる Lua API リファレンス
- SDK ダウンロード — Panic から無料で入手できます
- Playdate 開発者フォーラム — コミュニティの Q&A とサンプル
コメント
Markdown 対応