#include "Game.hpp" #include #include #include #include #include "Engine/Misc/Logger.hpp" #include "Engine/Utils/Perfs.hpp" #define COCKPIT_VIEW_RATIO (400.f / WNDSIZE_DEFAULT_H) using std::make_shared; using std::make_unique; Game* Game::smInstance = nullptr; /* Game class is the conductor of every part of this "piece of scr*tch" game engine. Only keyboard is implemented as input (but mouse alse for debug purpose). SFX and music are planned later... Rendering will be done with CPU intrinsics (SIMD) if available. Both 2D and 3D rendering is mixed to display interface to the player. The static and UI element are 2D. The world and some pieces of the UI are in 3D. For simplicity (and somehow, style effect), the game is in wireframe like old arcade game. > Game instance call UI Update() method for quick computation of state, but only a call to the Draw() method will proceed to the pixels drawing. THIS SHOULD NOT BE USED TO UPDATE THE WORLD STATE OR ANYTHING ELSE THAN UI! World is the module responsible of the game elements states, FSM and start/end condition. > The method Tick() is used to compute new elements states More details to write here... */ Game::Game(std::shared_ptr& mainWnd, bool dbgFlag) : mbDbgModeEnabled(dbgFlag), mMainWindow(mainWnd) { unsigned int wndWidth, wndHeight; GetDefaultWindowSize(wndWidth, wndHeight); mWorld3D = make_shared(); mCockpitUI = make_unique(wndWidth, (wndWidth * H_RATIO)); mWorldUI = make_unique(wndWidth, std::floor((wndWidth * H_RATIO) * COCKPIT_VIEW_RATIO), mWorld3D); if (mbDbgModeEnabled) { mDbgUI = make_unique(); mPerfsTimer = make_unique(); mPerfsTimer->Reset(); } } GAME_STATUS Game::Tick() { static std::vector msTicksMonitoring; static double mLastCycleSleepDelta = 0; static unsigned int mFrameCnt = 0; // Debug performance computations if (mDbgUI != nullptr && mPerfsTimer != nullptr) { msTicksMonitoring.push_back(((1000000.f / TARGET_FPS) - mLastCycleSleepDelta) * TARGET_FPS / 1000000.f); if (mPerfsTimer->GetDeltaTime() >= 1000) { // Every 1s // This average CPU/cycle_time method can monitor when CPU is in overload state (>0%) or in underload one (<0%) double cpuUsage = 100 * std::accumulate(msTicksMonitoring.begin(), msTicksMonitoring.end(), static_cast(0)) / msTicksMonitoring.size(); mDbgUI->UpdateDebugData( cpuUsage, ((1000000.f / TARGET_FPS) - mLastCycleSleepDelta) / 1000, 1000.f / TARGET_FPS, mFrameCnt, PerfsGetVirtMem() / 1000, PerfsGetPhysMem() / 1000, mWorld3D->GetDrawTriCount() ); mFrameCnt = 0; msTicksMonitoring.clear(); mPerfsTimer->Reset(); } } // Update game stats and internal stuff Update(); // Loop the timer when reached the target FPS if ((mSysTimer.GetDeltaTime() >= (1000/TARGET_FPS))) { mSysTimer.Reset(); mFrameCnt++; // Process to the final rendering if the window have the focus if (mMainWindow->hasFocus()) Render(); } // Sleep for remaining time to avoid useless CPU usage mLastCycleSleepDelta = (1000000.f / TARGET_FPS) - mSysTimer.GetDeltaTimeUs(); if (mLastCycleSleepDelta > 0) sf::sleep(sf::microseconds(mLastCycleSleepDelta + (1000000.f / TARGET_FPS * 0.02f))); else mLastCycleSleepDelta = 0; // Return the current process stat to the head process (TODO: convert to thread) if (mMainWindow->isOpen()) return GAME_RUNNING; else return GAME_QUIT; } void Game::EventMouseMoved(int _x, int _y) { if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) { float dx = M3D_Deg2Rad(0.25f * static_cast(_x - mPrevMousePos.x)); float dy = M3D_Deg2Rad(0.25f * static_cast(_y - mPrevMousePos.y)); mWorld3D->UpdateCamera(CAMERA_MOVE_PITCH, dy); mWorld3D->UpdateCamera(CAMERA_MOVE_YAW, dx); } mPrevMousePos.x = _x; mPrevMousePos.y = _y; } void Game::EventWindowSizeChanged(unsigned int w, unsigned int h) { mCockpitUI->SetRTSize(static_cast(w), (w * H_RATIO)); mWorldUI->SetRTSize(static_cast(w), std::floor((w * H_RATIO) * COCKPIT_VIEW_RATIO), true); mWorld3D->SetRTSize(static_cast(w), std::floor((w * H_RATIO) * COCKPIT_VIEW_RATIO)); } void Game::Update() { // Refresh keyboard inputs KeyboardInputsCheck(); // Game logic calls should go here... mWorldUI->Update(); mCockpitUI->Update(); } void Game::KeyboardInputsCheck() { const float deltaTimeS = mKeyEventTimer.GetDeltaTime() / 1000; mKeyEventTimer.Reset(); if (mMainWindow->hasFocus()) { if (sf::Keyboard::isKeyPressed(sf::Keyboard::Z)) { if (sf::Keyboard::isKeyPressed(sf::Keyboard::Scan::LShift)) mWorld3D->UpdateCamera(CAMERA_MOVE_WALK, 15.0f * deltaTimeS); else mWorld3D->UpdateCamera(CAMERA_MOVE_WALK, 10.0f * deltaTimeS); } if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) { if (sf::Keyboard::isKeyPressed(sf::Keyboard::Scan::LShift)) mWorld3D->UpdateCamera(CAMERA_MOVE_WALK, -15.0f * deltaTimeS); else mWorld3D->UpdateCamera(CAMERA_MOVE_WALK, -10.0f * deltaTimeS); } if (sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) mWorld3D->UpdateCamera(CAMERA_MOVE_STRAFE, -10.0f * deltaTimeS); if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) mWorld3D->UpdateCamera(CAMERA_MOVE_STRAFE, 10.0f * deltaTimeS); if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space)) mWorld3D->UpdateCamera(CAMERA_MOVE_FLY, 5.0f * deltaTimeS); if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)) mWorld3D->UpdateCamera(CAMERA_MOVE_FLY, -5.0f * deltaTimeS); } } void Game::Render() { // Clear the draw buffer mMainWindow->clear(sf::Color::Black); // Draw the arena view mWorldUI->Draw(mMainWindow); // Draw the UI above mCockpitUI->Draw(mMainWindow); // Draw the debug informations if enabled if (mDbgUI != nullptr) mDbgUI->DrawDebugData(mMainWindow); // Present the draw buffer mMainWindow->display(); }