Godot Development
Integrate Playcademy into your Godot projects
Overview
Integrate your Godot projects with the Playcademy platform using our Godot toolchain.
Installation
Install the Playcademy asset bundle from the Godot AssetLib and configure your project.
Install from AssetLib
- Open AssetLib tab in Godot
- Search for "Playcademy"
- Install the bundle
This adds addons/playcademy/ to your project.
Prefer the CLI?
If you already have the addon installed, you can update it to the latest version from the terminal:
$ playcademy godot syncSee godot sync for details.
Enable Plugins
Go to Project > Project Settings > Plugins and enable:
- Playcademy Backend (for local development servers)
- Playcademy Manifest Exporter (for deployment)
Setup AutoLoad
- Go to
Project > Project Settings > Globals > Autoload - Click the folder icon and navigate to
res://addons/playcademy/sdk/playcademy_sdk.gd - Name it
PlaycademySdk(this is the name you'll use in your code) - Click
+ Add
Need a Backend?
Godot's ecosystem doesn't offer a wide range of backend solutions like traditional app development.
Playcademy fills this gap with production-ready server infrastructure.
Backend Integrations
You can opt into server-side functionality using the Playcademy CLI:
Initialize Project
Run the init command in your Godot project directory:
$ playcademy init # Select integrations when promptedThis creates a playcademy.config.json with your project metadata.
Add Integrations
Add integrations during project setup or add them later:
$ playcademy timeback init # Educational tracking$ playcademy db init # SQLite database$ playcademy auth init # User accounts$ playcademy kv init # Key-value storage$ playcademy api init # Custom routesPlaycademy Backend Plugin
Upon starting, the Playcademy Backend plugin reads your playcademy.config.json.
It then launches the backend server with configured integrations.

Pre-configured Project
The fastest way to start a new Godot project with Playcademy:
bun create playcademy my-godot-appnpm create playcademy my-godot-apppnpm create playcademy my-godot-appyarn create playcademy my-godot-appWhen prompted, select Godot as your project type.
This scaffolds a project with:
- Playcademy addon pre-installed in
addons/playcademy/ - Sample scenes and scripts demonstrating fundamentals
playcademy.config.jsonconfigured for your project
Open the project folder in Godot and run Main.tscn to see the SDK in action.
Adding to Existing Project?
If you already have a Godot project, use playcademy init instead:
$ playcademy initThis adds the config file but won't install the addon automatically. See the Installation section for manual addon setup.
Local Development
The Playcademy Backend plugin automatically starts local development servers when you open your project in Godot.
- Sandbox Server: Simulates the Playcademy Platform API with mock data
- Backend Server: Runs your custom routes and integrations (call via
PlaycademySdk.backend)
Just Like the Vite Plugin
The Playcademy Backend plugin does for Godot what @playcademy/vite-plugin does for Vite projects.
In other words, it automatically manages local development infrastructure.
Project Settings
Configure the Playcademy Backend plugin via Godot's Project Settings.
- Go to
Project → Project Settings - Enable Advanced Settings (toggle in top-right)
- Find the
Playcademysection in the left sidebar
Runtime
| Setting | Type | Default | Description |
|---|---|---|---|
playcademy/runtime/mode | enum | platform | Launch mode for native (non-web) runs. See Launch Modes. |
Web Builds
Launch mode for web exports is set by the host page, e.g. the Playcademy platform. This project setting only affects native runs from the Godot editor. The PLAYCADEMY_MODE environment variable overrides this setting so programmatic runs don't need to edit project.godot.
Sandbox
| Setting | Type | Default | Description |
|---|---|---|---|
playcademy/sandbox/auto_start | bool | false | Automatically start sandbox server |
playcademy/sandbox/port | int | 4321 | Port for the sandbox server |
playcademy/sandbox/verbose | bool | false | Enable verbose logging |
playcademy/sandbox/url | String | http://localhost:4321 | URL of the sandbox server |
Backend
| Setting | Type | Default | Description |
|---|---|---|---|
playcademy/backend/auto_start | bool | false | Automatically start backend server on project open |
playcademy/backend/port | int | 8788 | Port for the backend server |
playcademy/backend/project_path | String | Auto | Path to project root (auto-detected from res://) |
Timeback
| Setting | Type | Default | Description |
|---|---|---|---|
playcademy/timeback/student_id | String | "" | Timeback student sourcedId (leave empty for auto-generated mock ID) |
playcademy/timeback/role | String | student | User role: student, parent, teacher, or administrator |
playcademy/timeback/organization_id | String | "" | Organization ID (leave empty for mock org) |
playcademy/timeback/organization_name | String | "" | Organization name (defaults to "Playcademy Studios") |
playcademy/timeback/organization_type | String | department | Organization type: school, district, department, local, state, or national |
Course Enrollment (Dynamic)
If you have a playcademy.config.json file with Timeback courses, the plugin automatically creates enrollment settings for each course:
| Setting | Type | Default | Description |
|---|---|---|---|
playcademy/timeback/courses/Math_3 | enum | Enrolled | Enroll mock user in Math Grade 3 |
playcademy/timeback/courses/Math_4 | enum | Enrolled | Enroll mock user in Math Grade 4 |
Set a course to "Not Enrolled" to test how your app behaves when a student is enrolled in specific grades only.
Requires JSON Config
Dynamic course settings only work with playcademy.config.json (not .js). GDScript can only parse JSON natively.
Testing Different Roles
Change the role setting to test how your app behaves for different Timeback user types during development.
For example, set role to parent to preview the parent experience, or teacher to test teacher-specific features.
Note: After changing Timeback settings, click Reset Database in the Playcademy dock to apply changes.
PlaycademySdk
This namespace is your main entry point for accessing the SDK in Godot projects.
Be sure to set it up as a global (autoload) in your settings for easy access throughout your project.
Initialization
Connect to SDK signals in your _ready() function:
func _ready():
if PlaycademySdk:
PlaycademySdk.sdk_ready.connect(_on_sdk_ready)
PlaycademySdk.sdk_initialization_failed.connect(_on_sdk_init_failed)
# Connect to API signals
PlaycademySdk.users.profile_received.connect(_on_profile_received)
func _on_sdk_ready():
print("SDK Ready!")
PlaycademySdk.users.me()
func _on_sdk_init_failed(error: String):
printerr("SDK failed:", error)Launch Mode
Every Playcademy project is launched in one of three launch modes.
The Godot Playcademy SDK exposes the current mode on PlaycademySdk.mode so you can branch on it:
func _on_sdk_ready():
match PlaycademySdk.mode:
"platform":
# Authenticated user, full SDK surface
PlaycademySdk.users.me()
"demo":
# Anonymous user, landing-page demo shell
start_demo_flow()
"standalone":
# Running outside any iframe — mock token, no real user
start_offline_flow()For web exports, the mode is set by the host page and propagated through the Playcademy shell.
For native runs (Godot editor), the mode comes from:
- The
playcademy/runtime/modeproject setting - Or, the
PLAYCADEMY_MODEenvironment variable
Identity-Bound APIs in Demo Mode
In demo mode the user is anonymous, so identity-bound namespaces like users and timeback will fail.
Gate them behind PlaycademySdk.mode == "platform" or use assert_platform_mode.
PlaycademySdk.demo
Only available when PlaycademySdk.mode == "demo". This is the counterpart of client.demo in the TS SDK.
Use it to manage the anonymous demo player's display name (surfaced on the leaderboard) and to tell the landing-page shell when the demo round has ended.
func _ready():
if PlaycademySdk.mode != "demo":
return
# Profile reads/writes resolve asynchronously via signals.
PlaycademySdk.demo.profile_get_succeeded.connect(_on_demo_profile)
PlaycademySdk.demo.profile_update_succeeded.connect(_on_demo_profile)
PlaycademySdk.demo.profile_get()
func _on_demo_profile(profile: Dictionary):
# profile → { "displayName": String, "isDefault": bool }
# `isDefault` means the player is still on the server-generated
# placeholder handle — prompt them before the next run.
if profile.get("isDefault", true):
PlaycademySdk.demo.profile_update({ "displayName": "linus-torvalds" })
func _on_round_finished(final_score: int, duration_ms: int):
PlaycademySdk.demo.end(final_score, {
"durationMs": duration_ms,
"metadata": { "level": current_level },
})Methods
| Method | Async? | Description |
|---|---|---|
profile_get() | ✅ | Read the anonymous demo player's profile. |
profile_update(updates) | ✅ | Set the demo player's displayName (the only updatable field today). |
end(score, options?) | — | Fire-and-forget. Signals the end of the demo round. score is required. |
Signals
Async methods resolve via signal pairs.
DemoProfile payloads are normalized to a Godot Dictionary with displayName: String and isDefault: bool.
| Signal | Payload | Emitted by |
|---|---|---|
profile_get_succeeded | profile_data: Dictionary | profile_get() on success |
profile_get_failed | error_message: String | profile_get() on error |
profile_update_succeeded | profile_data: Dictionary | profile_update() on success |
profile_update_failed | error_message: String | profile_update() on error |
The landing-page shell always needs something to display on the post-demo CTA, so score is required.
Optional properties:
| Option key | Type | Description |
|---|---|---|
durationMs | int | Active demo time in milliseconds |
metadata | Dictionary | Any additional data the shell needs |
assert_platform_mode
Returns true when in platform mode, prints an error and returns false otherwise.
func try_submit_score(score: int):
if not PlaycademySdk.assert_platform_mode("scores.submit"):
return
PlaycademySdk.scores.submit(score, { "level": current_level })Core Namespaces
PlaycademySdk.users
User profile management:
# Get current user
PlaycademySdk.users.me()
func _on_profile_received(user_data: Dictionary):
print("User:", user_data.name)
print("ID:", user_data.id)PlaycademySdk.scores
Submit scores:
# Submit a score
PlaycademySdk.scores.submit(1500, { "level": 5, "difficulty": "hard" })
func _on_submit_succeeded(score_data: Dictionary):
print("Score submitted!")PlaycademySdk.runtime
Game lifecycle:
# Signal ready
PlaycademySdk.runtime.ready()
# Exit
PlaycademySdk.runtime.exit()Integration Namespaces
PlaycademySdk.timeback
Track learning activities with automatic XP calculation. Access user context for content gating.
# Start tracking an activity
var activity_metadata = {
"activityId": "math-quiz-1",
"grade": 3,
"subject": "Math"
}
PlaycademySdk.timeback.start_activity(activity_metadata)
# ... student completes the activity ...
# End activity and submit results (XP calculated automatically)
var score_data = {
"correctQuestions": 8,
"totalQuestions": 10
}
PlaycademySdk.timeback.end_activity(score_data)Auto-filled Metadata
The SDK automatically fills in metadata from your project config:
activityName: Derived fromactivityId(math-quiz-1→Math Quiz 1)appName: Fromplaycademy.config.json'snamefieldsensorUrl: Your deployed project URL
You can override any of these by providing them explicitly in start_activity().
User Context
Access the user's Timeback context via PlaycademySdk.timeback.user:
# Access user properties
var user = PlaycademySdk.timeback.user
var id = user.id # TimeBack user ID
var role = user.role # "student", "parent", "teacher", etc.
var enrollments = user.enrollments # Array of { id, subject, grade, courseId }
var orgs = user.organizations # Array of { id, name, type }
# Fetch fresh Timeback user data
PlaycademySdk.timeback.user.fetch()
# Fetch XP data
PlaycademySdk.timeback.user.xp.fetch({"include": ["today"]})
# Fetch highest grade mastered for a subject
PlaycademySdk.timeback.user.highest_grade_mastered.fetch({"subject": "Math"})user.id
The user's unique Timeback identifier:
var id = PlaycademySdk.timeback.user.id
# "abc123-def456-..."user.role
The user's primary Timeback role:
var role = PlaycademySdk.timeback.user.role
# "student", "parent", "teacher", "administrator", or "guardian"user.enrollments
Array of courses the user is enrolled in, scoped to your project:
var enrollments = PlaycademySdk.timeback.user.enrollments
# [{ "subject": "Math", "grade": 3, "courseId": "..." }, ...]App-Scoped
Enrollments are filtered to courses defined in your playcademy.config.json.
user.organizations
Array of all organizations (schools/districts) the user is affiliated with:
var orgs = PlaycademySdk.timeback.user.organizations
# [{ "id": "...", "name": "Playcademy Studios", "type": "school" }, ...]App-Scoped
Like enrollments, organizations are app-scoped.
Only organizations associated with the user's enrollments for your project are included.
user.fetch()
Fetch fresh user data from the server:
PlaycademySdk.timeback.user.fetch()Emits user_fetch_succeeded or user_fetch_failed signals. See Timeback Signals.
When to Fetch
The user context is initialized when the SDK loads.
Use fetch() if you need the latest data (e.g. after a user might have been enrolled in a new course mid-session).
user.xp
Access the user's XP data via PlaycademySdk.timeback.user.xp:
# Fetch XP data
PlaycademySdk.timeback.user.xp.fetch({
"include": ["today"], # Include today's XP breakdown
"force": false # Use cached data if available
})user.xp.fetch()
Fetch XP data from the server:
# Basic fetch (uses 5-second cache)
PlaycademySdk.timeback.user.xp.fetch()
# Include today's XP in response
PlaycademySdk.timeback.user.xp.fetch({
"include": ["today"]
})
# Filter by grade and subject (must provide both)
PlaycademySdk.timeback.user.xp.fetch({
"grade": 3,
"subject": "Math",
"include": ["today", "perCourse"]
})
# Bypass cache for fresh data
PlaycademySdk.timeback.user.xp.fetch({
"force": true
})Emits xp_fetch_succeeded or xp_fetch_failed signals. See Timeback Signals.
Options:
| Option | Type | Description | Default |
|---|---|---|---|
grade | int | Filter by grade level (requires subject) | - |
subject | String | Filter by subject (requires grade) | - |
include | Array | Extra data: "today", "perCourse" | [] |
force | bool | Bypass 5-second cache | false |
Response Dictionary:
| Field | Type | Description | Always Present |
|---|---|---|---|
totalXp | int | Total XP earned | Yes |
todayXp | int | XP earned today (if "today" in include) | No |
courses | Array | Per-course breakdown (if "perCourse" in include) | No |
Each course in courses array:
| Field | Type | Description |
|---|---|---|
grade | int | Course grade level |
subject | String | Course subject |
title | String | Course display name |
totalXp | int | XP earned in course |
todayXp | int | Today's XP (if included) |
Caching
XP data is cached for 5 seconds to prevent excessive API calls. Use "force": true when you need guaranteed fresh data (e.g., after completing an activity).
user.highest_grade_mastered
Access the student's highest mastered grade for a subject via PlaycademySdk.timeback.user.highest_grade_mastered:
PlaycademySdk.timeback.user.highest_grade_mastered.fetch({
"subject": "Math",
"force": false
})user.highest_grade_mastered.fetch()
Fetch highest grade mastered data from the server:
# Basic fetch (uses 5-second cache)
PlaycademySdk.timeback.user.highest_grade_mastered.fetch({
"subject": "Math"
})
# Bypass cache for fresh data
PlaycademySdk.timeback.user.highest_grade_mastered.fetch({
"subject": "Math",
"force": true
})Emits highest_grade_mastered_fetch_succeeded or highest_grade_mastered_fetch_failed signals. See Timeback Signals.
Options:
| Option | Type | Description | Default |
|---|---|---|---|
subject | String | Timeback subject | Required |
force | bool | Bypass 5-second cache | false |
Response Dictionary:
| Field | Type | Description | Always Present |
|---|---|---|---|
subject | String | Subject that was requested | Yes |
highestGradeMastered | int | null | Highest mastered grade, or null when none | Yes |
No Mastered Grade Yet
highestGradeMastered: null is a valid response. It means Timeback does not have a mastered-grade signal for that subject yet. Do not treat it as grade 0 or an error; fall back to your normal placement behavior.
start_activity
Begin tracking a learning activity. Starts an internal timer and sets up metadata for OneRoster submission.
# Minimal (most common)
var activity_metadata = {
"activityId": "math-quiz-1",
"grade": 3,
"subject": "Math"
}
# Activity name auto-derived: "Math Quiz 1"
PlaycademySdk.timeback.start_activity(activity_metadata)
# With custom name override
var custom_activity = {
"activityId": "multiplication-drill",
"activityName": "Advanced Multiplication Drill",
"grade": 4,
"subject": "Math"
}
var result = PlaycademySdk.timeback.start_activity(custom_activity)
var run_id = result.get("runId", "")Required Fields:
| Field | Type | Description | Example |
|---|---|---|---|
activityId | String | Identifier for this activity | "math-quiz-1" |
grade | int | Grade level | 3 |
subject | String | Subject area | "Math" |
Optional Fields:
| Field | Type | Description | Default |
|---|---|---|---|
activityName | String | Display name for the activity | Prettified activityId |
appName | String | Application name | From playcademy.config.json |
sensorUrl | String | URL where activity is hosted | Deployed project URL |
Course Routing
The grade and subject fields determine which OneRoster course receives the activity data.
Ensure these match a course in your playcademy.config.json Timeback configuration.
Options:
start_activity() takes an optional second Dictionary that tunes tracking behavior:
# Resumable activity - pass a stable UUID across sessions so the
# dashboard correlates related runs (persist it however you like)
PlaycademySdk.timeback.start_activity(
{ "activityId": "level-1-quiz" },
{ "runId": "123e4567-e89b-42d3-a456-426614174000" }
)
# Disable the periodic heartbeat interval (final flushes still run)
PlaycademySdk.timeback.start_activity(
activity_metadata,
{ "heartbeatIntervalMs": 2147483647 }
)
# Stop paused heartbeats after 5 minutes of hidden/idle auto-pause
PlaycademySdk.timeback.start_activity(
activity_metadata,
{ "pausedHeartbeatTimeoutMs": 300000 }
)| Field | Type | Description | Default |
|---|---|---|---|
runId | String | Stable UUID to correlate resumable activities | Random UUID |
pausedHeartbeatTimeoutMs | int | Stop heartbeats after a long automatic hidden/idle pause | 600000 (10 m) |
heartbeatIntervalMs | int | Heartbeat flush interval | 15000 (15 s) |
inactivityTimeoutMs | int | Visible keyboard/mouse idle timeout before pausing | 600000 (10 m) |
start_activity() returns { "runId": String }, using your supplied UUID when provided or a new SDK-generated UUID otherwise.
During an active activity, the same value is available as PlaycademySdk.timeback.current_run_id; it is an empty string when no activity is active.
Automatic Tracking
Once started, the web SDK flushes heartbeats every 15 seconds, auto-pauses the timer when the tab is hidden or the Cademy shell sends PAUSE, and sends a final beacon on pagehide. See Timeback Integration → Activity Tracking for the full behavior and tuning options.
In local/sandbox mode the heartbeat and pause-timing options are accepted but no-op — runId is forwarded to end_activity so the same call signature works in both environments.
end_activity
End the current activity and submit results to OneRoster. Calculates XP based on accuracy and active time.
# Auto-calculate XP based on score
var score_data = {
"correctQuestions": 8,
"totalQuestions": 10
}
PlaycademySdk.timeback.end_activity(score_data)
# Report mastery (e.g., unit completed)
var score_with_mastery = {
"correctQuestions": 8,
"totalQuestions": 10,
"masteredUnits": 1 # Student mastered 1 unit
}
PlaycademySdk.timeback.end_activity(score_with_mastery)
# Override XP calculation
var score_with_custom_xp = {
"correctQuestions": 8,
"totalQuestions": 10,
"xpAwarded": 15 # Award exactly 15 XP
}
PlaycademySdk.timeback.end_activity(score_with_custom_xp)Required Fields:
| Field | Type | Description | Example |
|---|---|---|---|
correctQuestions | int | Number of correct answers | 8 |
totalQuestions | int | Total number of questions | 10 |
Optional Fields:
| Field | Type | Description | Default |
|---|---|---|---|
xpAwarded | int | Override automatic XP calculation | Based on time and accuracy |
masteredUnits | int | Units mastered (negative to subtract) | 0 |
When to Report Mastery
Send masteredUnits: 1 when the student completes a discrete learning unit in your app:
- Level-based: Student completes a level, stage, or world
- Rank-based: Student earns a rank, tier, or badge
- Skills-based: Student masters a skill, competency, or standard
- Module-based: Student completes a module, quiz, or chapter
The platform tracks cumulative mastery and calculates completion automatically based on your mastery configuration.
XP Calculation
By default, XP is calculated as:
Base XP = Active time in minutes × Accuracy multiplier
| Accuracy | Multiplier | Example (10 min) |
|---|---|---|
| 100% | 1.25× | 10 min × 1.25 = 12.5 XP |
| 80-99% | 1.0× | 10 min × 1.0 = 10 XP |
| < 80% | 0× | 0 XP (mastery not demonstrated) |
Base rate: 1 minute of active learning = 1 XP
Re-attempts earn diminishing XP: 50% on 1st re-attempt, 25% on 2nd, 0% on 3rd+.
pause_activity
Pause the activity timer. Use this during non-instructional moments like showing feedback or explanations.
PlaycademySdk.timeback.start_activity({
"activityId": "speed-math-1",
"grade": 4,
"subject": "Math"
})
# Student attempts a problem...
func _on_answer_submitted(is_correct: bool):
if not is_correct:
# Pause timer during feedback
PlaycademySdk.timeback.pause_activity()
# Show correct answer or explanation
show_correct_answer()
await get_tree().create_timer(3.0).timeout
# Resume timer when they continue
PlaycademySdk.timeback.resume_activity()
# End activity (only active time counted)
PlaycademySdk.timeback.end_activity({
"correctQuestions": 40,
"totalQuestions": 50
})When to Pause
Pause when:
- Tutorial/instruction screens
- Showing hints or explanations
- Waiting for external resources to load
- Any non-active learning time
This ensures XP reflects actual learning time.
resume_activity
Resume the activity timer after a pause.
# After pausing
PlaycademySdk.timeback.pause_activity()
# ... show feedback ...
# Resume when ready
PlaycademySdk.timeback.resume_activity()Must Call start_activity First
You must call start_activity() before using pause_activity() or resume_activity().
Calling these methods without an active activity will trigger a failure signal.
Timeback Signals
Connect to signals to handle activity, user, XP, and highest grade mastered responses:
func _ready():
# Activity signals
PlaycademySdk.timeback.end_activity_succeeded.connect(_on_end_activity_succeeded)
PlaycademySdk.timeback.end_activity_failed.connect(_on_end_activity_failed)
PlaycademySdk.timeback.pause_activity_failed.connect(_on_pause_activity_failed)
PlaycademySdk.timeback.resume_activity_failed.connect(_on_resume_activity_failed)
# User fetch signals
PlaycademySdk.timeback.user_fetch_succeeded.connect(_on_user_fetch_succeeded)
PlaycademySdk.timeback.user_fetch_failed.connect(_on_user_fetch_failed)
# XP fetch signals
PlaycademySdk.timeback.xp_fetch_succeeded.connect(_on_xp_fetch_succeeded)
PlaycademySdk.timeback.xp_fetch_failed.connect(_on_xp_fetch_failed)
# Highest grade mastered fetch signals
PlaycademySdk.timeback.highest_grade_mastered_fetch_succeeded.connect(_on_hgm_fetch_succeeded)
PlaycademySdk.timeback.highest_grade_mastered_fetch_failed.connect(_on_hgm_fetch_failed)
# Handle activity success
func _on_end_activity_succeeded(response):
print("Activity ended! XP awarded:", response.xpAwarded)
# Handle activity failures
func _on_end_activity_failed(error: String):
printerr("Failed to end activity:", error)
func _on_pause_activity_failed(error: String):
printerr("Failed to pause activity:", error)
func _on_resume_activity_failed(error: String):
printerr("Failed to resume activity:", error)
# Handle user fetch
func _on_user_fetch_succeeded(user_data: Dictionary):
print("User data refreshed:", user_data)
func _on_user_fetch_failed(error: String):
printerr("Failed to fetch user data:", error)
# Handle XP fetch
func _on_xp_fetch_succeeded(xp_data: Dictionary):
print("Total XP:", xp_data.get("totalXp", 0))
if xp_data.has("todayXp"):
print("Today's XP:", xp_data.todayXp)
func _on_xp_fetch_failed(error: String):
printerr("Failed to fetch XP data:", error)
# Handle highest grade mastered fetch
func _on_hgm_fetch_succeeded(data: Dictionary):
var highest_grade_mastered = data.get("highestGradeMastered", null)
if highest_grade_mastered == null:
print("No mastered grade yet for ", data.get("subject", ""))
else:
print("Highest grade mastered:", highest_grade_mastered)
func _on_hgm_fetch_failed(error: String):
printerr("Failed to fetch highest grade mastered:", error)Available Signals:
| Signal | When | Payload |
|---|---|---|
end_activity_succeeded | Activity ended successfully | Dictionary |
end_activity_failed | Failed to end activity | String |
pause_activity_failed | Failed to pause activity | String |
resume_activity_failed | Failed to resume activity | String |
user_fetch_succeeded | User data fetched | Dictionary |
user_fetch_failed | Failed to fetch user data | String |
xp_fetch_succeeded | XP data fetched | Dictionary |
xp_fetch_failed | Failed to fetch XP data | String |
highest_grade_mastered_fetch_succeeded | Highest grade mastered data fetched | Dictionary |
highest_grade_mastered_fetch_failed | Failed to fetch highest grade mastered | String |
PlaycademySdk.backend
Call your custom backend API routes.
These methods connect to the server-side routes you create in your server/api/ directory.
Learn more about custom routes.
request
Make HTTP requests to your custom backend routes.
# Connect signal
func _ready():
PlaycademySdk.backend.request_succeeded.connect(_on_data_received)
# Make GET request
func fetch_player_stats():
PlaycademySdk.backend.request("/stats", "GET")
# Handle response
func _on_data_received(response: Dictionary):
print("Player stats:", response)
var score = response.get("score", 0)
var level = response.get("level", 1)
update_ui(score, level)# Connect signal
func _ready():
PlaycademySdk.backend.request_succeeded.connect(_on_answer_validated)
# Submit answer
func submit_answer(answer: String):
var body = {
"answer": answer,
"questionId": current_question_id,
"timestamp": Time.get_unix_time_from_system()
}
PlaycademySdk.backend.request("/validate", "POST", body)
# Handle validation result
func _on_answer_validated(response: Dictionary):
var is_correct = response.get("correct", false)
if is_correct:
show_correct_feedback()
else:
show_incorrect_feedback()# Connect signal
func _ready():
PlaycademySdk.backend.request_succeeded.connect(_on_backend_success)
# Update user settings
func update_settings(volume: float, difficulty: String):
var settings = {
"volume": volume,
"difficulty": difficulty,
"lastUpdated": Time.get_unix_time_from_system()
}
PlaycademySdk.backend.request("/settings", "PUT", settings)
# Clear cache
func clear_cache():
PlaycademySdk.backend.request("/cache/clear", "DELETE")
# Handle success
func _on_backend_success(response: Dictionary):
print("Operation completed:", response)# Connect both signals
func _ready():
PlaycademySdk.backend.request_succeeded.connect(_on_backend_success)
PlaycademySdk.backend.request_failed.connect(_on_backend_error)
# Make request with error handling
func save_app_state(app_data: Dictionary):
PlaycademySdk.backend.request("/save", "POST", app_data)
show_loading_indicator()
# Handle success
func _on_backend_success(response: Dictionary):
hide_loading_indicator()
show_notification("Progress saved!")
print("Save successful:", response)
# Handle errors
func _on_backend_error(error: String):
hide_loading_indicator()
show_notification("Failed to save. Please try again.")
printerr("Backend error:", error)
# Maybe retry or fallback to local save
save_to_local_storage(app_data)Use per-request callbacks when you want a response handler scoped to a single call.
func fetch_player_stats():
PlaycademySdk.backend.request(
"/stats",
"GET",
{},
_on_stats_received,
_on_stats_failed
)
func _on_stats_received(response: Dictionary):
print("Player stats:", response)
func _on_stats_failed(error: String):
printerr("Failed to fetch stats:", error)If you are using per-request callbacks and you also have global backend listeners
func fetch_player_stats_quiet():
PlaycademySdk.backend.request(
"/stats",
"GET",
{},
_on_stats_received,
_on_stats_failed,
true # suppress_signals
)Callbacks + Signals
By default, callbacks are additive: if you pass on_success / on_failure,
the SDK will still emit request_succeeded / request_failed unless you pass
true for the sixth positional argument (suppress_signals).
Parameters:
| Parameter | Type | Description | Example |
|---|---|---|---|
path | String | API route path | "/hello" |
method | String | HTTP method (GET, POST, PUT, DELETE) | "POST" |
body | Dictionary | Request body (optional) | { "answer": "A" } |
on_success | Callable | Called on success (optional) | _on_stats_received |
on_failure | Callable | Called on failure (optional) | _on_stats_failed |
suppress_signals | bool | If true, do not emit request_succeeded / request_failed for this request | true |
Supported Methods:
GET- Retrieve dataPOST- Create or submit dataPUT- Update dataPATCH- Partial updateDELETE- Remove data
Backend Signals
Connect to signals to handle backend responses:
func _ready():
PlaycademySdk.backend.request_succeeded.connect(_on_backend_succeeded)
PlaycademySdk.backend.request_failed.connect(_on_backend_failed)
func _on_backend_succeeded(response: Dictionary):
print("Backend response:", response)
# Access response data
if response.has("message"):
print("Message:", response.message)
func _on_backend_failed(error: String):
printerr("Backend request failed:", error)Available Signals:
| Signal | Description | Payload |
|---|---|---|
request_succeeded | Request completed | Dictionary |
request_failed | Request failed or timed out | String |
Custom Routes
To create backend routes, add files to your server/api/ directory:
export default defineRoute({
GET: async (req, ctx) => {
return { message: 'Hello from backend!' }
},
})See Custom Routes for complete documentation.
Export and Deploy
The CLI can automatically export and deploy your Godot project.
Configure Web Export
Create a Web export preset in Godot:
- Go to
Project → Export... - Add
Web (Runnable)preset - Set
Custom HTML Shellto:res://addons/playcademy/shell.html
Deploy
Run the deploy command:
$ playcademy deployThe CLI will:
- Detect your Godot project automatically
- Prompt: "Export Godot project?"
- Run headless export to generate build files
- Deploy to Playcademy
Automatic Export
The CLI finds your Godot executable and runs the export headlessly; no need to manually export from the editor!
See CLI Deployment for complete deployment documentation.
Signal Reference
All SDK methods emit signals for responses. Some APIs (like backend.request) also support per-request callbacks as an alternative to global listeners.
Users API:
| Signal | When |
|---|---|
profile_received | User data loaded |
profile_fetch_failed | Failed to load user |
Scores API:
| Signal | When |
|---|---|
submit_succeeded | Score submitted |
submit_failed | Submit error |
get_by_user_succeeded | Scores retrieved |
get_by_user_failed | Retrieve error |
Integration Signals
For Timeback and Backend API signals, see their respective sections:
What's Next?
Custom Routes
Add server-side logic and backend APIs to your Godot project.
Timeback Integration
Set up educational tracking and XP rewards for learning activities.
Deployment Guide
Master the deployment workflow for Godot projects.
Web Development Guide
Learn about web-specific features and best practices.
