プレイヤーアカウント

自分のゲームのプレイヤーに、あらゆるaukimiゲームで持ち歩けるアカウントを与える。セーブデータはaukimi自身のサービス上でも、自分自身のサーバー上でも、デバイスをまたいで付いてくる。

一言で言うと#

プレイヤーはplay.aukimi.comで一度サインインすれば、aukimiで作られたどのゲームでも、そのアカウント(セーブデータを含めて)を見つけられます。あなたのゲームがするのはアカウントを要求することだけで、パスワードを扱うことは決してありません。

あなたのゲームがパスワードを見ることのない理由#

誰でもゲームを公開できます。aukimiのパスワードを求めるゲームは、それだけで完璧に説得力のあるフィッシングページになってしまい、あなた自身の善意がどれだけあっても、別の開発者が何を送り出すかは変えられません。

だからパスワードはplay.aukimi.comでのみ入力され、他のどこにもありません。あなたのゲームはPlayerLoginAsync()を呼び、aukimi自身の配色でブラウザウィンドウが開き、プレイヤーが承認すると、そのウィンドウがゲームにトークンを渡します。

そのトークンはあなたのゲームだけに対して発行されます。ゲームAのトークンをゲームBに提示しても拒否されます。無視されるのではなく、拒否されます。これが要点です。共有されるのはアカウントであって、アクセス権ではありません。これがなければ、悪意のあるゲーム1つが、他のすべてのゲームにおけるプレイヤーのセーブデータを読めてしまいます。

プレイヤーについて受け取る情報#

次の2つだけで、それ以上はありません。

受け取るもの受け取らないもの
表示名(PlayerName()メールアドレス
不透明なid、ゲームごとに異なるPlayerID()他の場所でその人を特定できるもの

idがゲームごとに変わるのは意図的です。2人の開発者がそれぞれのプレイヤーリストを比べても、同じ人物を見ているとは分かりません。これはまた、あなたをそのデータの管理者(controller)ではなく処理者(processor)にとどめるものでもあり、これは法的な区分であって、単なる飾りではありません。

何もゲームループを止めない#

ネットワークに話しかけるすべてのコマンドは、結果ではなくジョブ番号を返します。あなたのループは、リクエストが処理中であっても毎秒60フレームで動き続けます。これこそがゲームが単純に「サーバーを待つ」ことができない理由そのものです。

job = PlayerLoginAsync()

そして、毎フレーム、次のようにします。

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

結果を読んだらジョブを解放してください。 解放しなかったジョブは、セッションの残り時間中ずっと結果をメモリに保持し続けます。何も壊れず、何も警告してくれず、長いプレイセッションになって初めてその漏れが表面化します。

アカウントなしでプレイする#

誰もがゲームを試す前にサインアップしたいわけではありません。PlayerGuestAsync(name$)は、メールアドレスを持たない、このブラウザに紐付いたアカウントを作ります。

これは本物のアカウントです。セーブでき、ロードでき、後から引き継ぐこともできます。プレイヤーがメールアドレスとパスワードを追加すれば、既存のセーブデータは移動することなくそのまま付いてきます。

正直に言うべき落とし穴は、ブラウザのサイトデータを消去するとそのアカウントは失われ、取り戻す方法がないことです。それが「サインアップ不要」の代償であり、あなたのゲームはプレイヤーが後から気づくのではなく、そのことを最初に伝えるべきです。PlayerIsGuest()は、まさにその一文のために存在します。

セーブする#

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

セーブデータは、自分で決めた形のJSONとして、(このプレイヤー、このゲーム)というペアに対して保存されます。別のブラウザ、別のマシン、別のプラットフォームへも、そのプレイヤーに付いてきます。

少数の値だけでよければ、SetCloudDataVariable()CloudDataVariable()の方が簡単です。ジョブもJSONも要りません。まずCloudDataAllowed()を確認してください。サインインしているプレイヤーがいないときは0を返し、その状態で書き込んでも何も届きません。

画面を描く#

aukimiはサインインフォームを描きません。 エンジンが組み立てたフォームは、いつでもピクセルアートのゲームにWebページを貼り付けたように見えてしまい、しかもあなたのゲームの中で唯一、自分でスタイルを変えられない部分になってしまいます。

それを描くのはあなたです。ブラウザから来なければならない唯一の部品はテキスト入力なので、CreateEditBox()が、自分で位置を決められる本物の入力欄を与えてくれます。

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

Engineのサンプル一覧にあるPlayer Accountデモは、およそ100行で完結する実際に動く画面です。開いて読んで、そのうえですべてのPrint()を自分のアートワークに置き換えてください。

これが動く前に必要なこと#

ゲームは、My gamesaukimi.com/app/games)の下で、自分のaukimiアカウントから宣言されている必要があります。タイトル、そのゲームがプレイされるアドレス、そしてそのゲームが戻ってこられるアドレスです。それまではPlayerAvailable()は0を返し、良いゲームはフリーズしているように見せるのではなく「アカウント機能は利用できません」と伝えます。

