simplestarの技術ブログ

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

Unity:オンラインVRM TPSゲーム作りログ3

■前書き
https://simplestar-tech.hatenablog.com/entry/2019/05/02/073820 の続きです
前回を要約すると

具体的には、チュートリアルルームを作りこんで、出口があり
出口にキーカードをかざすようにして、プレイヤー名、サインインするフローを用意し
プログラムを繋ぎこみます。

■横道
Unity プロジェクト単体によるリアルタイム通信と数十MB単位の巨大なデータの即時受け渡しができるようになったわけで
これってかなり自由にオンラインゲーム作れるってことじゃないですか
土台として固めて、なんとか形にしたい

■本題
まずは出口付近でアクションするとカードキーをかざす動作をするというものを作りますか

一度もそんなアニメーションを再生したことないな、どうすれば?
あと、ボタンの関連づけをより自然に直します。

いい感じの扉のあるチュートリアルルームのアセット買ったのでライトベイクはじめたら、永遠に終わりそうにない…

あと5分して進捗なければ消します。
進捗あったので残しました。

ボタン配置はどこで変えられる?
これで変えられました

var vInput = vrmRoot.AddComponent<vThirdPersonInput>();
vInput.jumpInput = new GenericInput("Space", "A", "A");
vInput.rollInput = new GenericInput("Q", "Y", "Y");
vInput.crouchInput = new GenericInput("C", "X", "X");
var vAction = vrmRoot.AddComponent<vGenericAction>();
vAction.actionInput = new GenericInput("E", "B", "B");

ライトベイクはよくわからないが、扉の前でカードキーイベントを発生させたい。
次に、カードキーアクションを発生させる environment があるか調べてみます。

vTriggerGenericAction クラスをアタッチした Collider が指定したアニメを再生させる仕組みの様子
動きを見たら実装を追ってみます。

Animation名を指定する欄で Press_Lever を記入したら、ドアのレバーアクションが行われることを確認できました。

■ドアアクションで、ルームログインのパネルを表示

具体的に
ユーザー名、パスワード→初めての方はWebを開く
ログイン完了と同時に S3 へ現在の vrm データをアップロード
プレイヤー名を記入する項目が出てきて、ディフォルト値があれば利用
オンラインルームに入る、ボタンを押すと
アップロード完了時のイベントで vrm のダウンロード用のオブジェクトキーを作成
オブジェクトキーが作成されるのを待ち、 Magic Onion のリアルタイム通信の規定ルームに入る

をやってみます。

Sign Up ボタンを押すと所定の url に飛ぶような Unity 機能ありますかね
これかな
Unity - Scripting API: Application.OpenURL

using UnityEngine;

public class OpenURL : MonoBehaviour
{
    [SerializeField] string url;

    public void OnClick()
    {
        Application.OpenURL(url);
    }
}

期待通り機能しました。
アクションが発生したときのイベントもあるので、これを利用します。

S3 アップロードのサンプル一式をコピーします。

コピーしたのは
Assets/AWS_packages をまるごとコピー
AWSS3 サンプルフォルダを切って、サンプルのスクリプトファイルをコピーしました。

UI が出てもマウスカーソルが表示されません。
誰がどこで制御しているのでしょうか

■UI表示時にのみ、マウスカーソルを表示

参考:
kan-kikuchi.hatenablog.com

たぶんどこかでCursor.lockState を制御しているコードがあるんじゃないかな
発見

vThirdPersonInput.cs

        public virtual void LockCursor(bool value)
        {
            if (!value)
                Cursor.lockState = CursorLockMode.Locked;
            else
                Cursor.lockState = CursorLockMode.None;
        }

外部から制御するため、この関数を呼ばせてもらいますか
正常に機能しました。

■UI操作中はキャラクターアクションしないでほしい
インプットを切るとかあるんですかね

Invector の Trigger アクションの With GameObject の使い方を知りたい
実装を追うと UnityEvent の使い方になった
www.urablog.xyz

あれ、UnityEvent call 対象のコールバック関数に問題あるのかなー?呼ばれるけど引数が null になってしまう

Invoke するときは GameObject 渡しているのに

どういうこと?
ここを読みます。
UnityEvent - Unity マニュアル

足りない!次
unitygeek.hatenablog.com

設定可能な関数一覧リストは、'Dynamic'と'Static'の2つに分けられている
Dynamic/Staticの両方に表示されている。引数を動的に指定したい場合は、'Dynamic'の方を使う。

この説明で理解しました。
私はいつも通り静的な関数を設定していました。
ここを動的な関数に選択しなおします。

その後に lockInput フラグを変更すれば、ロックはできるはず
できました。

イベントの動作だけブロックできなかった。
GenericAction についてもこんな対応を入れて解決する?

this.lockInputTarget = vrmRoot.GetComponent<vThirdPersonInput>();            
this.lockInputTarget.lockInput = true;
this.lockActionTarget = vrmRoot.GetComponent<vGenericAction>();
this.lockActionTarget.actionInput.useInput = false;

解決しました。

■プレイヤー名記入とチャットルームへのジョイン

今の進捗を確認すると、VRM 選択→チュートリアルルームで自由行動
扉の前で入力モードが切り替わり、Amazon Cognito の認証フロー完了 + S3 のクライアント有効化まで

