スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

C++とDirectX弄りの備忘録(2)

ということで2。今回はDirectInputの話。
ゲームにしろなんにしろユーザーからの入力は必要、ということで入力デバイスを一手に担ってくれるのが、
DirectXのなかでもDirectInputということになる。実際のところ使い方は割と簡単。

問題は、このAPIバージョン8からさっぱりアップデートがないこと。MSによると、

「DirectInputはもうやめた。ゲームパッドに関してはこれからはXInputを使え。
ただし対応しているのは箱のコントローラだけだ(笑。マウスとキーボードがほしけりゃWindowsのAPI使え」

つまるところ箱のコントローラを使わない限りは、WindowsのAPIを直接たたくかほかの手段を探すってことになる。
ゲームパッドに関してはそれ専用の取得APIがあるみたいで、めんどくさいが使えなくはない。
キーボードとマウスはAPIを使うとなると、GetAsyncKeyState()だったりGetCursorPos()あたりを使うか、
プロシージャのコールバックを利用しての実装をしろ、という意味なんだと思う。

どちらにしても割と仕込むのが面倒くさいので、やっぱりDirectInputを使うか、
箱(もしくは互換のある)コントローラを使うことを強要する、というくらいしか選択肢がないから
結局DirectInput使ったほうが楽っていうのが現状…なのでしょうか?
よくわからないからもっとほかの使いやすいライブラリがあったら教えてほしい。



ところでスプライトバッチの方は、ソートとかはめんどくさいのでとりあえずは、
指示された描画が同一テクスチャである限りはこれをバッファに貯め、
End()メソッドかテクスチャが変更されたときに動的頂点バッファに書き込んで
DrawIndexedPrimitive()する、って形だとリリースビルド時・最適化最大・最小限のコードで、
同一テクスチャだと7000枚程度の板ポリゴンであればぎりぎり60fpsを維持できるような感じにはなった。
ただ、単純計算28000頂点を処理したところで限界が来ているから、まだあまり効率がいいってわけではないか。
ちょっとまじめなゲームだったら数万頂点あるのは当たり前だろうしねぇ。
実際にはあたり判定だとかテクスチャの変更は起こるわけだから、まだまだ遅くなる。
ついでにUSAGEにDYNAMICを指定して頂点バッファを作成しているからか、
メモリの確保場所をPOOL_DEFAULTに設定しないとうまく頂点バッファが確保されない。
インデックスバッファは静的でいいから大丈夫なんだが、動的に操作するような場合は
POOL_DEFAULTフラグを設定しないとうまくいかないようだ。毎フレーム書き換えるからDYNAMIC指定したが、
もしかしたらWRITE_ONLYフラグだけのときと変わらないのかもしれない。おまじないおまじない。
これの問題は、デバイスロスト時にリソースのバックアップが行われないため再作成の必要があること。
まぁ7に変えてから未だかつてデバイスロストにあったことがない(XPは割とすぐなった)ので、
デバイスロストしたらご愁傷様でした、でももういい気がしてる。

あと効率化するとすれば、頂点情報に回転とスケール情報を埋め込んでGPU側に各々頂点計算をさせるか。
やはりそうなると頂点シェーダが必要になってくるし、例えば画面全体に適当なスクリーンをかぶせて色調を
ある程度統一させるといったエフェクトをやるならば、効率的にはピクセルシェーダを使わなければならない。

ここらでDirectX10or11に行ってみるか。いったん何かしらまとまったものを作ってから。
奴らはプログラマブルシェーダ必須だからいろいろ弄れるだろうし、用意されたスプライトクラスも
DirectX9のころとは違って即時描画じゃなくて一応そこそこのバッチ処理をしてくれるようになっているっぽい。
ついでにどうやらうちのPCはPS,VSともに3.0以上っぽいのでだいたいのことは試せるみたい。
ストリームソースを2つ使って、片方を反復して使用するような設定にしてやれば
(名前から察するにたぶんSetStreamSourceFreqだろうか)ハードウェアインスタンシングも可能か。



さて、DirectInputの使い方。ほかのDirectX系と同じく、
基本となるDirectInputインタフェースの作成→使用する各デバイス管理インタフェースの作成、
という具合でやってやればだいたい大丈夫っぽい。

ところでLinaxとかだと外部の入力装置ってってどうやって取得するんですかね。
たぶん直接、割と低レベルでの操作しないといけないからわけわからんのでしょうな。

1.DirectInputの初期化

基本的にはDirectInput8Create()の呼び出しに成功すればDirectInputそのものの初期化、
というよりはインタフェースの取得は完了する。
引数として入力をとるアプリケーションのインスタンスハンドルとバージョン情報が必要となるが、
どうやらバージョン情報のマクロみたいなのは定義されていないみたいなので上のようにしてやるか、
直接バージョンを指定する。インスタンスハンドルはメインの引数から持ってきてもいいし、
上のようにGetModuleHandle()に0を指定することで取得できるのでめんどくさいならそれでいいと思う。
ただし、GetModuleHandle(0)はDLL内で呼ぶとDLLのハンドルを返してくれるとかなんとかなので注意。

で、あとはマウス・キーボード・ゲームパッドと、デバイスを取得してやればいい。
マウス・キーボードはデフォルトの指定があるので簡単だが、ゲームパッドはつながっているか
わからないので列挙関数を使って調べ上げてやる必要があるみたいだ。

詳細は割愛。要は上のように取得したDirectInput本体を通して、
入力デバイスであるIDirectInputDevice8へのポインタを取得しさえすればいい。
あとは適当にフォーマットと強調レベル(ウィンドウが後ろでも入力取るかとか)を
それっぽい感じに指定すればよい。

と、ここまでですでにすべてのデバイスで入力を感知できるようになるわけですね。
DirectXって一つづつ見ていけば割と主要な動作は短くて簡潔だと最近気づいた。

2.入力インタフェースの管理
ということで入力デバイスを管理するクラスを考えてみる。

とりあえず入力情報として知りたいのは、「今押されているか」、
「ちょうど押されたところか」、「ちょうど離されたところか」、といったタイミングでしょうか。
となると、前回入力を保存しておいて入力があったかなかったかを見てやればいいことになります。
キーボードにもマウスにもゲームパッドにもボタンはありますから、マウスの指示やゲームパッドの
アナログ入力とか言ったのはそれぞれ固有の能力として、ほぼ共通のインタフェースで
ボタン押下を取得できるようなインタフェースにしたらいいかなと考えました。

とりあえず次のようなインタフェースを実装することにします

IRefferenceCountは参照カウンタを埋め込むためのクラスなので気にしないでください。
boostのintrusive_ptrみたいなもんだと思う。もしくはCOMインタフェースとか。
これらは、KEYBOARD_BUTTON・GAMEPAD_BUTTON・MOUSE_BUTTON列挙子で与えられたボタンが、
STATE_TYPE列挙子の状態であるかどうかを返すような関数をなっています。
KEYBOARD_BUTTON・GAMEPAD_BUTTON・MOUSE_BUTTONは長ったらしいので、ひたすら
キーボードのキーとパッドのボタン、マウスのボタンが列挙してあるだけだと思ってください。

STATE_TYPEは先に述べたようにどの状態を判定するのかをただ単に列挙しただけです。
あとは、これらのインタフェースを継承したクラスを実装します。
これでとりあえずDirectInputによる取得でもAPIによる取得でも、中の実装をがんばれば
ある程度同様に扱うことができるようになりました。

3.仮想入力機器の管理
さてこれでそれなりに一般的に入力を扱えるようになったわけですが、
例えばキーボードの左矢印と、パッドの左入力は同様なものとして扱いたいと思います。
そこで、上記3つの入力デバイスを一元管理する仮想入力機器を用意します。
3つのデバイスを登録することでそれらをすべて同一の「コントローラ」として扱うことが目的です。

とりあえずコンストラクタで3つのデバイスを受け取り、派生クラスでそれらを組み合わせて
入力処理とキーコンフィグを実装してもらいます。
軸入力および位置入力などはおそらくそれぞれゲームパッドとマウスのメソッドをそのまま
リダイレクトすれば十分だと思います。
問題は、それぞれのデバイスはそれぞれボタンの数が大きく違い、それらをまとめて
CONTROL_BUTTON列挙子による単一のフラグでどのように処理を振り分けるかです。
あんまりいい方法が思いつかなかったのでとりあえずデフォルト実装は次のようにしました。

まず上記のように、今考えるコントローラのボタンの個数だけButtonSet構造体を保持します。
するとキーコンフィグメソッドは次のように実装できると思います。

デフォルト設定はあまりfor文を回せるような構造でないので仕方なくごり押しで代入です。
そして、各種デバイスとコントローラとの紐づけは次のようになります。

それぞれ指定のキー情報をキーコンフィグ情報に設定してやります。
配列でキーコンフィグ情報を保持しているので、列挙型の特性上ピンポイントでアクセスできます。
またこれによって、CONTROL_BUTTON列挙子の各値とキーボード・マウス・パッドの各列挙子を
それぞれ紐づけすることが可能となります。これを使ってボタン入力の取得を次のようにしてやります。

まぁまるで効率的ではないですね。それぞれのデバイスが存在していた場合、
キーコンフィグに何らかのボタンが設定されていたら、それを引数として各入力デバイスから
入力を取得し、最後にその論理和を返してやります。



とりあえずこれで3つのデバイスとそれを一元管理するコントローラクラスを実装できました。
クライアント側では次のようにそれなりにわかりやすい記述で使うことができます。

名前空間で囲むと名前修飾があほみたいなことになるのどうにかなりませんかね。
何回コロン打てばいいんだよ、みたいな。


こんな感じで今回は入力の一元管理方法についてやってみたということで。
たださっぱり効率が良くない気がするので、なんかいい方法思いついたら誰か教えてくれって感じですね。


NISHI::~NISHI(){finish();}





スポンサーサイト

Comment

No:223|承認待ちコメント
このコメントは管理者の承認待ちです

コメントの投稿

Comment
管理者にだけ表示を許可する
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。