Player accounts

Give the players of your game an account they keep across every Aukimi game, with saves that follow them from one device to the next, on Aukimi's service or on a server of your own.

In one line#

A player signs in once on play.aukimi.com and finds that account in every game made with Aukimi — saves included. Your game asks for the account; it never handles the password.

Why your game never sees the password#

Anyone can publish a game. A game that asked for an Aukimi password would be a perfectly convincing phishing page, and no amount of good intentions on your part would change what a different developer could ship.

So the password is typed on play.aukimi.com and nowhere else. Your game calls PlayerLoginAsync(), a browser window opens on Aukimi's own colours, the player approves, and the window hands your game a token.

That token is issued for your game only. A token for game A presented to game B is refused — not ignored, refused. That is the point: the account is shared between games, the access is not. Without this, one malicious game would read a player's saves in all the others.

What you receive about a player#

Two things, and no more:

You getYou never get
A display name (PlayerName())The email address
An opaque id, different in every game (PlayerID())Anything that identifies them elsewhere

The id changes from one game to the next on purpose. Two developers comparing their player lists cannot tell they are looking at the same person. It also keeps you a processor rather than a controller of that data, which is a legal distinction and not a decorative one.

Nothing blocks the game loop#

Every command that talks to the network returns a job number, never a result. Your loop keeps running at sixty frames per second while the request is in flight — which is the whole reason a game cannot simply "wait for the server".

job = PlayerLoginAsync()

Then, once per frame:

etat$ = PlatformJobState(job)
if etat$ = "done"
    resultat$ = PlatformJobResult(job)
    PlatformJobRelease(job)
endif
if etat$ = "error"
    Print(PlatformJobError(job))
    PlatformJobRelease(job)
endif

Release the job once you have read it. A job you never release keeps its result in memory for the rest of the session. Nothing breaks, nothing warns you, and the leak only shows up in a long play session.

Playing without an account#

Not everyone wants to sign up before trying a game. PlayerGuestAsync(name$) creates an account with no email, kept in this browser.

It is a real account: it saves, it loads, and it can be claimed later — the player adds an email and a password, and the existing saves follow without moving.

The honest catch: clearing the browser's site data loses that account, and there is no way to recover it. That is the price of "no sign-up", and your game should say so rather than let the player find out. PlayerIsGuest() is there for exactly that sentence.

Saving#

job = PlayerSaveAsync(donnees$)
job = PlayerLoadAsync()

The save is JSON of your own shape, stored against the pair (this player, this game). It follows them to another browser, another machine, another platform.

For a handful of values, SetCloudDataVariable() and CloudDataVariable() are simpler — no job, no JSON. Check CloudDataAllowed() first: it returns 0 when no player is signed in, and writing anyway would silently go nowhere.

Drawing the screen#

Aukimi draws no sign-in form. A form composed by the engine would always look like a web page dropped onto a pixel-art game, and it would be the one part of your game you could not restyle.

You draw it. The only piece that has to come from the browser is text entry, so CreateEditBox() gives you a real input field you position yourself:

CreateEditBox(1)
EditBoxPosition(1, 20, 42)
EditBoxSize(1, 60, 8)
nom$ = EditBoxText(1)

The Player Account demo in the Engine's sample list is a complete working screen in about a hundred lines. Open it, read it, then replace every Print() with your own artwork.

Before any of this works#

The game must be declared from your Aukimi account, under My games (aukimi.com/app/games): a title, the address where it can be played, and the addresses your game is allowed to return to. Until then PlayerAvailable() returns 0, and a good game says "accounts are not available" instead of appearing to hang.

That same screen is where you see who is playing. Read the numbers for what they are: it counts players who signed in and sessions started, never games played. A game that never asks anyone to sign in shows nothing there, and the screen says so rather than showing a silent zero.

Using your own account server#

Everything above talks to Aukimi's account service. If you host your game on your own server, the same twelve commands can talk to your service instead, and your script does not change by a single line.

In the Engine: panel Multiplayer → section Player accounts → choose My own server → fill in one field, the address of your server. That address is a setting of the scene, so two games can use two different services.

Two ways to sign a player in#

The choice is made in your script, not in a setting. Both forms exist whatever server you point at.

The window (what you already know)#

job = PlayerLoginAsync()

A window opens, the player signs in there, and it hands your game back the right to act on their behalf. With Aukimi's service that window is play.aukimi.com. With your own server, that page is yours to build.

Directly, with a username and a password#

job = PlayerLoginAsync(username$, password$)

No window. Your game reads what the player typed on your own screen and sends it to your server. Simpler to write, and it lets you keep your game's look from end to end.

Warning: in this form, the password travels from your game to your server as typed. Two consequences, both firm: the address of your server must start with https:// (only a test on your own machine is exempt), and this form is only ever meant for a server you own.

play.aukimi.com refuses it, on purpose. Aukimi never wants to be in a position to see a player's password, and neither should a game you did not write.

The two choices are independent of the setting: a home-made server may perfectly well offer the window only, and never accept a password directly.

A concrete example#

A game with its own "Sign in" screen: two text fields drawn by the game, one button, and this behind the button.

job = PlayerLoginAsync(typedName$, typedPassword$)
DO
    etat$ = PlatformJobState(job)
    if etat$ = "done"
        PlatformJobRelease(job)
        Print("Welcome " + PlayerName())
    endif
    if etat$ = "error"
        PlatformJobRelease(job)
        Print(PlayerLastError())
    endif
    Sync()
LOOP

Everything after the sign-in is identical to the rest of this page: PlayerSaveAsync, PlayerLoadAsync, PlayerName and the others do not know, and do not care, which server answered.

What your server has to answer#

Four addresses are required, whichever sign-in you offer:

What it is forWhat the game expects
Play as a guestHand back an account with no email
Who is this playerThe display name, the id, whether they are a guest
Read the saveThe saved data
Write the saveStore what the game sends

Then, depending on what you choose to offer:

  • The window form needs a sign-in page on your server, which tells the game who signed in once it is done.
  • The direct form needs one more address, which takes a username and a password and answers yes or no.

The exact addresses, the shape of each answer and the error codes are in the repository, in docs/engine-player-accounts.md. That file is the contract; this page is the map.

Note: none of this works in a native export. The Player* commands need a server to talk to and a browser to talk through, and a native build has neither.

The commands#

CommandWhat it does
PlayerAvailable()Is the account service configured for this game
PlayerLoginAsync()Opens the sign-in window. Returns a job
PlayerLoginAsync(user, pass)Signs in directly, on your own server only. Returns a job
PlayerGuestAsync()Creates an account with no email. Returns a job
PlayerLoggedIn()Is someone signed in right now
PlayerName()Their display name
PlayerID()Their opaque id, specific to this game
PlayerIsGuest()Is this an account without an email
PlayerSaveAsync()Writes the save. Returns a job
PlayerLoadAsync()Reads it back. Returns a job
PlayerRefreshAsync()Renews the token before it expires
PlayerLogout()Signs out on this device
PlayerLastError()Why the last call failed