転がるユニティちゃんを立たせてみる

痴話喧嘩とかけてTENGAと解く。その心は...

どちらも摩擦がつきものでしょう(ドヤァ)。

「何言ってんだこいつ」というツッコミはさておき、前回を思い出していただこう。

himatsubushi-industry.hatenablog.com

 f:id:himatsubushi-industry:20180816125428g:plain

筆者は、「Tスタンスで直立不動のユニティちゃんが上下左右に移動する」ことを想像していたが、現実はこの通りである。

すなわち、ユニティちゃんに置き換える前のあの球体は、本当に転がっていたのである。やるな、球体!

さてそうなると、どのように転がっているのか調べねばなるまい。
というわけでScript解読である。

Roll-a-ballに含まれるScriptは"CameraController"、"PlayerController"、"Rotater"の三つである。
名前を見れば一目瞭然であるが、"Camera..."はカメラ、"Player..."は球体、もといユニティちゃん、"Rotatter"はモブの直方体に紐づけされている。

ちなみに、Scriptが(Scriptに限った話ではないが)どのGameObjectから参照されているか逆引きする場合は、"Project"から対象のScriptファイルを右クリックしてポップアップメニューから"Find Reference In Scene"を実行すると、該当するGameObjectが"Hierarchy"に表示される。

tsubakit1.hateblo.jp

で、多分焦ることになるのが、"Hierarchy"の元の表示への戻し方だと思う。
少なくとも筆者は一瞬焦った。

"Hierarchy"上部を見ていただけると、クソ細かい字で検索入力項目があるので、そこの×アイコンをクリックするか、入力項目を消すかすればOKである。

さておき、早速"PlayerController"の中を覗いて見ることに。

ソースコードの中身はイベントドリブンでシンプルな構成である。
普段C言語でメインループからゴリゴリコード書いてる身からすると、とても羨ましい。
というか、上流設計やってる身で何で下流工程もやってんだ?俺(答:人材不足)。

グチは置いておこう。

移動に関わる部分を引用してみた。

    // Each physics step..
    void FixedUpdate ()
    {
        // Set some local float variables equal to the value of our Horizontal and Vertical Inputs
        float moveHorizontal = Input.GetAxis ("Horizontal");
        float moveVertical = Input.GetAxis ("Vertical");

        // Create a Vector3 variable, and assign X and Z to feature our horizontal and vertical float variables above
        Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);

        // Add a physical force to our Player rigidbody using our 'movement' Vector3 above, 
        // multiplying it by 'speed' - our public player speed that appears in the inspector
        rb.AddForce (movement * speed);
    }

 "Fixed"って何やねん?と調べてみると、"Fixed"じゃないただのUpdateがあることも確認。では両者の違いはというと、物理演算系のサイクルでイベントハンドラが呼び出されるか、描画処理系のサイクルでイベントハンドラが呼び出されるかの違い、らしい。

この一文で専門用語がしこたま並んでアタマがスポンジになったそこの君。
要は高橋名人が1秒間に16回ボタンを押すが如く、Unityの中の人が1秒間に60回ぐらいのタイミングで、ここに記載されている内容を実行してくれるということだけ理解してもらえればいいだろう。
筆者もいちいち説明するのは面倒だ。

さて、"Input.GetAxis"はカーソルキーの入力を取得する。どっかのSCE、もといSIE製ゲームハード用のDUALでSHOCKなコントローラとボタン配置が同じコントローラであれば左スティックである(設定によります)。

"Horizontal"は水平、すなわちヨコ方向、"Vertical"は垂直、すなわちタテ方向の単位時間当たりの操作量を表す。0/1のディジタル値ではなくアナログ値だ。
取り得る値は-1.0~1.0で、左/下がマイナス、右/上がプラスとなる。

docs.unity3d.com

次に、これらを移動に反映する...前の計算をする。

重力はY軸マイナス方向に働くことがこの世の定理なので、地表位置はX軸とZ軸で表現される。つまりX軸は経度、Y軸は高度、Z軸は緯度である(尚、天動説)。

ちなみに、定理は神の手により改変可能である。

qiita.com

余談が多くて申し訳ない。次に進もう。

カメラ視点的には、キーまたは左スティックの操作方向と、Scene空間の座標方向は一致させられているので、"Vector3"を使って操作量をそのままタテヨコのベクトルとして合成して、進むべきベクトルを求めている。

docs.unity3d.com

ぶっちゃけると、キーやスティックがどっち向きに操作されているかを求めているのである。

