DXライブラリ向け 簡易キー入力クラス
はじめに
DXライブラリで、キー入力の開始や終了を取る際、自前で機能を用意する必要があります。
必要になるたびに作るのが面倒そうなので、キー入力クラステンプレート的なものを置いときます。
対象は、キーボードとマウスです。
昔、DxLibをラップした自作ライブラリで学習がてら作ったものから、
切り出しだけなので、気になる部分があれば、適当に調整してください。
実装
押した瞬間、押している間、離した瞬間 が取れる実装です。
ビルド環境:Microsoft Visual C++ 2015
(長いので折り畳み)
#include <DxLib.h>
#include <array>
namespace dxlut
{
// 入力クラス
struct Input final
{
// 入力情報更新
static bool Update() {
Mouse::Update();
return Keyboard::Update();
}
// 何らかのキーの入力開始
static bool AnyKeyPress() {
return DxLib::CheckHitKeyAll() != 0;
}
// 何らかのキーの入力中
static bool AnyKeyDown() {
return Keyboard::AnyDown() || Mouse::AnyDown();
}
// 何らかのキーの入力終了
static bool AnyKeyUp() {
return Keyboard::AnyUp() || Mouse::AnyUp();
}
// キーボード
struct Keyboard final
{
typedef std::array<char, 256> KeyStateBuffer;
// 入力情報更新
static bool Update() {
keyBuffer_.swap(prevKeyBuffer_);
return GetHitKeyStateAll(&keyBuffer_.front()) == 0;
}
// 入力開始
static bool IsPress(const int keyCode) {
return keyBuffer_[keyCode] ? true : false;
}
// 入力中
static bool IsDown(const int keyCode) {
return keyBuffer_[keyCode] && !prevKeyBuffer_[keyCode];
}
// 入力終了
static bool IsUp(const int keyCode) {
return !keyBuffer_[keyCode] && prevKeyBuffer_[keyCode];
}
// 何らかのキーの入力開始(キーボード)
static bool AnyPress() {
for (unsigned idx = 0; idx < keyBuffer_.size(); ++idx) {
if (keyBuffer_[idx] != 0) {
return true;
}
}
return false;
}
// 何らかのキーの入力中(キーボード)
static bool AnyDown() {
for (unsigned idx = 0; idx < keyBuffer_.size(); ++idx) {
if (keyBuffer_[idx] && !prevKeyBuffer_[idx]) {
return true;
}
}
return false;
}
// 何らかのキーの入力終了(キーボード)
static bool AnyUp() {
for (unsigned idx = 0; idx < keyBuffer_.size(); ++idx) {
if (!keyBuffer_[idx] && prevKeyBuffer_[idx]) {
return true;
}
}
return false;
}
private:
static KeyStateBuffer keyBuffer_;
static KeyStateBuffer prevKeyBuffer_;
};
// マウス
struct Mouse final
{
// 入力情報更新
static void Update() {
backKeyBuffer_ = keyBuffer_;
keyBuffer_ = GetMouseInput();
}
// 入力開始
static bool IsPress(const int keyCode) {
return (keyBuffer_ & keyCode) != 0;
}
// 入力中
static bool IsDown(const int keyCode) {
return ((keyBuffer_ & keyCode) != 0) && !((backKeyBuffer_ & keyCode) != 0);
}
// 入力終了
static bool IsUp(const int keyCode) {
return !((keyBuffer_ & keyCode) != 0) && ((backKeyBuffer_ & keyCode) != 0);
}
// 何らかのボタン入力開始(マウス)
static bool AnyPress() {
return keyBuffer_ != 0;
}
// 何らかのボタン入力中(マウス)
static bool AnyDown() {
return (keyBuffer_ != 0) && !(backKeyBuffer_ != 0);
}
// 何らかのボタン入力終了(マウス)
static bool AnyUp() {
return !(keyBuffer_ != 0) && (backKeyBuffer_ != 0);
}
private:
static int keyBuffer_;
static int backKeyBuffer_;
};
};
Input::Keyboard::KeyStateBuffer Input::Keyboard::keyBuffer_ = {};
Input::Keyboard::KeyStateBuffer Input::Keyboard::prevKeyBuffer_ = {};
int Input::Mouse::keyBuffer_ = 0;
int Input::Mouse::backKeyBuffer_ = 0;
// DXライブラリ簡易ラップクラス
struct DxLibApp final
{
DxLibApp()
{
SetGraphMode(1024, 600, 32);
ChangeWindowMode(TRUE);
if (DxLib_Init() == -1) {
throw;
}
SetDrawScreen(DX_SCREEN_BACK);
}
~DxLibApp() {
DxLib_End();
}
static bool Update() {
return (ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0 && Input::Update());
}
} _app;
}
// エントリーポイント
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
using namespace dxlut;
// メインループ
while (DxLibApp::Update())
{
// Esc 終了
if (Input::Keyboard::IsDown(KEY_INPUT_ESCAPE)) {
break;
}
// とりあえず、「キーボードのZ」と「マウス左クリック」の入力検査サンプル
// 他のキーは、キーコードだけ変えればよし
// Zキーの状態
if (Input::Keyboard::IsDown(KEY_INPUT_Z)) {
DxLib::printfDx(_T("[Z] 入力開始\n"));
}
if (Input::Keyboard::IsPress(KEY_INPUT_Z)) {
DxLib::printfDx(_T("[Z] 入力中\n"));
}
if (Input::Keyboard::IsUp(KEY_INPUT_Z)) {
DxLib::printfDx(_T("[Z] 入力終了\n"));
}
// 左クリックの状態
if (Input::Mouse::IsDown(MOUSE_INPUT_LEFT)) {
DxLib::printfDx(_T("[Mouse L] 入力開始\n"));
}
if (Input::Mouse::IsPress(MOUSE_INPUT_LEFT)) {
DxLib::printfDx(_T("[Mouse L] 入力中\n"));
}
if (Input::Mouse::IsUp(MOUSE_INPUT_LEFT)) {
DxLib::printfDx(_T("[Mouse L] 入力終了\n"));
}
}
return 0;
}
あとがき
たまたま、DXライブラリ製の小物プログラムを改修する機会がありました。
入力周りの制御をネットで調べると意外と簡易的なものが見つからなかったので、昔のコードを掘り返し備忘録行き。
DXライブラリは、よく使いそうな便利関数も自分で用意しないといけない部分が多い印象です。
この点において、イマドキだとUnityだったり、C++ならSiv3D(OpenSiv3D)が便利関数豊富で初心者が取っつきやすい環境かなと感じています。
WSH WSF JScript・VBScript をバッチに埋め込む方法(現在時刻を読み上げるバッチファイル)
メモ
タイトルの通り、wsf 形式のWSHスクリプトをバッチファイルに埋め込む方法です。
wsfを使えば、VBScript と JScript を混在させたり、
HTML の scriptタグ と同じように記述できるのでCDNを利用できます。
バッチに埋め込むメリットは、用途次第ということで省略。
コマンド
おまじない部分は、下記の通り。
<!-- : @%windir%\System32\cscript.exe //nologo "%~f0?.wsf" %* @exit /b %errorlevel% -->
これを駆使して、現在時刻を読み上げる機能を作ると下記のようになります。
<!-- : @%windir%\System32\cscript.exe //nologo "%~f0?.wsf" %*||pause @exit /b %errorlevel% --> <!-- : WSH WSF をバッチに埋め込む方法 サンプル: 現在時刻を読み上げるバッチファイル --> <job> <script language="VBScript"> ' 現在時刻を返すだけ の関数 Function GetDateTime() GetDateTime = Now End Function </script> <script language="JScript"> /** * @brief エントリーポイント用即時関数 */ WSH.Quit(function () { var sp = WSH.CreateObject("SAPI.SpVoice"); // 現在時刻を読み上げる sp.Speak(GetDateTime()); return 0; }()); </script> </job>
参照
埋め込む方法は、下記から。 stackoverflow.com
WSH JScript Chakra を使用した ES2015(ES6) 対応 ( スクリプトエンジン まとめ )
はじめに
WSH JScript (wscript cscript) で使用できる スクリプトエンジン まとめです。
JavaScript限定、プリインストールされている物だけなので注意。
ツールとして独立しているChakraCoreや、その他サードパーティーは含みません。
各 JavaScript エンジン について
JScript (JavaScript) だけでも、4種類ほどスクリプトエンジンがあるようです。
以下、それぞれバッチファイルから cscript で実行する際のサンプルコマンドです。
JScript
JScript を使用する際、一般的に指定されるスクリプトエンジンです。
バージョンは、環境によって異なりますが、
現時点のOSサポートを考慮すれば、5.7 か 5.8 でしょう。
rem jscript.dll %windir%\System32\cscript.exe /nologo /E:JScript "%~f0" %*
JScript.Compact
JScript Compact Profile (ECMA 327) で実行する、スクリプトエンジンです。
MSの情報が少ないので、詳細は未確認ですが
簡単に説明すれば、「いくつかの機能を制限して、パフォーマンス向上を図る設定を使用する」と言った感じでしょうか。
機能の制限について、少なくとも with ステートは、サポートされていないようです。
rem JScript Compact Profile (ECMA 327) %windir%\System32\cscript.exe /nologo /E:JScript.Compact "%~f0" %*
JScript9
IE9 から使用されている、JScript9.dll をスクリプトエンジンとして指定できます。
ProgID が公開されていないので、CLSID を指定する必要があります。
ちなみに、JScript9 の 9 は、エンジンバージョンと言うわけでは無い様で、
Windows10 では、バージョン 11.0 として扱われていました。(中身は、Chakra っぽい?)
JScript と同程度の機能しか使えませんが、JScript9.dll の方が高速に動作します。
ただし、JScript 互換は完全ではなく WScript.Quit が使用できません。
rem jscript9.dll %windir%\System32\cscript.exe /nologo /E:{16d51579-a30b-4c8b-a276-0ff4dc41e755} "%~f0" %*
Chakra
Microsoft Edge の JavaScriptエンジン です。
こちらも、ProgID が公開されていないので、CLSID を指定する必要があります。
Chakra を使用する場合は、ES2015(ES6) に対応
している為、クラスなどが使用できます。
ただし、JScript9 と同様に、WScript.Quit に加え GetObject、VBArray 等が、使用できません。
rem chakra.dll %windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*
Chakraを使用したスクリプト を バッチに埋め込む場合
JScript9までは、@if を使用してバッチファイルにJScriptを埋め込むことが出来ましたが、
Chakra は、IE由来の条件付きコンパイルをサポートしていないため、@if を解釈できません。
JScript でハマる日々 - m2
@if(0==0) /* @rem 指定できるエンジンは、JScript9 まで。Chakra 不可 @%windir%\System32\cscript.exe /nologo /E:JScript "%~f0" %*||pause @exit /b %errorlevel%&*/@end
JScript共通の埋め込み
Chakra 以外のエンジンを共存させる場合は、
@if を使用しないshebangモドキがあるので、そちらを使います。
ildar-shaimordanov/hello.jscript.bat
0</* :: @%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*||pause @exit /b %errorlevel%&*/0;
Chakra限定の埋め込み
Chakra のみ使う場合は、
Chakra が「<!--」をコメント扱いすることを利用した方法があります。
https://qiita.com/snipsnipsnip/items/50e4ca88e3ce3f8cffda
<!-- : ^ /* @%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*||pause @exit /b %errorlevel%&*/
JScript9 や Chakra で、戻り値を返したい場合(2022/05/28 更新)
WScript.Quit は、例外を握りつぶすと使えます。
.bat
にコピペで、動作確認できます。
0</* :: @%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %* @echo 戻り値は、%errorlevel%です。&pause @exit /b %errorlevel%&*/0; // エントリ let global_result=(()=>{ // ポップアップを表示 // ActiveXObject は未定義なので、CreateObject を使う const shell = WSH.CreateObject("WScript.Shell"); shell.popup(`${WSH} for Chakra`); return 123; })(); try {WScript.Quit(global_result); }catch(e){// JavaScript runtime error: Object doesn't support this action }
補足
一応触れておくとChakraCore
を利用する場合、
WScript.Quit は定義済みで普通に呼び出せるので、このような悩みとは無縁です。
https://github.com/microsoft/ChakraCore/blob/bd91335361ddd215622d810cc7341a180abd2db4/bin/ch/WScriptJsrt.cpp#L1090
おわりに
WSH ってだけで、今更感満載のレガシーなのですが、
初期状態のWindowsで使用できる手軽さで、根強く?使われている様子。
今回のまとめは、WSH JScript で、クライアントの環境に依存せずに
ES2015 ベースのコードを使えないか調査したときの副産物です。
結果的に、ES2015 対応は行っていませんが、
TypeScript 経由で、対応できそうかなと言ったところでオチ。
参考リンク
- リファレンス
- JScript.Compact
- ECMAScript - Wikipedia
ECMA 327 について簡単な説明 - 'with' not available in the ECMA 327 Compact Profile Microsoft Docs
with が使用できない点について、MSのリファレンス
- ECMAScript - Wikipedia
- Chakra
- Chakra - Wikipedia
JScript9.dll を含めた簡単な概要
- Chakra - Wikipedia
WSH JScript JSONファイルを使用する
はじめに
WSH JScript で、設定ファイルに対応する際に使用した方法です。
WSH(VBScript, JScript) から設定ファイルを扱う場合、JSON を使用すると楽できます。
注意
文字コードは、SJIS を使用しています。
それ以外の文字コードでは、動作検証していません。
また、Chakra をスクリプトエンジンとして利用する場合は、下記を参照。
WSH JScript で、JSONファイルをパースする(Chakra を利用する場合) - イネマルのプログラミング備忘録
実装
- sample.json
{ "Date": { "Year": 2018, "Date": "03/21", "Time": "12:34" }, "Users": [ { "Country": "Austria", "Name": "Hans Meier", "Age": 18 }, { "Country": "Canada", "Name": "Joe Blow", "Age": 19 }, { "Country": "Japan", "Name": "山田 太郎", "Age": 20 } ] }
- JSONParser.bat
@if(0)==(0) echo off title %~n0 %windir%\System32\cscript.exe //nologo //E:JScript "%~f0" %* echo;&echo;&pause&goto:EOF @end /** * @brief エントリーポイント用即時関数 */ WSH.Quit(function () { // 初期化 var jsonFilePath = ".\\sample.json"; var savePath = ".\\output.json"; var parser = GetJSONParser(jsonFilePath); var fso = parser.GetFileSystem(); if (!fso.FileExists(jsonFilePath)) { WSH.Echo("ファイルが見つかりません。"); return -1; } // 解析 try { var conf = parser.Parse(jsonFilePath); } catch(e){ WSH.Echo("解析に失敗しました。\n" + e); return -2; } // 読み込み WSH.Echo(conf.Date.Year); WSH.Echo(conf.Date.Date); WSH.Echo(conf.Date.Time); for (var idx = 0; idx < conf.Users.length; idx++) { WSH.Echo("----------------"); var user = conf.Users[idx]; WSH.Echo(user.Country); WSH.Echo(user.Name); WSH.Echo(user.Age); } // 保存 // ユーザー情報だけ、保存 parser.Save(savePath, conf.Users); return 0; }()); /* * @brief JSONParser 取得 * @return JSONParser オブジェクト * @note * JSON Object (Windows Scripting - JScript) * https://msdn.microsoft.com/en-us/library/cc836458(v=vs.84).aspx */ function GetJSONParser() { return new((function () { // コンストラクタ var JSONParser = function () { this.fso = WSH.CreateObject("Scripting.FileSystemObject"); var htmlfile = WSH.CreateObject("htmlfile"); htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=9"/>'); htmlfile.close(this.parser = htmlfile.parentWindow.JSON); } // アクセサ var p = JSONParser.prototype; p.GetFileSystem = function () {return this.fso;} /** * @brief 解析 * @param[in] path jsonファイルのパス * @return 解析結果 */ p.Parse = function (path) { var jsonFile = this.fso.OpenTextFile(path, 1, true); var jsonText = jsonFile.ReadAll(); jsonFile.Close(); return this.parser.parse(jsonText); } /** * @brief 保存 * @param[in] path 保存先のパス * @param[in] obj 保存するデータ */ p.Save = function (path, obj) { var jsonFile = this.fso.OpenTextFile(path, 2, true, -2); jsonFile.Write(this.parser.stringify(obj)); jsonFile.Close(); } return JSONParser; })())(); }
あとがき
ちなみに、保存機能を考慮しなければ、テキストを直接 eval に流すだけでも対応できます。
JScript で、JSON オブジェクトを取得する方法は、下記から。
stackoverflow.com
C++ Windowsで ini ファイルを読み込む
はじめに
Windows で、ini ファイルの読み込みと言えば、GetPrivateProfileString
です。
時間をかけずに、手っ取り早く設定ファイル対応を行うときに使えるかもしれない方法です。
注意
GetPrivateProfileString は、下記に引用した通り
16ビット互換 のために残されているAPIなので、非推奨
と考えた方がよさそうです。
MSDN より引用
注意 この関数は、16 ビット Windows ベースのアプリケーションとの互換性を保つ目的でのみ提供されています。Win32 ベースのアプリケーションでは、初期化情報をレジストリに格納してください。
実装
- config.ini
; 設定ファイル [System] WindowText = ウィンドウタイトル WindowWidth = 1280 WindowHeight = 720
- main.cpp
#include <Windows.h> // GetPrivateProfileString #include <iostream> // cout #include <array> // array #include <string> // string std::string GetConfigString(const std::string& filePath, const char* pSectionName, const char* pKeyName) { if (filePath.empty()) { return ""; } std::array<char, MAX_PATH> buf = {}; GetPrivateProfileStringA(pSectionName, pKeyName, "", &buf.front(), static_cast<DWORD>(buf.size()), filePath.c_str()); return &buf.front(); } int main() { std::string filePath = ".\\config.ini"; auto WindowText = GetConfigString(filePath, "System", "WindowText"); auto WindowWidth = GetConfigString(filePath, "System", "WindowWidth"); auto WindowHeight = GetConfigString(filePath, "System", "WindowHeight"); std::cout << WindowText << std::endl; std::cout << WindowWidth << std::endl; std::cout << WindowHeight << std::endl; system("pause"); return 0; }
あとがき
APIの引数的に、情報取得毎にファイルを読み込みに行く疑惑があるので、
もしかすると、ファイルサイズの影響を受けて、遅くなるかもしれません。
そもそも、互換目的で残されているだけっぽいので、代替機能を用意するなり、適宜対応した方が良さそう。
C++ ビルド時の西暦を表示
メモ
定義済みマクロ の、__DATE__
は文字列として扱えるので、
西暦部分の先頭アドレスがあれば事足りる。
実装
#include <iostream> // cout constexpr const char* YEAR = (__DATE__ + 7); int main() { std::cout << "ビルド年:" << YEAR << std::endl; return 0; }
あとがき
西暦のみの定義済みマクロが無いのか調べた結果、無さそうという結論。
よく考えたら、__DATE__
から取り出せるよねと言う話。
C++ バージョン番号を比較する方法
はじめに
複数の値から形成されているバージョン番号などの値を比較する際、
一つずつ比較するのは、手間ですが、std::lexicographical_compare を使用すると、一行で書けます。
なお、std::tuple でより短くスマートに実装できるので、追記要参照。
実装
#include <algorithm> // lexicographical_compare #include <iostream> // cout #include <cstdint> // int32_t #include <array> // array // バージョン比較用共用体 union CompVersion { struct { int32_t minor; int32_t major; int32_t revision; int32_t build; }; std::array<int32_t, 4> data; }; int main() { // 適当なバージョンデータ CompVersion appA = { 1, 0, 100, 2 }; CompVersion appB = { 2, 0, 100, 3 }; // 比較(appA < appB) bool result = std::lexicographical_compare(appA.data.begin(), appA.data.end(), appB.data.begin(), appB.data.end()); // 結果 if (result) { std::cout << "appAの方が古い" << std::endl; } else { std::cout << "appBの方が古い" << std::endl; } system("pause"); return 0; }
あとがき
ショートコーディングとしては、かなり有用なので覚書。
元ネタは、下記から。
stackoverflow.com
追記
この記事を公開直後に、バンビちゃんさんに高速カウンターをいただきました。
std::tuple を使用すると、もっとスマートに実装できるとのこと。
tuple便利!
さっきのやつをささっと
— バンビちゃん@実際いません (@pink_bangbi) March 5, 2018
C++ で std::tuple を使ってバージョン番号を比較する - Secret Garden(Instrumental) https://t.co/nYr0xrfS8z
std::tuple を使えば1発で… https://t.co/HVV3qwq1pd https://t.co/kalcnzr1jd
— バンビちゃん@実際いません (@pink_bangbi) March 5, 2018