質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

NoSQL

NoSQL(not only SQL)は、リレーショナルデータベース管理システムとは異なるデータベースシステムを指す言葉です。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

1868閲覧

Firebaseでランダムマッチング機能を実装したい

intenseG

総合スコア34

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

NoSQL

NoSQL(not only SQL)は、リレーショナルデータベース管理システムとは異なるデータベースシステムを指す言葉です。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

3クリップ

投稿2018/10/16 13:01

編集2018/10/17 13:59

前提・実現したいこと

開発ツールはUnity(2018.2.2f1)言語はC#での開発を前提とさせて頂きます。

現在、スマートフォン向けの囲碁のオンライン対戦ゲームを制作中なのですが、NoSQLデータベースのFirebase Realtime Databaseを使ってランダムマッチング機能を実装していて、本当にこのデータベース構造でいいのかと疑問に思いました。

  • 対局室一覧(Games)
"Games" : { "dummyGame1" : { "blackUser" : { "achievement" : "第1期 最強位戦 優勝", "name" : "dummy1", "rating" : 3500.0, "strength" : 10 }, "boardSize" : 9, "mainTime" : [ 180.0, 180.0 ], "moveList" : [ { "color" : 0, "isKo" : false, "koIndex" : -1, "moveCount" : 1, "putIndex" : 40 } ], "result" : "Draw", "spectators" : [ "dummy3" ], "state" : "Finish", "whiteUser" : { "achievement" : "第1期 最強位戦 準優勝", "name" : "dummy2", "rating" : 3400.0, "strength" : 10 } }, "dummyGame2" : { ... }, "dummyGame3" : { ... } }
  • 登録ユーザ一覧(Users)
"Users" : { "dummy1" : { "achievement" : "第1期 最強位戦 優勝", "name" : "dummy1", "rating" : 3500, "strength" : 10 }, "dummy2" : { "achievement" : "第1期 最強位戦 準優勝", "name" : "dummy2", "rating" : 3400, "strength" : 10 }, "dummy3" : { ... }, "dummy4" : { ... }, "dummy5" : { ... } }
  • 対局待ちユーザ一覧(Waiting)
"Waiting" : { "dummy4" : { "boardSize" : 9, "rating" : 3000 }, "dummy5" : { "boardSize" : 9, "rating" : 2800 }, "dummy6" : { ... }, "dummy7" : { ... } }

QiitaのFirebase記事やteratailの質問など一通り読みましたが「これだ!」という内容のものが見当たらず。

質問したいポイントは以下の3つです。

  1. 対局待ちユーザが2人以上、かつレーティング差が400未満のときに対局室一覧に入れて対局開始!という流れで構成していますが、APIの呼び出し回数が多いのではないか(※対局待ちユーザ一覧から相手のユーザIDを参照 -> ユーザ一覧からそのユーザIDを検索して対局室一覧に書き込むため)
  2. Games/gameIdツリー以下(今回の場合、gameIdはdummyGame1)のmoveListが着手情報ですが、gameIdツリー以下を呼び出すたびにmoveList以外も読み込まれるので分離したほうがいいのではないか
  3. 同じくGames/gameIdツリー以下のmainTimeは残り時間情報(float配列)ですが、着手情報に含めてしまったほうがいいのではないか

この3つでかなり頭を悩ませています。皆さんならどういう構造にするのかを教えて頂けると非常に助かります。よろしくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

ゲームの開発経験がないので確たることは言えませんが、その前提でお読みください。

DB構造を考える前に気になったことが、マッチングはトランザクションを組む必要があるのではないか?ということです。
マッチングを各プレイヤーのクライアントで行うのだろうと想像しますが、A~Cの3人がいて、
A ⇒ B
B ⇒ C
とAとBがそれぞれの端末でマッチングをかけていたとします。
このとき、AとBのwaiting状態は同時に変更されなければいけませんし、BとCのwaiting状態も同様です。
A ⇒ Bが先に成立すれば、B ⇒ Cは失敗させる必要がありますし、
B ⇒ Cが先に成立すれば、A ⇒ Bは失敗させて、Aはまたwaiting状態のプレイヤーを探しに行く必要があります。

RealtimeDatabaseはトランザクションがあまり得意ではありません。
トランザクションは1つのノード下にしかかけられず、トランザクションが失敗するとリトライするのですが、
リトライの都度トランザクションをかけたノード配下をreadするので、利用料が跳ね上がります。
仮にご質問に示されている構造を採用したとすると、Waitingノード配下すべてが何度もreadされるということです。