パネルは引き続き、プレイヤー名とチャットルームへのジョインについて切り替わるべきで、入力ロックもまだ続けておく必要があった
そうした変更を反映させます。

S3 アップロードが完了できていること
ジョインすることができ、ジョイン完了の通知が届くことを確認しました。

■S3アップロード完了の通知とオブジェクトキーの送信

C# のコールバックの書式は…調べます。
Action なんちゃらだったと思ったけど

UnityAction callback

っていうのが使えた
オブジェクトキーの受信まで確認できました。

■別プレイヤーがログオンした際のオブジェクトキーによるダウンロード

別のプレイヤーが入ってきたときに、そのプレイヤーをインスタンス化する処理を書きます。
他のプレイヤーが参加してきたときにオブジェクトキーを取得できることを確認したので、これをダウンロードして byte 配列にします。
そしてこれを VRM インスタンスとして配置し、username ごとの配列

さて、ダウンロードしたデータから GameObject が作成されることをデバッグ実行して確認したが、シーン内にいない
どういうこと?

追記:vThirdPersonController がシングルトンとして、二つ目を消すコードになってたからでした。

あと、ビルドしたクライアントを終了するとフリーズする

プレイヤータグを付けないようにしてみるなど
現れた
相変わらず終了時にフリーズする
仕方ない、アプリ終了時に何かするコードを仕込みます。

それでもダメ Esc で必ずログアウトとCleanUp するようにしてみたがどうか
フリーズは避けられたけど、サーバー側で例外が発生していた

Leave を無理に呼ばないようにするとか
いえ、Leave イベントこなくなり悪化しました。

Quit するのをずっと先にしてみるとか

結局解決せず、最低限、フリーズしないようにするワークアラウンドのコードがこちら

void Start()
{
    this.InitializeClient();
    Application.wantsToQuit += WantsToQuit;
}

bool WantsToQuit()
{
    CleanUpMagicOnion();
    return false;
}
        
private async void CleanUpMagicOnion()
{
    if (this.isJoin)
    {
        await this.streamingClient.LeaveAsync();
        this.isJoin = false;
    }
    // Clean up Hub and channel
    await this.streamingClient.DisposeAsync();
    await this.channel.ShutdownAsync();

    Application.wantsToQuit -= WantsToQuit;
    Application.Quit();
}

エラー内容はサーバーからのステータス送信に失敗というものなので、クライアントは正しく切断処理をしていそう
サーバー側に流れるエラーは、ここまでの作業で無視することにしました。

エラー内容

W0503 19:36:35.140967 Grpc.Core.Server Exception while handling RPC. System.InvalidOperationException: Operation is not valid due to the current state of the object.

小ゴールとして、正しくダウンロードしてキャラクタがゲーム内に表示されているのでよしとします。

デバッグ機能:ダウンロードしたキャラクターを何かしらのインプットで動かす

通信する内容を精査していきます。
現在、他プレイヤーのデータをダウンロードして VRM キャラがゲーム内に出現している状態です。
これを操作するように出来る機能をローカルで確認していきます。

まずは、ファイルからデータを取り出してゲーム内に出現させるフローを確立します
そのあとにいじっていきましょう。

Animator は割り当たっているから、ステートマシンの再生でアクションできるはずなので、外から強制的に動かす信号を送れるようにしてみます。
具体的には

Animator のステートとしてディフォルトでは LocoMotion が選択されている
Animator パラメータの Inut Magnitude を 0~1.5 で操作し
各種 InputHorizontal , InputVertical などを増減させると歩きモーションを変えられる

ステートの変化はどうやっているか調べてみると

animator.CrossFadeInFixedTime("StateName", 0.1f);

試しにローリングするか調べてみます。
位置に変化がない状態でその場でローリングを行いました。

なるほど、アクションが実行されたら、そのタイミングでアクションをブロードキャストすると同期は可能ですね。
Animator パラメータは入力に変化があったときにスナップショットを送り付ける
定期的に絶対座標と回転をブロードキャストして補間するという仕組みがぱっと浮かびました。

ひとまず小ゴールクリアですね。考えていきます。

■キャラクターのステートの変化をキャッチしてブロードキャストする

アクションの実行など、アニメーションパラメータやステートに変化が生まれた時にこれを察知するスマートな方法は議論されていないのでしょうか。
調べます。

コード見る感じ AnimatorControllerParameter[] parameters { get; } でステートを逐一チェックして
ローカルで大きな変化があったパラメータをリストアップして、変化したパラメータをブロードキャストするとか?

全部ハッシュ値でアクセスしつつ、これ毎フレームチェックするべきなのかな
計算コスと大きいかな

でも通信量を極力抑えることの方が大事だと思われる

アニメーションパラメータは値が前フレームの状態より変化したらとしますか、これなら実装が見えるので作業するだけかな
アクションはどうする、これもステートとしてとれる?

レイヤーごとにこれで取ってこれそう
AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex);

関連
int layerCount { get; }


StringToHash でステート名を hash にして、hasy 値で StateInfo は構築されている様子、効率的に処理できそう

bodyPositionとかRotationとか
deltaRotation, deltaPosition ってのはアニメだけの話?

ちょっとモニタリングしてみます。

日が暮れたので、次の記事につなげます。
やっと試行錯誤できてますね。GWの目的達成です。