simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

CubeWalk:ワールドチケットの仕組み

実装メモです
整理出来たら消します。

ワールドチケット?

世界を編集できる権限をユーザーに付与したいという要求に対して、出した答え

ユーザーのインベントリに ticket カタログに所属するワールドチケットアイテムを買ってもらい
そのチケットには有効期限が設定されていて、購入から時間が経過すると消えていくしくみ

スタックできるので 2個買うと、1チケット消費され、さらに有効期限が続くという考え方から始まります。

実装試験してみると、購入からの有効期限となっており、2チケット買っても、消費される瞬間はほぼ同じ
残念ながらスタックすることはできないため、出端から計画は崩れました。

既存のキューブとのバッティング

PlayFab のアイテムはカタログに所属しています。
いままでは cube カタログしか使ってきませんでした。

チケットは ticket カタログ
キューブは cube カタログ

にそれぞれ所属するとします。

カタログを分けて扱うようにするので、cube カタログのものと ID がバッティングするものを作らない限りは影響なしと思いたい
既存の各種クライアントコードにてカタログバージョンチェックを入れる必要がありそうだ

実際 ID 重複が許されるのかも見ると→重複は可能でした

ので、ID決定で困ることはないですが、クライアントのシステムにてカタログ所属考慮が抜けているとロジック破壊が起きるので
インベントリの情報を扱っているところはすべてカタログで分岐を書かなくてはならない 書いてなかったら直さなければならないことになりました。

準備として修正を進めます。→修正完了

いきなり編集権利を買うのではなく、交換チケットを買ってほしいときに権利と交換する案

チケットの種類は worldチケット 1つ、買うときにカウント消費アイテムとして、永続化して保持してもらう
新たに、ワールドごとの編集権利アイテムというものを購入不可能なものとして、world チケットと交換可能で作る
ワールド編集時に権利アイテムが無ければチケットと交換、これをユーザーに確認してサーバー処理で安全に交換する

良い点:
チケット購入時にワールドを確認する必要がなくなる
買ってすぐに使わなくても価値は下がらない
編集開始時にチケット交換をたずねるだけになれる
権利の重複フローをシステムで消せる

どうやって購入?

ストアという概念を定義し、システムメニューから選べるようにします
できれば、ユーザー特定情報がユーザーから申告できるように、プロフィール画面を作る→名前で検索できるのでしばらくはいらない

ストアについて記事を探して実装イメージを固めてみます

ストアとリアルマネー

なるほどストア機能は仮想通貨による価格設定などが良さそうですね。
ただ、仮想通貨とリアルマネーのトレードをして管理するようになると、仮想通貨でゲーム内でいろいろなアイテムを購入できるものにした場合
これは日本国では資金決済法の適用が義務付けられるので、例えば同人誌即売のようにアイテムを一意に決めて売るとは勝手が違うため、事業者登録が必要になります。
なので、このゲームは複雑にならないよう、仮想通貨によるアイテムの販売、そのための仮想通貨の購入は避けます。

そこで、直接ワールド編集権利のみを、このゲームのサーバー運営代として支払ってもらう仕組みにします。
権利だけを得るチケットをリアルマネーと交換するだけ
simplestar-tech.hatenablog.com
こちらの記事が参考になりそう。

メニューにストアを作り、そこから購入したチケット一覧を並べて、所持数を表示し
チケットと枚数を指定したら購入 url が開き、購入後に購入しましたボタンを押したら
成功→チケット数が増える
のフローを経て、ストア画面が更新表示される

という UI とロジック動くところを作ってみます。

ショップUIを新規作成

現在は Esc キーでシステムメニューが出る 今はそこからフライトカメラモードとゲーム終了・再開を押せる状況
このメニューにタブ切り替え機能を用意して、そこで新しいコンテンツビューを表示できるようにします

アイテムの表示

やり方としては catalog と store の両方の情報を取り出し、インベントリと合わせて
各種アイテム情報と所持数、購入ボタンを配置することになる

所持数は…購入画面にて + ボタンを押すと、所持数+ いくつ という表示で行おうと思います。
ひとまずショップ内容の表示まで手を動かしました。


アイテム購入画面

新しくダイアログを表示して、既存の所持数 + ボタンで購入するアイテム数を制御し
そのうえで購入ボタンを押すと、アイテム数が増える

なんと、キューブアイテムは 2 個で 1キューブなので、価格を倍額で表示しておこう

現在の所持金も表示しなければならないことにも作っていて気が付けました

