さて、前準備も後半。9章までになりますので、もうしばし辛抱してください。
この章ではキーボードの入力状態を管理するクラスを作ります。
この章で説明することはゲームプログラミングの館をご覧頂いている方にはおなじみです。
こちらです。
https://dixq.net/g/02_09.html
こちらの仕組みをちょこっと手を加えてクラス化しただけです。
DXライブラリにはCheckKeyInputというキーボードの入力状態を0か1で返す関数がありますが、これだけでは不十分です。
windowsプログラミングやAndroidアプリプログラミングをしていると、キーが押された瞬間に発火する関数があります。
onClick(int keycode);
みたいな関数が発火して、今の瞬間押されたことが検出できます。
しかしDXライブラリにはそのような機能は無いので、常にキーボードの入力状態を監視し、
前のフレームでは押されていなかったが、今のフレームで押されていたら、今の瞬間押されたんだということを識別できるようにしなければなりません。
また、0.5秒以上押し続けていたらキーリピートしたいといったことを実現するためにも
特定のキーが何フレーム押されているのか、特定のキーが何フレーム離されているのか、を取得できるようにしてみましょう。
さて、今回はシングルトンを利用します。
先の章でも言いましたが、シングルトンパターンは初心者がつかうとダメな設計になります。
シングルトンとはC言語のグローバル変数の言い訳でしかありません。
このようなシングルトンパターンは使わずに設計可能です。可能な限り使わないように設計しましょう。
とは言え、今回私が紹介するように、状態の更新やメンバ変数の更新のトリガは必ず管理部で行い、
その他はReadOnlyになるようにするのであればシングルトンパターンのデメリット部分がかなり削減できます。
このようにあちこちから書き換えが起きないように設計していることに着目してご覧ください。
Singleton.h
#pragma once template <typename _T> class Singleton { protected: Singleton() = default; virtual ~Singleton() = default; Singleton(const Singleton& r) = default; Singleton& operator=(const Singleton& r) = default; public: static _T* getIns() { static _T inst; return &inst; }; };
これは
https://dixq.net/g/
sp.1章、sp.2章にて解説していますので、今回省略します。そのコードをほぼそのまま流用しただけです。
シングルトンクラスの抽象クラスです。以下キーボードクラスに継承します。
Keyboard.h
#pragma once #include "Singleton.h" #include <array> class Keyboard final : public Singleton<Keyboard> { Keyboard() = default; friend Singleton< Keyboard >; public: bool update(); //更新 int getPressingCount(int keyCode);//keyCodeのキーが押されているフレーム数を取得 int getReleasingCount(int keyCode);//keyCodeのキーが離されているフレーム数を取得 private: static const int KEY_NUM = 256; //キー総数 std::array<int, KEY_NUM> _pressingCount;//押されカウンタ std::array<int, KEY_NUM> _releasingCount;//離されカウンタ bool isAvailableCode(int keyCode);//keyCodeが有効なキー番号か問う };
Keyboard.cpp
#include "Keyboard.h" #include <DxLib.h> bool Keyboard::update() { char nowKeyStatus[KEY_NUM]; GetHitKeyStateAll(nowKeyStatus); //今のキーの入力状態を取得 for (int i = 0; i<KEY_NUM; i++) { if (nowKeyStatus[i] != 0) { //i番のキーが押されていたら if (_releasingCount[i] > 0) {//離されカウンタが0より大きければ _releasingCount[i] = 0; //0に戻す } _pressingCount[i]++; //押されカウンタを増やす } else { //i番のキーが離されていたら if (_pressingCount[i] > 0) { //押されカウンタが0より大きければ _pressingCount[i] = 0; //0に戻す } _releasingCount[i]++; //離されカウンタを増やす } } return true; } /*! @brief keyCodeのキーが押されているフレーム数を返す */ int Keyboard::getPressingCount(int keyCode) { if (!isAvailableCode(keyCode)) { return -1; } return _pressingCount[keyCode]; } /*! @brief keyCodeのキーが離されているフレーム数を返す */ int Keyboard::getReleasingCount(int keyCode) { if (!isAvailableCode(keyCode)) { return -1; } return _releasingCount[keyCode]; } /*! @brief keyCodeが有効な値か否かを返す */ bool Keyboard::isAvailableCode(int keyCode) { if (!(0 <= keyCode && keyCode<KEY_NUM)) { return false; } return true; }
これもゲームプログラミングの館で紹介しているコードをほぼそのまま流用しています。
前の入力状態と今の入力状態を比較して、
押し中を示すpressingCountを加算したり、
離し中を示すreleasingCountを加算したりしており、
getPressingCountやgetReleasingCountを通じて外からキーボードの入力状態を取得できるようにしています。
ではTitleScene.cppで使っていたCheckHitKeyを新しく作ったKeyboardクラスに置き換えてみます。赤字部が変更個所です。
TitleScene.cpp
#include "TitleScene.h" #include <DxLib.h> #include "GameScene.h" #include "Define.h" #include "Keyboard.h" TitleScene::TitleScene(IOnSceneChangedListener* impl, const Parameter& parameter) : AbstractScene(impl, parameter) { } void TitleScene::update() { if (Keyboard::getIns()->getPressingCount(KEY_INPUT_E) == 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" using namespace std; Looper::Looper() { Parameter parameter; _sceneStack.push(make_shared<TitleScene>(this, parameter)); //タイトル画面シーンを作ってpush } /*! @brief スタックのトップのシーンの処理をする */ bool Looper::loop() { Keyboard::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; } }
getPressingCountに確認したいキーIDを渡すと何フレーム押されているかが返って来ます。
1が返って来たということは、押されてから1フレーム目、まさに今の瞬間押されたことが分かります。
また、Keyboardクラスはどこからか更新をかける必要があります。
Looper::loop()内からその更新をかけています。
さて、退屈な前準備も次の章で終わりです。次はこれをジョイパッドに適用させましょう。