XBoxコントローラーを抽象化する正しい方法


12

アプリケーションの入力として使用したいXBox360コントローラーがあります。

私が解決できないのは、インターフェースを介してこれを公開するベストプラクティスの方法です。

舞台裏では、コントローラーを処理するクラスはポーリングボタンの状態に依存しています。

最初に何かリンクを試しました:

Event ButtonPressed() as ButtonEnum

どこにButtonEnumあったButtonRedButtonStartなど

これは、ボタンの押下のみをサポートし、ホールド/パターン(2回押すなど)をサポートしないという点で少し制限されています

次のアイデアは、単にボタンの状態をアプリに公開することです

Property RedPressed as Boolean
Property StartPressed as Boolean
Property Thumb1XAxis as Double

これは非常に柔軟ですが、実際にはアプリに多くの作業を強制し、アプリにポーリングを要求します-可能であればイベント駆動型を好むでしょう。

たとえば、複数のイベントを追加することを検討しました。

Event ButtonPressed(Button as ButtonEnum)
Event ButtonPressedTwice(Button as ButtonEnum)
Event ButtonHeldStart(Button as ButtonEnum)
Event ButtonHeldEnd(Button as ButtonEnum)

しかし、これは少し不格好なようで、「バインドボタン」画面に大きな痛みがありました。

誰かが、コントローラーからの入力を処理する「正しい」方法を教えてください。

注:インターフェイスを実装するクラス内でSlimDXを使用しています。これにより、状態を非常に簡単に読み取ることができます。私の問題を解決する代替品も歓迎します

回答:


21

プラットフォーム固有の抽象化を提供する完璧なマッピングはありません。明らかに、360コントローラーにとって意味のあるほとんどの識別子はPlayStationコントローラーにとって間違っています(XではなくA、CircleではなくB)。そしてもちろん、Wiiコントローラーもまったく別のものです。

これに対処するために私が見つけた最も効果的な方法は、3層の実装を使用することです。下位層は完全にプラットフォーム/コントローラー固有であり、使用可能なデジタルボタンとアナログ軸の数を認識しています。ハードウェアの状態をポーリングする方法を知っているのはこのレイヤーであり、ボタンが押されたとき、または1ティック以上ダウンしているか、押されていないかを知ることができるほど以前の状態を記憶するのはこのレイヤーです。それ以外は愚かです-単一のタイプのコントローラーを表す純粋な状態クラスです。本当の価値は、中間層からコントローラーの状態を照会することの核心を抽象化することです。

中間層は、実際のボタンからゲームの概念への実際のコントロールマッピングです(A->ジャンプなど)。特定のコントローラータイプにバインドされなくなったため、ボタンではなくインパルスと呼びます。このレイヤーでは、コントロールを再マップできます(開発中または実行時にユーザーの要求に応じて)。各プラットフォームには、仮想インパルスに対するコントロールの独自のマッピングがあります。これから逃れることはできませんし、すべきではありません。すべてのコントローラーは一意であり、独自のマッピングが必要です。ボタンは(ゲームモードに応じて)複数のインパルスにマップでき、複数のボタンは同じインパルスにマップできます(たとえば、AとXは両方とも加速し、BとYは両方とも減速します)。マッピングはそのすべてを定義し、

上の層はゲーム層です。衝動がかかり、それらがどのように生成されたかは気にしません。たぶん、彼らはコントローラーから来たのか、コントローラーの記録から来たのか、それともAIから来たのかもしれません。このレベルでは、気にしません。気にするのは、新しいジャンプインパルスがあるか、加速インパルスが継続しているか、ダイブインパルスの値がこのティックで0.35であることです。

この種のシステムでは、各コントローラーに対して最下層を1回書き込みます。上位層はプラットフォームに依存しません。中間層のコードは1回だけ書く必要がありますが、データ(再マッピング)はプラットフォーム/コントローラーごとにやり直す必要があります。


これは非常にクリーンでエレガントなアプローチのようです。ありがとうございました!
基本的な

1
非常に素晴らしい。私よりはるかに良い:P
ヨルダンミロナス

3
非常に素晴らしい抽象化。ただし、実装時には注意してください。すべてのユーザーアクションに対して新しいインパルスオブジェクトを作成および破棄しないでください。プーリングを使用します。ガベージコレクターに感謝します。
グレガg

絶対に。同時インパルスの最大数に合わせたサイズの静的配列は、ほとんどの場合最良のオプションです。ほとんどの場合、常に各インパルスのインスタンスを1つだけアクティブにする必要があります。ほとんどの場合、その配列には数個の項目しか含まれていないため、反復処理は高速です。
MrCranky

@gregaはあなたの両方に感謝します-私はそれを考えていませんでした。
基本的な

1

正直なところ、最適なインターフェイスはゲームの使用法に大きく依存すると思います。ただし、一般的な使用シナリオでは、イベントベースのアプローチとポーリングベースのアプローチを分離する2層アーキテクチャをお勧めします。

下層は「ポーリングベース」層と見なすことができ、ボタンの現在の状態を公開します。そのようなインターフェイスの1つには、単純に2つの機能がGetAnalogState(InputIdentifier)ありGetDigitalState(InputIdentifier)InputIdentifierチェック対象のボタン、トリガー、またはスティックを表す列挙値があります。(ボタンに対してGetAnalogStateを実行すると、1.0または0.0が返され、アナログスティックに対してGetDigitalStateを実行すると、プリセットのしきい値を超える場合はtrue、そうでない場合はfalseが返されます)。

次に、2番目の層は下位層を使用して状態が変化するとイベントを生成し、要素がコールバックに登録できるようにします(C#イベントは素晴らしいです)。これらのコールバックには、プレス、リリース、タップ、ロングホールドなどのイベントが含まれます。アナログスティックの場合、ジェスチャ(戦闘ゲームのQCFなど)を含めることができます。公開されるイベントの数は、ディスパッチするイベントの詳細度によって異なります。ButtonStateChanged(InputIdentifier)他のすべてを別のロジックで処理したい場合は、単純なものを起動できます。

したがって、入力ボタンまたはコントロールスティックの現在の状態を確認する必要がある場合は、下の層を確認してください。入力イベントで関数を単純に起動する場合は、2番目の層からのコールバックに登録します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.