"Waiting": { "playerA": { // 対局を開始するにはここと }, "playerB": { // ここを同時に削除する必要があるので // 上位のWaitingノードにトランザクションを張るけど }, "playerC": { // 他にも待ちプレイヤーは沢山いて }, "playerD": { // トランザクションが失敗する度に }, "playerE": { // Waiting配下がすべてreadされて }, "playerF": { // お金もかかるし性能も落ちる ... }

トランザクションを使わずに、rulesの.validateを上手く書けば、似たようなことはできそうです。
ここまでの話は、こちらのQiitaをご参照ください。
※もし質問を立てる前に読まれていないなら、この後を読む前にお読みになってもいいかもしれません
※RealtimeDatabaseのrulesについては公式ドキュメントを読まれるとよいでしょう

というわけで、私でしたらRealtimeDatabaseではなく、Firestoreを採用します。Firestoreでしたら、ドキュメント単位で複数のトランザクションが張れます(上の例ですとplayerA: {} 配下とplayerB: {} 配下のみ)。
ついでにFirestoreだと、対戦相手の検索クエリもRealtimeDatabaseより強力です。レーティング差400以内のwaitingユーザーを1クエリで取得できます。

・・・という前提をひっくり返すお話を最初にして申し訳ないです。
さて、いやそれでもRealtimeDatabaseを使うのだ、頑張ってrulesを書いて疑似トランザクションも実装するのだ、というのでしたら、以下のような構造にします。

まず先に紹介したQiita記事にも書かれていますが、RealtimeDatabaseのデータ設計は「画面から」です。
「作る画面」で必要なデータを、1つのノード配下に置きます。

まず「対局相手検索中」画面を考えます。
対局相手検索中画面で必要なデータは、「待ちユーザー」「ユーザーのレート」なので以下のノードを作ります。

"Matching": { "user11": { // ユーザーid。番号が飛んでいるのはログインしてないユーザーがいることを想定 "rating": 3000, "wating": false // 対局中 }, "user20": { "rating": 3100, "wating": false // 対局中 }, "user36": { "rating": 2500, "wating": true // 検索中 }, "user44": { "rating": 2900, "wating": true // 検索中 }, ... }

このノードに、ratingフィールドをOrderByしてプラマイ400のユーザーを取得し、さらにクライアントでwating == trueで絞れば対戦相手候補は取れるかなと。
※ちなみにFirestoreならクエリ時点でwating == trueまで絞れます

うまいことマッチングできたら次は対局画面です。
対局画面にはユーザー名とレーティングと称号を表示させないとな、じゃあユーザーノードが要るな。

"Users": { "user00": { "name": "ユーザー00", "rating": 1200, "achievement": "第1期 最強位戦 優勝" }, "user01": { "name": "ユーザー01", "rating": 1000, "achievement": "新参" }, ... }

対局の内容も記録しないといけないな。対局ノードがいるな。

"Games": { "game000": { // 対局id "black": { "user11", "rating": 3000, // 対局開始時のレーティングとか(記録しておきたければ) }, "white": { "user20", "rating": 3100, }, "moveList": [], // 詳しくないので中身省略 "spectators": ["user50", "user67"] } }

対局者と観戦者は、Games/game000配下を1回読み取り対局者のidを取得、
Users/user11Users/user20を読み取り対局者情報を取得、
その後Games/game000/moveListをListenして盤面にリアルタイムに反映。
readの無駄もなく悪くないのではないでしょうか。

ここまでで質問ポイントの1.と2.は解決したと思います。
3.残り時間情報(float配列)ですが、着手情報に含めてしまったほうがいいのではないか
は、その通りだと思います。
対局者と観戦者は(このデータ構造ならば)Games/game000/moveList配下をListenしているので、刻々と変わる残り持ち時間はmoveList配下に持つべきでしょう。

観戦者リストをリアルタイムに画面に反映したければ、Games/game000/spactatorsもListenする必要があります。
もしGames/game000配下に「別の画面では使わない大きなデータ」がある場合は、データ構造の平準化を考えなければいけませんが、今のところなさそうなので、ここでは例を示しません。

RealtimeDatabaseのデータ設計は「画面から」です。
画面で使うデータをまるっとノード配下に収める。
ネストが深くなりそうだったり、ある画面では使って別の画面では使わない大きなデータがでてきたら平準化する。

しかしあくまで、私は今回の場合はFirestoreを使った方がいいんじゃないかなぁと思います。

投稿2018/10/20 15:40

gekijin

総合スコア187

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

intenseG

2018/10/21 05:23

丁寧かつ詳細な回答に感謝です...! > というわけで、私でしたらRealtimeDatabaseではなく、Firestoreを採用します。Firestoreでしたら、ドキュメント単位で複数のトランザクションが張れます Firestore、、そうですよねー。(Unity用SDKが無い><) functions噛ませれば出来ないことは無さそうですが、Unity用SDKが出るかもしれないと思うと手をつけられなくて。 > RealtimeDatabaseのデータ設計は「画面から」 これまで「画面から」というイメージができていませんでしたが、「作る画面に必要なデータから連鎖的に逆算する」という手法があると分かった瞬間、ピンと来ました。ありがとうございます! > しかしあくまで、私は今回の場合はFirestoreを使った方がいいんじゃないかなぁと思います。 そう思います。(Unity用SDKさえ出れば...) 現段階ではRealtimeDatabaseで開発を進めますが、もし開発中にFirestoreのUnity用SDKがリリースされたら即移行を考えます。 それでは、gekijinさんをベストアンサーに選ばせて頂きます。ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問