ソーシャルゲームのクライアントエンジニア入門以前を読む
この文書の対象読者としては「Unityでゲームを作る、というのは分かるけどサーバサイドが絡むソーシャルゲームは作ったことがない人」を想定しています。 1. APIサーバとの通信(Unityでの留意点)
2. UI画面の優位点
アウトゲームのUI周りのprefab運用、UIライブラリに応じた設計 3. リリース後の運用
HTTP1.1
HttpClient(.NET)もつかえはする
ゲームではGETとPOSTしか使っていないらしい?
PUTとDELETEはUnityバージョンによって実装が怪しかったことがあった気がします
code:UnityWebRequestのサンプル.cs
string userStatus = JsonUtility.ToJson<PlayerStatus>(someData);//こうして、リクエストボディ(本文)を文字列にします
var request= UnityWebRequest.Post(url,userStatus);//Postの第二引数がリクエストボディの文字列です
request.SetRequestHeader(dic.Key, dic.Value);//こうしてリクエストヘッダを付与する
var p = request.SendWebRequest();//ここでPostアクセスする
//ここでpの完了を待つ
var responseCode = (int)request.responseCode;//レスポンスコード、200は成功。404は見つからない、401が認証エラー、503がサーバが混んでる、みたいなやつ
var responseHeaders = request.GetResponseHeaders();//レスポンスヘッダはこうして取得します。
var result = Encoding.UTF8.GetString(request.downloadHandler.data);//全然直感的じゃないですが、ここにレスポンスの本文が入ってます。
//ここでresultをまたJsonUtilityなどを使ってデシリアライズする。
UnityWebRequestにないので自作のラッパーをかく
複数回のPOSTを防ぐ
リクエスト自体にIDをふる
アクセストークンを毎回変えて一個前のトークンとレスポンス全文を保持しておくと、リトライで届いた通信は一個前のトークンから判断して一緒に保存したレスポンス全文をもう一回送信する事でサーバー内のデータを全く操作せずにリトライへの対応を簡単に行えます。
この日本語が難しいので分解する(PRするかも)
アクセストークンを毎回変えて一個前のトークンとレスポンス全文を保持しておくと、
サーバからクライアントに発行するアクセストークンを毎回変える
サーバで、一個前のトークン(tokenA)とレスポンス全文を保存しておく(A)
リトライで届いた通信は一個前のトークンから判断して一緒に保存したレスポンス全文をもう一回送信する事でサーバー内のデータを全く操作せずにリトライへの対応を簡単に行えます。
クライアントがリトライしてサーバに重複してリクエストが送られた場合(tokenAをつかったリクエスト)、サーバ側でtokenA - Aの対応が有るのでAを再送する。こうするとDBへのクエリを再送しなくてよくて便利
JsonUtilityを使うとUnityでJsonを扱える
Unity標準のJSONパーサーは型定義が分かっていればstructにスポンと入れてくれますが、任意形式のJSONには全く対応出来ないためサードパーティのパーサーを用いる必要があります
リアルタイム通信では、サーバサイドに永続化しないデータがある
非永続的なデータはUNETだったりPHOTONだったりMONOBITだったり自前のリアルタイムサーバで処理して、永続的なデータだけはHTTP1.1のPOSTやGETで通信します
10rps以上ではHTTPをつかわない
上で説明したようにHTTP1.1の通信はヘッダ情報などの様々な追加データが載っている為、FPSのキャラ移動など秒間10回以上更新されるリアルタイム情報に使うにはオーバーヘッドが大きすぎます。 なので、FPSゲームなどではリアルタイム通信を永続的(HTTP1.1)ではない普通のUDPやTCPベースで通信します。後述するPhotonなどがこの方式です。 現在においてはソーシャルゲームの通信暗号化をするのが一般的です。
レベル3:レベル2に加えて、request bodyやresponse bodyにAESなどの暗号化したJsonを渡す
UnityではSystemInfo.deviceUniqueIdentifierで取得できる
モバイルアプリとブラウザの連携など、cookieを直接渡せない時でも、毎回のやりとりにJWTを付与すれば、認証情報の引き回しが可能となります。 ブラウザの機能が内包されていて、外部との連携を行わない場合、Cookieを用いるのが楽で確実です
匿名ログイン
仮メールアドレスとパスワードを発行して、クライアントに暗号化して保持
ソーシャルログイン
時刻の扱い
Unityだと DateTimeOffset.UtcDateTime を使うことが多いです。
時刻同期イベントの基準時間を決める
デイリーイベント
ログイン時のAPIで実施する事が多い
途中中断は体験が悪いので、きりが良い画面で戻す実装にするとよい
みんなが寝てる4:00ごろに判定するとユーザー負荷が低い
サーバーでの時刻はAPIコールの時刻を引き廻すのがおすすめ
API callで複数の情報を保存したときに時刻が微妙にずれるとトラブル対策で手間が多くなる
基素.icon実際の時刻を保存しないほうが良いのか~
クライアントは必ず時刻を投げ、サーバーは時刻がずれすぎていたら弾くようにする
バージョンアップの誘導
クライアントがバージョンを贈り、最新かどうか判定するAPIを叩く
専用の [GET] /ios/version を使うより、httpのresponse headerで判定 することが多い
最新でなければダイアログを出す
大体の開発現場では、Application.version ではなく独自にAndroidとiOSで別のバージョンを内部で持っている気がします。
Application.version は一度appleやgoogle等のプラットフォーマーに申請すると、次の申請時には必ず数字が大きくなってないと受け付けてもらえません。ですが、審査入りした後に社内でバグを見つけた時などは審査中のバージョンはスキップしてバグを直したパッケージをリリースするべく動く事があります。この様な場合には、内部バージョンとApplication.versionを別にしておくと、サーバーと絡む内部バージョンをずらす必要が無くなり都合が良いです。
例:審査中バージョン(feature A + B)が1.1のとき、バグ修正バージョン(feature A)は1.2で出さないといけなくなってしまう
In App Purchace
レシート検証
IAPの結果のレシートを受け取って検証する
基素.iconめんどくさそう
サブスクは異常系が面倒
チュートリアル
最近はAppStore審査で、ゲーム開始後すぐにアセットバンドルなどのデータダウンロードさせるゲームはリジェクトされます。
チュートリアルだけシーンを分けて、アセットバンドルダウンロード無しでチュートリアルまでは遊べるように考慮しておく事が必須です。
楽しい解像度
インゲームの面白さや有利不利がアスペクト比によって変わるようだと、一気に考えることが増えてきます。r@2.2/manual/index.html Introduction | Device Simulator | 2.2.4-preview]
多言語対応
Unity標準のlocalizationライブラリが出そう
非同期通信のUI
サーバのレスポンスが連続してかえったり(画面遷移後に前の画面の結果が帰ってきた!)、10秒ぐらい帰ってこないとき(ユーザー「こわれた」)にゲームのUIを配慮する
クライアントアプリの環境わけ
開発者モード
デバッグログが使える
公開モード
やり方
ifdef
asmdef単位での切り分け
環境は現場によって様々
QAやテスターに遊ばせる環境の構築
InHouseBuiild、Deploygate
同一のバイナリで、接続先を切り替えられる仕組みを用意しておきましょう。
典型的な実装としては、クライアント起動時にまずサーバに自らのバイナリバージョンを送信してどのサーバに接続すれば良いか問合せ、サーバがそのバイナリバージョンに応じた接続先を返すという物です。 このような仕組みを用意しておくと、バイナリの接続先をApp Storeの審査中はステージング環境、審査通過後は本番環境へと切り替えることが出来ます。
この仕組みを用意していないと、新機能実装など大規模アップデート時の審査に、本番環境ではバイナリが起動しないという状況でApp Storeのバイナリが本番環境に接続してしまうとアプリがクラッシュし、100%審査に落ちてしまいます。
特にApp Storeでよく問題になる
審査通過後にバイナリを差し替えることは出来ません
メジャーなライブラリ
メジャーなミドルウェア
以下に挙げたものは、いずれもUnityとは独立したツールでデータオーサリングを行い、Unity側ではSDKを通じて各種アセットを再生するものです。
音声をオーサリングできる
プッシュ通知
必要な人に、必要なだけ通知
社内にプッシュ通知基盤があればそれにのる
無い場合はFirebase Analysticsと組み合わせたユーザ属性のフィルタ、ログイン頻度ベースのフィルタ、ゲーム固有のレベルベースのフィルタなどを駆使して、必要な事だけをプッシュ通知で送っていきましょう
サーバープッシュ
運営都合の通知
ローカルプッシュ
アプリ起動時のみ通知叶
ユーザ操作に基づく通知(例:放置ゲーの達成通知)
マスターデータダウンロード
Excelの表で管理で出来るようなデータ
サーバー側でも使いたいのでDBに保存する事が多い
必要なデータを都度ダウンロード
機種変引き継ぎ対応
離脱率低下施策
ナイーブな引き継ぎ実装はAppleの審査を通らない
ゲーム内にお問い合わせを送る画面を用意しておきましょう。これを用意しておくと、ユーザからのフィードバック頻度がだいぶ変わります
メンテナンスモード
トラブル時のアナウンスのためのシステム
起動時にアナウンス(FB/Twitterなど)に遷移できるようにする
このボタンを押せる状態を担保するため、タイトル画面ではユーザー操作が行われるまでは自動的にAPIサーバーと通信しない様にしておく
問い合わせフォームにユーザーIDやバージョン情報を供給出来ると色々捗る
クラッシュレポート収集
リリースのたびにすべての端末でクラッシュしないのを保証するのは無理
早期発見のためにクラッシュレポートを分析したい
自動化FWを導入する
ユーザーの行動を分析して、今後の運用や機能追加の方針を決めるための指標
DAU, MAU, DPU, ARPPU、継続率 など
デバッグ機能
時間ずらし
期間限定イベントのデバッグ
エクセルの表の調整は辛いのでこういうのが必要
サーバーや端末の時刻をずらすのは大変なので、実際の時刻との差をサーバーとクライアントで共有し、各々のプログラムはその差を加味した時刻を元にイベント発動などの評価を行う様に作る事で実現します。
リアルタイムとの差をサーバーが保持する領域に秒数の数字等で保持し、クライアントはログイン時にサーバーから差のデータを取得してそれを元に挙動を調整する形が無難かと思います。
ゲームデータコピー
特定の状況でのバグを再現する
プレイヤーの状態を開発環境にインポートする