1import{u as r,j as e}from"./index-yLHt_u4B.js";import{a,S as t}from"./SectionHeading-B6aRHkIu.js";import{I as o}from"./InfoCard-BaeDCBld.js";import{A as s}from"./ApiMethodCard-BgUvn-qY.js";import{C as n}from"./CodeBlock-BImFnJQR.js";const h=()=>(r({title:"Achievements API | PIX3L SDK | Player 3",description:"PIX3L.achievements runtime API reference â report incremental and absolute progress, list achievements, and celebrate unlocks in-game.",canonical:"https://player3.gg/pix3l-sdk/achievements-api"}),e.jsxs(a,{children:[e.jsx(t,{id:"achievements",icon:"emoji_events",title:"Achievements"}),e.jsxs("p",{className:"text-muted-foreground leading-relaxed",children:[e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"PIX3L.achievements"})," ","is the runtime API your game uses to report progress. You author achievements in the Studio dashboard, then report progress from gameplay using the achievement's stable"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"key"}),". Player 3 validates every report server-side and unlocks automatically when the target is reached."]}),e.jsxs("div",{className:"rounded-md border border-border bg-secondary/40 p-4 text-sm text-muted-foreground leading-relaxed",children:["New here? Read the"," ",e.jsx("a",{href:"/pix3l-sdk/achievements",className:"text-primary font-medium hover:underline",children:"Achievement System overview"})," ","first to learn how to define achievements and choose the right mode (increment, absolute, boolean)."]}),e.jsxs("div",{className:"grid sm:grid-cols-3 gap-4",children:[e.jsx(o,{icon:"bolt",title:"Client Reports Progress",children:"Your game reports progress against an achievement's key. Reports are queued locally, then flushed to Player 3."}),e.jsx(o,{icon:"schedule",title:"3-sec Auto-Batching",children:"Events are automatically coalesced and flushed every 3 seconds (or when the buffer fills), minimizing network chatter."}),e.jsx(o,{icon:"verified_user",title:"Server-Side Unlocks",children:"The platform validates each report, enforces rate limits, and unlocks atomically. Clients cannot forge unlocks."})]}),e.jsx(t,{id:"ach-methods",icon:"terminal",title:"Methods"}),e.jsxs("div",{className:"space-y-4",children:[e.jsx(s,{signature:"PIX3L.achievements.progress(key, amount?)",badge:"void",description:"Reports incremental progress toward an achievement. Use this for counters like coins collected, enemies defeated, or matches played. Deltas are added to the running total server-side.",params:[{name:"key",type:"string",description:"The achievement key defined in the Studio dashboard."},{name:"amount",type:"number",description:"Positive integer to add to the counter.",default:"1"}],notes:"Works with achievements in `increment` mode. Multiple calls in the same 3-second window are coalesced into a single sum.",example:{title:"Report incremental progress",code:`// Player picked up a coin 2PIX3L.achievements.progress("coins_1000"); 3 4// Player picked up 25 coins from a chest 5PIX3L.achievements.progress("coins_1000", 25); 6 7// Player defeated an enemy 8PIX3L.achievements.progress("enemies_100");`}}),e.jsx(s,{signature:"PIX3L.achievements.setProgress(key, value)",badge:"void",description:"Reports an absolute value. Use this for high scores, current level, or longest survival time. The server keeps the maximum ever reported (monotonic max), smaller values are ignored.",params:[{name:"key",type:"string",description:"The achievement key defined in the Studio dashboard."},{name:"value",type:"number",description:"The current absolute value (e.g. score, level)."}],notes:"Works with achievements in `absolute` mode. Safe to call every frame, the SDK coalesces to the highest value in the current window.",example:{title:"Report a high score",code:`// On score change 9function onScoreChange(score) { 10 PIX3L.achievements.setProgress("high_score", score); 11} 12 13// On level up 14function onLevelUp(level) { 15 PIX3L.achievements.setProgress("reach_level_50", level); 16}`}}),e.jsx(s,{signature:"PIX3L.achievements.list()",badge:"async",description:"Fetches every achievement defined for the current game, along with the current player'
16s progress and unlock status.",returns:[{name:"AchState[]",type:"Promise<AchState[]>",description:"Array of achievement state records."}],example:{title:"Render an achievements screen",code:`const achievements = await PIX3L.achievements.list(); 17 18for (const a of achievements) { 19 console.log(a.title, a.progress + "/" + a.target, a.unlocked ? "â" : ""); 20}`}}),e.jsx(s,{signature:"PIX3L.achievements.onUnlock(callback)",badge:"() => void",description:"Subscribes to unlock events so your game can play a sound, show a custom celebration, or trigger a cutscene. Player 3 also renders its own themed toast automatically.",params:[{name:"callback",type:"(ach: AchState) => void",description:"Fires once per achievement, the moment it unlocks."}],returns:[{name:"unsubscribe",type:"() => void",description:"Call to remove the listener."}],example:{title:"Play a custom unlock sound",code:`const off = PIX3L.achievements.onUnlock((ach) => { 21 console.log("Unlocked:", ach.key, ach.title); 22 playSfx("fanfare"); 23 showCustomBanner(ach.title); 24}); 25 26// Later, on scene teardown: 27off();`}})]}),e.jsx(t,{id:"ach-types",icon:"data_object",title:"TypeScript Reference"}),e.jsxs("p",{className:"text-sm text-muted-foreground leading-relaxed",children:["The ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"AchState"})," ","interface is returned by"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"list()"})," and passed to"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"onUnlock"})," callbacks."]}),e.jsx(n,{title:"AchState.d.ts",language:"typescript",code:`type AchMode = "increment" | "absolute" | "boolean"; 28 29interface AchState { 30 /** Stable identifier used to report progress. */ 31 key: string; 32 /** Display title from the Studio dashboard. */ 33 title: string; 34 /** Optional description shown in achievement lists. */ 35 description: string; 36 /** Progress mode chosen when authoring the achievement. */ 37 mode: AchMode; 38 /** Target value required to unlock. */ 39 target: number; 40 /** Current player progress toward the target. */ 41 progress: number; 42 /** True once progress >= target. */ 43 unlocked: boolean; 44 /** ISO timestamp of the unlock, or null if still locked. */ 45 unlockedAt: string | null; 46 /** Whether the achievement is hidden until unlocked. */ 47 hidden: boolean; 48 /** Optional resolved icon URL for the achievement. */ 49 iconUrl: string | null; 50} 51 52interface Pix3lAchievements { 53 progress(key: string, amount?: number): void; 54 setProgress(key: string, value: number): void; 55 list(): Promise<AchState[]>; 56 onUnlock(cb: (ach: AchState) => void): () => void; 57}`}),e.jsx(t,{id:"ach-tips",icon:"lightbulb",title:"Developer Tips"}),e.jsxs("div",{className:"p-5 rounded-md border border-primary/40 bg-primary/5 space-y-2",children:[e.jsxs("h4",{className:"font-display font-semibold text-foreground",children:["No ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"unlock()"})," method by design"]}),e.jsxs("p",{className:"text-sm text-muted-foreground leading-relaxed",children:["There is intentionally no client-side"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"unlock()"})," call. Unlocks happen automatically on the server the moment progress reaches the target. For"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"boolean"})," ","achievements, any positive report unlocks them, call"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:'progress("secret_room")'})," ","once."]})]}),e.jsxs("div",{className:"p-5 rounded-md border border-border bg-card space-y-2",children:[e.jsx("h4",{className:"font-display font-semibold text-foreground flex items-center gap-2",children:"Offline & standalone fallback"}),e.jsxs("p",{className:"text-sm text-muted-foreground leading-relaxed",children:["When your game runs outside the Player 3 runtime (e.g. local development, a standalone build, or an unhosted preview),"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"PIX3L.achievements"})," ","degrades gracefully, calls become no-ops, and"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"list()"})," resolves with an empty array. You never need to guard calls with"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"if (PIX3L)"}),"."]})]}),e.jsxs("div",{className:"p-5 rounded-md border border-border bg-card space-y-2",children:[e.jsx("h4",{className:"font-display font-semibold text-foreground",children:"Rate limiting & safeguards"}),e.jsxs("ul",{className:"text-sm text-muted-foreground space-y-1.5 list-disc list-inside",children:[e.jsxs("li",{children:["Events are buffered locally and flushed every ",e.jsx("strong",{className:"text-foreground",children:"3 seconds"})," or when the buffer reaches ",e.jsx("strong",{className:"text-foreground",children:"32 events"}),"."]}),e.jsxs("li",{children:["Repeated"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"progress()"})," calls for the same key are summed; repeated"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"setProgress()"})," ","calls keep the max."]}),e.jsxs("li",{children:["The server enforces per-player rate limits and caps per-report deltas via each achievement's"," ",e.jsx("code",{className:"font-mono text-xs px-1 py-0.5 rounded bg-secondary text-foreground",children:"max_step"}),"."]}),e.jsx("li",{children:"Already-unlocked achievements silently ignore further reports."})]})]}),e.jsx(t,{id:"ach-example",icon:"integration_instructions",title:"Full Example"}),e.jsx(n,{title:"Wiring achievements into gameplay",code:`// Celebrate unlocks with a custom sound + banner 58const offUnlock = PIX3L.achievements.onUnlock((ach) => { 59 playSfx("fanfare"); 60 showBanner(\`Unlocked: \${ach.title}\`); 61}); 62 63// Increment counters as gameplay happens 64function onCoinPickup(count = 1) { 65 PIX3L.achievements.progress("coins_1000", count); 66} 67 68function onBossDefeated() { 69 // Boolean achievement â one call is enough 70 PIX3L.achievements.progress("first_boss"); 71} 72 73// Absolute values â safe to call on every score change 74function onScore(score) { 75 PIX3L.achievements.setProgress("high_score", score); 76} 77 78// Render an in-game achievements screen 79async function openAchievementsMenu() { 80 const list = await PIX3L.achievements.list(); 81 renderAchievementList(list); 82}`})]}));export{h as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.