Thread: Engine Architecture - Where should I put my INPUT object?

  1. #1
    Registered User
    Join Date
    Aug 2011
    Posts
    2

    Engine Architecture - Where should I put my INPUT object?

    Language: C/C++
    Compiler/Editor: MSVS 2010
    Graphics: DirectX 9.0c
    OS: Windows XP

    When school starts up again this year, I will be working on a game project with three other students. I'm attempting to architect our engine to avoid globals and be as modular as possible. I've come up with a basic idea of how I would like to structure the engine but I'm at a bit of a loss as what to do with the INPUT class, it just doesn't quite seem to fit in my design. The plan is to get keyboard input through the window messages WM_KEYDOW and WM_KEYUP in the WinProc function rather than using DirectInput. I've created an abstract SYSTEM class to act as the footprint for all the other engine components: WINDOW, GRAPHICS, INPUT, PHYSICS, AI, SOUND, ect. Here is my pseudo-code:

    system.h
    Code:
    .
    .
    .
    class SYSTEM
    {
      private:
      public:  SYSTEM();
               virtual SYSTEM();
               virtual void Load() = 0;
               virtual void Initialize() = 0;
               virtual void Update() = 0;
               virtual void Unload() = 0;
    };
    window.h
    Code:
    .
    .
    .
    class WINDOW : public SYSTEM
    {
      private:
      public:  WINDOW();
               ~WINDOW();
               void Load();
               void Initialize();
               void Update();
               void Unload();
    };
    input.h
    Code:
    .
    .
    .
    enum KEY_STATE { NO_STATE, RELEASED, HELD, PRESSED = 4};
    
    class INPUT : public SYSTEM
    {
      private: char key_[256];
      public:  INPUT();
               ~INPUT();
               void Load();
               void Initialize();
               void Update();
               void Unload();
    };
    window.cpp
    Code:
    .
    .
    .
    LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
      switch(message)
      {
        case WM_DESTROY:
        case WM_CLOSE:    PostQuitMessage(0);
    		      return 0;
        case WM_KEYDOWN:  //Update Input
                          break;
        case WM_KEYUP:    //Update Input
                          break;
      }
      return DefWindowProc(hWnd, message, wParam, lParam);
    }
    
    WINDOW::WINDOW()
    {
    }
    
    WINDOW::~WINDOW()
    {
    }
    .
    .
    .
    It makes sense to me for INPUT to inherit SYSTEM because INPUT "Is-a" SYSTEM. However, I need to somehow call INPUTS' Update() function in the WinProc() function which is in the Window.cpp file but is not a part of the WINDOW class. I've come up with a few ideas:

    1. Make some friend functions in the INPUT class:

    input.h
    Code:
    .
    .
    .
    enum KEY_STATE { NO_STATE, RELEASED, HELD, PRESSED = 4};
    
    class INPUT : public SYSTEM
    {
      private: char key_[256];
      public:  INPUT();
               ~INPUT();
               void Load();
               void Initialize();
               void Update();
               void Unload();
    
               friend void UpdateKeyPress(WPARAM key);
               friend void UpdateKeyRelease(WPARAM key);
               friend bool IsKeyPressed(int key);
               friend bool IsKeyHeld(int key);
               friend bool IsKeyReleased(int key);
    };
    .
    .
    .
    input.cpp
    Code:
    .
    .
    .
    INPUT *g_input = NULL; //ewww, a global.
    
    INPUT::INPUT()
    {
      g_input = this;
    }
    .
    .
    .
    void UpdateKeyPress(WPARAM key)
    {
      //will have coditional to determin if key PRESSED or HELD
      g_input->key_[key] = PRESSED;
    }
    void UpdateKeyRelease(WPARAM key)
    {
      //update function will reset a RELEASED keys to NO_STATE
      g_input->key_[key] = RELEASED;
    }
    
    bool IsKeyPressed(int key)  { return g_input->key_[key] & PRESSED;  }
    bool IsKeyHeld(int key)     { return g_input->key_[key] & HELD;      }
    bool IsKeyReleased(int key) { return g_input->key_[key] & RELEASED; }
    window.cpp
    Code:
    #include "Input.h"
    .
    .
    .
    LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
      switch(message)
      {
        case WM_DESTROY:
        case WM_CLOSE:    PostQuitMessage(0);
    		      return 0;
        case WM_KEYDOWN:  UpdateKeyPress(wParam);
                          break;
        case WM_KEYUP:    UpdateKeyRelease(wParam);
                          break;
      }
      return DefWindowProc(hWnd, message, wParam, lParam);
    }
    
    WINDOW::WINDOW()
    {
    }
    
    WINDOW::~WINDOW()
    {
    }
    .
    .
    .
    This works but I feel like I'm breaking some coding rules. Shouldn't friend functions only be used to avoid globals or public data members? Had to make a global INPUT pointer at top of input.cpp to make this work. This however makes it so that all I have to do is include "Input.h" in any file to get access to the functions that tell weather a key is pressed, held, or released; and, keys could be forced to be pushed, held, or released with the "UpdateKey..." functions.

    2. Option two is to just make a static INPUT object in the SYSTEM class. But I feel like I'm breaking some rule here too. Haven't tried this and I think I would get a compile error. SYSTEM "Has-a" INPUT object that "Is-a" SYSTEM? hmm...

    3. Don't make INPUT inherit SYSTEM at all and include it in SYSTEM. Doing this though would mean that I would have to write input related functions in the SYSTEM class, but all the SYSTEM components would have access to input.

    Code:
    .
    .
    .
    class SYSTEM
    {
      private: INPUT input_;
      public:  SYSTEM();
               ~SYSTEM();
               void UpdateKeyPress(WPARAM key);
               void UpdateKeyRelease(WPARAM key);
               bool IsKeyPressed(int key);
               bool IsKeyHeld(int key);
               bool IsKeyReleased(int key);
    };
    All of these solutions are, in my opinion, ugly and I'm out of ideas. Any advice on how to handle input or restructure my code would be appreciated. Maybe I should look into using function pointers somehow?

  2. #2
    ATH0 quzah's Avatar
    Join Date
    Oct 2001
    Posts
    14,826
    How does the whole thing get started, and how do you step through what is happening? Are you running a game that is waiting for input, or an input handler that is waiting for a game? Does the game run regardless of what your UI is doing? Or does it pause when you pull up menus and stuff? Does it do stuff in the background while you're playing in your inventory, or hunting through menus?


    Quzah.
    Hope is the first step on the road to disappointment.

  3. #3
    Registered User
    Join Date
    Aug 2011
    Posts
    2
    OK, so I have an ENGINE class, and in this class there will be a private vector array of SYSTEMs. When a SYSTEM object is created, it will be added to the ENGINE's vector array through the ENGINE's AddSystem() function. Then when the ENGINE's Update() function is called, it will loop through the vector of SYSTEM's and call each corresponding Update() function in the order the SYSTEMs were added to the ENGINE.

    INPUT will update every game loop.
    Need to detect if a key is being PUSHED, HELD, RELEASED, or NO_STATE.
    Game continues to run when player interacts with the UI.
    There will be a pause option in single player mode.

    Posted more pseudo-code, hope it helps.

    main.cpp
    Code:
    .
    .
    .
    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE pInst, LPSTR cmd, int show)
    {
      ENGINE engine;
      
      DEBUG debug;       //redirects printf to console window
      INPUT input;        
      WINDOW window;     //creates window and handles window messages
      GAME game;         //game logic
      PHYSICS physics;   //gravity, collision, force application
      GRAPHICS graphics; //directX
      
      //add  each SYSTEM to the vector array in ENGINE
      engine.AddSystem(&debug);
      engine.AddSystem(&input);
      engine.AddSystem(&window);
      engine.AddSystem(&game);
      engine.AddSystem(&physics);
      engine.AddSystem(&graphics);
    
      engine.Load();        //calls Load() for each SYSTEM
      engine.Initialize();  //calls Initialize() for each SYSTEM
    
      while(engine.active())
      {
        engine.Update();    //calls Update() for each SYSTEM
      }
      engine.Unload();      //calls Unload() for each SYSTEM
    
      return 0;
    }
    engine.h
    Code:
    .
    .
    .
    class ENGINE
    {
      private: bool shutdown_
               std::vector<SYSTEM*> system_;
      public:  ENGINE();
               ~ENGINE();
               void Load();
               void Initialize();
               void Update();
               void Unload();
    
               void Shutdown() { shutdown_ = true; }
               bool Active()   { return shutdown_; }
    };
    engine.cpp
    Code:
    .
    .
    .
    ENGINE::ENGINE()
    {
      shutdown_ = false;
    }
    
    ENGINE::~ENGINE()
    {
      //clean up vector array
    }
    
    ENGINE::Load()
    {
      for(int i = 0; i < system_.size(); i++)  //loop through load functions
        system_[i]->Load();
    }
    
    ENGINE::Initialize()
    {
      for(int i = 0; i < system_.size(); i++)  //loop through initialize functions
        system_[i]->Initialize();
    }
    
    ENGINE::Update()
    {
      for(int i = 0; i < system_.size(); i++)  //loop through update functions
        system_[i]->Update();
    }
    
    ENGINE::Unload()
    {
      for(int i = 0; i < system_.size(); i++)  //loop through unload functions
        system_[i]->Unload();
    }
    
    ENGINE::AddSystem(SYSTEM* system)
    {
      system_.push_back(system);
    }
    window.cpp
    Code:
    .
    .
    .
    LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    switch(message)
      {
        case WM_DESTROY:
        case WM_CLOSE:    PostQuitMessage(0);
    		      return 0;
        case WM_KEYDOWN:  //Update key presses
                          break;
        case WM_KEYUP:    //Update key releases
                          break;
      }
      return DefWindowProc(hWnd, message, wParam, lParam);
    }
    
    WINDOW::WINDOW()
    {
      //create window
    }
    
    WINDOW::~WINDOW()
    {
      //unregister and close window
    }
    .
    .
    .
    void WINDOW::Update()
    {
      while(PeekMessage(&msg_, NULL, 0, 0, PM_REMOVE))
      {
        TranslateMessage(&msg_);
        DispatchMessage(&msg_);
      }
    }
    .
    .
    .
    graphics.h
    Code:
    class Graphics : public System
    {
      private: LPDIRECT3D9 d3d_;               //DirectX library access
               LPDIRECT3DDEVICE9 d3ddev_;      //DirectX device(graphics card)
               D3DPRESENT_PARAMETERS d3dpp_;
      public:  Graphics();
               ~Graphics();
               void Load();
               void Initialize();
               void Update();         //Rendering
               void Unload();
    };
    graphics.cpp
    Code:
    .
    .
    .
    GRAPHICS::GRAPHICS()
    {
      //initialize and gain access to DirectX
    }
    
    GRAPHICS::~GRAPHICS()
    {
      //clean up DirectX
    }
    .
    .
    .
    void GRAPHICS::Update()
    {
        d3ddev_->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    
      if(d3ddev_->BeginScene())
      {
        //rendering occurs here
        d3ddev_->EndScene();
      }
      d3ddev_->Present(NULL, NULL, NULL, NULL);
    }
    .
    .
    .
    input.h
    Code:
    .
    .
    .
    enum KEY_STATE { NO_STATE, RELEASED, HELD, PRESSED = 4};
    
    class INPUT : public SYSTEM
    {
      private: char key_[256];
      public:  INPUT();
               ~INPUT();
               void Load();
               void Initialize();
               void Update();
               void Unload();
    };
    input.cpp
    Code:
    .
    .
    .
    INPUT::Update()
    {
      ///////////////////////////////////////
      //Can't access this function in WinProc
      //in the Window.cpp file. So how do I
      //update my key array?
      ///////////////////////////////////////
    }
    .
    .
    .

  4. #4
    Registered User
    Join Date
    Sep 2009
    Posts
    48
    Why not try putting the SYSTEMs in an std::map instead of an std::vector? Then you can have a function to get a specific system from a key, like so:

    Code:
    enum SysID
    {
         Input,
         Graphics,
         // etc...
    };
    
    std::map<SysID, SYSTEM*> system_;
    
    // ...
    
    void AddSystem(SYSTEM* system, SysID id)
    {
         system_[id] = system;
    }
    
    // ...
    
    SYSTEM* GetSystem(SysID id)
    {
         return system_[id];
    }
    This is a very simple version of what you should do if you want to try this (you'll want those functions to do some checking to be safe), but it's just to show you an example of what I mean. You should also think about using smart pointers, to make it easier to avoid memory issues.

    Using this system will allow you to get the Input system from your engine, like so:

    Code:
    engine->GetSystem(Input)->UpdateKeyPress(wParam);

  5. #5
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    When the windows message comes into the WndProc you can send it on via the observer / listener pattern or via a callback to any interested parties. I also do not recommend you use all caps for your class names.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. IRC Bot architecture
    By czar in forum C Programming
    Replies: 5
    Last Post: 08-15-2011, 08:35 AM
  2. PC architecture
    By Shotgun in forum C Programming
    Replies: 8
    Last Post: 04-28-2006, 12:53 AM
  3. Creating class object from user input?
    By Munkey01 in forum C++ Programming
    Replies: 8
    Last Post: 01-05-2003, 10:09 AM
  4. Changes: CPU Architecture
    By zahid in forum A Brief History of Cprogramming.com
    Replies: 11
    Last Post: 01-10-2002, 07:54 AM
  5. input/output + Object Files ?
    By Unregistered in forum C++ Programming
    Replies: 2
    Last Post: 12-17-2001, 09:32 AM