diff --git a/.gitignore b/.gitignore index 7e10ffd..51ceb82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ **/.DS_Store +.editorconfig .vscode .cache build diff --git a/CMakeLists.txt b/CMakeLists.txt index f2ba953..7ea034f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,13 @@ add_library( src/Trace.cpp src/Players/DummyPlayer.cpp src/Players/KeyboardPlayer.cpp - src/Sound.cpp) + src/Sound.cpp + src/MainMenu.cpp + src/GameManager.cpp + src/MenuLevel.cpp + src/Button.cpp + src/MenuItem.cpp + ) target_include_directories(tank_bot_fight_lib PRIVATE src) diff --git a/src/Board.cpp b/src/Board.cpp index 089cb2f..7abfc53 100644 --- a/src/Board.cpp +++ b/src/Board.cpp @@ -11,10 +11,8 @@ #include "Tank/TankFactory.hpp" #include "TracesHandler.hpp" -Board::Board() - : mWindow(sf::VideoMode(WIDTH, HEIGHT), "TankBotFight"), - mBackground(mStore), - mTankExplodeSound("explosion.flac") { +Board::Board(sf::RenderWindow& window) + : mWindow{window}, mBackground(mStore), mTankExplodeSound{"explosion.flac"} { constexpr float TANK_X = WIDTH / 2.0f; constexpr float TANK_Y = 50.f; constexpr float TANK2_X = WIDTH / 2.0f; @@ -31,7 +29,6 @@ Board::Board() void Board::register_missile(const Missle& missile) { mMissles.push_back(missile); } void Board::draw() { - mWindow.clear(); mBackground.draw(mWindow); if (mKeyboardPlayer) { mKeyboardPlayer->draw(mWindow); @@ -46,34 +43,23 @@ void Board::draw() { animation.draw(mWindow); }; display_speed(); - mWindow.display(); } -void Board::run() { - while (mWindow.isOpen()) { - sf::Event event{}; - while (mWindow.pollEvent(event)) { - if (event.type == sf::Event::Closed) { - mWindow.close(); - } +void Board::play(const sf::Event& event) { + if (mKeyboardPlayer) { + mKeyboardPlayer->handle_events(event); + } - if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) { - mWindow.close(); - } - if (mKeyboardPlayer) { - mKeyboardPlayer->handle_events(event); - } - } - if (mDummyPlayer) { - mDummyPlayer->update(); - } - if (mKeyboardPlayer) { - mKeyboardPlayer->update(); - } - draw(); - remove_missles(); - update_players(); + if (mDummyPlayer) { + mDummyPlayer->update(); + } + + if (mKeyboardPlayer) { + mKeyboardPlayer->update(); } + draw(); + remove_missles(); + update_players(); } void Board::display_speed() { diff --git a/src/Board.hpp b/src/Board.hpp index 94d6918..8d0cc66 100644 --- a/src/Board.hpp +++ b/src/Board.hpp @@ -10,11 +10,11 @@ #include "background/Background.hpp" class Board { public: - Board(); + Board(sf::RenderWindow& window); void register_missile(const Missle& missile); void register_animation(const Animation& animation); - void run(); + void play(const sf::Event& event); private: void remove_missles(); @@ -23,7 +23,7 @@ class Board { void draw(); TextureStore mStore; - sf::RenderWindow mWindow; + sf::RenderWindow& mWindow; Background mBackground; std::unique_ptr mKeyboardPlayer; std::unique_ptr mDummyPlayer; diff --git a/src/Button.cpp b/src/Button.cpp new file mode 100644 index 0000000..f3b0aed --- /dev/null +++ b/src/Button.cpp @@ -0,0 +1,54 @@ +#include "Button.hpp" + +Button::Button(std::string text, sf::Vector2f top_left_corner, unsigned int width, + unsigned int height, void (*callback)(void), ButtonType button_type, + MenuLevel* next_level) + : mTextContent{text}, + mCallback{callback}, + MenuItem{top_left_corner}, + mWidth{width}, + mHeight{height}, + mButtonType{button_type}, + mNextLevel{next_level} {} + +void Button::draw(sf::RenderWindow& window, const sf::Font& font) { + // draw rectangle + sf::RectangleShape rectangle(sf::Vector2f(mWidth, mHeight)); + + // draw rectangle outline + if (mIsSelected) { + rectangle.setOutlineThickness(10.0f); + rectangle.setOutlineColor(sf::Color(255, 0, 0)); + } else { + rectangle.setOutlineThickness(0.0f); + } + + // set text + mText.setFont(font); + mText.setCharacterSize(24); + mText.setColor(sf::Color::Red); + mText.setString(mTextContent); + + // move objects in window and draw + rectangle.setPosition(mPosition); + window.draw(rectangle); + + // center text + mText.setOrigin(mText.getGlobalBounds().getSize() / 2.f + mText.getLocalBounds().getPosition()); + mText.setPosition(rectangle.getPosition() + (rectangle.getSize() / 2.f)); + window.draw(mText); +} + +void Button::select() { mIsSelected = true; } + +void Button::deselect() { mIsSelected = false; } + +void Button::click() { + if (mButtonType == ButtonType::Callback && mIsSelected) { + mCallback(); + } +} + +ButtonType Button::get_button_type() const { return mButtonType; } + +MenuLevel* Button::get_next_level() const { return mNextLevel; } diff --git a/src/Button.hpp b/src/Button.hpp new file mode 100644 index 0000000..14bae50 --- /dev/null +++ b/src/Button.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +#include "MenuItem.hpp" +#include "MenuLevel.hpp" + +enum class ButtonType { Callback, LevelChanger }; + +class MenuLevel; + +class Button : MenuItem { + public: + Button(std::string text, sf::Vector2f top_left_corner, unsigned int width, unsigned int height, + void (*callback)(void), ButtonType button_type, MenuLevel* next_level = nullptr); + void draw(sf::RenderWindow& window, const sf::Font& font); + void select(); + void deselect(); + void click(); + ButtonType get_button_type() const; + MenuLevel* get_next_level() const; + + private: + std::string mTextContent; + sf::Text mText; + unsigned int mWidth; + unsigned int mHeight; + void (*mCallback)(void); + bool mIsSelected = false; + MenuLevel* mNextLevel; + ButtonType mButtonType; +}; \ No newline at end of file diff --git a/src/GameManager.cpp b/src/GameManager.cpp new file mode 100644 index 0000000..f8ce6c6 --- /dev/null +++ b/src/GameManager.cpp @@ -0,0 +1,97 @@ +#include "GameManager.hpp" + +#include "Size.hpp" + +// define static variables +bool GameManager::request_state_change = false; +GameManagerState GameManager::desired_state = GameManagerState::InvalidState; + +// Button callbacks: +void main_menu_start_callback(void) { + GameManager::request_state_change = true; + GameManager::desired_state = GameManagerState::Started; +} +void main_menu_exit_callback(void) { + GameManager::request_state_change = true; + GameManager::desired_state = GameManagerState::Exit; +} + +// define menus +extern MenuLevel options; + +const unsigned int buttonWidth = WIDTH / 5; +const unsigned int buttonHeight = HEIGHT / 10; +constexpr unsigned int verticalSpacing = 20; + +MenuLevel main_menu{ + {{"Start", sf::Vector2f((WIDTH - buttonWidth) / 2, verticalSpacing + buttonHeight * 1), + buttonWidth, buttonHeight, main_menu_start_callback, ButtonType::Callback, nullptr}, + {"Options", sf::Vector2f((WIDTH - buttonWidth) / 2, verticalSpacing + buttonHeight * 3), + buttonWidth, buttonHeight, nullptr, ButtonType::LevelChanger, &options}, + {"Exit", sf::Vector2f((WIDTH - buttonWidth) / 2, verticalSpacing + buttonHeight * 5), + buttonWidth, buttonHeight, main_menu_exit_callback, ButtonType::Callback, nullptr}}}; + +MenuLevel options{{"Back", sf::Vector2f(WIDTH / 2 - buttonWidth, 2 * buttonHeight + 20), + buttonWidth, buttonHeight, nullptr, ButtonType::LevelChanger, &main_menu}}; + +GameManager::GameManager() + : mWindow{sf::VideoMode(WIDTH, HEIGHT), "TankBotFight"}, + mBoard{mWindow}, + mMainMenu{mWindow, {&main_menu, &options}, &main_menu} {} + +void GameManager::start() { + while (mWindow.isOpen()) { + sf::Event event; + while (mWindow.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + mGameManagerState = GameManagerState::Exit; + } + if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) { + mGameManagerState = GameManagerState::Exit; + } + } + + // clear the window with black color + mWindow.clear(sf::Color::Black); + + performStateMachine(event); + + // end the current frame + mWindow.display(); + } +} + +void GameManager::performStateMachine(const sf::Event& event) { + switch (mGameManagerState) { + case GameManagerState::MainMenu: + mMainMenu.process_and_draw(event); + break; + case GameManagerState::Started: + mBoard.play(event); + break; + case GameManagerState::Exit: + mWindow.close(); + break; + } + + // check if state change is requested + if (request_state_change) { + request_state_change = false; + transitState(); + } +} + +void GameManager::transitState() { + switch (mGameManagerState) { + case GameManagerState::MainMenu: + if (desired_state == GameManagerState::Started) { + mGameManagerState = GameManagerState::Started; + } else if (desired_state == GameManagerState::Exit) { + mGameManagerState = GameManagerState::Exit; + } + break; + case GameManagerState::Started: + // TODO -> pause game + break; + } +} diff --git a/src/GameManager.hpp b/src/GameManager.hpp new file mode 100644 index 0000000..dd61d5f --- /dev/null +++ b/src/GameManager.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "Board.hpp" +#include "MainMenu.hpp" + +enum class GameManagerState { + MainMenu, + Started, + Exit, + InvalidState, +}; + +class GameManager { + public: + GameManager(); + void start(); + void performStateMachine(const sf::Event& event); + void transitState(); + + private: + // objects + GameManagerState mGameManagerState = GameManagerState::MainMenu; + sf::RenderWindow mWindow; + MainMenu mMainMenu; + Board mBoard; + static bool request_state_change; + static GameManagerState desired_state; + + // friends: buttons' callbacks + friend void main_menu_start_callback(void); + friend void main_menu_exit_callback(void); +}; \ No newline at end of file diff --git a/src/MainMenu.cpp b/src/MainMenu.cpp new file mode 100644 index 0000000..2dd4f44 --- /dev/null +++ b/src/MainMenu.cpp @@ -0,0 +1,23 @@ +#include "MainMenu.hpp" + +#include "Files.hpp" + +MainMenu::MainMenu(sf::RenderWindow& window, std::initializer_list menu_level_ptrs, + MenuLevel* current_level) + : mWindow{window}, mMenuLevelPtrs{menu_level_ptrs}, mCurrentLevel{current_level} { + mFont.loadFromFile(files::asset_path() + "DejaVuSans.ttf"); +} + +void MainMenu::process_and_draw(const sf::Event& event) { + // check if menu should be changed + if (mCurrentLevel->is_level_change_requested()) { + mCurrentLevel->reset_level_request(); + mCurrentLevel = mCurrentLevel->get_next_level(); + } + + // process inputs + mCurrentLevel->processEvents(event); + + // draw menu + mCurrentLevel->draw(mWindow, mFont); +} diff --git a/src/MainMenu.hpp b/src/MainMenu.hpp new file mode 100644 index 0000000..017cdf3 --- /dev/null +++ b/src/MainMenu.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +#include "Button.hpp" +#include "MenuLevel.hpp" + +class MainMenu { + public: + MainMenu(sf::RenderWindow& window, std::initializer_list menu_level_ptrs, + MenuLevel* current_level); + void process_and_draw(const sf::Event& event); + + private: + sf::RenderWindow& mWindow; + std::vector mMenuLevelPtrs; + MenuLevel* mCurrentLevel = nullptr; + sf::Font mFont; +}; \ No newline at end of file diff --git a/src/MenuItem.cpp b/src/MenuItem.cpp new file mode 100644 index 0000000..eadca8b --- /dev/null +++ b/src/MenuItem.cpp @@ -0,0 +1,3 @@ +#include "MenuItem.hpp" + +MenuItem::MenuItem(sf::Vector2f position) : mPosition{position} {} \ No newline at end of file diff --git a/src/MenuItem.hpp b/src/MenuItem.hpp new file mode 100644 index 0000000..1bfa3a2 --- /dev/null +++ b/src/MenuItem.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +class MenuItem { + public: + MenuItem(sf::Vector2f position); + + protected: + sf::Vector2f mPosition; +}; \ No newline at end of file diff --git a/src/MenuLevel.cpp b/src/MenuLevel.cpp new file mode 100644 index 0000000..31206da --- /dev/null +++ b/src/MenuLevel.cpp @@ -0,0 +1,61 @@ +#include "MenuLevel.hpp" + +MenuLevel::MenuLevel(std::initializer_list