f:id:simplestar_tech:20200724222139p:plain

アイテムの購入ボタンを押すと、所持金、個数、可能な購入個数などの制御が正しく動くところまでできました。
あとはキックされた処理でアイテム購入を行う部分を実装することになりそうです。

仮想通貨でのアイテムの購入処理

リアルマネーとは異なり、こちらは簡易な手順なので、試験することろまで進めます。

実はアイテムは個数指定で購入することができないのが PlayFab の作りでした。
ワークアラウンドとしては、アイテム数を増やしたりして、サーバー側でうまいこと仮想通貨を減らしてほしいとのこと

これは AzureFunction を増やして対処する

同じように、現実のお金の場合もアイテムは一つずつしか購入できないのか調査してみます。

PlayFabClientAPI.StartPurchase

リクエストに Quantity を指定できるのでうまくいっている様子
しかし?

RM 以外の仮想通貨も使えるのだろうか?

はい、その通り!

仮想通貨での動作を確認したときの記録がこちら

スロットを持たない新規アイテムの購入フロー

アイテムのうち、インベントリのスロット番号を持つものがありますが
新規購入をするとこの値が設定されていないので、いつ設定するか考える必要があります。

わざと所持していないアイテムを買ってみて動作をみてみましょう。

この考えは正しく、現在スロット操作が不能になる
理由 inventory item has no custom data slot

アプリの処理として、これを生成しているコードは AzureFunction
処理としては既存のインベントリから取り出したアイテムが Slot 指定を持たないことに疑問を持ち、不正リクエストとして返している
ただ、それは不正なリクエストではなく、サーバー側の不正状態といえる

今後、スロットに所属しないアイテムを許可することを考えると、ロジックを見直してみる必要がある
無視してもよさそうなので、無視

ここでスロットを持たないアイテムにスロットを追加するのはやめた。
逆にリクエストに問題が無ければ受け付ける仕組みとなっていたため、インベントリ情報をローカルで表示する際、アイテムにスロット番号が指定されていない場合は
ローカルにてスロット番号をリクエストすることで解決できるゴールが計算できる

この計算が正しいか確認する

残念ながら、クライアントからのリクエストは受け付けられなかった。
カスタムデータを持たないアイテムに対するスロット指定はもともと受け付けないようにしていたからである。

ただ、これからはスロット指定を持っていないアイテムについてもスロットを指定できるようにするため、この問題は既存ロジックの書き換えで対処できることになる
修正後

破壊的変更なのでメンテナンスして調べる
そろそろ staging 環境など用意した方が良さそう

アイテムは置けるので、スロットの反映は許可されることになった
f:id:simplestar_tech:20200725133958p:plain

スロットをはじめから持たないキューブが得られた時、これをクラアントから空いている箇所に設定したはずが
どういうわけか重複するエラーを作ることができた。
あとから設定されたものでスロットが上書きされるため、ローカルの見た目はアイテムが消えたように見える

再現しなくなってしまったが
サーバー側操作でわざとスロットを重複して与えることができるので、その場合に空いている場所へ退避してからリクエストするクライアントになるよう処理をくわえることにした。

動作に問題はない

リアルマネーによるアイテムの購入

仮想通貨に関しては現実資金の決済なしにアイテムは仮想通貨によって購入することができました。
実際のお金を必要とする場合は、確か、購入用の Web ページを表示するところからです。

実際に資金の移動と購入が行えるかチェックしてみます。

行えました。
返金は 180 日以内なら可能なので、試験的にお金を使ってもすべて取引は無かったことにできる様子
少しテストの障壁が低くなった

PayPal を選んでみたが、手数料は約 13% ほどとられるみたい

権利アイテムの定義

お金で買ったのはチケットなので、これは回数によって消費する券

これとは別で、編集したいワールドにて編集操作を行ったら、サーバー側で権利アイテムを持っているかチェックし
これを持たない場合にチケットを利用するか聞くフローが作られ
もしチケットも持っていない場合はチケット購入画面を開くフローが作られる

というのをやります。

最初はワールドごとの権利アイテムを作り、続いて、サーバー側で編集したいワールドの権利アイテムを所持しているかを見つける処理から進めてみましょう。

world カタログの、リクエストされている worldName と一致する ItemId のアイテムをインベントリに持っている場合は、権利アリ
そうでない場合は権利なしを返すことで作成できました。

権利なしを受け取ったら、権利購入へ進める

キューブ操作の戻り値を正しく処理することを今まで行っていないはずなので、その部分を見ていく
この点を確認すると、権利を持たない場合の戻り値を正しく受け止めることができるようになるだろう

