From 89fae1cb6f87964461564ea67c7493c938ee2c5a Mon Sep 17 00:00:00 2001 From: Adrian Conlon Date: Tue, 1 Oct 2019 23:54:48 +0100 Subject: [PATCH] Tidy the Gaming library a little: *) Add an SDLWrapper class to control the lifetime of SDL_Init/SDL_Quit *) Pass FPS as a float, rather than int *) Allow the key and button handlers to show whether an event was handled or not *) Add a full screen render option (F12) *) Use smart pointers in the GameController class Signed-off-by: Adrian Conlon --- Gaming/inc/Game.h | 35 ++++++-------- Gaming/inc/GameController.h | 8 ++-- Gaming/inc/SDLWrapper.h | 18 ++++++++ Gaming/src/Game.cpp | 76 +++++++++++++++++++++++-------- Gaming/src/GameController.cpp | 42 ++++++----------- Gaming/src/Gaming.vcxproj | 2 + Gaming/src/Gaming.vcxproj.filters | 6 +++ Gaming/src/SDLWrapper.cpp | 12 +++++ 8 files changed, 131 insertions(+), 68 deletions(-) create mode 100644 Gaming/inc/SDLWrapper.h create mode 100644 Gaming/src/SDLWrapper.cpp diff --git a/Gaming/inc/Game.h b/Gaming/inc/Game.h index 3492a81..e0c106a 100644 --- a/Gaming/inc/Game.h +++ b/Gaming/inc/Game.h @@ -11,22 +11,13 @@ #include #include "GameController.h" +#include "SDLWrapper.h" class Configuration; namespace Gaming { class Game : public EightBit::Device { public: - - static void throwSDLException(std::string failure) { - throw std::runtime_error(failure + ::SDL_GetError()); - } - - static void verifySDLCall(int returned, std::string failure) { - if (returned < 0) - throwSDLException(failure); - } - Game(); virtual ~Game(); @@ -34,7 +25,7 @@ namespace Gaming { virtual void raisePOWER() override; protected: - virtual int fps() const = 0; + virtual float fps() const = 0; virtual bool useVsync() const = 0; virtual int windowWidth() const noexcept { return displayWidth() * displayScale(); } @@ -59,6 +50,17 @@ namespace Gaming { virtual const uint32_t* pixels() const = 0; + virtual bool handleKeyDown(SDL_Keycode key); + virtual bool handleKeyUp(SDL_Keycode key); + + virtual bool handleJoyButtonDown(SDL_JoyButtonEvent event); + virtual bool handleJoyButtonUp(SDL_JoyButtonEvent event); + + virtual bool handleControllerButtonDown(SDL_ControllerButtonEvent event); + virtual bool handleControllerButtonUp(SDL_ControllerButtonEvent event); + + void toggleFullscreen(); + std::shared_ptr gameController(const int which) const { const auto i = m_gameControllers.find(which); if (i == m_gameControllers.cend()) @@ -81,6 +83,8 @@ namespace Gaming { std::shared_ptr pixelFormat() const noexcept { return m_pixelFormat; } private: + SDLWrapper m_wrapper; + std::shared_ptr m_window; std::shared_ptr m_renderer; std::shared_ptr m_bitmapTexture; @@ -97,14 +101,5 @@ namespace Gaming { void configureBackground() const; void createBitmapTexture(); - - virtual void handleKeyDown(SDL_Keycode key) {} - virtual void handleKeyUp(SDL_Keycode key) {} - - virtual void handleJoyButtonDown(SDL_JoyButtonEvent event) {} - virtual void handleJoyButtonUp(SDL_JoyButtonEvent event) {} - - virtual void handleControllerButtonDown(SDL_ControllerButtonEvent event) {} - virtual void handleControllerButtonUp(SDL_ControllerButtonEvent event) {} }; } diff --git a/Gaming/inc/GameController.h b/Gaming/inc/GameController.h index a2e79c9..0307067 100644 --- a/Gaming/inc/GameController.h +++ b/Gaming/inc/GameController.h @@ -1,5 +1,7 @@ #pragma once +#include + #include namespace Gaming { @@ -17,17 +19,17 @@ namespace Gaming { } auto getJoystickId() const noexcept { - return buildJoystickId(m_gameController); + return buildJoystickId(m_gameController.get()); } private: int m_index; - SDL_GameController* m_gameController = nullptr; + std::shared_ptr m_gameController; void open(); void close() noexcept; - SDL_Haptic* m_hapticController = nullptr; + std::shared_ptr m_hapticController; bool m_hapticRumbleSupported = false; void openHapticController(); diff --git a/Gaming/inc/SDLWrapper.h b/Gaming/inc/SDLWrapper.h new file mode 100644 index 0000000..316835d --- /dev/null +++ b/Gaming/inc/SDLWrapper.h @@ -0,0 +1,18 @@ +#pragma once + +namespace Gaming { + class SDLWrapper final { + public: + SDLWrapper(); + ~SDLWrapper(); + + static void throwSDLException(std::string failure) { + throw std::runtime_error(failure + ::SDL_GetError()); + } + + static void verifySDLCall(int returned, std::string failure) { + if (returned < 0) + throwSDLException(failure); + } + }; +} \ No newline at end of file diff --git a/Gaming/src/Game.cpp b/Gaming/src/Game.cpp index 6d19b16..477cfac 100644 --- a/Gaming/src/Game.cpp +++ b/Gaming/src/Game.cpp @@ -3,13 +3,9 @@ namespace Gaming { -Game::Game() { - verifySDLCall(::SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC), "Failed to initialise SDL: "); -} +Game::Game() {} -Game::~Game() { - ::SDL_Quit(); -} +Game::~Game() {} void Game::raisePOWER() { @@ -21,29 +17,29 @@ void Game::raisePOWER() { windowWidth(), windowHeight(), SDL_WINDOW_SHOWN), ::SDL_DestroyWindow); if (m_window == nullptr) - throwSDLException("Unable to create window: "); + SDLWrapper::throwSDLException("Unable to create window: "); ::SDL_DisplayMode mode; - verifySDLCall(::SDL_GetWindowDisplayMode(m_window.get(), &mode), "Unable to obtain window information"); + SDLWrapper::verifySDLCall(::SDL_GetWindowDisplayMode(m_window.get(), &mode), "Unable to obtain window information"); Uint32 rendererFlags = 0; m_vsync = useVsync(); if (m_vsync) { const auto required = fps(); - if (required == mode.refresh_rate) { + if (std::abs(required - mode.refresh_rate) < 0.001) { rendererFlags |= SDL_RENDERER_PRESENTVSYNC; ::SDL_Log("Attempting to use SDL_RENDERER_PRESENTVSYNC"); } else { m_vsync = false; - ::SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Display refresh rate is incompatible with required rate (%d)", required); + ::SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Display refresh rate is incompatible with required rate (%f)", required); } } m_renderer.reset(::SDL_CreateRenderer(m_window.get(), -1, rendererFlags), ::SDL_DestroyRenderer); if (m_renderer == nullptr) - throwSDLException("Unable to create renderer: "); + SDLWrapper::throwSDLException("Unable to create renderer: "); ::SDL_RendererInfo info; - verifySDLCall(::SDL_GetRendererInfo(m_renderer.get(), &info), "Unable to obtain renderer information"); + SDLWrapper::verifySDLCall(::SDL_GetRendererInfo(m_renderer.get(), &info), "Unable to obtain renderer information"); if (m_vsync) { if ((info.flags & SDL_RENDERER_PRESENTVSYNC) == 0) { @@ -54,20 +50,20 @@ void Game::raisePOWER() { m_pixelFormat.reset(::SDL_AllocFormat(m_pixelType), ::SDL_FreeFormat); if (m_pixelFormat == nullptr) - throwSDLException("Unable to allocate pixel format: "); + SDLWrapper::throwSDLException("Unable to allocate pixel format: "); configureBackground(); createBitmapTexture(); } void Game::configureBackground() const { - verifySDLCall(::SDL_SetRenderDrawColor(m_renderer.get(), 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE), "Unable to set render draw colour"); + SDLWrapper::verifySDLCall(::SDL_SetRenderDrawColor(m_renderer.get(), 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE), "Unable to set render draw colour"); } void Game::createBitmapTexture() { m_bitmapTexture.reset(::SDL_CreateTexture(m_renderer.get(), m_pixelType, SDL_TEXTUREACCESS_STREAMING, rasterWidth(), rasterHeight()), ::SDL_DestroyTexture); if (m_bitmapTexture == nullptr) - throwSDLException("Unable to create bitmap texture"); + SDLWrapper::throwSDLException("Unable to create bitmap texture"); } void Game::runLoop() { @@ -120,7 +116,7 @@ void Game::runLoop() { if (!m_vsync) { const auto elapsedTicks = ::SDL_GetTicks() - m_startTicks; - const auto neededTicks = (m_frames / (float)fps()) * 1000.0; + const auto neededTicks = (m_frames / fps()) * 1000.0; const auto sleepNeeded = (int)(neededTicks - elapsedTicks); if (sleepNeeded > 0) { ::SDL_Delay(sleepNeeded); @@ -173,11 +169,11 @@ std::shared_ptr Game::chooseController(const int who) const { } void Game::updateTexture() { - verifySDLCall(::SDL_UpdateTexture(m_bitmapTexture.get(), nullptr, pixels(), displayWidth() * sizeof(Uint32)), "Unable to update texture: "); + SDLWrapper::verifySDLCall(::SDL_UpdateTexture(m_bitmapTexture.get(), nullptr, pixels(), displayWidth() * sizeof(Uint32)), "Unable to update texture: "); } void Game::copyTexture() { - verifySDLCall( + SDLWrapper::verifySDLCall( ::SDL_RenderCopy(m_renderer.get(), m_bitmapTexture.get(), nullptr, nullptr), "Unable to copy texture to renderer"); } @@ -186,4 +182,48 @@ void Game::displayTexture() { ::SDL_RenderPresent(m_renderer.get()); } +void Game::toggleFullscreen() { + auto wasFullscreen = ::SDL_GetWindowFlags(m_window.get()) & SDL_WINDOW_FULLSCREEN_DESKTOP; + SDLWrapper::verifySDLCall(::SDL_SetWindowFullscreen(m_window.get(), wasFullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP), "Failed to modify the window full screen setting"); + ::SDL_ShowCursor(wasFullscreen ? 1 : 0); +} + +bool Game::handleKeyDown(SDL_Keycode key) { + switch (key) { + case SDLK_F12: + // Don't let it get poked. + return true; + break; + default: + return false; + } +} + +bool Game::handleKeyUp(SDL_Keycode key) { + switch (key) { + case SDLK_F12: + toggleFullscreen(); + return true; + break; + default: + return false; + } +} + +bool Game::handleJoyButtonDown(SDL_JoyButtonEvent event) { + return false; +} + +bool Game::handleJoyButtonUp(SDL_JoyButtonEvent event) { + return false; +} + +bool Game::handleControllerButtonDown(SDL_ControllerButtonEvent event) { + return false; +} + +bool Game::handleControllerButtonUp(SDL_ControllerButtonEvent event) { + return false; +} + } \ No newline at end of file diff --git a/Gaming/src/GameController.cpp b/Gaming/src/GameController.cpp index 1ea8345..27d305f 100644 --- a/Gaming/src/GameController.cpp +++ b/Gaming/src/GameController.cpp @@ -5,7 +5,7 @@ namespace Gaming { GameController::GameController(int index) - : m_index(index) { + : m_index(index) { open(); } @@ -16,58 +16,46 @@ namespace Gaming { void GameController::open() { SDL_assert(::SDL_NumJoysticks() > 0); if (::SDL_IsGameController(m_index)) { - m_gameController = ::SDL_GameControllerOpen(m_index); - if (m_gameController == nullptr) { - Game::throwSDLException("Unable to open game controller: "); - } + m_gameController.reset(::SDL_GameControllerOpen(m_index), ::SDL_GameControllerClose); + if (m_gameController == nullptr) + SDLWrapper::throwSDLException("Unable to open game controller: "); openHapticController(); - auto name = ::SDL_GameControllerName(m_gameController); + auto name = ::SDL_GameControllerName(m_gameController.get()); ::SDL_Log("Game controller name: %s", name); - } - else { + } else { ::SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Joystick is not a game controller!!"); } } void GameController::openHapticController() { - m_hapticController = ::SDL_HapticOpen(m_index); - if (m_hapticController == nullptr) { - Game::throwSDLException("Unable to open haptic controller: "); - } - Game::verifySDLCall(::SDL_HapticRumbleInit(m_hapticController), "Unable to initialise haptic controller: "); - m_hapticRumbleSupported = ::SDL_HapticRumbleSupported(m_hapticController) != SDL_FALSE; + m_hapticController.reset(::SDL_HapticOpen(m_index), ::SDL_HapticClose); + if (m_hapticController == nullptr) + SDLWrapper::throwSDLException("Unable to open haptic controller: "); + SDLWrapper::verifySDLCall(::SDL_HapticRumbleInit(m_hapticController.get()), "Unable to initialise haptic controller: "); + m_hapticRumbleSupported = ::SDL_HapticRumbleSupported(m_hapticController.get()) != SDL_FALSE; } void GameController::closeHapticController() noexcept { - if (m_hapticController != nullptr) { - ::SDL_HapticClose(m_hapticController); - m_hapticController = nullptr; - } + m_hapticController.reset(); m_hapticRumbleSupported = false; } void GameController::close() noexcept { - if (m_gameController != nullptr) { - ::SDL_GameControllerClose(m_gameController); - m_gameController = nullptr; - } + m_gameController.reset(); closeHapticController(); } void GameController::startRumble() noexcept { if (m_hapticRumbleSupported) { - if (::SDL_HapticRumblePlay(m_hapticController, 1.0, 1000) < 0) { + if (::SDL_HapticRumblePlay(m_hapticController.get(), 1.0, 1000) < 0) ::SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unable to start haptic rumble: %s", ::SDL_GetError()); - } } } void GameController::stopRumble() noexcept { if (m_hapticRumbleSupported) { - if (::SDL_HapticRumbleStop(m_hapticController) < 0) { + if (::SDL_HapticRumbleStop(m_hapticController.get()) < 0) ::SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unable to stop haptic rumble: %s", ::SDL_GetError()); - } } } - } \ No newline at end of file diff --git a/Gaming/src/Gaming.vcxproj b/Gaming/src/Gaming.vcxproj index 67e3ed5..eced5f5 100644 --- a/Gaming/src/Gaming.vcxproj +++ b/Gaming/src/Gaming.vcxproj @@ -166,10 +166,12 @@ Create Create + + diff --git a/Gaming/src/Gaming.vcxproj.filters b/Gaming/src/Gaming.vcxproj.filters index 10b1d21..f566862 100644 --- a/Gaming/src/Gaming.vcxproj.filters +++ b/Gaming/src/Gaming.vcxproj.filters @@ -20,6 +20,9 @@ Source Files + + Source Files + @@ -31,5 +34,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/Gaming/src/SDLWrapper.cpp b/Gaming/src/SDLWrapper.cpp new file mode 100644 index 0000000..257de9d --- /dev/null +++ b/Gaming/src/SDLWrapper.cpp @@ -0,0 +1,12 @@ +#include "pch.h" +#include "SDLWrapper.h" + +using namespace Gaming; + +SDLWrapper::SDLWrapper() { + verifySDLCall(::SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC), "Failed to initialise SDL: "); +} + +SDLWrapper::~SDLWrapper() { + ::SDL_Quit(); +} \ No newline at end of file