KOSAKA LABORATORY->Tips

XNAとWiimoteLibで3Dオブジェクトを操作

更新作業中です。
 
 こではXNAとWiimoteLibを使った簡単なプログラミングを行います。 
Wiiリモコンを傾けると3Dオブジェクトが同じように傾くといったプログラムを作成したいと思います。 

3Dオブジェクトのダンロード
 なんでもかまいませんが、ここではせっかくWiiリモコンを使うのでWiiリモコンに似せた(そっくりそのままでは問題ありそうなので)3Dオブジェクトを使います。 以下からダウンロードしてください。 

 ダウンロードした2つのファイルを自分のドキュメントのプロジェクトのContentフォルダに入れてください。 

リソースの読み込み
 ダウンロードした2つのファイルをリソースとして読み込みます。 
右側の[ソリューションエクスプローラ]の[Content]を右クリック、[追加(D)]→[既存の項目(G)]を選択し、先ほどダウンロードした2つのファイルを指定し、[追加(A)]を選択します。



右側の[ソリューションエクスプローラ]の[Content]に2つのファイルが読み込まれました。 


プログラム
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using WiimoteLib;                //WiimoteLibの読み込み
using System.Collections;        //Collectionの読み込み


namespace XNAWii {
  /// <summary>
  /// This is the main type for your game
  /// </summary>
  public class Game1 : Microsoft.Xna.Framework.Game {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    private Model xfile;                 //Xファイル
    Wiimote wm = new Wiimote();          //Wiimoteの宣言
    ArrayList[] Accel = new ArrayList[2];//傾きセンサの値格納
    

    public Game1() {
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Content";
    }

    protected override void Initialize() {

      base.Initialize();

      //生のデータを扱うと、センサ値のブレが酷いので指定した回数の平均を取るためのリスト
      this.Accel[0] = new ArrayList();  //リスト定義
      this.Accel[1] = new ArrayList();  //リスト定義
      this.wm.Connect();                //接続
      this.wm.SetReportType(InputReport.IRExtensionAccel, true);//レポートタイプの設定
      this.wm.WiimoteChanged += wm_WiimoteChanged;              //イベント関数の登録
      this.wm.SetLEDs(0);

    }

