# Project Overview ## CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.16) project(DangerousRacers VERSION 0.2.0 LANGUAGES CXX) # ========================= # C++ Standard # ========================= set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # ========================= # Build Type Default # ========================= if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") # ========================= # MSVC Runtime (IMPORTANT) # Use dynamic runtime (/MD, /MDd) # Reduces antivirus false positives # ========================= if (MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") endif() # ========================= # SFML Configuration # ========================= include(FetchContent) # Debug = static (easy local dev) # Release = shared (better distribution) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(BUILD_SHARED_LIBS OFF) set(SFML_STATIC_LIBRARIES ON) else() set(BUILD_SHARED_LIBS ON) set(SFML_STATIC_LIBRARIES OFF) endif() FetchContent_Declare( SFML GIT_REPOSITORY https://github.com/SFML/SFML.git GIT_TAG 2.6.1 GIT_SHALLOW ON ) FetchContent_MakeAvailable(SFML) # ========================= # Source Files # ========================= set(SOURCE_FILES src/main.cpp src/Scene.cpp src/SceneManager.cpp src/DrawableEntity.cpp src/Label.cpp src/Button.cpp src/Scenes/MainMenu.cpp src/Scenes/studio_splashscreen.cpp src/Scenes/GameOver.cpp src/AnimatedSprite.cpp ) # ========================= # Executable # ========================= # WIN32 removes console window (optional) add_executable(DangerousRacers ${SOURCE_FILES}) # Only apply for Release builds on Windows if(WIN32) target_link_options(DangerousRacers PRIVATE $<$:/SUBSYSTEM:WINDOWS> $<$:/ENTRY:mainCRTStartup> ) endif() target_sources(DangerousRacers PRIVATE resources/version.rc) # ========================= # Includes # ========================= target_include_directories(DangerousRacers PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) # ========================= # Compile Definitions # ========================= if(SFML_STATIC_LIBRARIES) target_compile_definitions(DangerousRacers PRIVATE SFML_STATIC) endif() # ========================= # Linking # ========================= target_link_libraries(DangerousRacers PRIVATE sfml-graphics sfml-window sfml-system sfml-audio ) # ========================= # Compiler Flags # ========================= if(MSVC) target_compile_options(DangerousRacers PRIVATE $<$:/O2 /DNDEBUG> $<$:/Zi> ) endif() # ========================= # Copy Assets # ========================= add_custom_command(TARGET DangerousRacers POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/assets $/assets ) add_custom_command(TARGET DangerousRacers POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/resources/README.md $/README.md ) # ========================= # OpenAL DLL (needed for audio) # ========================= if (WIN32) if (CMAKE_SIZEOF_VOID_P EQUAL 8) set(OPENAL_DLL "${sfml_SOURCE_DIR}/extlibs/bin/x64/openal32.dll") else() set(OPENAL_DLL "${sfml_SOURCE_DIR}/extlibs/bin/x86/openal32.dll") endif() add_custom_command(TARGET DangerousRacers POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${OPENAL_DLL}" $ ) endif() # ========================= # Copy SFML DLLs (Release builds) # ========================= if(WIN32 AND BUILD_SHARED_LIBS) add_custom_command(TARGET DangerousRacers POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ $ $ $ ) endif() # ========================= # Install / Packaging (optional) # ========================= install(TARGETS DangerousRacers DESTINATION .) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/assets DESTINATION ./assets) # ========================= # Output Directories # ========================= set_target_properties(DangerousRacers PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) ``` ## include/DangerousRacers/AnimatedSprite.h ```cpp #pragma once #include "DrawableEntity.h" class AnimatedSprite : public DrawableEntity { private: int frames = 1; int fps = 1; int currentFrame = 1; float frameTimer = 0.f; public: AnimatedSprite(const float x, const float y, const std::string& texturePath, const int _frames = 1, const int _fps = 1); void render(sf::RenderWindow& window); void update(const float deltaTime); }; ``` ## include/DangerousRacers/Button.h ```cpp #pragma once #include "Label.h" #include "SFML/Graphics/RectangleShape.hpp" #include "SFML/Graphics/RenderWindow.hpp" class Button { private: sf::RectangleShape buttonRect; Label label; bool selected = false; public: Button(const std::string &labelString, sf::Vector2f position); void setText(const std::string &labelString); void render(sf::RenderWindow& window) const; void setSelected(const bool _selected) { selected = _selected; label.setDarkMode(selected); if (selected) buttonRect.setFillColor({255, 255, 255}); else buttonRect.setFillColor({0, 0, 0, 64}); }; }; ``` ## include/DangerousRacers/DrawableEntity.h ```cpp #pragma once #include #include "SFML/Graphics/RenderWindow.hpp" #include "SFML/Graphics/Sprite.hpp" #include "SFML/Graphics/Texture.hpp" class DrawableEntity { protected: sf::Texture texture; sf::Sprite sprite; sf::Vector2f position, startPosition, targetPosition; float moveDuration = 0.f, moveElapsed = 0.f; bool moving = false; void _setPosition(const float x, const float y); public: DrawableEntity(const float x, const float y, const std::string& texturePath); sf::Sprite& getSprite(); void loadTexture(const std::string& texturePath); void _setTexture(const sf::Texture& _texture); void moveTo(const float x, const float y, const float timeToMove = 0.f); bool isMoving() const; sf::Vector2f getPosition() const { return position; }; sf::Vector2i getRelativePosition() const; int getHeight() const; void render(sf::RenderWindow& window); void update(const float deltaTime); }; ``` ## include/DangerousRacers/GameManager.h ```cpp #pragma once class GameManager { public: int score = 0; int bubblesPopped = 0; int getScore() const { return score; } void addScore(const int amount) { score += amount; } void resetScore() { score = 0; } int getBubblesPopped() const { return bubblesPopped; } void addBubblesPopped(const int amount) { bubblesPopped += amount; } void resetBubblesPopped() { bubblesPopped = 0; } void reset() { resetScore(); resetBubblesPopped(); } }; ``` ## include/DangerousRacers/Label.h ```cpp #pragma once #include "SFML/Graphics/Text.hpp" #include "SFML/Graphics/RenderWindow.hpp" class Label { private: sf::Text labelText; bool center; bool outline; bool darkMode = false; public: Label(const std::string &labelString, sf::Vector2f position, const bool _center = false, const bool _outline = true); void setText(const std::string &labelString); void setPosition(const sf::Vector2f position); void setFillColor(const sf::Color color) { labelText.setFillColor(color); }; void setOutlineColor(const sf::Color color) { labelText.setOutlineColor(color); }; sf::FloatRect getBounds() const { return labelText.getGlobalBounds(); }; void render(sf::RenderWindow& window) const; void setDarkMode(const bool _darkMode) { darkMode = _darkMode; labelText.setFillColor(darkMode ? sf::Color::Black : sf::Color::White); if (outline) { labelText.setOutlineColor(darkMode ? sf::Color::White : sf::Color::Black); labelText.setOutlineThickness(2); } }; }; ``` ## include/DangerousRacers/Scene.h ```cpp #pragma once #include #include "SceneManager.h" class Scene { private: public: SceneManager& sceneManager; sf::Clock clock; std::string name; // std::vector dialogues; Scene(SceneManager& _sceneManager, std::string _name) : sceneManager(_sceneManager), name(std::move(_name)) {}; virtual void start(); virtual void update(float deltaTime); virtual void render(sf::RenderWindow& window); virtual void eventHandler(sf::Event& event); // void addDialogue(Dialogue* dialogue); }; ``` ## include/DangerousRacers/SceneManager.h ```cpp #pragma once #include #include #include "GameManager.h" #include "SFML/Graphics/RenderWindow.hpp" class Scene; class SceneManager { private: sf::RenderWindow& gameWindow; GameManager& gameManager; std::unordered_map> scenes; std::string activeScene; public: SceneManager(sf::RenderWindow& gameWindow, GameManager& gameManager); template void addScene(Args&&... args) { // scenes.push_back(std::make_unique(*this, std::forward(args)...)); auto scene = std::make_unique(*this, std::forward(args)...); std::string name = scene->name; // assuming public member scenes.emplace(name, std::move(scene)); } void update(float deltaTime) const; void render(sf::RenderWindow& window) const; std::string getCurrentScene() const { return activeScene; }; GameManager& getGameManager() const { return gameManager; } void changeScene(const std::string& sceneName); void eventHandler(sf::Event& event) const; void _quit(); }; ``` ## include/DangerousRacers/Scenes/GameOver.h ```cpp #pragma once #include "DangerousRacers/Label.h" #include "DangerousRacers/Scene.h" #include "SFML/Graphics/RectangleShape.hpp" #include "SFML/Graphics/Texture.hpp" class GameOverScene : public Scene { private: bool wallTileLoaded = false; sf::Texture wallTileTexture; sf::RectangleShape wallTile; Label scoreLabel{"Score: 0\nBubbles Popped: 0", {576.f/2,768.f/2}, true, true}; public: GameOverScene(SceneManager& _sceneManager); void start(); void update(float deltaTime); void render(sf::RenderWindow& window); void eventHandler(sf::Event& event); }; ``` ## include/DangerousRacers/Scenes/MainMenu.h ```cpp #pragma once #include "DangerousRacers/Button.h" #include "DangerousRacers/Scene.h" #include "SFML/Graphics/Sprite.hpp" #include "SFML/Graphics/Texture.hpp" class MainMenuScene : public Scene { private: sf::Texture backgroundTexture; sf::Sprite backgroundSprite; char selectedButton = 0; sf::Texture titleTexture; sf::Sprite titleSprite; Button strategyButton{"Strategy Mode", {576.f / 2.f, 768.f * 0.25}}; Button rhythmButton{"Mania Mode (Rage Mode)", {576.f / 2.f, 768.f * 0.35}}; public: MainMenuScene(SceneManager& _sceneManager); void start(); void update(float deltaTime); void render(sf::RenderWindow& window); void eventHandler(sf::Event& event); }; ``` ## include/DangerousRacers/Scenes/studio_splashscreen.h ```cpp #pragma once #include "DangerousRacers/Scene.h" #include "SFML/Graphics/Texture.hpp" #include "SFML/Graphics/Sprite.hpp" class StudioSplashScreenScene : public Scene { public: sf::Texture logoTexture; sf::Sprite logoSprite; bool fadingIn = true; float fadeTime = 2.5f; StudioSplashScreenScene(SceneManager& _sceneManager); void start(); void update(float deltaTime); void render(sf::RenderWindow& window); void eventHandler(sf::Event& event); }; ``` ## src/AnimatedSprite.cpp ```cpp #include AnimatedSprite::AnimatedSprite(const float x, const float y, const std::string &texturePath, const int _frames, const int _fps) : DrawableEntity(x, y, texturePath) { fps = _fps; frames = _frames; sprite.setTextureRect({0, 0, getHeight(), getHeight()}); } void AnimatedSprite::render(sf::RenderWindow &window) { DrawableEntity::render(window); } void AnimatedSprite::update(const float deltaTime) { DrawableEntity::update(deltaTime); frameTimer += deltaTime; if (frameTimer >= 1.f / static_cast(fps)) { frameTimer = 0.f; sprite.setTextureRect({getHeight() * (currentFrame % frames), 0, getHeight(), getHeight()}); currentFrame++; } } ``` ## src/Button.cpp ```cpp #include constexpr int BUTTON_X_PADDING = 48, BUTTON_Y_PADDING = 32; Button::Button(const std::string &labelString, const sf::Vector2f position) : label("", {0,0}, true, false) { label.setText(labelString); label.setPosition(position); // label.setColor({0, 0, 0}); const sf::FloatRect bounds = label.getBounds(); buttonRect.setSize({bounds.width + BUTTON_X_PADDING, bounds.height + BUTTON_Y_PADDING}); buttonRect.setOrigin(bounds.width / 2.f + (BUTTON_X_PADDING / 2), bounds.height / 2.f - 14 + (BUTTON_Y_PADDING / 2)); buttonRect.setPosition(position); buttonRect.setFillColor({0, 0, 0, 64}); } void Button::setText(const std::string &labelString) { label.setText(labelString); } void Button::render(sf::RenderWindow &window) const { window.draw(buttonRect); label.render(window); } ``` ## src/DrawableEntity.cpp ```cpp #include #include #include #include "SFML/Graphics/Texture.hpp" DrawableEntity::DrawableEntity(const float x, const float y, const std::string &texturePath) { loadTexture(texturePath); this->_setPosition(x, y); } void DrawableEntity::loadTexture(const std::string &texturePath) { if (texturePath.empty() || !texture.loadFromFile(texturePath)) { std::cerr << "Failed to load texture: " << texturePath << std::endl; } texture.setSmooth(false); sprite.setTexture(texture); } void DrawableEntity::_setPosition(const float x, const float y) { startPosition = position; targetPosition = sf::Vector2f(x, y); position = targetPosition; moveDuration = 0; moveElapsed = 0.f; moving = false; sprite.setPosition(position); } sf::Sprite& DrawableEntity::getSprite() { return sprite; } void DrawableEntity::moveTo(const float x, const float y, const float timeToMove) { startPosition = position; targetPosition = sf::Vector2f(x, y); moveDuration = timeToMove; moveElapsed = 0.f; moving = timeToMove > 0.f; if (!moving) { position = targetPosition; sprite.setPosition(position); }; } void DrawableEntity::_setTexture(const sf::Texture &_texture) { texture = _texture; sprite.setTexture(texture, true); } sf::Vector2i DrawableEntity::getRelativePosition() const { return {static_cast(targetPosition.x) / 64, static_cast(targetPosition.y) / 64}; } bool DrawableEntity::isMoving() const { return moving; } void DrawableEntity::render(sf::RenderWindow &window) { window.draw(sprite); } void DrawableEntity::update(const float deltaTime) { if (!moving) return; moveElapsed += deltaTime; float t = moveElapsed / moveDuration; if (t >= 1.f) { t = 1.f; moving = false; } position = startPosition + (targetPosition - startPosition) * t; sprite.setPosition(position); } int DrawableEntity::getHeight() const { return static_cast(sprite.getGlobalBounds().height / sprite.getScale().y); } ``` ## src/Label.cpp ```cpp #include #include #include Label::Label(const std::string &labelString, const sf::Vector2f position, const bool _center, const bool _outline) { center = _center; outline = _outline; static bool fontLoaded = false; static sf::Font font; if (!fontLoaded) { std::cout << "Loading font..." << std::endl; fontLoaded = true; font.loadFromFile("assets/jersey.ttf"); font.setSmooth(false); } labelText.setFont(font); labelText.setString(labelString); labelText.setPosition(position); labelText.setCharacterSize(32); labelText.setFillColor(darkMode ? sf::Color::Black : sf::Color::White); if (outline) { labelText.setOutlineColor(darkMode ? sf::Color::White : sf::Color::Black); labelText.setOutlineThickness(2); } } void Label::setText(const std::string &labelString) { labelText.setString(labelString); if (center) labelText.setOrigin(labelText.getLocalBounds().width / 2, labelText.getLocalBounds().height / 2); } void Label::setPosition(const sf::Vector2f position) { if (center) labelText.setOrigin(labelText.getLocalBounds().width / 2, labelText.getLocalBounds().height / 2); labelText.setPosition(position); } void Label::render(sf::RenderWindow &window) const { window.draw(labelText); } ``` ## src/main.cpp ```cpp #include #include "DangerousRacers/SceneManager.h" #include "DangerousRacers/GameManager.h" #include "DangerousRacers/Scenes/GameOver.h" #include "DangerousRacers/Scenes/MainMenu.h" #include "DangerousRacers/Scenes/studio_splashscreen.h" int main() { // Setup View related stuffs sf::Vector2u RESOLUTION(576, 768); sf::VideoMode VIDEO_MODE = sf::VideoMode::getDesktopMode(); sf::RenderWindow GAME_WINDOW; GAME_WINDOW.create(sf::VideoMode(RESOLUTION.x, RESOLUTION.y), "SFML Game", sf::Style::None); GAME_WINDOW.setMouseCursorVisible(true); GAME_WINDOW.setMouseCursorGrabbed(true); // Make sure the background is black sf::RectangleShape bg_rect(sf::Vector2f(static_cast(RESOLUTION.x), static_cast(RESOLUTION.y))); bg_rect.setFillColor(sf::Color::Black); // THe big bad delta clock sf::Clock deltaClock; auto gameManager = std::make_unique(); auto sceneManager = std::make_unique(GAME_WINDOW, *gameManager); sceneManager->addScene(); sceneManager->addScene(); sceneManager->changeScene("StudioSplashScreenScene"); while (GAME_WINDOW.isOpen()) { sf::Event event; while (GAME_WINDOW.pollEvent(event)) { if (event.type == sf::Event::Closed) { sceneManager->_quit(); } if ((event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)) { if (sceneManager->getCurrentScene() != "MainMenu") sceneManager->changeScene("MainMenu"); else sceneManager->_quit(); } sceneManager->eventHandler(event); } float deltaTime = deltaClock.restart().asSeconds(); sceneManager->update(deltaTime); GAME_WINDOW.clear(); GAME_WINDOW.draw(bg_rect); sceneManager->render(GAME_WINDOW); GAME_WINDOW.display(); } return 0; } ``` ## src/Scene.cpp ```cpp #include void Scene::start() { clock.restart(); } void Scene::update(const float deltaTime) { } void Scene::render(sf::RenderWindow& window) { } void Scene::eventHandler(sf::Event& event) { } ``` ## src/SceneManager.cpp ```cpp #include #include #include #include "DangerousRacers/Scene.h" SceneManager::SceneManager(sf::RenderWindow& gameWindow, GameManager& gameManager) : gameWindow(gameWindow), gameManager(gameManager) { } void SceneManager::changeScene(const std::string& sceneName) { std::cout << "Changing scene to " << sceneName << std::endl; const auto &scene = scenes.at(sceneName); if (!scene) { std::cout << "Scene not found?" << std::endl; return; }; this->activeScene = sceneName; scene->start(); } void SceneManager::update(const float deltaTime) const { if (activeScene.empty()) return; scenes.at(activeScene)->update(deltaTime); } void SceneManager::render(sf::RenderWindow& window) const { if (activeScene.empty()) return; scenes.at(activeScene)->render(window); } void SceneManager::eventHandler(sf::Event &event) const { if (activeScene.empty()) return; scenes.at(activeScene)->eventHandler(event); } void SceneManager::_quit() { this->activeScene.clear(); scenes.clear(); this->gameWindow.close(); } ``` ## src/Scenes/GameOver.cpp ```cpp #include "DangerousRacers/Scenes/GameOver.h" GameOverScene::GameOverScene(SceneManager &_sceneManager) : Scene(_sceneManager, "GameOverScene") { if (!wallTileLoaded) { wallTileTexture.loadFromFile("assets/wall-tile.png"); wallTileTexture.setSmooth(false); wallTileTexture.setRepeated(true); wallTileLoaded = true; } // 768 wallTile.setSize({576.f, 768.f}); wallTile.setTexture(&wallTileTexture); wallTile.setTextureRect({0, 0, 576/2, 768/2}); // wallTile.setFillColor(sf::Color::Red); } void GameOverScene::start() { Scene::start(); scoreLabel.setText( "Game Over!\n\n" "Score: " + std::to_string(sceneManager.getGameManager().getScore()) + "\n" "Bubbles Popped: " + std::to_string(sceneManager.getGameManager().getBubblesPopped()) ); } void GameOverScene::update(const float deltaTime) { Scene::update(deltaTime); } void GameOverScene::render(sf::RenderWindow &window) { Scene::render(window); window.draw(wallTile); scoreLabel.render(window); } void GameOverScene::eventHandler(sf::Event &event) { Scene::eventHandler(event); } ``` ## src/Scenes/MainMenu.cpp ```cpp #include #include "SFML/Window/Event.hpp" MainMenuScene::MainMenuScene(SceneManager& _sceneManager) : Scene (_sceneManager, "MainMenu") { backgroundTexture.loadFromFile("assets/background.png"); backgroundSprite.setTexture(backgroundTexture); strategyButton.setSelected(true); titleTexture.loadFromFile("assets/title.png"); titleSprite.setTexture(titleTexture); titleSprite.setOrigin(titleTexture.getSize().x / 2, titleTexture.getSize().y / 2); titleSprite.setPosition(576.f / 2.f, 768.f / 6.f); } void MainMenuScene::start() { Scene::start(); } void MainMenuScene::update(const float deltaTime) { Scene::update(deltaTime); } void MainMenuScene::render(sf::RenderWindow &window) { Scene::render(window); window.draw(backgroundSprite); window.draw(titleSprite); strategyButton.render(window); rhythmButton.render(window); } void MainMenuScene::eventHandler(sf::Event &event) { Scene::eventHandler(event); if (event.type == sf::Event::KeyPressed) { if (event.key.code == sf::Keyboard::Enter) { if (selectedButton == 0) this->sceneManager.changeScene("StrategyGamemode"); else this->sceneManager.changeScene("RhythmGamemode"); return; } if (event.key.code == sf::Keyboard::Up) selectedButton--; else if (event.key.code == sf::Keyboard::Down) selectedButton++; if (selectedButton < 0) selectedButton = 1; else if (selectedButton > 1) selectedButton = 0; strategyButton.setSelected(selectedButton == 0); rhythmButton.setSelected(selectedButton == 1); } } ``` ## src/Scenes/studio_splashscreen.cpp ```cpp #include #include "SFML/Graphics/Texture.hpp" #include "SFML/Graphics/Sprite.hpp" #include "SFML/Window/Event.hpp" StudioSplashScreenScene::StudioSplashScreenScene (SceneManager& _sceneManager) : Scene (_sceneManager, "StudioSplashScreenScene") { logoTexture.loadFromFile("assets/StudioLogo.png"); logoSprite.setTexture(logoTexture); logoSprite.setOrigin(logoTexture.getSize().x / 2, logoTexture.getSize().y / 2); logoSprite.setPosition(576.f / 2.f, 768.f / 2.f); logoSprite.setColor({255, 255, 255, 0}); } void StudioSplashScreenScene::start() { Scene::start(); } void StudioSplashScreenScene::update (const float deltaTime) { Scene::update (deltaTime); if (fadingIn && this->clock.getElapsedTime().asSeconds() > fadeTime) { fadingIn = false; this->clock.restart(); } else if (!fadingIn && this->clock.getElapsedTime().asSeconds() > fadeTime) { this->sceneManager.changeScene("MainMenu"); return; } float percent = this->clock.getElapsedTime().asSeconds() / fadeTime; if (!fadingIn) percent = 1.f - percent; logoSprite.setColor({255, 255, 255, static_cast(255 * percent)}); } void StudioSplashScreenScene::render (sf::RenderWindow& window) { Scene::render (window); window.draw(logoSprite); } void StudioSplashScreenScene::eventHandler (sf::Event& event) { Scene::eventHandler (event); if (event.type == sf::Event::JoystickButtonPressed || event.type == sf::Event::KeyPressed) { this->sceneManager.changeScene("MainMenu"); } } ```