週刊ラジコンモデル#3 カメラを動かす

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

 

 

先日子供の運動会へ行ってきました。

去年は予定があって子供含め出られなかったので、去年の分まで写真や動画を取ろうと意気込みつつ、別にデジカメやビデオカメラを持っているわけではないので、スマホで撮影していたわけです。

しかし、実はこのブログのための動画を撮影するにあたって、スマホを使って、カメラをオフにして撮影していたわけで、それがそのままだったがために音声が録れていないという事態に。

なんとか途中で思い出したものの……、来年こそは!

 

さて、前回はフォークリフトが動くようになりましたが、フォークリフトが画面外へ出ていくと見えなくなってしまいます。

というわけで、徒競走する子供を追う親のように、カメラも動かしていきましょう。

そもそもな話ですが、Demo3Dをスクリプト編集するまで使い込んでいる人でも、そのスクリプトからカメラを操作したことがあるという人はなかなかいないのではないでしょうか。

というわけで、カメラの取得方法から説明していきましょう。

 

現在Viewで使われているカメラの取得方法はいろいろあるのですが、次回のことも考えて、こんな方法をご紹介します。

var view = app.Display.GetView(0);
var camera = view.Camera;
IView view = app.Display.GetView(0);
Camera camera = view.Camera;

app.Displayで現在のディスプレイを取得し、そこからビューを取得するためのGetViewメソッドを実行する方法です。

GetViewメソッドの引数には0から始まる数値が入るのですが、通常は最初の要素である「0」を指定します。というか、普通はViewWindowは1つしか表示されてませんからね。

そして得られたViewが使用しているカメラをCameraプロパティから取得します。これでカメラが取得できました。

 

さてお次はというと、このカメラの使い方ですね。

あまり大したことをするつもりはないので、次の2つのプロパティを覚えていただければいいだけだと思っています。

・Positionプロパティ:カメラの位置

・Targetプロパティ:カメラの注視先

 

たとえば、新規作成したモデルの初期状態でのカメラ位置と注視先は下図のようになっています。

カメラの位置はまあなんとなくそんな感じかなって値ですし、注視先もよく見ると原点(0, 0, 0)を見ているような気がしますね。

 

ということは、常にフォークリフトを注視するようにしてみたらいいのではないでしょうか。

function Forklift1_OnInitialize( sender : Demo3D.Visuals.BoxVisual )
{
    // コントローラの取得
    var controllers = Demo3D.Input.Controller.GetControllers();
    if (controllers.Count < 1) return;
    var controller = controllers[0];

    // カメラの取得
    var view = app.Display.GetView(0);
    var camera = view.Camera;
        
    // 入力チェックループ
    while(true) {
        wait(0.1);
                
        if (controller.Poll()) {
            // 閾値チェック
            if (Math.Abs(controller.X) < 0.1 && Math.Abs(controller.Y) < 0.1) continue;
            
      // 移動処理
            var offset = new Vector3(-controller.Y, 0, -controller.X);
            sender.WorldLocation = sender.TransformToWorld(offset);

            // カメラ処理
            camera.Target = sender.WorldLocation;
            view.Camera = camera.Clone();
        }
    }
}
[Auto] IEnumerable OnInitialize(Visual sender) {
    // コントローラの取得
    List controllers = Controller.GetControllers();
    if (controllers.Count < 1) yield break;
    Controller controller = controllers[0];

    // カメラの取得
    IView view = app.Display.GetView(0);
    Camera camera = view.Camera;

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

      // 移動処理
            Vector3 offset = new Vector3(-controller.Y, 0, -controller.X);
            sender.WorldLocation = sender.TransformToWorld(offset);

            // カメラ処理
            camera.Target = sender.WorldLocation;
            view.Camera = (Camera)camera.Clone();
        }
    }
}

こんな感じで、移動が終わったらカメラの注視先を変更します。

ちなみに、最後にview.Cameraに対してカメラのクローンを設定しなおしているのは、こうでもしないと画面が更新されなかったためです。ほかにいい方法があればいいのですが。。。。。

 

