概要
この記事はVRサービス「VirtualCast」で使用できるVCIという仕組みについて扱った記事です。
今回はVCIの機能である「ストレージ」の概要とそれを用いて作成したVCIの仕組みの紹介です。
結構中級者向きの記事だと思います。
ストレージ機能とは?
ざっくりいうとサーバーにデータを保存しておくことができる仕組みです。
(詳細は 公式ページ 参照)
主に以下のような特徴があります
- VCIが消えてもデータが残り続け、次回VCIを出したときにデータを参照できる
- 同じVCIであれば、自分が複数出したり他の人が出したものでも共通のデータが参照できる
上記を利用することでランキングの管理などがやりやすくなります。
ガチャVCIの説明
今回はこのストレージ機能を使用してみんなでコンプを目指すガチャを作成したので、そのなかでどうストレージ機能を使用したかを説明したいと思います。
VCIの主な仕様は以下のとおりです
- ガチャで出るのは1~100までの数字
- それぞれの数字をはじめに出した人の情報を名前を記録して表示している
- 回数が毎日決まった回数分ガチャを引ける回数が付与される。
- ストックできるガチャ回数は上限がある
- 上記の付与回数、ストック上限回数は設定で決まっている。
また、説明に先立ち把握しておいてほしいストレージの機能を少しだけ説明します。
- ストレージはkey-valueの形式で保存されており最大1000件まで保存可能
- データは任意のタグを付けることができ、取得時にはタグを指定することで複数のデータをまとめて取得可能
ストレージに保存するデータ
以下の2つの情報を保存しています
- ガチャ設定:デイリー加算回数などの設定データ(全体で1つ)
- ガチャコンプデータ:ガチャのコンプ状況のデータ(ガチャの数字単位)
- 個人データ:引ける回数を管理するデータ(ユーザー単位)
データの構造は以下のような感じです。
Key:"Config"
Tag:なし
Data:{
"DailyPullNum", //1日に加算するガチャ回数
"StockMaxNum", //累積で貯められるガチャ回数
}
Key:ガチャの数字
Tag:"GachaNumber"
Data:ユーザー名
Key:ユーザーID
Tag:"Player"
Data:{
"info.lastAccessDate", //最後にガチャを引いた日
"info.remainingPullNum", //引ける残回数
}
ガチャを数字ごとに分けて保存しているのは、同時にガチャを引いたときに更新の競合が起きる可能性を下げるためです。
実際の処理
上記のデータ構造を踏まえ、実際にどの用にストレージAPIを使用しているかを説明していきます。
他のところで定義しているモジュールや変数を使用しているためそのままコピペでは動かないです。ご了承ください
初期化処理
VCI読み込み時に3つのデータすべてを取得します。
詳細なフローは以下のシーケンス図のとおりです。
初めてVCIを使用するユーザーには個人データがなく取得できないため、その場合は初期データを作成しています。
コード的には以下のようになります。
vci.vc.storage.Get(EachOne.key,function(r, v)
if r.isSuccess then
-- 取得した情報をモデルに格納して画面に反映
local data = json.parse(v)
EachOne.gachaData = GachaInfo.__new(data)
EachOne.gachaData:UpdateAccessInfo()
Logger.Info("[getPlayerToStorage]success get storage data")
else
-- データがない場合は新しく個人でデータを作成
if r.errorType == vci.vc.storage.errorTypes.KeyNotFound then
EachOne.gachaData = GachaInfo.__new()
EachOne.gachaData:UpdateAccessInfo()
Logger.Info("[getPlayerToStorage]success create new data")
else
Logger.Error("[getPlayerToStorage]failed to get "..tostring(EachOne.key)..": "..r.errorMessage)
end
end
end)
ガチャコンプデータを取得する場合は以下のようにタグの機能を使用して取得しています。
vci.vc.storage.GetValuesFromTag(StorageTag.GachaNumber,function(r, vList)
if r.isSuccess and vList then
-- ガチャの数字分の長さのから配列を作成
for i=1,100 do
EachOne.CompList[i] = ""
end
-- ガチャの数字ごとにストレージから取得したデータがあれば反映
for index,keyVal in pairs(vList) do
if keyVal.value ~= nil and keyVal ~= "" then
local s_name = keyVal.value
local number = tonumber(keyVal.key)
if number then
EachOne.CompList[number] = s_name
end
end
end
Logger.Info("success get gachaComp data")
else
if r.errorType == vci.vc.storage.errorTypes.TagNotFound then
-- ストレージに1つもデータが無かったらから配列を作成
for i=1,100 do
EachOne.CompList[i] = ""
end
Logger.Info("success create gachaComp init data")
else
Logger.Error("failed to get "..StorageTag.GachaNumber..": "..r.errorMessage)
end
end
refreshCompList()
EachOne.compBook:refresh()
end)
ガチャを引く処理
ガチャを引く際には個人データの更新とガチャコンプデータの更新をしています。
詳細なフローは以下のシーケンス図のとおりです。
図に載せ忘れましたが、ガチャの数字決定のあとにガチャ残回数を減らす処理もしています。
また、最後にガチャコンプデータ全件分を取得しているのは、現状だと他に取得するところがなくローカルのデータとストレージのデータの差異が広がっていってしまうのでそれを防ぐためです。
個人データの更新は以下のようなコードで行っています。
特に特殊な処理はありません
vci.vc.storage.SetWithTag(EachOne.key, dataStr,StorageTag.Player,function(r, v)
if r.isSuccess then
Logger.Info("success set player storage data")
else
Logger.Error("failed to get "..tostring(EachOne.key)..": "..r.errorMessage)
end
end)
ガチャコンプデータを更新する処理は以下の通りとなっています。
更新前に再度最新のデータを取得してデータが無いか再確認しています。
vci.vc.storage.Get(key,function(r, v)
-- ストレージからデータ再取得し、既にデータがあるかを判定(ある場合更新不可)
local canUpdate = false
if r.isSuccess then
Logger.Info("key found. key:"..key.." value:"..tostring(v))
local s_name = v
if s_name == "" then
canUpdate = true
else
Logger.Info("UpdataGachaToStorage:[Get] "..key.."is filled by "..tostring(s_name))
end
else
if r.errorType == vci.vc.storage.errorTypes.KeyNotFound then
Logger.Info("UpdataGachaToStorage:[Get] key not found")
canUpdate = true
end
end
-- 更新可の場合更新する。更新後再度すべてのガチャコンプデータを取得する
if canUpdate then
vci.vc.storage.SetWithTag(key,name,StorageTag.GachaNumber,function(r, v)
if r.isSuccess then
Logger.Info("set storage data:"..tostring(key))
getGahcaFromStorage()
else
Logger.Info("UpdataGachaToStorage:[SetWithTag] failed to get "..key..": "..r.errorMessage)
end
end)
end
end)
注意点
今回はユーザーごとにkeyを分けてストレージに登録しましたが、ストレージは登録上限が1000件までという制限があるため、1000名を超えるユーザーの情報は登録できません。(実際には他のデータも保存してるため今回の場合は実質900人くらいまでです)
また、ストレージの取得・更新には数秒かかることがあるため、複数のVCIで同タイミングで処理が走ると整合性が取れない場合があります。
例):
1.ユーザーAがガチャVCIで数字①を引く
2.ユーザーBが別のガチャVCIで数字①を引く(1との間隔は1秒くらい)
→本来ユーザーAが引いたという情報が優先的に保存されるべきであるが、ユーザーBが引いたという情報で上書きされてしまう。
根本的な対策は思いついてはいないです。なにか有効な対応策があれば教えてほしいです。