加えて、ALB (Amazon のアプリケーションロードバランサー)の拒否戻り値を使って、ユーザーにメンテ中のサーバーであることを知らせるなど
はじめて、ユーザに理由を添えて示すことができるようになるはずである

できますね。
ステータスコードで分離してみます。

キューブ編集中の状態でエラーが起きるので、操作不能にしてからメッセージを表示し、閉じたら元に戻すようにしたいところ

操作不能、復帰処理

入力ステート管理があり、このステートを切り替えるで完了
今までシステム操作ステートに入ったらシステムメニューが表示されるような仕組みだったが、明示的に Esc キーを押したときだけ表示に切り替えた

権利がないときの UI

新しい独立パネルを作り、現在の保有チケット数を表示
権利を取得したい、チケットを使いたいワールド名を示して、一日編集権に交換することを確認する

確認できたら、編集権を得た画面になり、パネルと閉じた後は 1日だけ利用できるようになる
アイテムは勝手に 1日経つと消費される

新規実装ですね、パネルから作っていきます。

持っているチケットアイテムが列挙されるパネルが、編集権を持たないワールドを編集したときに現れるようになりました。(試験目的で同じチケットを3つ並べてます)
f:id:simplestar_tech:20200726220433p:plain

チケットアイテムを一つも持たない場合は、ショップへ移動するフローが構築されることになってます。
f:id:simplestar_tech:20200726220827p:plain

チケット購入ページへの遷移

無いので作ってみます。

UI操作をコード化して、ショップのチケットカタログを開くところまでは作れました。

チケットを消費してワールド編集権利を得る

バンドル化するのも良いと思いましたが、チケットとワールドごとの権利は 1対多なので
新しく AzureFunction を作ろうと思います。

構想したものが連休中に終らず…タイムアップです

これは新しい Azure Function ですね

ゴリゴリ実装してコード整理、動作確認完了!

決済操作した後、オーダー番号で確認フローを進めない限り、クラアントからの支払いは完了しないことがわかった。

あとは権利を手にしたときに、サーバーが眠ってたら起こす仕組みを作れば、課金まわりの一通りの機能がそろう

バックアップ時に残り日数を減らし 0 になったら寝る

残り日数とは?というものですね。
概念というよりは、ワールドデータサーバーごとに参照、変更できるゲーム内で一意の値のことですね。

一つのキーに複数のワールドデータを詰めると、競合で消えてしまうデータができてしまうので
一つのキーに一つのワールドの活動限界日数を記録することにします。


000 : 1

Set で null 文字を渡すと消せることから、バックアップで 1 → 0
0 → null としてカウントダウンした値を Set する仕組みにし、 null のときはバックアップしない(ワールド寝てるから)

ユーザーが編集権利を購入したとき、現在のワールドチケットの有効稼働日数を Set する
セット前に null であることを確認したら、ワールドを呼び覚ます AzureFunction を実行する

で、いけると思っている

作ってみます。。。。

ワールドを寝かしたり、呼び起こす AzureFunction はできた。
これを他の AzureFunction 実行時にも呼び出せるか調べて、書き換えてみます。

タイトルデータ internal が PlayFab にはあってそうですが、その特性を調べていきます。

最大で 15分ほど遅れがあるそうですが、全然大丈夫
一日ごとのバックアップ時に 1ずつ減らして 0 → null のときに、サーバーを眠らせるようにして、動作確認とれました。

権利購入成功時に、サーバーが寝てたら起こす

権利購入時には TitleInternalData で、ワールドの稼働日数を確認し null だったときに
start インスタンスでサーバーを起こす

その後 TitleInternalData にはワールド名をキーにチケットの日数を与える

動きました!!
ワールドチケットの仕組み、最初に思い描いた仕様を形にすることができました。

とまぁ、ゲームの一機能を作るって、だいたいこんな感じですね。
めっちゃつまらない&苦痛の連続でした

AWS:LambdaにAuthorizerを設置してからAPIGatewayを公開する

前書き

インスタンスを start するだけで docker コンテナが起動して、機能し始めるというのは用意できてます。
あとはこれを PlayFab から HTTP リクエストで呼び出すだけ、なのですが、ここで公開 API でありつつも
認可の仕組みありきという API Gateway と接続した Lambda のお仕事にするので、そのあたりの具体的な手順と概念について記録します。

API Gateway