さて、これを実行すると以下のようになりました。

これで多少はわかりやすくなりましたね。

ただ、やはりフォークリフトが離れていくにつれて見える大きさが小さくなっていくので、移動を続けると見えなくなってしまうのは変わらないといえるでしょう。

 

となると、やはりカメラの位置を変えるほかありません。

方法は単純に、フォークリフトから見て後ろ側の方にカメラのPositionを設定するだけでいきましょう。

// カメラ処理
camera.Target = sender.WorldLocation;
camera.Position = sender.TransformToWorld(new Vector3(-5, 4, 0));
view.Camera = camera.Clone();
// カメラ処理
camera.Target = sender.WorldLocation;
camera.Position = sender.TransformToWorld(new Vector3(-5, 4, 0));
view.Camera = (Camera)camera.Clone();

実行結果が以下の動画。

 

これでカメラがフォークリフトの後ろを追従するようになりました。

 

ただ、ちょっと前方が見づらいですね。Targetがフォークリフトの原点(=フォークリフトの底の方)なので、もう少し高い位置にするなり、さらに前の方を向けるなどして調整すればこの問題は解決するかと思います。

 

あとは、前回気になった「横方向に動かすとそのまま横移動してしまう」問題を解消させましょう。

解消方法はいろいろ考えられますが、ここは最も簡単かつレースゲームっぽい操作感を意識した方法で解決したいと思います。

 

創刊号で見せた人モデルの動きは、どちらかというと「人を動かすアクションゲーム」の操作感でした。

ただ今回はフォークリフトを使っていますし、「車を動かすレースゲーム」のように

・スティック左右で操舵

・RTボタンでアクセル

という仕様に変えることにします。

 

まずはアクセルから。

今まではスティックの上方向(=-Y)の場合にフォークリフトの前(=+X)へ進んでいたのを、違う値を基準に変更します。

ではRTボタンはどのように取得するのかというと、実はXboxコントローラのLT・RTボタンはほかのボタンとは違う扱いになっていて、ともにコントローラクラスのZプロパティから取得することになっています。

このとき、LTが正(0~1)、RTが負(-1~0)となり、その2つの差がZプロパティに格納されます。

ということは、このZプロパティの値をそのまま使ってやることで

・ボタンの押し加減による速度調節

・LTボタンによるバック走行

が実現できるわけですね。

 

実装は以下のような感じです。

// 移動処理
var offset = new Vector3(-controller.Z, 0, 0);
sender.WorldLocation = sender.TransformToWorld(offset);
// 移動処理
Vector3 offset = new Vector3(-controller.Z, 0, 0);
sender.WorldLocation = sender.TransformToWorld(offset);

もともとのoffsetの取得部分をちょっと変えただけですね。

 

そしてこのまま操舵部分も実装しましょう。

ここでは単純に左スティックのX軸のみを取得して、その入力値によってフォークリフトの向きを回転させます。

// 転換処理
var x = Math.Abs(controller.X) < 0.1 ? 0 : controller.X;
var rotate = 15.0 * x;
sender.WorldRotationYDegrees += rotate;
// 転換処理
double x = Math.Abs(controller.X) < 0.1 ? 0 : controller.X;
double rotate = 15.0 * x;
sender.WorldRotationYDegrees += rotate;

ニュートラル時にきっちり0にならない問題については、前回とちょっと違ったアプローチとして、絶対値が一定値に満たない場合は0にしてしまう方法法で実装しました。

そして得られたぶんだけY軸の角度を変えていきます。

 

さて、実行してみます。

加速が無かったり、バック時も同じスピードだったりと、いろいろ直すところはあるでしょうけど、結構それっぽいんじゃないでしょうか。

もっとリアルな実装については、ネットや書籍なんかでレースゲームのアルゴリズムを調べてもらえばいいかなと思うので、ここでは割愛したいと思います。


 

次回の週刊ラジコンモデルは最終巻、「二人同時プレー」を実装します。

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