同じ画面から、誰がプレイしているかも見られます。その数字はあるがまま受け止めてください。数えているのはサインインしたプレイヤー開始されたセッションであって、プレイされたゲームの数ではありません。誰にもサインインを求めないゲームはそこに何も表示されず、画面は黙ってゼロを示すのではなく、その旨を伝えます。

自分自身のアカウントサーバーを使う#

ここまではすべてaukimiのアカウントサービスに話しかけています。自分のゲームを自分のサーバーでホストしているなら、同じ12個のコマンドは代わりにあなたのサービスに話しかけることができ、あなたのスクリプトは1行も変わりません。

Engineの中では、パネルMultiplayer → セクションPlayer accountsMy own serverを選び、1つの項目、あなたのサーバーのアドレスを入力します。そのアドレスはシーンの設定なので、2つのゲームが2つの異なるサービスを使うこともできます。

プレイヤーをサインインさせる2つの方法#

その選択はあなたのスクリプトの中で行われるもので、設定の中ではありません。どちらの形も、どのサーバーを向いていても存在します。

ウィンドウを使う(すでに知っている方法)#

job = PlayerLoginAsync()

ウィンドウが開き、プレイヤーはそこでサインインし、そのウィンドウがあなたのゲームに、そのプレイヤーの代わりに動く権利を渡します。aukimiのサービスでは、そのウィンドウはplay.aukimi.comです。自分自身のサーバーを使うなら、そのページはあなたが作るものです。

直接、ユーザー名とパスワードで#

job = PlayerLoginAsync(username$, password$)

ウィンドウはありません。あなたのゲームは、あなた自身の画面にプレイヤーが入力した内容を読み取り、それをあなたのサーバーに送ります。書くのは簡単で、最初から最後までゲームの見た目を保てます。

警告: この形では、パスワードはあなたのゲームからあなたのサーバーへ入力されたそのままの形で移動します。これには2つの、どちらも譲れない帰結があります。あなたのサーバーのアドレスはhttps://で始まらなければならない(自分自身のマシン上でのテストだけが例外です)こと、そしてこの形はあなたが所有するサーバーに対してのみ使うべきだということです。

play.aukimi.comはこれを意図的に拒否します。aukimiはプレイヤーのパスワードを見られる立場に立ちたくありませんし、あなたが書いたのではないゲームにもそれを許すべきではないからです。

この2つの選択は設定から独立しています。自分で作ったサーバーが、ウィンドウ方式だけを提供し、パスワードを直接受け取ることを一切しない、ということも十分にあり得ます。

具体例#

自作の「サインイン」画面を持つゲーム。ゲームが描く2つのテキストフィールド、1つのボタン、そしてそのボタンの裏にあるこのコードです。

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

サインインより後のすべては、このページの他の部分とまったく同じです。PlayerSaveAsyncPlayerLoadAsyncPlayerName、その他のコマンドは、どのサーバーが答えたかを知りませんし、気にもしません。

あなたのサーバーが答えなければならないこと#

どちらのサインイン方式を提供するとしても、4つのアドレスが必要です。

何のためかゲームが期待すること
ゲストとしてプレイするメールアドレスのないアカウントを返す
このプレイヤーは誰か表示名、id、ゲストかどうか
セーブデータを読む保存されたデータ
セーブデータを書くゲームが送ってきたものを保存する

そのうえで、何を提供するかによって次が必要になります。

  • ウィンドウ方式には、あなたのサーバー上のサインインページが必要で、それが完了したらゲームに誰がサインインしたかを伝えます。
  • 直接方式には、もう1つのアドレスが必要で、ユーザー名とパスワードを受け取って、可否を答えます。

正確なアドレス、それぞれの答えの形、そしてエラーコードは、リポジトリのdocs/engine-player-accounts.mdにあります。そのファイルが契約であり、このページはその地図です。

注: これはいずれも、ネイティブ書き出しでは動きません。Player*系のコマンドは、話しかけるサーバーと、話す経路となるブラウザの両方を必要としますが、ネイティブビルドにはそのどちらもありません。

コマンド#

コマンド何をするか
PlayerAvailable()このゲームでアカウントサービスが設定されているか
PlayerLoginAsync()サインインウィンドウを開く。ジョブを返す
PlayerLoginAsync(user, pass)直接サインインする。自分自身のサーバーでのみ使用可。ジョブを返す
PlayerGuestAsync()メールアドレスのないアカウントを作る。ジョブを返す
PlayerLoggedIn()現在誰かがサインインしているか
PlayerName()その表示名
PlayerID()このゲームに固有の不透明なid
PlayerIsGuest()メールアドレスのないアカウントかどうか
PlayerSaveAsync()セーブデータを書き込む。ジョブを返す
PlayerLoadAsync()それを読み戻す。ジョブを返す
PlayerRefreshAsync()期限が切れる前にトークンを更新する
PlayerLogout()このデバイスでサインアウトする
PlayerLastError()直前の呼び出しが失敗した理由