投稿日 : 2016/01/17 4:21:09
iOSでiCade対応のジョイスティックを使う方法
iPhoneやiPadなどでジョイスティックを使うにはiOS7以上でかつAppleのMFi認証済みのコントローラーが必要です。例えばPS3などのコントローラーはBluetoothに対応していますが、MFi認証はされていないためそのままではiOS上で使用することは出来ません。このためiOSでコントローラーを使うためには高額な専用コントローラーを買わなければならず、おそらくこれがジョイスティックに対応したゲームが少ない理由だと思われます。

iCadeコントローラー

iOSでは一般的なBluetoothのキーボードを使うことが出来ますが、海外のとあるメーカーがこれに着目し、コントローラーの入力をキーボードに見立てて作ったのがiCadeコントローラーです。このコントローラーはレトロゲーム用に特化しており、デザインは小さなアーケード筐体で画面にiPadを挟んで使用するような仕様でした。


これに合わせたゲームもいくつかリリースされていますが、最近ではこのiCadeの仕様に合わせたファミコン型のコントローラーなどが売られており、これによりiPad以外のiPhoneやiPodTouchなどでも遊べるようなアプリも登場しています。

iCadeは全てキーボードのON/OFFをエミュレートしているため、欠点としてスティックなどのアナログ入力には対応していません。このためWindowsやAndroid、iOS(iCade)対応と書かれているコントローラーにアナログスティックが付いている場合、アナログは単に上下左右のキーのON/OFFに割り当てられるようになっています。


※このコントローラーは3プラットフォームに対応していますが、iCadeモードで起動するにはAボタンを押しながら電源を入れる必要があります

iCadeの仕様

ネット上でいろいろ情報を探してみたのですが、対応したゲームのリストばかりが出てくるだけでiCadeがどういう仕様なのかについての資料がほとんどありませんでした。というのもiCade自体は公式仕様ではないため、海外のBBSとか個人ブログとかにしか無く、それらをまとめていくことでようやく仕様が見えてきました。

まずiCadeとはボタンの入力をキーボードに置き換えているということなので、ボタンを押せばキーコードが来るというのは想像出来ましたが、実はボタンを離した時は押した時とは別のキーコードが来ると言う仕様になっていました。
つまり1つのボタンに対して押したときと離した時で2つのキーコードが来るので、これを監視してONとOFFを識別するという仕様です。

押されたキーコードはUIKeyInputのコールバックとして通知されるので、入力を受け取りたいビューにそのコールバックを書いておき、送られたキーコードを判断してそれぞれのボタンをON/OFFしたことにします。
- (void)insertText:(NSString *)text { 
    char ch = [text characterAtIndex:0]; 
      :
}

以下は各ボタンに対するキーコードのリストです。
キー ON OFF
w e
d c
x z
a q
A y t
B h r
C u f
D j n
E i m
F k p
G o g
H l v

ちなみにFC30 Proの場合は以下のようになっていました。

※/の左がONの時、右がOFFの時、-は反応無し

iCadeをプログラムに組み込む

以下にiCadeを簡単に組み込むためのプロジェクトのサンプルがありました。
https://github.com/banwanko/iCade-iOS/tree/support-arc

実は元となっているライブラリはとても古いらしく新しいXcodeでビルド出来なくなっていたため、上記のものは修正されたコードになっています。
ただしXcode7.2でも既にそのままビルド出来なくなっており一部修正が必要です。
※iCadeReaderView.mの[control release];は必要無いなど

この中のiCadeフォルダにあるiCadeReaderView.hなどのソースをそのまま自分のプロジェクトに入れて使うのが一番手っ取り早いのですが、これを使うと結局どういう挙動で動いているのか理解しないまま使うことになるため、何か問題があったときに対応出来なくなってしまいます。そこでここではプログラムに必要な要点をまとめてみました。

ちなみに上記のiCadeのソースコードはMITライセンスと言うことになっていますが、ここで説明している内容を理解出来れば別にこのライブラリを使わずに実装出来るので、個人だろうが企業だろうがライセンス問題も無くなるためiCade対応がもっと進むのではと思われます。
※実はせっかくコントローラー買ったのにiCadeに対応した日本語対応のゲームがほとんど無く、いざ実装しようとしたら意外と面倒だったというのがこの記事の始まりだったりw

