玩家帳號

讓你遊戲的玩家擁有一個可以帶著走的帳號,在每一款 Aukimi 遊戲裡通用,存檔也能跟著他們從一台裝置換到另一台,可以用 Aukimi 的服務,也可以用你自己的伺服器。

一句話說明#

玩家在 play.aukimi.com 登入一次,就能在每一款用 Aukimi 做出來的遊戲裡找到那個帳號,存檔也包含在內。你的遊戲只會請求那個帳號,永遠不會經手密碼。

為什麼你的遊戲永遠看不到密碼#

任何人都能發布一款遊戲。一款會要求輸入 Aukimi 密碼的遊戲,會是一個極具說服力的釣魚頁面,而不論你的用意有多良善,都無法改變「另一個」開發者可能會發布出什麼東西。

所以密碼只會輸入在 play.aukimi.com 上,別無他處。你的遊戲呼叫 PlayerLoginAsync(),一個瀏覽器視窗會以 Aukimi 自己的配色開啟,玩家在裡面同意授權,然後那個視窗會把一個權杖交給你的遊戲。

那個權杖只對你的遊戲有效。一個發給遊戲 A 的權杖,如果拿去遊戲 B 使用會被拒絕,不是被忽略,而是被明確拒絕。這正是重點所在:帳號是在遊戲之間共用的,存取權則不是。少了這一點,任何一款惡意遊戲都能讀取玩家在其他遊戲裡的存檔。

你能取得關於一個玩家的哪些資訊#

只有兩件事,不會更多:

你會取得你永遠不會取得
一個顯示名稱(PlayerName()電子郵件地址
一個不透明的 id,每款遊戲都不一樣PlayerID()任何能在別處識別出他們的資訊

這個 id 會刻意在不同遊戲之間改變。兩位比對各自玩家清單的開發者,無法看出他們看到的是同一個人。這也讓你在法律定義上只是一個處理者,而不是一個控制者,這是一個法律上的區分,不是裝飾用的說法。

沒有東西會卡住遊戲迴圈#

每一個與網路對話的指令,回傳的都是一個工作編號(job number),而不是結果本身。你的迴圈會繼續以每秒六十幀的速度運作,同時請求在背景進行,這正是一款遊戲不能單純「等待伺服器」的全部原因。

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不會畫出任何登入表單。一個由引擎自己組出來的表單,看起來永遠會像是一個網頁被硬塞進一款像素藝術遊戲裡,而且會是你遊戲裡唯一一個你無法重新設計外觀的部分。

由你自己畫。唯一必須來自瀏覽器的部分是文字輸入,所以 CreateEditBox() 會給你一個可以自行擺放位置的真正輸入欄位:

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

Engine 範例清單裡的 Player Account 示範,是一個大約一百行就能完成的完整畫面。打開它、讀懂它,然後把每一個 Print() 換成你自己的美術內容。

在這一切能運作之前#

這款遊戲必須先從你的 Aukimi 帳號登錄,在My gamesaukimi.com/app/games)底下:一個標題、遊戲可以被遊玩的網址,以及你的遊戲被允許返回的網址。在那之前,PlayerAvailable() 會回傳 0,一款好的遊戲應該顯示「帳號功能目前無法使用」,而不是看起來像卡住了一樣。

那同一個畫面,也是你能看到誰在玩的地方。請照它原本的意思去理解那些數字:它計算的是登入過的玩家數已開始的遊戲階段數,永遠不是玩過的遊戲場次。一款從來不要求任何人登入的遊戲,在那裡會什麼都看不到,而畫面也會明確說明這一點,而不是顯示一個沒有說明的零。

使用你自己的帳號伺服器#

以上所有內容說的都是與 Aukimi 的帳號服務對話。如果你把遊戲架設在自己的伺服器上,同樣的十二個指令可以改為與你的服務對話,而你的腳本連一行都不用改。

在 Engine 裡:Multiplayer 面板 → Player accounts 區塊 → 選擇 My own server → 填入一個欄位,也就是你伺服器的網址。那個網址是場景的一項設定,所以兩款遊戲可以各自使用兩個不同的服務。

兩種讓玩家登入的方式#

這個選擇是在你的腳本裡決定的,不是在某個設定裡。無論你指向哪一種伺服器,兩種形式都存在。

視窗方式(你已經認識的那一種)#

job = PlayerLoginAsync()

一個視窗會開啟,玩家在裡面登入,接著它會把代表玩家行動的權力交還給你的遊戲。使用 Aukimi 的服務時,那個視窗就是 play.aukimi.com如果使用你自己的伺服器,那個頁面就由你自己來建置。

直接方式,帶使用者名稱和密碼#

job = PlayerLoginAsync(username$, password$)

不需要視窗。你的遊戲讀取玩家在你自己的畫面上輸入的內容,並把它送到你的伺服器。寫起來更簡單,也讓你能從頭到尾保持遊戲自己的外觀風格。

警告: 用這種形式時,密碼會以輸入時的原樣從你的遊戲傳到你的伺服器。這帶來兩個必須遵守的後果:你伺服器的網址必須以 https:// 開頭(只有在你自己機器上做測試時例外),而且這種形式只應該用在你自己擁有的伺服器上。

play.aukimi.com 會刻意拒絕這種方式。Aukimi 絕對不想處在一個能看見玩家密碼的位置,而你沒有寫出來的遊戲也不應該處在那個位置。

這兩種選擇彼此獨立於伺服器設定之外:一個自架的伺服器完全可以只提供視窗方式,永遠不接受直接輸入的密碼。

一個具體的例子#

一款有自己「登入」畫面的遊戲:兩個由遊戲自己畫出來的文字欄位,一個按鈕,按鈕背後是以下這段程式:

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 和其他指令既不知道、也不在乎是哪個伺服器回應的。

你的伺服器必須回答什麼#

無論你提供哪一種登入方式,都需要四個網址:

用途遊戲期望得到什麼
以訪客身分遊玩交還一個沒有電子郵件的帳號
這位玩家是誰顯示名稱、id,以及是否為訪客
讀取存檔已存的資料
寫入存檔儲存遊戲傳來的內容

接著,依你選擇提供的方式而定:

  • 視窗方式需要你伺服器上有一個登入頁面,並在完成後告訴遊戲是誰登入了。
  • 直接方式需要多一個網址,接收使用者名稱和密碼,並回答是或否。

確切的網址、每個答案的格式,以及錯誤碼,都在儲存庫裡的 docs/engine-player-accounts.md。那份文件是規格;這一頁是地圖。

注意: 這一切在原生匯出版本中都不會運作。Player* 系列指令需要一個可以對話的伺服器,以及一個可以透過它對話的瀏覽器,而原生組建兩者都沒有。

指令一覽#

指令作用
PlayerAvailable()這款遊戲的帳號服務是否已設定完成
PlayerLoginAsync()開啟登入視窗,回傳一個工作
PlayerLoginAsync(user, pass)直接登入,僅限用在你自己的伺服器,回傳一個工作
PlayerGuestAsync()建立一個沒有電子郵件的帳號,回傳一個工作
PlayerLoggedIn()目前是否有人已登入
PlayerName()他們的顯示名稱
PlayerID()他們在這款遊戲中專屬的不透明 id
PlayerIsGuest()這是不是一個沒有電子郵件的帳號
PlayerSaveAsync()寫入存檔,回傳一個工作
PlayerLoadAsync()讀回存檔
PlayerRefreshAsync()在權杖過期前更新它
PlayerLogout()在這台裝置上登出
PlayerLastError()上一次呼叫失敗的原因