あとは、これをユニティちゃんに作用させているのだが、

        // Add a physical force to our Player rigidbody using our 'movement' Vector3 above, 
        // multiplying it by 'speed' - our public player speed that appears in the inspector
        rb.AddForce (movement * speed);

あれ?Translateは?Rotateは?

そう、最後の一文は「rdに力を加えよ」で終了なのである。
ちなみにrbは"Rigbody"を参照するオブジェクト、"speed"は"Player Controller(Script)"Componentで設定した"Speed"の値を参照するオブジェクトである。

要は「ユニティちゃんをベクトル方向に指定の速度で押せ」としか言ってないのである。

モノを押せばどうなるか?
無論、押された方向にモノは動くが、現実世界ならどうなるだろう?
球なら転がり、人なら転ぶ。
何故転がるか?接触面に摩擦力が発生するからだ。

Unity空間には摩擦が存在したんだ!ΩΩΩ<な、なんだってー!!

はい、ボケ回収しましたー(死んだ魚の目日照不足シャトルラン部をしながら)。

俺はユニティちゃん立たせたまま移動させたいんやー

さてどうするか?

こういうときは立って動いてるサンプルのScriptを素直に解読しよう。
今度はユニティちゃん側のScript、"UnityChanControlScriptWithRgidBody"の中を覗いてみた。
結構なボリュームであるが、"FixedUpdate"を見ておけばだいたい解決する話である。
関係するところを引用する。

        // 以下、キャラクターの移動処理
        velocity = new Vector3(0, 0, v);        // 上下のキー入力からZ軸方向の移動量を取得
        // キャラクターのローカル空間での方向に変換
        velocity = transform.TransformDirection(velocity);
        //以下のvの閾値は、Mecanim側のトランジションと一緒に調整する
        if (v > 0.1) {
            velocity *= forwardSpeed;        // 移動速度を掛ける
        } else if (v < -0.1) {
            velocity *= backwardSpeed;    // 移動速度を掛ける
        }

(中略)

        // 上下のキー入力でキャラクターを移動させる
        transform.localPosition += velocity * Time.fixedDeltaTime;

        // 左右のキー入力でキャラクタをY軸で旋回させる
        transform.Rotate(0, h * rotateSpeed, 0);    

  そうそう!これこれ!
平行移動と回転こそがモデル移動の全てである(老害論)。

そして、上記を参考にして書いたのがこちら。

    // Each physics step..
    void FixedUpdate ()
    {
/*        // Set some local float variables equal to the value of our Horizontal and Vertical Inputs
        float moveHorizontal = Input.GetAxis ("Horizontal");
        float moveVertical = Input.GetAxis ("Vertical");*/
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");

        velocity = new Vector3(0, 0, v);
        velocity = transform.TransformDirection(velocity);
        if (v > 0.1) {
            velocity *= forwardSpeed;
        } else if (v < -0.1) {
            velocity *= backwardSpeed;
        }
        transform.localPosition += velocity * Time.fixedDeltaTime;
        transform.Rotate(0, h * rotateSpeed, 0);    

/*        // Create a Vector3 variable, and assign X and Z to feature our horizontal and vertical float variables above
        Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);

        // Add a physical force to our Player rigidbody using our 'movement' Vector3 above, 
        // multiplying it by 'speed' - our public player speed that appears in the inspector
        rb.AddForce (movement * speed);*/
    }

 殆どコピペであるが、動けばええねん!理解することが目的である。
ちなみにコード中の0.1と-0.1は入力に不感帯を設ける為のもの。
要は入力の「遊び」である。

これを0.0なんかにするとプルプルすること請け合いである。
ゲームなら笑って済ませられるが組み込みシステムではエライことになるので気を付けよう。

で、動かした結果がこちらである。

f:id:himatsubushi-industry:20180817220037g:plain

惜しい。というか、何故転ぶ?
今度はGameObject全体を見比べてみる。

..."Angular Drag"!"Freeze Rotation”!貴様らか!!

docs.unity3d.com

マニュアルはよく読みましょうね(自戒をこめて)。

とりあえず、"Angular Drag"を"0"、"Freeze Rotation”は全軸チェック、つまり一切回らない設定にして、ようやく期待の動作になった。

次回予告

 ユニティちゃんは立ったけど、残念ながらアニメーションまで辿り着けなかったので、次回はアニメーションを組み込もうかと思いつつ、ジャンプとかダッシュ移動とか横ダッシュ移動とか、操作系に手を出したい気もしている。

どっちをやるかは気分次第である。

著作情報

ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています