週刊ラジコンモデル#2 モデルを動かす

こんにちは、開発担当の杉原です。

 

いきなりですが、毎号パーツがついてくるあの本、完走し切ったことがある方はいますか?

小学生のころ、蓄光の恐竜骨格を組み立てるようなものが刊行されていて、集めた覚えがあるのですが、ティラノサウルスを完成させたと思ったら、次はトリケラトプスが始まって、その途中でリタイアしてしまいました。

調べると、ティラノサウルスが1~8号とのことだったので、まあそこまで長く付き合ったわけではないですが、2号目以降490円って、よくもまあ小学生のときにそろえたなぁと思う次第です。

 

このブログは値段的な制約はないですが、はたして飽きずに見ていただけるか、それだけは気になるところですね。

 

さて、前回Xboxコントローラからの入力チェックをして、左スティックの軸値をログ出力しました。

今回はこの軸値をもとにして、実際にモデルを動かしてみたいと思います。 

動かすにあたって、まずはX軸およびY軸の入力から得られる値を確認します。

ちょうど前回のモデルで軸値を出力しているので、それを使って確認してみましょう。

コントローラをつないでモデルを開始し、左スティックの上、下、右、左と順に入力し、最後に何も入力しない(ニュートラル)状態に戻したときのX、Yそれぞれの入力値です。

この結果から、コントローラの方向とX、Y値については下図のような関係にあることがわかりました。

一般的に縦軸は上がプラスのイメージがあるかと思いますが(少なくとも自分はそうでした)、上がマイナスだということに注意してください。

 

またこのほかにログから読み取れることは

・中途半端な入力の場合、0.5のように1未満(または-1を超える)の値が得られる

・ニュートラルにしても完全には0にならない

ということです。

前者については、スティックの入力度合いによって移動スピードを調節するのにちょうどいいので、うまく活用したいと思います。

後者については、コントローラの不調もあるとは思うのですが、「コントローラを認識したときのスティックの状態」が完全なニュートラル(X:0 Y:0)な状態となるためどうしても0.001単位の値が得られてしまうことがあると考えています。入力度合いでスピード調節するのであれば、この微妙な入力についても考慮する必要があります。

 

ではそろそろモデルを動かしたいと思います。

モデルを動かす方法はいろいろあるかとは思いますが、今回は単純にモデルのワールド座標(WorldLocation)を変更することで移動させてみましょう。

function Forklift1_OnInitialize( sender : Demo3D.Visuals.BoxVisual )
{
    // コントローラの取得
    var controllers = Demo3D.Input.Controller.GetControllers();
    if (controllers.Count < 1) return;
    var controller = controllers[0];
        
    // 入力チェックループ
    while(true) {
        wait(0.1);
                
        if (controller.Poll()) {
            // 閾値チェック
            if (Math.Abs(controller.X) < 0.1 && Math.Abs(controller.Y) < 0.1) continue;
            
      // 移動処理
            sender.WorldLocationX += controller.X;
            sender.WorldLocationZ -= controller.Y;
        }
    }
}
[Auto] IEnumerable OnInitialize(Visual sender) {
    // コントローラの取得
    List controllers = Controller.GetControllers();
    if (controllers.Count < 1) yield break;
    Controller controller = controllers[0];

    // 入力チェックループ
    while(true) {
    yield return Wait.ForSeconds(refreshTime);
        if (controller.Poll()) {
            // 閾値チェック
            if (Math.Abs(controller.X) < 0.1 && Math.Abs(controller.Y) < 0.1) continue;

      // 移動処理
            sender.WorldLocationX += controller.X;
            sender.WorldLocationZ -= controller.Y;
        }
    }
}

先ほど挙げたポイントのとおり、ニュートラル時には動かしたくないので、ニュートラルと判断するための閾値を設けて、範囲外の場合は移動処理を行わないようにしています。

ここでのポイントは、入力値は-1~1という値をとるため、閾値も上記コードの場合であれば-0.1~0.1という範囲を取るということです。そこで「0.1未満かつ-0.1を超える場合はなにもしない」と判断するのですが、面倒なのでMathクラスのAbsメソッドを使って絶対値を取得し、絶対値が0.1未満であるかどうかで判断しています。