Lambda の試験を終えて、外から呼び出そうとトリガーに API Gateway を追加します。(ウィザードで新規作成)
f:id:simplestar_tech:20200719123820p:plain

プロトコルを HTTP にして、Authorizer 作成をします。

発行者URLに AWS Cognito の次の形式の url を書いて渡します
https://cognito-idp.us-west-2.amazonaws.com/us-west-2_XXXXXXX

一緒に Client ID を求められるので、 AWS Cognito でアプリクライアントを新規登録して、それの ID を打ち込みます。

これで Authorizer 設定完了

AWS Cognito

作るだけです。
昔やった
simplestar-tech.hatenablog.com

で、ユーザーのサインアップを許可しますか? 管理者のみがユーザーを作成できます として、勝手にユーザー追加されないようにします。

で、管理者としてユーザーを追加した後
Amazon Cognito ドメイン名を空いている prefix で取得し、次の通り、リダイレクトURL付で URL を作って
docs.aws.amazon.com

ログインの画面を開きます

あー実は 有効な ID プロバイダを Cognito で作らないとダメ(次の画面の設定にする)

f:id:simplestar_tech:20200719124633p:plain

管理者が追加した e-mail ユーザーに初回パスワードが送られてくるので、このログイン画面で初回パスワードを入れログインし
すぐに新しいパスワードに切り替えて、認証成功させる

上記画像のリダイレクト url とリクエストする リダイレクト url が一致しないとエラー画面になるので、しっかり合わせること
一つでも間違えると、理由なきエラー画面なので、なんとかネット上のやりとりで、url に違いがあるとダメということにたどり着けた

これでログイン画面の表示と、正しい email と パスワードでログイン成功となる
成功すると、リダイレクト後の url に AuthorizationCode つまり code=~~のクエリ文字列があるので記録する

Authorization Code

さっきリダイレクト url で code= で得たものを
/login
と同様に
/oauth2/token
のcode=AUTHORIZATION_CODEに設定して、次の通り POST リクエストを送る

docs.aws.amazon.com

サンプルでいうところのこれ

POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token&
Content-Type='application/x-www-form-urlencoded'&
Authorization=Basic aSdxd892iujendek328uedj

grant_type=authorization_code&
client_id=djc98u3jiedmi283eu928&
code=AUTHORIZATION_CODE&
redirect_uri=com.myclientapp://myclient/redirect

ああ、 Basic の次に続くやつは Baes64 文字列で、変換元は "client_id:client_secret" これは Cognito の管理画面で手に入る

得られる json を見ると長ーい文字列で

"id_token" が得られるので、これを記録
あと、Cognito でトークンの有効期間が設定できて、最長で 10年設定できる(今日から10年に設定した)

API Gateway を呼ぶ

これで設定した Authorizer にマッチする管理ユーザーの 10年有効な id_token を得られたので、Authorizer 設定で指定した通り Authorization ヘッダーに仕込む

自分はこれで認可されて APIGateway の先の Lambda をたたくことができた

追記

実は id_token は 3600秒後にかならず有効期限が来て使えなくなります。
一緒に refresh token が得られていると思うので、

grant_type = refresh_token
refresh_token = それ

として oauth2/token API をたたきます

更新トークンは 30~最大設定で 3650 日、有効なので

また id_token を 1時間で有効期限付きで取得できます。

自動化したいなら更新トークンで id_token を得て、その id_token で API Gateway の Lambda を実行する、といった感じですね


参考:こちらはトークンオーソライザー使ってますね(過去の自分)
simplestar-tech.hatenablog.com

課金するとデータサーバーが起動して、お金が尽きたら勝手にデータサーバーを止める仕組みづくり

前書き

長いことはじまりの世界のデータサーバーは一つ
AWS Elastic Beanstalk で環境を用意して運用して半年~1年が過ぎようとしてます

ここでモチベーションがあがる機能追加案がありまして、世界を広げるという操作をプレイヤーが行えるようにする
具体的にはプレイヤーがお金を払い、サーバー運用費を越える額がたまった後、それをトリガーに一つとなりのワールドのデータサーバーが立ち上がり
これを編集できるようになるというものです。

ec2 インスタンスに docker 環境を作る

こちらの記事を参考に手を動かしてみます。
Amazon Linux2にDockerをインストールする - Qiita

AWS ec2 インスタンスを作る
ssh 接続はセキュリティーグループで自宅の IP アドレスからのアクセスからのみ許可
秘密鍵を自身の .ssh/aws フォルダに配置して、これを指定して ec2-user というディフォルトユーザー名で、出来上がった ec2 インスタンスDNS アドレスにたいして ssh 接続を成功させます。