    protected override void LoadContent() {
      spriteBatch = new SpriteBatch(GraphicsDevice);


      this.xfile = this.Content.Load<Model>("wiimodoki");  //Xファイルの読み込み
      foreach (ModelMesh mesh in this.xfile.Meshes)  //メッシュごと
      {
        foreach (BasicEffect effect in mesh.Effects) {
          //ビュー行列 カメラの視点を設定 x,y,zが(0.0f,0.0f,10.0f)の位置から原点を見る
          effect.View =
                      Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 10.0f), Vector3.Zero, Vector3.Up);

          //プロジェクション行列 視野角などの設定
          effect.Projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(45.0f),
            (float)this.GraphicsDevice.Viewport.Width / (float)this.GraphicsDevice.Viewport.Height,
            1.0f,
            50.0f
          );
        }
      }


    }

    protected override void UnloadContent() {
    }

    protected override void Update(GameTime gameTime) {
      // Allows the game to exit

      //そのまま実行すると以下のようなエラーが発生します。
      //「エラー 1 'ButtonState'は、'Microsoft.Xna.Framework.Input.ButtonState' と
                                                 'WiimoteLib.ButtonState'' 間のあいまいな参照です。
      //'Microsoft.Xna.Framework.Input.ButtonState' と 'WiimoteLib.ButtonState'のButtonState、
                                    どっちらを使うのかよくわからないと怒られます。
      //ここでは'Microsoft.Xna.Framework.Input.ButtonState'を使いますので、以下のように追加します。
      // 修正前:  if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
                                                  ButtonState.Pressed)
      // 修正後:  if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
                                                  Microsoft.Xna.Framework.Input.ButtonState.Pressed)

      if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
                                                  Microsoft.Xna.Framework.Input.ButtonState.Pressed)
        this.Exit();


      base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime) {
      GraphicsDevice.Clear(Color.CornflowerBlue);

      float x, y;     //回転角度を格納
      float tmp;      //計算用変数



      //Xの平均を求める
      tmp = 0;  //tmpの値を0にする
      //合計を求める
      for (int i = 0; i < this.Accel[0].Count; i++) {
        tmp = tmp + (float)this.Accel[0][i];
      }
      //平均を求める 合計を個数で割る
      x = tmp / this.Accel[0].Count;




      //Yの平均を求める
      tmp = 0;  //tmpの値を0にする
      //合計を求める
      for (int i = 0; i < this.Accel[1].Count; i++) {
        tmp = tmp + (float)this.Accel[1][i];
      }
      //平均を求める 合計を個数で割る
      y = tmp / this.Accel[1].Count;

      


      //90に拡張
      //センサの値を角度に変換
      x = (-x * 90.0f);
      y = (-y * 90.0f);

      //角度をラジアンに変換
      x = x / 180 * 3.14f;
      y = y / 180 * 3.14f;

      //画面に描画する
      foreach (ModelMesh mesh in this.xfile.Meshes) {
        foreach (BasicEffect effect in mesh.Effects) {
          //回転角度を設定 Yaw Pitch Rollを指定する。 Yawは使わないのでPitchにyをRollにxを設定
          effect.World = Matrix.CreateFromYawPitchRoll(0, y, x);
        }
        mesh.Draw();//meshを描画
      }


      base.Draw(gameTime);
    }

    //Wiiリモコン値が変更したら
    void wm_WiimoteChanged(object sender, WiimoteChangedEventArgs args) {
      WiimoteState ws = args.WiimoteState;        //WiimoteStateの値を取得

      //リストに突っ込む
      this.Accel[0].Add(ws.AccelState.Values.X);
      this.Accel[1].Add(ws.AccelState.Values.Y);


      int avg_count = 50;       //平均を取る数
			
      //avg_count個得たら古い値を1つ削除する
      //常に最新の状態のavg_count個データが格納される。
      if (this.Accel[0].Count > avg_count) { this.Accel[0].RemoveAt(0); }
      if (this.Accel[1].Count > avg_count) { this.Accel[1].RemoveAt(0); }
    }

  }
}


実行してみよう
  Wiiリモコンを接続した状態で[F5]キーを押して実行してみてください。 Wiiリモコンをクルクル回転させるなどの動作を行ってみてください。どうでしょう?Wiiリモコンの動作に合わせて画面のWiiリモコンが動いたでしょうか?


解説
  XNAでプロジェクトを作成すると標準で5個のメソッドができます。
メソッド説明このプログラムでの処理
Initialize初期化
変数の初期化 Wiimoteの接続
LoadContentグラフィック関係の読み込み
グラフィックスの初期化など
wiimodoki.xファイルの読み込み、視点位置と視野角の設定
UnloadContentグラフィック関係の破棄
グラフィックスが破棄された時の処理
Updateグラフィック以外の定期更新
グラフィック以外の処理を常に行う場合、ここに書く
フレーム毎に処理される
Drawグラフィック関係の定期更新
グラフィック関係の処理を常に行う場合、ここに書く
フレーム毎に処理される
2つの角度の平均を求め、回転させ画面に描写

 センサの値は常に変化しています。そのため、センサ値の角度でそのまま描写するとオブジェクトがガクガクになってしまいます。そのため、ある一定の数で平均を取ると滑らかに回転することができます。試しに、wm_WiimoteChanged()の中のavg_countの値を5に変更してみてください。どうでしょう?さっきよりガクガクになりましたよね。avg_countの値を大きいものにすれば滑らかに回転しますが、あまり大きくしすぎると回転するまで時間がかかってしまいます。 
 ここでは過去50個の平均を取っています。wm_WiimoteChanged()の中で、2つのセンサの値をリストに追加していきます。そしてリストが50個を超えたら、一番古いリストを削除しています。