0
\$\begingroup\$

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
  • Game/
    • Classes/
      • GameClass/
        • GameClass.cpp
        • GameClass.h
    • Main.cpp

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.

\$\endgroup\$
5
  • \$\begingroup\$ Maybe I missed it in there, but what specifically is your question? \$\endgroup\$
    – Pikalek
    Commented Sep 2 at 15:04
  • \$\begingroup\$ Pikalek I would say this isn't just one question in particular, but you can answer whichever one(s) you want to. I'm sorry if this isn't more helpful. \$\endgroup\$
    – whitecloth
    Commented Sep 2 at 22:25
  • \$\begingroup\$ The Window is just a "view" into the engine. The engine should be able to run without any "devices" attached at all ... which is what happens practically "in the cloud". If that helps. The "model"; inputs and outputs; actions and reactions; is the thing. \$\endgroup\$ Commented Sep 3 at 17:18
  • \$\begingroup\$ Curious what the "mbr" prefix of your variables mean :P \$\endgroup\$
    – Charanor
    Commented Oct 28 at 22:50
  • \$\begingroup\$ “mbr” means “member”, although maybe I should have just used “m” for that. \$\endgroup\$
    – whitecloth
    Commented Dec 9 at 3:21

1 Answer 1

0
\$\begingroup\$

My suggestion would be to:

  • Separate the Windows specific code into a general platform class -> Platform -> WindowsDirectX
  • Keep your C++ as standards compliant as possible.
  • Do not assume a specific resolution, orientation, or aspect ratio - plan for all accordingly
  • Never use hard-coded strings in code. Always reference a single global lookup array loaded from a file.
  • What attributes need to be replicated for the given World/Session/Object... for network gameplay?

These are simple changes now, but down the road it will save you from many near total rewrites for other platforms(Mac,Linux,IOS,Android) and local language and network support.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .