ProtoTank/Game.cpp

180 lines
6.3 KiB
C++

#include "Game.hpp"
#include <numeric>
#include <SFML/System.hpp>
#include <SFML/Window/Keyboard.hpp>
#include <SFML/Window/Mouse.hpp>
#include "Engine/Misc/Logger.hpp"
#include "Engine/Utils/Perfs.hpp"
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<sf::RenderWindow> mainWnd, bool dbgFlag) : mbDbgModeEnabled(dbgFlag), mMainWindow(mainWnd) {
unsigned int wndWidth, wndHeight;
GetDefaultWindowSize(wndWidth, wndHeight);
mWorld3D = make_shared<Graphic3DRenderer>();
mCockpitUI = make_unique<CockpitUI>(wndWidth, (wndWidth * H_RATIO));
mWorldUI = make_unique<WorldUI>(wndWidth, std::floor((wndWidth * H_RATIO) * 0.45f), mWorld3D);
if (mbDbgModeEnabled) {
mDbgUI = make_unique<DebugUI>();
mPerfsTimer = make_unique<SysTimer>();
mPerfsTimer->Reset();
}
}
GAME_STATUS Game::Tick() {
static std::vector<double> 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<double>(0)) / msTicksMonitoring.size();
mDbgUI->UpdateDebugData(
cpuUsage,
((1000000.f / TARGET_FPS) - mLastCycleSleepDelta) / 1000,
1000.f / TARGET_FPS,
mFrameCnt,
PerfsGetVirtMem() / 1000,
PerfsGetPhysMem() / 1000
);
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<float>(_x - mPrevMousePos.x));
float dy = M3D_Deg2Rad(0.25f * static_cast<float>(_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<float>(w), (w * H_RATIO));
mWorldUI->SetRTSize(static_cast<float>(w), std::floor((w * H_RATIO) * 0.45f), true);
mWorld3D->SetRTSize(static_cast<float>(w), std::floor((w * H_RATIO) * 0.45f));
}
void Game::Update() {
// Refresh keyboard inputs
KeyboardInputsCheck();
// Game logic calls should go here...
mWorldUI->Update();
mCockpitUI->Update();
}
void Game::KeyboardInputsCheck() {
const float deltaTimeS = mSysTimer.GetDeltaTime() / 1000;
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();
}