I'm trying to create a simple-ish game/engine/framework thing in C++, and I'm trying to come up with my own code instead of following a tutorial so I can learn to do it in a freer and more hands-on way. Right now I want ideas on how to have the window and game class be separate, which logic runs in each, and how I go about all this. I think I want to have the window creation in one class (Window), and the window instantiation/game loop in another (GameClass). I am using Visual Studio as my IDE and MSVC as my compiler.
Here is my project structure and include directory:
Project/
- Engine/
- Classes/
- Window/
- Window.cpp
- Window.h
- Window/
- Classes/
- Game/
- Classes/
- GameClass/
- GameClass.cpp
- GameClass.h
- GameClass/
- Main.cpp
- Classes/
Include directory: $(ProjectDir);$(VC_IncludePath);$(WindowsSDK_IncludePath);
And here's the code:
Main.cpp: Where all the magic happens. An instance of Game, myGame, is created. It then has the Run() method called, which is stored in an int named gameResult. This gameResult is used as a return value, if it returns 0, everything went well.
#include <Game/Classes/GameClass/GameClass.h>
int main()
{
Game myGame;
int gameResult = myGame.Run();
return gameResult;
}
GameClass.h: The header file for the game class. It has a constructor and a destructor (both empty). In Run(), the Window instance is created/allocated in memory, and the game loop happens until mbr_IsRunning is set to false by Quit(), which also deallocates memory for the Window instance.
#pragma once
#include <Engine/Classes/Window/Window.h>
class Game
{
public:
Game();
~Game();
int Run();
int Quit();
Window* mbr_Window = nullptr;
protected:
bool mbr_IsRunning = true;
};
Game.cpp: The source file for the Game class. Memory is allocated for the Window instance and stored in mbr_Window, and then call mbr_Window->Startup(). We then create a new variable, Msg. We then make a while loop that runs as long as mbr_IsRunning holds true. In that loop, if PeekMessage(&Msg) returns something, we translate and dispatch the window messages coming from the window procedure (I think). If Msg.message is WM_QUIT, Game::Quit() is called. I assume this calls Quit for the current instance of the Game class. Quit() deletes mbr_Window (I'll put additional cleanup shit here later) and then sets mbr_IsRunning to false, ending the loop in Startup(). I suspect I should set mbr_IsRunning to false and then delete mbr_Window, but that doesn't have any effect I can see so far.
#include "GameClass.h"
Game::Game()
{}
Game::~Game()
{}
int Game::Run()
{
mbr_Window = new Window;
mbr_Window->Startup();
MSG Msg;
while (mbr_IsRunning)
{
if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
if (Msg.message == WM_QUIT)
{
Game::Quit();
}
Sleep(1);
}
return 0;
}
int Game::Quit()
{
delete mbr_Window;
mbr_IsRunning = false;
return 0;
}
Window.h: The header file for the Window class. It has a constructor/destructor (again, both empty) and an int called Startup, which creates the window using the Win32 API. The resulting window is then stored in the HWND member variable, m_Window.
#pragma once
#include <Windows.h>
class Window
{
public:
Window();
int Startup();
~Window();
public:
HWND m_Window = NULL;
};
Window.cpp: The source file for the Window class. We have a WindowProcedure that checks if p_Msg is WM_CLOSE, if so, we post the quit message 0. Otherwise, we return DefWindowProc with the parameters passed in above. If it can't return DefWindowProc for some reason, we return NULL instead. The method used to start up the window is int Startup(), which finally makes its integer nature useful now. It returns different values at different points of execution (e.g. 0: successful, 1: failed to register class, 2: failed to create window). This method doesn't handle the loop and keep the window up, that happens in the Game class.
#include "Window.h"
LRESULT CALLBACK WindowProcedure(HWND p_hWnd, UINT p_Msg, WPARAM p_W, LPARAM p_L)
{
switch (p_Msg)
{
case WM_CLOSE:
{
PostQuitMessage(0);
}
default:
{
return DefWindowProc(p_hWnd, p_Msg, p_W, p_L);
}
return NULL;
}
}
Window::Window() {}
int Window::Startup()
{
HRESULT Result = NULL;
LPCWSTR ClassName = L"Window class";
LPCWSTR WindowTitle = L"I should be on the title bar";
DWORD WindowStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
RECT WindowRect = { 0, 0, 1600, 900 };
WNDCLASSEX wc = { 0 };
wc.lpszClassName = ClassName;
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WindowProcedure;
Result = RegisterClassEx(&wc);
if (FAILED(Result)) return 1;
AdjustWindowRect(&WindowRect, WindowStyle, false);
m_Window = CreateWindowEx ( NULL, ClassName, WindowTitle, WindowStyle, CW_USEDEFAULT, CW_USEDEFAULT, WindowRect.right, WindowRect.bottom, NULL, NULL, NULL, NULL );
if (m_Window == NULL) return 2;
ShowWindow(m_Window, SW_SHOW);
return 0;
}
Window::~Window() {}
The code seems to do what I want it to do (it makes a window and keeps it up until I close it), but I want to know if there are any ramifications to the approach I am taking right now, what better code, design, naming, practices and structuring choices I could make or otherwise how I could just improve my code. My primary goal is for the engine to be as well-optimized as humanly possible (otherwise I wouldn't be making my own game engine, among other reasons), but I also want the whole thing to be organized in a clean and intuitive way. I want to have both of these whenever possible (or at the very least, hit a good balance between the two) but I'm insecure about my ability as a C++ programmer, especially when it comes to creating a game engine like this.
Thanks for the help! I look forward to seeing any suggestions I could try out.