ロブロックスは、サーバーにデータを保存できます。プレイヤーの所持金や持ち物、どこのステージまで進んだのかなどの情報が保存できます。
ここでは、プレイヤーがゲームに入ってきたときにロードする、ゲームから退出したときにセーブする、一定間隔で自動セーブする方法の3通りについて解説します。

スクリプトの作成

  1. Explorer の ServerScriptService に Script を作成し、名前を PlayerSetup に変更します。
  2. ロードやセーブは、データストレージサービス(DataStoreService)を使用しますので、簡単にアクセスできるように準備しておきます。
local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

データのロード

サーバーに保存してあるデータをロードする関数を作成します。パラメーターの key はデータ名を、 val には新規プレーヤー用のデータの初期値を指定します。エラーになった場合も初期値がそのまま返ります。

  • key:データ名
  • val:データの初期値
  1. playerData:GetAsync(データ名)で、保存したデータがロードできます。
    データストレージのリクエストは、すべてのネットワークプログラムと一緒で、接続性の悪さやその他の問題により失敗することがあります。pcall() を使用すると、関数のコールでエラーが起きたのか、データが保存されていないのかを判別することができます。pcall は Lua の標準関数です。
  2. データが正しく取得できた場合は、success が true になり、data にその値が入ります。その値を val に代入し、return 値とします。
  3. データが存在していない場合は、success は true ですが、data には nil が入ります。しかしながらその場合は val に代入しませんので、return 値は初期値のままになります。
  4. GetAsync でエラーになった場合は、正常に呼び出せるまで無限に再試行します。
  5. 最後に return で、データの値を返します。
local function loadData(key,val)
    repeat
        local success, data = pcall(function()
            return playerData:GetAsync(key)
        end)
        if success and data then
            val = data
        end
    until (success)

    return val
end

ゲーム開始時のロード

プレイヤーがゲームに入ってきたときに、データをロードします。今回はリーダーボードを使います。過去に取得したコインの値を読み込み、リーダーボードに反映させます。リーダーボードについての解説はこちらのページをご覧ください。

  1. ゲームに入ってきたときに呼ばれるイベントを作成します。
  2. リーダーボードを作成します。
  3. コインの値を入れる項目(Coin)を作ります。値は整数になりますので、IntValue 型を使用します。
  4. 先ほど作成した loadData を呼び出して、リーダーボードの coin に代入します。
    データ名は、プレイヤーIDの後ろに「-coin」と付けた名称にします。初期値は0にします。
    player.UserId で、プレイヤーのID(ログロックスのアカウントID)が取得できます。実際のデータ名は「12304567890-coin」のようになります。
  5. 最後に、プレイヤーが入ってきたときに呼ばれる PlayerAdded イベントに紐づけします。

local function onPlayerJoin(player)
    local leaderstats = Instance.new("Folder")    -- フォルダーを作成
    leaderstats.Name = "leaderstats"       -- フォルダ名を「leaderstats」にする
    leaderstats.Parent = player            -- リーダーボードの親は player
	
    local coin = Instance.new("IntValue")  -- コインは整数値
    coin.Name = "Coin"                     -- 表示は「Coin」にする
    coin.Parent = leaderstats              -- coin の親は leaderstats
    coin.Value = loadData(player.UserId.."-coin",0)
end

game.Players.PlayerAdded:Connect(onPlayerJoin)   -- PlayerAdded イベントに紐づけする

データのセーブ

サーバーにデータを保存する関数を作成します。パラメーターの key はデータ名、 val はその値です。エラーになった場合は3回だけ再試行するようにします。

  • key:データ名
  • val:データの値
  1. playerData:SetAsync(データ名,値)で、データのセーブができます。
  2. SetAsync でエラーが発生した場合は、1秒だけ待ちます。
  3. エラーが発生した場合は、3回まで再試行します。
local function saveData(key,val)
    local tries = 0	
    local success
    repeat
        tries = tries + 1
        success = pcall(function()
            playerData:SetAsync(key, val)
        end)
        if not success then wait(1) end
    until tries == 3 or success   -- エラーは3回まで再試行

    if not success then
        warn("Cannot save data for player!")
    end
end

ゲーム退出時のセーブ

プレイヤーがゲームから退出したときに、データをセーブします。

  1. 先ほど、作成した saveData を呼び出すだけです。データ名は、必ずロードと同じ名称にしなくてはなりませんので注意してください。
  2. 最後に、プレイヤーが退出したときに呼ばれる PlayerRemoving イベントに紐づけします。
local function onPlayerExit(player)
    saveData(player.UserId.."-coin",player.leaderstats.Coin.Value)
end

game.Players.PlayerRemoving:Connect(onPlayerExit)

自動セーブ

長時間プレイするようなタイプのゲームにお勧めなのは、こちらの自動セーブです。ゲームプレイの最中に機器がフリーズして終了できなくなっても安心です。

  1. 自動セーブを行うインターバルの間隔を設定します。設定になりますので、スクリプトの一番上に入れた方が良いでしょう。
  2. Players からプレーヤーの情報を取得します。
  3. wait(AUTOSAVE_INTERVAL) で、60秒に1回、forループを実行します。ゲームに入っているプレーヤー全員のデータをセーブします。ここの例では、PlayerRemoving イベントである onPlayerExit を直接呼び出していますが、もしこのイベントでセーブ以外の処理を行っている場合は、関数を分け、セーブのみを行っている方を呼び出すようにしてください。
  4. 最後に、spawn() 関数を使って、autoSave を登録します。spawn はロブロックスのグローバル関数で、指定したコールバック関数を別のスレッドで実行するというものです。
