Making a game using an ECS architecture (C++): Part 5 - Error checking and Macros
In the previous section, we have made our code organized. This is a great improvement, but let's fine-tune it a bit more. In game programming, there is a lot of error and null checks. Let's add some macros to shorten them. Also, while we're at it, let's move our current macros to another file.
// Macro.h. #pragma once extern void Assert(bool _check); // Error check. #define ASSERT(check) Assert(check) #define RETURN(check) do { if (!(check)) { return; } } while (false) #define RETURN_RET(check, returnValue) do { if (!(check)) { return returnValue; } } while (false) #define VRETURN(check) do { if (!(check)) { ASSERT(check); return; } } while (false) #define VRETURN_RET(check, returnValue) do { if (!(check)) { ASSERT(check); return (returnValue); } } while (false) #define CONTINUE(check) if (!(check)) continue #define VCONTINUE(check) if (!(check)) ASSERT(check); if (!(check)) continue #define BREAK(check) if (!(check)) break #define VBREAK(check) if (!(check)) ASSERT(check); if (!(check)) break // Macros to increase readability. #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 #define COLOR_BIT_DEPTH 32 // Colors. #define WHITE GetColor(255, 255, 255) #define BLACK GetColor(0, 0, 0) #define RED GetColor(255, 0, 0) #define GREEN GetColor(0, 255, 0) #define BLUE GetColor(0, 0, 255)
// Macro.cpp. #include "Macro.h" // If the condition is false, throw exception. // The program will pause here if an exception is triggered. void Assert(bool _check) { if (!_check) { throw(0); } }
This gives us a convenient way to perform a null/condition check, while using return/break/continue all with one statement. I also added some macros for some colors we might use in the future.
Now, let's apply the macros to our main code.
// Main.h. #pragma once #include "Macro.h" // Temporary variable to represent something in-game triggering the game to end. bool bEndGame = false; // Settings before launching application. bool PreInit() { // Settings before launching application goes here. VRETURN_RET(SetOutApplicationLogValidFlag(TRUE) == 0, false); VRETURN_RET(SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32) == 0, false); // Set resolution. VRETURN_RET(SetWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT) == 0, false); // Set window size. VRETURN_RET(ChangeWindowMode(TRUE) == 0, false); // Window mode/Fullscreen: true for windowed mode. VRETURN_RET(SetMainWindowText("Game") == 0, false); // Name of the window. VRETURN_RET(SetAlwaysRunFlag(TRUE) == 0, false); // Game runs in background. VRETURN_RET(SetWaitVSyncFlag(TRUE) == 0, false); // Limits FPS. return true; } // Settings after launching application. bool PostInit() { VRETURN_RET(SetDrawScreen(DX_SCREEN_BACK) == 0, false); return true; } // Check if the game should end. bool IsEndGame() { VRETURN_RET((ScreenFlip() == 0), true); // Switch the front and back screen. VRETURN_RET((ClearDrawScreen() == 0), true); // Clear everything drawn on the screen. RETURN_RET((CheckHitKey(KEY_INPUT_ESCAPE)) == 0, true); // Set game to end if escape key is pressed. VRETURN_RET((ProcessMessage() == 0), true); // Allows Windows to process other applications/services. return false; }
// Main.cpp. #include <DxLib.h> #include "Main.h" int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) { // Settings before launching application goes here. VRETURN_RET(PreInit(), -1); // Launch application. VRETURN_RET((DxLib_Init() == 0), -1); // Settings after launching application goes here. VRETURN_RET(PostInit(), -1); // Don't do anything until escape is pressed, or an error occurs. while (!IsEndGame()) { DrawFormatString(0, 0, WHITE, "Hello World!"); } // End application. DxLib_End(); return 0; }
Now we have a pretty simple but good macro system for error checks, and a well organized program structure.
You could make the Assert
function fancier, by showing a Windows MessageBox popup window, or adding messages showing where the assert came from (file name, line number...etc.), but this should do for now.
When the assert is triggered, your program will pause as if you used a breakpoint, so you can check where the problem is by checking the call stack.
You can check if your Assert
function works by adding a ASSERT(false);
anywhere in your main function.
In the next part, let's add a Game
class. This class will contain managers for entity, components and systems and control many aspects of our game. We won't be adding all those in one section, but we'll be creating the base structure of the class.