システムキーボード

まずは前提として、iOSではテキストボックスをクリックして文字入力を行う状態になるとシステムキーボードが下から出てくるようになります。しかしBluetoothなどのキーボードが接続されている場合、システムキーボードは表示されなくなるため全画面をそのまま使用することが出来ます。

同じくiCade対応のコントローラーはシステムからはキーボードとして認識しているため、このコントローラーが接続している状態ならばシステムキーボードは表示されませんが、問題はコントローラーが接続されていない場合はシステムキーボードが表示されることです。これはゲーム途中でコントローラーの電源を切った場合も同じで、外部キーボードが切断されたとみなされるとシステムキーボードが登場します。ゲーム画面の上にキーボードが表示されると画面がその分見えなくなるため、場合によってはゲームが続行不可能になったり、そもそもゲーム画面上にキーボードが表示されているのは明らかにバグのように見えます。

これを避けるためには外部キーボードが接続されていない場合は入力対象のビューからフォーカスを外せばよいですが、現時点で外部キーボードが接続されているかを取得する方法は用意されていません。また途中でキーボードが接続されたり切断されたりといった通知も来ないため、ゲーム中にフォーカスを切り替えるといったことも出来ません。
※CoreBluetoothで監視しようと試みましたが結局はダメでした

このため実はiCadeライブラリでは少し特殊な方法でこれを回避しています。

カスタムキーボード

iOS7からキーボードを自前で作ることが出来るようになりましたが、iCadeライブラリでは実はこれを利用しています。

まず何も無い空っぽのビューを作り、これをシステムキーボードの代わりにしています。これにより実際にはキーボードは表示されているが、サイズが0なので見た目上は表示されていないように見えるわけです。
- (id)initWithFrame:(CGRect)frame { 
    self = [super initWithFrame:frame]; 
    inputView = [[UIView alloc] initWithFrame:CGRectZero];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; 

    return self;
}
このダミーのビューを入力画面として参照させるために、このビューをinputViewとして返しています。
- (UIView*) inputView {
    return inputView;
}
実はこのメソッドが重要で、これが無いとダミービューが使用されずにシステムキーボードが表示されてしまいます。
※ソースコードを自前のプログラムに移植している際にこれを見逃していて2日ほど悩みました

iCadeReaderViewの実装

iCadeライブラリのiCadeReaderViewには2つの機能が実装されています。

まず1つが上記のカスタムキーボード用のダミービューで、もう1つが入力された文字を受け取るためのUIKeyInputインターフェースを実装したiCadeReaderViewというビューです。これ以外にも受け取った文字を実際のボタンのON/OFFのイベントに変換して上位に返すという機能も入っていますが、キーの解析は自前でもっと効率よく処理することが出来るので、ここではこの機能に関してはとりあえず無視しています。

このiCadeReaderViewというビューは、呼び出し元のビューにaddSubview()することで入力対象として指定することが出来るようになっています。このビューもサンプルではサイズ0で作っているので、addSubviewされても実際には画面上には何も表示されません。またサンプルではキーの入力があると呼び出し元に通知するために、delegate先として自分自身(self)を指定しています。
- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 
    iCadeReaderView *control = [[iCadeReaderView alloc] initWithFrame:CGRectZero]; 
    [self.view addSubview:control]; 
    control.active = YES; 
    control.delegate = self; 
    // [control release];
    _stickCenter = stick.center; 
} 
この中でObjective-Cの基本がよく分かってないと理解しにくいのが「control.active = YES」ですが、これはiCadeReaderViewのactive変数をただYESにするのではなく、実際には引数にYESを指定してsetActive()関数を呼び出したことになります。つまり「[control setActive:YES];」と等価です。

Objective-Cは@Propertyで宣言した変数は自動的にセッターとゲッター関数が定義されますが、ここではさらにsetActive()をオーバーライドすることで同時に入力フォーカスを与えるような処理を行っています。

つまり上記のコードはiCadeReaderViewを構築して即座に入力を開始したということになります。

これでジョイスティックの入力があればこのビューコントローラーに通知されるようになり、またジョイスティックが切断されたとしてもシステムキーボードが表示されてしまうようなことはありません。
コメントはまだ登録されていません。
コメントする
名前
コメント
※タグは使用出来ません
この記事に関連するタグ
iOS