玩家帳號
讓你遊戲的玩家擁有一個可以帶著走的帳號,在每一款 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 games
(aukimi.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
登入之後的一切,都與這一頁其他部分完全相同:
PlayerSaveAsync、PlayerLoadAsync、PlayerName 和其他指令既不知道、也不在乎是哪個伺服器回應的。
你的伺服器必須回答什麼#
無論你提供哪一種登入方式,都需要四個網址:
| 用途 | 遊戲期望得到什麼 |
|---|---|
| 以訪客身分遊玩 | 交還一個沒有電子郵件的帳號 |
| 這位玩家是誰 | 顯示名稱、id,以及是否為訪客 |
| 讀取存檔 | 已存的資料 |
| 寫入存檔 | 儲存遊戲傳來的內容 |
接著,依你選擇提供的方式而定:
- 視窗方式需要你伺服器上有一個登入頁面,並在完成後告訴遊戲是誰登入了。
- 直接方式需要多一個網址,接收使用者名稱和密碼,並回答是或否。
確切的網址、每個答案的格式,以及錯誤碼,都在儲存庫裡的
docs/engine-player-accounts.md。那份文件是規格;這一頁是地圖。
注意: 這一切在原生匯出版本中都不會運作。
Player*系列指令需要一個可以對話的伺服器,以及一個可以透過它對話的瀏覽器,而原生組建兩者都沒有。
指令一覽#
| 指令 | 作用 |
|---|---|
PlayerAvailable() | 這款遊戲的帳號服務是否已設定完成 |
PlayerLoginAsync() | 開啟登入視窗,回傳一個工作 |
PlayerLoginAsync(user, pass) | 直接登入,僅限用在你自己的伺服器,回傳一個工作 |
PlayerGuestAsync() | 建立一個沒有電子郵件的帳號,回傳一個工作 |
PlayerLoggedIn() | 目前是否有人已登入 |
PlayerName() | 他們的顯示名稱 |
PlayerID() | 他們在這款遊戲中專屬的不透明 id |
PlayerIsGuest() | 這是不是一個沒有電子郵件的帳號 |
PlayerSaveAsync() | 寫入存檔,回傳一個工作 |
PlayerLoadAsync() | 讀回存檔 |
PlayerRefreshAsync() | 在權杖過期前更新它 |
PlayerLogout() | 在這台裝置上登出 |
PlayerLastError() | 上一次呼叫失敗的原因 |