2015年7月10日金曜日

ゲームパッドの値をシリアルポートに吐き出す

ゲームパッドを使いたい と思うことは度々あると思います。が、面倒ですよね? Javaとかいう言語を使ったりするのは。ということでC#でやってみたいと思います。
ここでは条件として、Xbox360のコントローラに限定したいと思うます。そうすることでC#で簡単に使えるようになります。

Xbox360のコントローラなんてダサいって? このコントローラは米軍も使用している由緒正しいコントローラです!

さて、今回はXNA Game Studioというフレームワークを使用しました。これは既に開発が終了していますが、C#でゲームを動かすために作られたもので、2Dや3Dなどのレンダリングをすることが出来ます(もっとも、レンダリングをすること"しか"できないので、3Dの表現は大変ですが)。
また、XNAはXboxのゲーム開発環境としても想定されており、年間所定のライセンスを支払うとXboxで遊んだり、安価に販売することも出来たようです。そのために、XNAにはXbox360のコントローラを使用するためのGamePadStateというクラスが用意されています。

この機能を使用するためにはまず参照設定でMicrosoft.Xna.Frameworkを読み込む必要があります。また表示のためにPictureBoxをフォームに貼り付け、AnchorをTop, Bottom, Left, Rightに設定して下さい。そしてFormにはLoadとFormClosedのイベントを作成し、またTimerとSerialPortのコンポーネントを追加して下さい。
今回のGUIではシリアルポートのOpen・Close処理は起動時・終了時に行います。そのためSerialPortコンポーネントで使用するCOMポート名と通信フォーマットを適切に設定しておいて下さい。
それから、Timerはゲームパッドの読み込みに使用します。Enableはtrueにし、Intervalは適当な値(50-500程度)に設定して下さい。

実際にデータを読んで送信するための処理はソースコードを読んでみてください。画像にして表示する部分が半分以上で、実際の送受信処理は非常に簡単です。

今回は左スティックのみを使用しましたが、XNAではXboxボタン(中央にある椎茸みたいなボタン)以外のすべてのボタンを読み込むことが出来ます。そのため、XBee等を使用して戦車のラジコンを操縦しようと思えば、FPSゲームと全く同じコントロール方法で制御することも可能になります(もちろん操作対象にマイコンを組み込んで操作を理解させる必要がありますが)。また、情報を読み込む以外にも、コントローラの振動を制御することも可能です。これは例えば戦車のラジコンであれば、ラジコン側に加速度センサを搭載しておき、一定以上の振動が検出されたらゲームパッドを振動させる事により、段差を乗り越えたらコントローラを通じて操縦者に伝えることが出来ます。他にも、主砲を撃つ操作をした時に振動させるなどの演出もできるでしょう。
また、XNAではGamePad.GetStateの引数にPlayerIndex.Oneを渡していますが、これは1-4までを指定できます。つまり1台のPCで最大4台のコントローラを区別することが出来ます。これは4台の戦車で対戦したりとか、1個の複雑なロボットアームを複数人で操作することが可能になります。


using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace XnaPortDump {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();

            Size += new Size(400, 400) - pictureBox1.Size;
        }

        private void timer1_Tick(object sender, EventArgs e) {
            GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);

            float x = gamePadState.ThumbSticks.Left.X;
            float y = gamePadState.ThumbSticks.Left.Y;

            Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
            using (Graphics gra = Graphics.FromImage(bmp)) {
                gra.FillEllipse(Brushes.Lime, bmp.Width / 2 + (x * bmp.Width / 2 * 0.95f) - 5, bmp.Height / 2 - (y * bmp.Height / 2 * 0.95f) - 5, 10, 10);
            }

            if (pictureBox1.Image != null) {
                pictureBox1.Image.Dispose();
            }
            pictureBox1.Image = bmp;

            x *= 2500;
            y *= 2500;

            serialPort1.Write("motorrun 0 " + x.ToString("0") + "\n");
            serialPort1.Write("motorrun 1 " + y.ToString("0") + "\n");

            Text = x.ToString("+0000;-0000") + " " + y.ToString("+0000;-0000");
        }

        private void Form1_Load(object sender, EventArgs e) {
            serialPort1.Open();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
            serialPort1.Close();
        }
    }
}

0 件のコメント:

コメントを投稿