この時点で、自宅と同じ IP アドレスであり、インスタンス作成時に作った自分しか知らない秘密鍵を持ち、これを指定した者だけが
この ec2 インスタンスに入って自由に操作できるようになります。

とりあえず最新状態にして docker エンジンのコマンドを打てるようにしてみましょう

sudo yum update

で y 押す

docker をインストール
sudo yum install -y docker
インストールした docker をスタート
sudo service docker start
sudo 打たなくて良いように ec2-user アカウントを docker グループに追加(※有効化されるのは次のログインからだから注意)
sudo usermod -a -G docker ec2-user
インスタンス再起動時に docker コマンドが打てるよう自動起動を有効
sudo systemctl enable docker
docker-compose 実行ファイルをダウンロードして、実行権限付与
sudo curl -L https://github.com/docker/compose/releases/download/1.26.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

動作確認
docker --version
docker-compose -v

どちらもバージョン番号が出力されれば ec2 インスタンスを起動して Amazon Linux 2 にて docker 環境整えるはクリア

docker 環境に ECR から pull してアプリの動作確認

まずローカルで docker image を作成します。

FROM golang:1.14 AS build
RUN apt-get update && apt-get install -y \
    python3-dev \
    python3-pip
RUN go get -u github.com/aws/aws-sdk-go
WORKDIR /app
COPY . .
RUN GOOS=linux CGO_ENABLED=0 go build -o /tmp/cubedataserver application.go

FROM alpine:3.12
COPY --from=build /tmp/cubedataserver /bin/cubedataserver
ENTRYPOINT ["cubedataserver"]

この Dockerfile には go build した成果物をビルドに必要なライブラリのないスリムな alpine image 上で実行 という
コンテナを小さくするアイディアが記載されています。

ecr にログインして、docker push して、IAM で ecr リソース指定アクセス権限ポリシーをつけておけば docker pull できました。
いざ、ローカルテストと同じように docker コンテナが機能し、セキュリティーグループで自宅から指定ポートのインバウンド設定すれば
自宅からキューブ情報の操作ができること確認しました。(アプリ層でトークンが正しいこと確認してます)

lambda でインスタンスを stop, start する

できれば ALB のルールもつけたり、消したりしてほしい

参考にした記事はこちら
dev.classmethod.jp

docker run するときに --restart=always オプション付けていれば、インスタンスの起動完了と同時にサーバーとして機能をはじめました。

IAM ロールで、指定リージョンの指定インスタンスIDに関してのみの start, stop ポリシーを付けたので、暴走する関数にはならない
lambda を実行すれば機能したので、これを PlayFab から呼び出せるので、すべての技術課題は解決した

完成はしてないが、あとは調査いらずの作業だけになったぞ

参考
PlayFab Cloud Script → API Gateway → Lambda(python) → EC2 start, stop

CubeWalkGame:AIが使う脳内コンテキスト

前書き

自作ゲームの開発ネタです。
考えていることを書くと、頭が整理されてコードに落とし込める気がして

最近の進捗絵はこんな感じ

f:id:simplestar_tech:20200616212308p:plain
木のぼり

動画はこちら

作りたいのはワールドロジックとその共用

世界がどうなるかの先行きを小さなロジックを使って取り扱いたくて
キューブのデータをある場所を中心に切り取ってコピーを受け取れる方法がこちら

        /// <summary>
        /// 対象のチャンク、チャンク内のキューブ位置インデックスと格納先配列を渡すと、データを埋める
        /// </summary>
        /// <param name="chunkInt3">ベースとなるチャンクの位置インデックス</param>
        /// <param name="chunkCubeInt3">ベースチャンク内での中心となるキューブの位置インデックス</param>
        /// <param name="radius">ppCubeデータの半径キューブ数</param>
        /// <param name="ppCubeData">埋めてもらうキューブデータ配列</param>
        static unsafe void FillAroundCubeData(Vector3Int chunkInt3, Vector3Int chunkCubeInt3, int radius, byte** ppCubeData)

radius * 2 + 1 の 3乗要素数の byte x 4 配列を確保して渡すと、指定した位置を中心にローカル最新データを埋めてくれるもの

うまくいきまして、AIが周囲のキューブの状態から未来予測を行い、木が育つことをサーバーに報告すると
サーバーが検算して合格したら、本当に木が育つようになりました!

最近の様子を記録したので載せておきますね

Python:コマンドの実行と標準出力文字列の取得+aws cli 呼び出し例

