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 get | You 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.comrefuses 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 for | What the game expects |
|---|---|
| Play as a guest | Hand back an account with no email |
| Who is this player | The display name, the id, whether they are a guest |
| Read the save | The saved data |
| Write the save | Store 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#
| Command | What 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 |