-- 自動セーブする間隔(下記の例では60秒毎)
local AUTOSAVE_INTERVAL = 60

-- 定期的にサーバーにセーブする関数
local function autoSave()
    local Players = game:GetService("Players")

    while wait(AUTOSAVE_INTERVAL) do
        for _, player in pairs(Players:GetPlayers()) do
            onPlayerExit(player)
        end
    end
end
 
-- バックグラウンドで autoSave 関数の実行を開始します
spawn(autoSave)

完成したスクリプト

local AUTOSAVE_INTERVAL = 60    -- 自動セーブしない場合は不要

local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

local function loadData(key,val)
    repeat
        local success, data = pcall(function()
            return playerData:GetAsync(key)
        end)
        if success and data then
            val = data
        end
    until (success)

    return val
end

local function onPlayerJoin(player)
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player
	
    local coin = Instance.new("IntValue")
    coin.Name = "Coin"
    coin.Parent = leaderstats
    coin.Value = loadData(player.UserId.."-coin",0)
end

local function saveData(key,val)
    local tries = 0	
    local success
    repeat
        tries = tries + 1
        success = pcall(function()
            playerData:SetAsync(key, val)
        end)
        if not success then wait(1) end
    until tries == 3 or success

    if not success then
        warn("Cannot save data for player!")
    end
end

local function onPlayerExit(player)
    saveData(player.UserId.."-coin",player.leaderstats.Coin.Value)
end

-------------- 自動セーブ用のプログラム(自動セーブしない場合は不要)------------->
local function autoSave()
    local Players = game:GetService("Players")

    while wait(AUTOSAVE_INTERVAL) do
        for _, player in pairs(Players:GetPlayers()) do
            onPlayerExit(player)
        end
    end
end

spawn(autoSave)
------------------------ 自動セーブしない場合はここまで不要-----------------------<

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerExit)

動作確認

上記のロードとセーブが正常に動作するかどうかを確認してみます。プレイヤーがパーツに触れると、リーダーボードの Coin の値が1加算され、パーツが消えるようにします。

  1. Workspace にパーツを1つ置きます。
  2. そのパーツに Script を追加し、下記のプログラムを入力してください。
  3. このパーツを何個か、コピー&ペーストで配置してください。
local coin = script.Parent

local function onTouched(part)
    local humanoid = part.Parent:FindFirstChild("Humanoid")
    if humanoid then 
        local playerName = part.Parent.Name
        local player = game.Players:FindFirstChild(playerName)
        player.leaderstats["Coin"].Value = player.leaderstats["Coin"].Value + 1   -- リーダーボードのCoinに1を加算
        coin:Destroy()
    end
end

coin.Touched:Connect(onTouched)

テストプレイする前に

  1. ロブロックスのサーバーで実行する必要がありますので、セーブは必ず「Publish to Roxlox」で行います。
  2. [GameSetting] の Options の Enable Studio Access to API Services を On に設定してください。これを行わないとセーブした際にエラーが発生します。

テスト

  1. [Play] でテストプレイします。パーツを触れるとリーダーボードのCoinの値が増えると思います。
  2. この値を覚えておいてゲームを終了します。
  3. 再度、テストプレイします。覚えた値になっていれば、正常に保存されたということになります。もし、初期値の0になってしまう場合は保存されていないということになります。プログラムの入力ミスはないかを確認しましょう。
    なお、Roblox Studio のテストプレイでは保存されなくても、ロブロックスの方では正常に保存されることがあるようです。

Robloxの方でテスト

Roxlox Studio のテストプレイで、期待通りにCoinの値が保存できなかった場合、一度、ロブロックスの方からゲームを行ってみてください。それを行うには、Private の設定を変更する必要があります。

  1. [GameSetting] の Permissions の Playability を Friends に変更して公開します。友人以外は入れない設定です。
  2. ブラウザから Roblox を起動し、ゲームに入って試してみてください。
  3. 保存できることが確認できると思います。

By schilverberch

ROBLOXでゲームを作ろう! 一緒にプログラミングを学びましょう。

4 thoughts on “プレイヤーデータの保存”
  1. 上記のコードを参考に若干手を加えたオリジナルコードを作成したのですがplayer removingイベントでは途中の処理まで実行されてset Asyncの処理が実行されません。
    ロブロックスでも保存されませんでした。
    同じ関数を使用した自動セーブ用のプログラムは正常に作動するのですが。。。
    コードを添付していない状況ですが何か原因として挙げられるものはありますか?

    1. 一応、動作確認はしていますが、ROBLOXも頻繁にアップデートされるので以前動作していたものが動かなくなることがあります。
      onPlayerExitが呼ばれないということでしょうか?
      呼ばれないようでしたら、下記のようにしてみてください。

      — game.Players.PlayerAdded:Connect(onPlayerJoin)
      — game.Players.PlayerRemoving:Connect(onPlayerExit)

      local function onPlayerAdded(player)
      onPlayerJoin(player)
      player.CharacterRemoving:Connect(onPlayerExit)
      end

      game.Players.PlayerAdded:Connect(onPlayerAdded)

  2. 「データをセーブ」のところで
    1.playerData:GetAsync(データ名,値)で、データのセーブができます。
    とありますがGetAsyncではなくSetAsyncだと思います。

コメントを残す