皆さんこんにちは、Reveです。
今日も自作ゲームコントローラーの記事を書いていきます。
表を作ろうとしたら固まったので、また一から書き直しました汗
一連の自作ゲームコントローラー特集ですが、今回は少し趣向を変えてソフトウェアからコントローラーの設定を変更できるシステムを制作していきたいと思います。今までは簡単な回路とプログラムのみを作りましたが、今回は少し複雑なプログラムを使います。
(仕組みの解説)
今までずっと、Arduino Microを使用してきましたが、このArduinoもシリアル通信が使用できます。さらに、USBエミュレータと同時並行で使用できるため、シリアル通信で設定を変更しながらUSBコントローラーとして使うことができるのです。
そのため今回は、Arduinoのプログラムとは別に、コントローラー設定アプリケーションを用意します。
当方の場合は、C#でシリアル通信によりArduinoへデータを送信するプログラムをこちらで作成しました(ダウンロードも可能ですが、いかなる損傷・誤動作が発生した場合も当方は責任を負いませんので、ご了承ください)。
(シリアル通信とは)
シリアル通信という用語は本来、情報を1ビットずつ送受信する通信方式を表し、その中に様々な規格があります。
Arduino-PC間で最もよく使用されるのはRS 232Cという規格で、これをシリアル通信と呼ぶことも多々あります。
RS 232Cは、1969年にコンピューターとモデム間の通信規格として策定された、歴史ある通信用インタ-フェース(レガシーインターフェース)です。モデムだけなく、コンピューター同士や他の周辺機器との接続にも広く使用されてきましたが、現在ではその役割をUSBに譲っています。
最近のPCでは搭載されていないことも多いのですが、Arduinoなどマイコンと接続する場合であれば、USBとシリアル通信を変換する機器(USB-シリアル変換ケーブルなど)を使用することがほとんどです。ですが、Arduinoの中にはUSB-シリアル変換機能が搭載されたものもあり、Arduino Microもその一つであるため、追加で購入する必要はありません。
RS 232Cのコネクタには、D-Sub9ピンとD-Sub25ピンの2種類がありますが、Arduinoとの通信ではD-Sub9ピンの方を使います。端子9本の中には通信制御のものも含まれており、通信を行うだけなら以下の3本をつなげば実現できます。
Arduino | RS 232C |
0(RX) | 3(TxD) |
1(TX) | 2(RxD) |
GND | 5(GND) |
なお、(最新の)RS 232Cの正式な規定は仕様が少し異なり、名称も変わりますが、従来の規定を使用するものが多いです(名称もRS 232C準拠のまま)。
(回路の構成)
回路の構成は基本的にボタンとジョイスティックを使うものとなっています。
ボタン用のピンはArduinoの2~13とA3、A4の14ピン(アナログ入力ピンでデジタル入力も可能です)、ジョイスティック用のピンはA0、A1、A2(マウス左ボタン用)の3ピンを割り当てています。
これを今までの記事(ボタン、ジョイスティック)と同様につなげば回路ができます。
ボタンは最大14個までですが、必要な数だけ実装すればOKです。
(写真がなくてごめんなさい。後日用意します)
(プログラム)
Arduinoプログラム、およびコントローラー設定変更用のアプリケーション(.exe)はここから「Controller_config_App」と「Controller_sample6」の2種類をそれぞれフォルダごとダウンロードしてください。
なお、コントローラー設定用のアプリケーション実行には、「.NET Frameworks 4.5」のインストールが必要です(それ以上のバージョンでも可)。
コントローラー設定用のアプリケーションについては、ソースの解説は省きます。
もしご要望があれば、ソース公開や解説も載せていきたいと思います(無くてもやるかもw)。
一方、Arduinoプログラムについては一部の解説をしたいと思います。
解説が前の記事と重なる部分もあるため、ソース全体についてはダウンロードしたファイルと前の記事をご参照ください。
今回新しく登場するのはシリアル通信に関する実装です。
まず、シリアル通信を開始するにはSerial.beginメソッドをsetup関数内に記述します。カッコ内の数値は通信速度(bps)で、特定の数値だけ与えられます(Arduinoの仕様を参照)。今回は9600にしました。
1 |
Serial.begin(9600); |
続いて、シリアル通信処理を行う関数を実装します。
今回作るシステムにおいては、独自の通信仕様(プロトコル)を定めており、以下のルールに従って情報がまとめて送られます(上から送信順に並んでいる)。ちなみに、このひとまとまりのデータをパケットと呼びます。
Header(0xE5) | ヘッダー (16進数の0xE5固定) |
ID | パケット種類を表す (1バイト) |
Length | データの長さ(バイト数)を表す (0 ~ 255) |
Data | コントローラー設定の情報 |
Check Sum | チェックサム(データ誤送信があるかの確認用) |
そして、現状ではパケットの種類として、以下の2種類を用意しています。
ID番号 | 内容 |
11 | キー割り当て設定 |
13 | ジョイスティック感度設定 |
つまり、このプロトコルを基にPC側ではパケットを作成して送信し、それをArduino側で受け取ってパケット内容を解析しています。
そのため、Arduinoではパケットを受信して解析する関数が必要です。当方では、パケット構造を解析する関数と、データ内容(Data)の情報を解析する関数を分けて実装しました。
まず、パケット構造を解析する関数から見てみます。
データを受信している場合、Serial.readメソッドで読み取って変数inBufferに入れ、その中身がパケットのどの部分に当たるかを見て読み取った内容を解析します。
図にすると、大体こんな感じです。
実装した関数は下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
const int HEADER = 0xE5; //header byte ptPckt = 0; //the order of the packet the method assets currently byte errCode = 0; //error code byte pcktID = 0; //packet ID byte datLen = 0; //data length byte datBuf[255]; //buffer of the packet data void serialReadPacket() { if(Serial.available() > 0){ int inBuffer = Serial.read(); //header if(ptPckt == 0){ if(inBuffer != HEADER){ errCode = 2; ptPckt = 0; } else{ errCode = 0; ptPckt++; } } //ID else if(ptPckt == 1){ pcktID = (byte)inBuffer; ptPckt++; } //data length else if(ptPckt == 2){ datLen = (byte)inBuffer; ptPckt++; } //check sum else if(ptPckt == datLen + 3){ int sum = 0; for(int i = 0; i < datLen; i++){sum += datBuf[i];} sum = (sum + inBuffer) & 0xFF; if(sum == 0xFF){ errCode = 0; decodePacketData(datLen, pcktID, datBuf); //この下で実装されます } else{ errCode = 1; } ptPckt = 0; } //data overflow else if(ptPckt > datLen + 3){ errCode = 3; ptPckt = 0; } //data else{ datBuf[ptPckt - 3] = (byte)inBuffer; ptPckt++; } } } |
そして何のエラーもない場合は、受信データから得られたコントローラーの設定情報を基に内容の解析を行います。ID番号、データ長さ、そしてデータ内容の配列を引数に与えて、Arduino内部の設定を変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void decodePacketData(byte len, byte id, byte* data){ switch(id){ case 11: //key config for(int i = 0; i < len; i++){ keyConfig[i] = (char)datBuf[i]; } break; case 13: //controller setting if(len >= 4){ bstShot = datBuf[0]; anStick = (datBuf[1] != 0) ? true : false; range = datBuf[2]; threshold = datBuf[3]; } break; default: break; } } |
後は、serialReadPacket関数をloop関数内で使用すれば、シリアル通信でコントローラーの設定を変更できるようになります。
ちなみに、ボタンも複数使用できるよう、以前紹介した工夫をforループをかけて処理できるようにしましたが、詳細はソースをご覧ください。
(ゲームをやってみよう)
今回ご紹介するゲームは、TANAKA U様が運営されているNEXTFRAME(!)が制作された、Flashゲームで有名なアクションゲームのSeventh Sky(http://nextframe.jp/flash/seventhSky64M.html)です(リンクフリーとのこと)。
ロボットを操作して空中を浮遊し、全方位から攻めてくる敵をガンやブレードで倒していく2Dシューティングアクションですが、ロボットを操作して敵を倒す爽快感がたまりません。マウスとキーボードの両方を使用するため、慣れるのに少し時間がかかりますが、一度操作を覚えればこのゲームにやみつきになるでしょう。
ちなみに、個人的には水色の機体を使ってます。大火力のビームを撃ち、多くの敵を一度に薙ぎ払うように倒していくのが好きです。バスターライフル!!
(終わりに)
今回で書きたいことはほぼ全て書き終わったので、このテーマはお終いにします。
あまり上手な説明ではなかったかと思いますが、もし参考になった、あるいは楽しんでいただけたのであれば幸いです。
図や説明が足りない部分もあると思うので、また編集していきたいと思います。
では(・ω・)ノシ
コメント