さて、面倒な前準備も最後です。
入力系は最初にやっておかないと後で作ると既存コードをあちこち書き換えないといけないので先にやってしまいます。
この章ではジョイパッドコントローラーに適用させます。
ジョイパッドで困るのがジョイパッドの入力状態を取得する関数で取得してきた値において、
対応する番号と想定しているボタンの対応がメーカーによってバラバラだということです。
例えばPlayStationのコントローラーの〇の位置にあるボタンを押したとしても、
PSコントローラーならID「5」で返ってくるが、特定のジョイパッドならID「6」で返ってくる等、バラバラです。
更に、ショットボタンは私は△ボタンにしたい、×ボタンにしたい、といったパッドコンフィグにも対応させたいとすると
ボタンIDと対応するボタンとの対応付けを管理する必要があります。
DXライブラリではジョイパッドの入力状態を取得する関数としてGetJoypadInputStateがあります。
これは返って来たint型がビットアサインとなっており、16個のボタンを16bitで識別する形になっています。
例えば
0bit目が0ならID0のボタンの入力はされていない、1ならID0のボタンの入力がされている。
1bit目が0ならID1のボタンの入力はされていない、1ならID1のボタンの入力がされている。
2bit目が0ならID2のボタンの入力はされていない、1ならID2のボタンの入力がされている。
・・・といった具合です。
例えばshort intを2進数で表すと
0000000000000000
このようになるわけですが、ID2のボタンの入力がされていたら
0000000000000100
となるわけです。
計算する時は、論理積の計算結果が0か0以外かで識別します。
論理積とは
0と0は0
1と0は0
0と1は0
1と1は1
という計算になりますから、例えば今のジョイパッドの入力状態が
1010011010110101
であったとして、ここからID2のボタンの入力状態を取り出す時は
0000000000000100 (すなわち1<<2)
との論理積の計算結果が
0なら押されていない、0以外なら押されているという事が分かります。
上記の計算結果は
1010011010110101 & 0000000000000100 → 0000000000000100
となります。100は10進数で4を意味しますから0以外のためID2は入力されていることが分かります。
このように
フィルタする位置
0000000000000100
この1の位置を一つずつずらしながら論理積を計算することで対応したIDのボタンの入力状態が分かるのです。
ではPadクラスの中身を見てみましょう。
Pad.h
#pragma once #include "Singleton.h" #include <array> enum ePad { left, up, right, down, shot, bom, slow, start, change }; class Pad final : public Singleton<Pad> { public: Pad(); ~Pad() = default; void update(); int get(ePad eID) const; //eIDのボタンの入力状態を取得 private: void merge(); const static int PAD_KEY_NUM = 16; std::array<int, PAD_KEY_NUM> _idArray; //どのボタンがどのボタンに割り当たっているかを示す std::array<int, PAD_KEY_NUM> _pad; //16ボタンのpad入力状態格納 };
Pad.cpp
#include "Pad.h" #include <DxLib.h> #include "Keyboard.h" #include "Define.h" #include <string> #include <algorithm> using namespace std; Pad::Pad() { _idArray[ePad::down] = 0; _idArray[ePad::left] = 1; _idArray[ePad::right] = 2; _idArray[ePad::up] = 3; _idArray[ePad::bom] = 4; _idArray[ePad::shot] = 5; _idArray[ePad::slow] = 11; _idArray[ePad::start] = 12; _idArray[ePad::change] = 8; } void Pad::update() { int padInput; padInput = GetJoypadInputState(DX_INPUT_PAD1);//パッドの入力状態を取得 for (int i = 0; i<16; i++) { if (padInput & (1<<i)) { _pad[i]++; } else { _pad[i] = 0; } } merge(); } /*! @brief パッドと、それに対応するキーボードの入力状態をマージする */ void Pad::merge() { _pad[_idArray[ePad::left]] = max(_pad[_idArray[ePad::left]], Keyboard::getIns()->getPressingCount(KEY_INPUT_LEFT)); _pad[_idArray[ePad::up]] = max(_pad[_idArray[ePad::up]], Keyboard::getIns()->getPressingCount(KEY_INPUT_UP)); _pad[_idArray[ePad::right]] = max(_pad[_idArray[ePad::right]], Keyboard::getIns()->getPressingCount(KEY_INPUT_RIGHT)); _pad[_idArray[ePad::down]] = max(_pad[_idArray[ePad::down]], Keyboard::getIns()->getPressingCount(KEY_INPUT_DOWN)); _pad[_idArray[ePad::shot]] = max(_pad[_idArray[ePad::shot]], Keyboard::getIns()->getPressingCount(KEY_INPUT_Z)); _pad[_idArray[ePad::shot]] = max(_pad[_idArray[ePad::shot]], Keyboard::getIns()->getPressingCount(KEY_INPUT_RETURN)); _pad[_idArray[ePad::bom]] = max(_pad[_idArray[ePad::bom]], Keyboard::getIns()->getPressingCount(KEY_INPUT_X)); _pad[_idArray[ePad::slow]] = max(_pad[_idArray[ePad::slow]], Keyboard::getIns()->getPressingCount(KEY_INPUT_LSHIFT)); _pad[_idArray[ePad::start]] = max(_pad[_idArray[ePad::start]], Keyboard::getIns()->getPressingCount(KEY_INPUT_ESCAPE)); _pad[_idArray[ePad::change]] = max(_pad[_idArray[ePad::change]], Keyboard::getIns()->getPressingCount(KEY_INPUT_LCONTROL)); _pad[_idArray[ePad::change]] = max(_pad[_idArray[ePad::change]], Keyboard::getIns()->getPressingCount(KEY_INPUT_C)); } /*! @brief 渡されたパッドキー番号の入力フレーム数を返す */ int Pad::get(ePad eID) const { return _pad[_idArray[eID]]; }
ePadのenumで使用するボタンを定義します。
四聖龍神録2では
・下キー
・左キー
・右キー
・上キー
・ボムキー
・ショットキー
・低速移動キー
・スタートキー
・変身キー
を利用するのでそれを定義しています。
publicなメソッドはupdate()とget(ePad eID)です。
updateはKeyboardと同様に1フレームに一度Looperからコールされます。
getは必要なクラスから呼び出します。
例えばショットキーが押されているフレーム数を取得したい時は
Pad::getIns()->get(ePad::shot)
こんな形で取得できるわけです。
private変数にはまず_idArrayがあります。
これはどのIDがどのボタンに対応するかという対応付けの情報を入れます。
コンストラクタでは、うちの環境で一番ちょうどいいボタン設定を入れてあります。
四聖龍神録2のキーコンフィグ画面で出てくる数値と同じですので、
ボタンの対応に何の数値を入れたらいいか分からない人は四聖龍神録2のキーコンフィグで押してみて確認してもよいです。
ようするに取れてくるビットアサインの何番目に対応するかという事を意味します。
update()ではパッドキーの入力状態を監視し、押されていたら_padをカウントアップします。
論理積の部分は先ほど紹介した理屈の通りです。
ビットアサインされている1bitずつ調べて_padに格納しています。
mergeは、キーボードとのマージです。
例えばジョイパッドの決定ボタンでも、キーボードのエンターでも同じ動きにしたい時、
ジョイパッドの決定ボタンとキーボードのエンターをマージします。
マージというと大げさですが、どちらか大きな値を_padに格納しているだけです。
maxはstdのalgorismにも、windows.hにも実装のある関数/マクロです。2つの引数の内大きな方を返します。
では先ほど同様TitleScene.cppをジョイパッド対応にしてみましょう
TitleScene.cpp
#include "TitleScene.h" #include <DxLib.h> #include "GameScene.h" #include "Define.h" #include "Keyboard.h" #include "Pad.h" TitleScene::TitleScene(IOnSceneChangedListener* impl, const Parameter& parameter) : AbstractScene(impl, parameter) { } void TitleScene::update() { if (Pad::getIns()->get(ePad::shot) == 1) { Parameter parameter; parameter.set(GameScene::ParameterTagLevel, Define::eLevel::Easy); const bool stackClear = false; _implSceneChanged->onSceneChanged(eScene::Game, parameter, stackClear); return; } if (Keyboard::getIns()->getPressingCount(KEY_INPUT_N) == 1) { Parameter parameter; parameter.set(GameScene::ParameterTagLevel, Define::eLevel::Normal); const bool stackClear = false; _implSceneChanged->onSceneChanged(eScene::Game, parameter, stackClear); return; } } void TitleScene::draw() const { DrawString(100, 100, "タイトル画面", GetColor(255,255,255)); }
Looper.cpp
#include "Looper.h" #include "TitleScene.h" #include "Error.h" #include "GameScene.h" #include "Macro.h" #include "Keyboard.h" #include "Pad.h" using namespace std; Looper::Looper() { Parameter parameter; _sceneStack.push(make_shared<TitleScene>(this, parameter)); //タイトル画面シーンを作ってpush } /*! @brief スタックのトップのシーンの処理をする */ bool Looper::loop() { Keyboard::getIns()->update(); //キーボードの更新 Pad::getIns()->update(); //ジョイパッドの更新 _sceneStack.top()->update(); //スタックのトップのシーンを更新 _sceneStack.top()->draw(); //スタックのトップのシーンを描画 _fps.draw(); //FPSの表示 _fps.wait(); //設定したFPSになるように待機 return true; } /*! @brief シーン変更(各シーンからコールバックされる) @param scene 変更するシーンのenum @param parameter 前のシーンから引き継ぐパラメータ @param stackClear 現在のシーンのスタックをクリアするか */ void Looper::onSceneChanged(const eScene scene, const Parameter& parameter, const bool stackClear) { if (stackClear) {//スタッククリアなら while (!_sceneStack.empty()) {//スタックを全部ポップする(スタックを空にする) _sceneStack.pop(); } } switch (scene) { case Title: _sceneStack.push(make_shared<TitleScene>(this, parameter)); break; case Game: _sceneStack.push(make_shared<GameScene>(this, parameter)); break; default: ERR("あるはずのないシーンが呼ばれました"); break; } }
これでジョイパッドに対応できました。
PCにお使いのジョイパッドを使いで確認してみてください。
四聖龍神録2でショットキーに当たるボタンを押すとシーンが変わるはずです。
ePad::shotはZキーとエンターキーにマージしていますので、ジョイパッドが無い時はZキーかエンターキーを押しても同じことになります。
さて、ようやく前準備が終わりましたので、具体的な見える形の作り込みをしていきましょう。