はじめに
DxLibでゲームを作っている際に,assert
を使いたいと思うことがありました.
しかし,DxLibでassert
を使うとDxLibの終了処理DxLib_End
を呼ぶ前にプログラムが終了してしまうという問題があるため,そのまま使うことはできません.(assert
ってなにという人はこちら)
「龍神録2ゲームプログラミングの館」ではこれの対策として,アサートを自作されています.
内容としてはERR
というマクロを定義し,マクロからエラー発生条件の表示とDxLib_End
を呼ぶ機能を持った関数を呼びだすというものです.関数の中ではwhile(!ProcessMessage())
を読んでプログラムが終了しないようにされており,printfDx
でエラー文を表示しています.
大変参考になる実装であり,私も長らくこれを使わせていただいていました.しかし,使用していく中で以下のような要望が自分の中で出てきました.
-
DxLib_Init
を呼ぶ前でも使用できるようにしたい -
assert
のようにReleaseビルド時には処理されてほしくない - 折角なら
__LINE__
や__FILE__
ではなく,source_location
を使って呼び出し位置を表示したい
これらを踏まえて作成した,DxLib専用の自作Assertを紹介します.
ソースコード
ソースコードは以下の通りです.C++20で作成していることに留意してください.
#pragma once
#include <source_location>
#include <stdexcept>
#include <string>
#include <DxLib.h>
// 呼び出さないこと.
namespace assert_internal {
void ErrorAssert(const std::string& conditional_expression, const std::string& error_mes,
const std::string& file, const std::string& func, const int line);
} // namespace assert_internal
#ifdef _DEBUG
#define INTERNAL_ERROR_MESSAGE(expression, error_mes) \
const std::source_location location = std::source_location::current(); \
::assert_internal::ErrorAssert(expression, error_mes, location.file_name(), \
location.function_name(), location.line());
//! @brief エラーが発生したときにエラーメッセージを表示する.
//! DxLib の動作を止め,独自のエラーメッセージを表示する.
//! DxLib が初期化されていない場合は,例外を投げる.
//! @param expr TRUE であることが期待される条件
//! @param error_mes エラーメッセージ
#define ASSERT(expr, error_mes) \
do { \
if (!(expr)) { \
if (DxLib_IsInit() != TRUE) { \
throw std::runtime_error("DxLib is not initialized."); \
} else { \
const std::string expr_str = #expr; \
const std::string message = error_mes; \
INTERNAL_ERROR_MESSAGE(expr_str, message); \
} \
} \
} while (0)
#define ASSERT_NOT_NULL_PTR(ptr) ASSERT((ptr) != nullptr, "nullptr passed.")
#define ASSERT_MUST_NOT_REACH_HERE() ASSERT(false, "This part is never reached.")
#else
#define ASSERT(expr, error_mes) ((void)0)
#define ASSERT_NOT_NULL_PTR(ptr) ((void)0)
#define ASSERT_MUST_NOT_REACH_HERE() ((void)0)
#endif // _DEBUG
#include "dxlib_assert.h"
#include <format>
#include <DxLib.h>
void ::assert_internal::ErrorAssert(
const std::string& conditional_expression,
const std::string& error_mes, const std::string& file,
const std::string& func, const int line) {
// DxLib がまだ初期化されていない場合は,終了する.
if (DxLib::DxLib_IsInit() != TRUE) {
return;
}
// エラーメッセージを表示する.
DxLib::clsDx();
DxLib::printfDx("Error! Please press the X button on the window to exit! \n\n");
DxLib::printfDx("%s", std::format("Error Condition : {}\n\n", conditional_expression).c_str());
DxLib::printfDx("%s", std::format("Error Cause : {}\n\n", error_mes).c_str());
DxLib::printfDx("%s", std::format("File Name : {}\n\n", file).c_str());
DxLib::printfDx("%s", std::format("Function Name : {}\n\n", func).c_str());
DxLib::printfDx("%s", std::format("Line Number : {}\n\n", line).c_str());
// 何もせずに待つ.
while (!DxLib::ProcessMessage()) {
DxLib::ClearDrawScreen();
DxLib::ScreenFlip();
}
DxLib::DxLib_End();
// プログラムを終了する.
exit(99);
}
マクロについて
ASSERT(expr, error_mes)
基本的なアサートの処理です.expr
のほうにtrue
となっていてほしい条件式,error_mes
のほうに表示されるエラーメッセージを渡してください.なお,マクロは全てReleaseビルドの際は((void)0)
に置き換えられ無視されます.
また,DxLib_Init
を呼ぶ前に使用すると,例外を出すようになっています.
ASSERT_NOT_NULL_PTR(ptr)
ptr
をnullptr
と比較し,ヌルポインタではないことを確認します.個人的にnullチェックはたくさん書くので,専用のマクロを用意してみました.
ASSERT_MUST_NOT_REACH_HERE()
switchのdefault
など「ここまで処理が来るはずない」というところで使用します.ASSERT(false, "This part is never reached.")
に置換されるので,必ずアサートは失敗します.
使用例
#include <DxLib.h>
#include "dxlib_assert.h"
void Success() {
ASSERT(1 == 1, "これは呼ばれません");
}
void Failure() {
ASSERT(1 == 2, "条件式がfalseなので,アサートが呼ばれます");
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
ChangeWindowMode(TRUE);
SetGraphMode(800, 600, 32);
SetMainWindowText("dxlib_assert");
if (DxLib_Init() == -1) {
return -1;
}
SetDrawScreen(DX_SCREEN_BACK);
while (!ProcessMessage()) {
ClearDrawScreen();
Success();
Failure();
ScreenFlip();
}
DxLib_End();
return 0;
}
プログラムを実行すると以下のようにアサートが呼ばれます.
参考にしたリンク
assert
についてしらない方は,以下のページが参考になると思います.