ゲーム開発においてシングルトンパターンでオブジェクトを管理したい部分はどうしてもあります。
シングルトンパターン特有の冗長な<クラス名>getInstance()->… の記述を省略する方法はあるでしょうか?
それは例えばゲームコントローラなど。
ゲームコントローラはゲームに対して複数ありませんから(Iコン、IIコンなどの話ではない)
そのメソッドを呼び出しする際に以下のように書きます。(中身はかなり端折って記載)
まあこんなコードがあったとしましょう(って実際のコード引用だけど😅
// Singleton.h namespace abstract__ { // Singleton pattern class. template <class T> class MysticObject { private: MysticObject(const MysticObject& args) = delete; MysticObject& operator=(const MysticObject& args) = delete; MysticObject(MysticObject&&) = delete; MysticObject& operator=(MysticObject&&) = delete; protected: MysticObject() {}; virtual ~MysticObject() {}; public: static T* getInstance(void) { static T obj; return &obj; } }; template <class T> using Singleton = MysticObject<T>; } // namespace abstract__
// inputkey.h #include <array> #include <DxLib.h> #include "mystic_object.h" namespace { typedef struct tC16Button { std::array<__int32, g_joypadkey> button_; } *LPtC16Button; } namespace input { enum class JOYPAD_BUTTON { UP , DOWN , LEFT , RIGHT , START , BACK , LSTICK , RSTICK , L , R , LZT , RZT , A , B , X , Y }; class InputKeys final : public abstract__::Singleton<InputKeys> { friend abstract__::Singleton<InputKeys>; private: tC16Button joybtn_; // ジョイパッドボタン配列。 tC16Button joybtn_released_; // ジョイパッドボタン配列。(解放時間用) int xinput_btn[g_joypadkey] = { XINPUT_BUTTON_DPAD_UP , XINPUT_BUTTON_DPAD_DOWN , XINPUT_BUTTON_DPAD_LEFT , XINPUT_BUTTON_DPAD_RIGHT , XINPUT_BUTTON_START , XINPUT_BUTTON_BACK , XINPUT_BUTTON_LEFT_THUMB , XINPUT_BUTTON_RIGHT_THUMB , XINPUT_BUTTON_LEFT_SHOULDER , XINPUT_BUTTON_RIGHT_SHOULDER , 10 , 11 , XINPUT_BUTTON_A , XINPUT_BUTTON_B , XINPUT_BUTTON_X , XINPUT_BUTTON_Y }; public: InputKeys() : joybtn_{} {}; ~InputKeys() = default; bool updateJoyBtnStateKey(void); __int32 getPressJoyBtnKey(JOYPAD_BUTTON btn_num) const; __int32 getReleaseJoyBtnKey(JOYPAD_BUTTON btn_num) const; }; } // namespace input
// inputkey.cpp #include "inputkey.h" #include <numeric> #include <DxLib.h> namespace input { bool InputKeys::updateJoyBtnStateKey(void) { XINPUT_STATE input; std::array<char, 256> key_state_array; if (isXInputEnabled()) { GetJoypadXInputState(DX_INPUT_KEY_PAD1, &input); // joybtn_.button_; // 代入... // joybtn_released_.button_; // 代入... } else { GetHitKeyStateAll(key_state_array.data()); // joybtn_.button_; // 代入... // joybtn_released_.button_; // 代入... } return true; } __int32 InputKeys::getPressJoyBtnKey(JOYPAD_BUTTON btn_num) const { return joybtn_.button_[static_cast<int>(btn_num)]; } } // namespace input
// main.cpp #include <DxLib.h> #include "inputkey.h" namespace main { common::Evaluate Service(common::Evaluate evals) { // NOTE:基本的に1フレームで行う処理を順次実行させる形で記載する。1フレームより多いループ処理は行わない。(暗転中ローディング処理などは例外) using namespace input; if (!InputKeys::getInstance()->updateJoyBtnStateKey()) { (void)stream::writeStatusLog("", "ジョイパッドの更新に失敗しました。", "エラーログ", stream::LogClass::LOG_LEVEL_ERROR); return common::Evaluate::PROC_FAILED; } DrawString(0, 0, "ゲーム起動中...", GetColor(255, 255, 255)); if (InputKeys::getInstance()->getPressJoyBtnKey(JOYPAD_BUTTON::LZT)) { (void)stream::writeStatusLog("", "Lトリガーが押されました!", "処理ログ", stream::LogClass::LOG_LEVEL_INFO); return common::Evaluate::PROC_QUIT; } if (ScreenFlip()) return common::Evaluate::PROC_FAILED; // 画面描画 return common::Evaluate::PROC_SUCCEED; } } // namespace main
main.cppのよく分からないコードは無視するとして、問題なのは
InputKeys::getInstance()->getPressJoyBtnKey(JOYPAD_BUTTON::LZT)
のコードです。この場合だとゲームコントローラのLトリガーの入力を監視しているコードになります。
長い(´・ω・`)
特にゲーム開発においてキー入力制御はメインの入力デバイスなので、この問題は可読性の良し悪しに影響を及ぼしてくると考えた私はこのコードを簡略化できないか?を模索し始めるわけです。
そして辿り着いた策がこちら。
// inputkey.h (2) #include <array> #include <DxLib.h> #include "mystic_object.h" /* DEFINE MACROS */ #define GController InputKeys::getInstance() #define GetKey InputKeys::getInstance()->getPressJoyBtnKey namespace { typedef struct tC16Button { std::array<__int32, g_joypadkey> button_; } *LPtC16Button; } namespace input { enum class JOYPAD_BUTTON { ... (以下同じ
せっかく元のプログラムからC++に昇格させたのに、問題の対応がC言語のプリプロセッサ・ディレクティブ・マクロというクソコードっぷり
なんの解決にもなってない😇
むしろイシュー行きの原点回帰もいいところです🤤
哀れな愚策ですが、結末は以下のようになりました。
// main.cpp (2) #include <DxLib.h> #include "inputkey.h" namespace main { common::Evaluate Service(common::Evaluate evals) { // NOTE:基本的に1フレームで行う処理を順次実行させる形で記載する。1フレームより多いループ処理は行わない。(暗転中ローディング処理などは例外) using namespace input; if (!GController->updateJoyBtnStateKey()) { (void)stream::writeStatusLog("", "ジョイパッドの更新に失敗しました。", "エラーログ", stream::LogClass::LOG_LEVEL_ERROR); return common::Evaluate::PROC_FAILED; } DrawString(0, 0, "ゲーム起動中...", GetColor(255, 255, 255)); if (GetKey(JOYPAD_BUTTON::LZT)) { (void)stream::writeStatusLog("", "Lトリガーが押されました!", "処理ログ", stream::LogClass::LOG_LEVEL_INFO); return common::Evaluate::PROC_QUIT; } if (ScreenFlip()) return common::Evaluate::PROC_FAILED; // 画面描画 return common::Evaluate::PROC_SUCCEED; } } // namespace main
GetKey(JOYPAD_BUTTON::LZT)
短い!!😃
やったああああああ!
(よくねぇよ💢💢