ログインしないときの動作を策定するにあたり、現状を整理し、作業ログを残します。
簡単に今のフローをおさらい
VRoid Hub のログインは VRoid SDK のサンプルに任せっきり
ゲームを起動すると「VRoid Hub に接続」ボタンを表示、同時に「☓」の閉じるボタンがある
接続ボタンを押すと、次のとおり Web ブラウザが起動し、連携許諾の画面の後、認証コードを表示する画面が出てくる一応毎回認証コード違うので、もうこの値を入れても有効にはなりません…モザイクもかけない
で、ゲーム画面に戻るとこのような画面になっているので、先程のコードを入力するキャラクター選択ができるが、このときも「☓」ボタンでダイアログを閉じることが可能
キャラを選択すると「利用する」「キャンセル」ボタンがある
キャンセルすると、一つ前のキャラクター選択画面に戻る
「利用する」を選ぶと、次の通りキャラクターがゲームに登場する
ログインをやめたとき
「次のディフォルトキャラを利用しますか?」
「利用する」「キャンセル」を出してみようと思う
この画面は CharacterLicenseScrollView といって
まぁ、調べると複雑なのでユーザーは VRoidHubController の SetOnCancelHandler ですべてのキャンセルをハンドリングします。
ここで CharacterLicenseScrollView をアクティブ化するロジックをハンドラに追加してみましょう。
期待では、キャラ情報のない画面が出る
具体的にはこのように書き
this.controller.SetOnCancelHandler(() => { this.licensePanel.SetActive(true); this.menuCanvas.SetActive(true); this.controller.gameObject.SetActive(true); });
結果は次の通り
ボタンとして「利用する」「キャンセル」があるが
「利用する」を押した場合は、独自の VRM ロードロジックに付け替える
「キャンセル」を押した場合は、元のログイン画面の表示につけかえる
想定できる問題に、キャンセルした後から普通のフローをたどると、「利用する」ボタンのロジックが切り替わったままになるので
キャンセル時にすべてを元に戻すことをしなければならない
では、まず、ロジック付替えるテストコード
動的に永続イベントを無効化して付け替えるには、このように書くしかなさそう…いい知見を得ました。
private void OnCancelButtonClicked() { // 固定キャラクター利用を拒否されたので、最初のログイン画面に切り替え this.loginCanvas.SetActive(true); this.mainPanel.SetActive(true); this.menuCanvas.SetActive(false); this.licensePanel.SetActive(false); // 切り替えたボタンのロジックを元に戻す this.acceptButton.onClick.RemoveAllListeners(); this.acceptButton.onClick.SetPersistentListenerState(0, UnityEventCallState.EditorAndRuntime); this.retryButton.onClick.RemoveAllListeners(); this.retryButton.onClick.SetPersistentListenerState(0, UnityEventCallState.EditorAndRuntime); this.cancelButton.onClick.RemoveAllListeners(); this.cancelButton.onClick.SetPersistentListenerState(0, UnityEventCallState.EditorAndRuntime); } private void OnAcceptButtonClicked() { // streamingAssets から vrm をインスタンス化 var vrmFilePath = Path.Combine(Application.streamingAssetsPath, "VRM/defaultvrm.vrm"); var context = new VRMImporterContext(); context.Parse(vrmFilePath); context.LoadAsync(() => { context.ShowMeshes(); var vrmModel = context.Root; vrmModel.name = "defaultvrm"; // プレイヤーとしてコンポーネントを追加 ComponentUtil.DeleteAllChildren(this.transform); int layerMask = LayerMask.NameToLayer(LayerMask_Player); vrmModel.SetLayerRecursively(layerMask); vrmModel.SetTagRecursively(Tag_Player); // ただし characterModelId はディフォルト値とする this.SetModel("defaultvrm", vrmModel, isUserPlayerFlag: true); // VRoid Hub メニューを閉じる this.controller.Close(); }, (exception) => { Debug.LogError($"defaultvrm load error: {exception.Message}"); }); } private void Start() { this.controller.Open(); this.controller.SetOnLoadHandler((characterModelId, vrmModel) => { ComponentUtil.DeleteAllChildren(this.transform); int layerMask = LayerMask.NameToLayer(LayerMask_Player); vrmModel.SetLayerRecursively(layerMask); vrmModel.SetTagRecursively(Tag_Player); this.SetModel(characterModelId, vrmModel, isUserPlayerFlag:true); }); this.controller.SetOnCancelHandler(() => { // キャラクター利用画面を表示 this.licensePanel.SetActive(true); this.menuCanvas.SetActive(true); this.mainPanel.SetActive(false); this.loginCanvas.SetActive(false); this.controller.gameObject.SetActive(true); // 既存の「利用する」ボタンと「キャンセル」ボタンの動作を無効化 this.acceptButton.onClick.SetPersistentListenerState(0, UnityEventCallState.Off); this.acceptButton.onClick.RemoveAllListeners(); this.retryButton.onClick.SetPersistentListenerState(0, UnityEventCallState.Off); this.retryButton.onClick.RemoveAllListeners(); this.cancelButton.onClick.SetPersistentListenerState(0, UnityEventCallState.Off); this.cancelButton.onClick.RemoveAllListeners(); // 新しいロジックに付け替え this.acceptButton.onClick.AddListener(this.OnAcceptButtonClicked); this.cancelButton.onClick.AddListener(this.OnCancelButtonClicked); // streamingAssets から vrm 情報を取得 var characterLicensePanel = this.licensePanel.GetComponent<CharacterLicensePanel>(); characterLicensePanel.Init(new VRoidSDK.CharacterModel { portrait_image = new VRoidSDK.PortraitImage { original = new VRoidSDK.WebImage { url = "" } }, name = @"<b><size=28>千駄ヶ谷 篠</size></b> <size=24> Welcome School Uniform 2019 </size> <size=24> 作者:<b> VRoidプロジェクト </b></size>", license = new VRoidSDK.CharacterLicense { characterization_allowed_user = "everyone", violent_expression = "allow", sexual_expression = "allow", corporate_commercial_use = "allow", personal_commercial_use = "profit", modification = "allow", redistribution = "allow", credit = "unnecessary" } }); this.caracterImage.texture = this.defaultvrmIcon; }); }
キャンセルして元に戻すことができているので、あとは利用するボタンを押した時に、リソースから VRM を読み出そうと思います。
Prefab のインスタンス化で良いかな?→惜しいところで、vrmAvatar が内蔵されないので、キャラクターを動かす時に不都合を確認
ファイルから直接読み込むように StreamingAssets から直接読み込むようにしました。
色々修正して上の実装の通り
現在、VRoid Hub の接続を拒むと、次の画面が出て、利用するボタンを押すと、この子が使えるようになってます。
あとは、通信時にキャラクターIDを正しく相手に送る必要があるので、正しい ID とやらを確認します。
648876553405728395 でした。
そして、VRoid Hub への接続を拒んだクライアントでは、常にキャラクターをこの子とする実装が求められます。
それもテストしてみましょう。
あれ、方やログインしてないのに動いちゃった
そういうものなのかな
ああ、キャッシュをクリアしたらやっぱり認証でエラーが確認できました。
そういうときは、ディフォルトの vrm を読み込むようにしましょう。
これで行けると思うんだけど…
IEnumerator CoWaitEmptyMapAndLoadAsync(ContentUrlResponse response) { // ロードは複数同時に走らせない while (this.isVRoidLoading) { yield return null; } this.isVRoidLoading = true; this.lastUrlResponse = response; if (Authentication.Instance.IsAuthorized()) { // ログインできている場合は API 経由でキャラデータをロード var characterModel = new CharacterModel(); characterModel.id = response.URL; HubModelDeserializer.Instance.LoadCharacterAsync(characterModel, OnLoadComplete, OnDownloadProgress, OnError); } else { // ログインしていない場合は、ディフォルト VRM で代用 var vrmFilePath = Path.Combine(Application.streamingAssetsPath, "VRM/defaultvrm.vrm"); var context = new VRM.VRMImporterContext(); context.Parse(vrmFilePath); context.LoadAsync(() => { context.ShowMeshes(); var vrmModel = context.Root; vrmModel.name = response.UserUniqueId.ToString(); this.OnLoadComplete(vrmModel); }, (exception) => { Debug.LogError($"defaultvrm load error: {exception.Message}"); }); } }
できました!
色んなユーザーがルームに入ってきても、全部ディフォルトのキャラとして映ります…
これにて機能追加完了