なお、ここで「0.1」という値で判別していますが、先のニュートラル時の値に基づいての設定なので、もしかするとちゃんとしたコントローラであればもっと小さい値でもいいのかもしれません。そこは一度、自身のコントローラのニュートラル時の値をチェックしてみてください。

 

チェックを通過すると、WorldLocationに軸値をそのまま加減算しています。

気を付けたいのは、

・WorldLocationは3次元であり、そのうちモデルを動かしたい方向はX軸とZ軸であること

・先のポイントのとおりY軸の入力は上が負数なので、Y軸の入力を受け付けるZ軸は加算ではなく減算をすること

の2点です。

ただ、後者については実際に動かしたい方向に準ずるので、そこは要件によって変わってくる話ではあります。

 

さて、早速動かしてみましょう。

なんかもうこれで十分な気がしなくもないですね。

とはいえ、横移動するときに向きを変えずに移動してしまってますし、そもそも左右入力で前後方向に移動するのって、なんか違いますよね。

 

フォークリフトの移動方向については、フォークリフトの向きを90度回転させれば合うようにはなるんですけども、そうじゃなくてもっとプログラム的になんとかしましょう。

 

 

Demo3Dに限らず、3Dの世界ではモデル全体の絶対的な座標であるワールド座標と、特定の対象物から見た相対的な座標であるローカル座標とが存在します。 

 向きが変わる原因は、X・Yの入力をワールド座標軸のX・Zにそれぞれ対応させてしまったことにあります。そのため、スティックのX軸入力を行うと、フォークリフトは度の向きを向いていても必ずワールド座標軸のX方向に移動してしまうというわけです。

 

 

これを解消するためには、スティックのX・Yの入力をフォークリフトのローカル座標軸に対して割り当てればいいわけです。

上の図ではすこしわかりづらいかとは思いますが、前方向がX軸の正方向、右方向がZ軸に負の方向となっています。

コントローラのスティックを上に傾けたら前に進むよう、それぞれを対応させると次のようになります。

・コントローラの上(-Y) → フォークリフトの前(X)

・コントローラの下(Y) → フォークリフトの後ろ(-X)

・コントローラの右(X) → フォークリフトの右(-Z)

・コントローラの左(-X) → フォークリフトの左(Z)

 

コードで書くとこうでしょうか。

var offset = new Vector3(-controller.Y, 0, -controller.X);
Vector3 offset = new Vector3(-controller.Y, 0, -controller.X);

これでローカル座標軸での移動先座標が決まりました。

しかし、これはあくまでもフォークリフトから相対的に見た位置です。実際にフォークリフトを動かすにはワールド座標がわからなければなりません。

そこで、フォークリフトのローカル座標をワールド座標に変換します。

var offset = new Vector3(-controller.Y, 0, -controller.X);
sender.WorldLocation = sender.TransformToWorld(offset);
Vector3 offset = new Vector3(-controller.Y, 0, -controller.X);
sender.WorldLocation = sender.TransformToWorld(offset);

VisualクラスにはTransformToWorldメソッドという、ローカル座標をワールド座標に変換してくれるメソッドが存在するため、これを利用します。

そして得られた座標をあらたなWorldLocationとして設定してやることで、フォークリフトはその向きにかかわらず、上入力で前へ、右入力で右へ移動するようになります。

こちらも動画を作成しましたのでご覧ください。

いい感じではないでしょうか?

 

右方向の入力に対してそのまま右に移動してしまう問題については、すみませんが今回は省略させてください。

いろいろ解決方法はあるかと思うのですが、紹介していると全4回に収まらなくなってしまう気がします。

ただ一応、次回ある程度対応出来たらなぁとは考えております。


 

次回の週刊ラジコンモデルでは、「カメラを動かす」部分を実装して動くフォークリフトに追従するようにしたいと思います。

更新は10/18(木)です。お楽しみに。