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 <Adrian.conlon@gmail.com>
This commit is contained in:
Adrian Conlon 2019-10-01 23:54:48 +01:00
parent 1577455a67
commit 89fae1cb6f
8 changed files with 131 additions and 68 deletions

View File

@ -11,22 +11,13 @@
#include <Device.h>
#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> 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<SDL_PixelFormat> pixelFormat() const noexcept { return m_pixelFormat; }
private:
SDLWrapper m_wrapper;
std::shared_ptr<SDL_Window> m_window;
std::shared_ptr<SDL_Renderer> m_renderer;
std::shared_ptr<SDL_Texture> 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) {}
};
}

View File

@ -1,5 +1,7 @@
#pragma once
#include <memory>
#include <SDL.h>
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<SDL_GameController> m_gameController;
void open();
void close() noexcept;
SDL_Haptic* m_hapticController = nullptr;
std::shared_ptr<SDL_Haptic> m_hapticController;
bool m_hapticRumbleSupported = false;
void openHapticController();

18
Gaming/inc/SDLWrapper.h Normal file
View File

@ -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);
}
};
}

View File

@ -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<GameController> 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;
}
}

View File

@ -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());
}
}
}
}

View File

@ -166,10 +166,12 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="SDLWrapper.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\inc\Game.h" />
<ClInclude Include="..\inc\GameController.h" />
<ClInclude Include="..\inc\SDLWrapper.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@ -20,6 +20,9 @@
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SDLWrapper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\inc\Game.h">
@ -31,5 +34,8 @@
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\SDLWrapper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

12
Gaming/src/SDLWrapper.cpp Normal file
View File

@ -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();
}