aws cli を呼べる環境なら、次の pthon で動作中の task arn を for 文で処理できるよ

import subprocess
import json
from typing import Union, List


def callCommand(commands: List[str]) -> str:
    """
    コマンド呼び出しと標準出力文字列を返す
    """
    cmd = []
    for command in commands:
        cmd.append(command)
    subprocess.call(cmd)
    output = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    return output.communicate()[0]


def main():
    cmd = []
    cmd.append("aws")
    cmd.append("ecs")
    cmd.append("list-tasks")
    cmd.append("--region=ap-northeast-1")
    cmd.append("--cluster")
    cmd.append("clustername")
    cmd.append("--service-name")
    cmd.append("servicename")
    jsonS = callCommand(cmd)
    for taskArn in json.loads(jsonS)['taskArns']:
        print(taskArn)


if __name__ == "__main__":
    main()

CubeWalk:自分で遊んでダメ出し

ずっと未公開だったメモ記事
公開して完成時に当時を振り返ります。

前書き

最近のチート対策のサーバー側の複雑な CustomData 計算は本当はやりたくなかったんですけど、こまめに記録しながらなんとか動き始めました。
たとえるなら、まだごみが詰まった歯車が回りだした状態で、ちょっとならし回転させて、不純物を取り除いていこうと思います。

今回は機能追加ではなく、動かして気になるところをつぶしていく回です。
未来の自分に仕事をまかせて、とりあえず何でも書いてみようと思います。

ゲーム開始までのながれがない

いきなり機能追加となりましたが、確かに起動に時間を要するうえ、変に操作されるとすぐに壊れてしまうので
今一度ゲーム開始までのながれを確認してみようと思います。

アイテムの増減は一瞬で行われてほしい

これについては前々から気づいていて、サーバーの結果をもらってからアイテム数の増減には問題がありますよね。
そこで、ローカルでは一瞬で操作結果を反映しつつ、サーバー側にはインターバルを組んでアクションを送信してもらいたいものです。
ローカルではアクションをキューに詰め込んで、順にサーバーにリクエストするようにしてみます。

これなんですが、レスポンスが来るまで破壊アニメーションとしようとしてます。(きっとうまくいく)

そろそろ音が鳴ってほしい

UI操作をしているときに、ピンポンと音が鳴ってくれると嬉しいのですけど

わかる

番号のテクスチャつまらなくなってきた

キューブのマテリアルをそろそろ作りこんでほしいですね

こちらは
simplestar-tech.hatenablog.com
こちらのシェーダを作って対処しました。

オンライン通信でキューブアクションを同期

互いにアクションを交換し合い、世界の共有を頑張ってほしいです。

サーバー側の関数見直しで、一緒に考えてみます。

CubeWalk:ゲーム開始までの流れをどう実装するか案

これまた下書きのままずっと放置されていた記事
Unity のコンポーネントが Start や Awake で好き放題やるのは、引き継いだり後から見直したときに
全体で何しているかわからないので、例えばオンラインのログイン完了後に処理してほしいとかそういうの指示するとき困るよね
これをどうするか考えた記事

開始フロー

調べてみると、各コンポーネントがスタート時に自由に開始していました。

やりたいことはゲームのタイトル情報、プレイヤーの現在の状況を読み込んでから

タイトルメニューで情報表示
プレイヤーの現在の状況から再スタート

バージョンとIDの表示だが

まずプレイヤーIDとは?

なるほどGenericIDとして、これを使ってサーバーAPIでPlayFabIDを問い合わせることができる

GenericIDの実態はサービス名とIDのペア

これを見てひらめいたのは、ゲーム内のキューブにGenericIDを刻み込み
サービス名はワールド名とし、IDにはワールド内の位置インデックス0~1700万くらいFFFFFFとする
ぎりぎり
その位置とキャラクターを結ぶようにGenericIDを加える

そうすれば、キューブ内のデータに描画用の情報を詰めてもよいことになる。
これがプレイヤー復活のためのメモリーキューブで、開始時にプレイヤーはしゃがんだ状態でキューブと交換するようにして現れることになる

開始フローを決める前に、まだまだ、そういうことしたいと思っていて要素がそろいきっていないことがわかった。

結論

最終的に GameManager が各種イベント接続をするコードを担当
そこを見ればゲームの処理の流れが追えるようになりました。

あとからどういうゲーム実装だっけ?と思って実装を読んだときに GameManger だけ読めば漏れなくわかる
というのを目指します。