From b2f0190f6b2a227a21dd4909476171f7cc371a2d Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 19 Mar 2022 16:46:19 -0400 Subject: menu! --- src/consts.h | 2 + src/game.cpp | 34 +++++++++----- src/game.h | 8 +++- src/main.cpp | 15 ++++--- src/menu.cpp | 109 +++++++++++++++++++++++++++++++++++++++++++++ src/menu.h | 42 ++++++++++++++++++ src/renderer.cpp | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/renderer.h | 12 +++++ 8 files changed, 334 insertions(+), 21 deletions(-) create mode 100644 src/menu.cpp create mode 100644 src/menu.h (limited to 'src') diff --git a/src/consts.h b/src/consts.h index ecb2306..4c69a58 100644 --- a/src/consts.h +++ b/src/consts.h @@ -15,5 +15,7 @@ constexpr int NUM_TITLES = 1; constexpr int MESSAGE_MARGIN = 64; constexpr int MESSAGE_TEXT_WIDTH = GAME_WIDTH - MESSAGE_MARGIN * 2; constexpr int CHARS_PER_BEEP = 8; +constexpr int MENU_PADDING = 40; +constexpr int MENU_BORDER = 2; #endif /* end of include guard: CONSTS_H_152EBF56 */ diff --git a/src/game.cpp b/src/game.cpp index f8508c2..501722d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -12,6 +12,7 @@ Game::Game(std::mt19937& rng, Muxer& muxer, Renderer& renderer) : rng(rng), muxer(muxer), + renderer(renderer), sign(renderer.getFont()) { losePopLampTimer.accumulate(losePopLampTimer.getDt()); @@ -740,13 +741,7 @@ void Game::updatePlaying(size_t frameTime) { { if (e.type == SDL_QUIT) { - if (losing != LoseState::None) - { - quit = true; - } else { - losing = LoseState::PoppingLamps; - muxer.stopMusic(); - } + quit = true; } else if (e.type == SDL_KEYDOWN) { switch (e.key.keysym.sym) @@ -755,10 +750,13 @@ void Game::updatePlaying(size_t frameTime) { { if (losing != LoseState::None) { - quit = true; + if (quitting) { + quit = true; + } else { + losing = LoseState::Done; + } } else { - losing = LoseState::PoppingLamps; - muxer.stopMusic(); + menu.open(*this); } break; @@ -922,11 +920,21 @@ void Game::updatePlaying(size_t frameTime) { { if (numDust == 0) { - quit = true; + if (quitting) { + quit = true; + } else { + losing = LoseState::Done; + } } break; } + + case LoseState::Done: + { + // Do nothing. + break; + } } switch (signInstructionState) { @@ -1035,7 +1043,9 @@ void Game::updatePlaying(size_t frameTime) { } void Game::update(size_t frameTime) { - if (sign.signDisplayState != SignInstructionState::Hidden) { + if (menu.menuState != MenuState::Closed) { + menu.update(frameTime, *this); + } else if (sign.signDisplayState != SignInstructionState::Hidden) { sign.update(frameTime, *this); } else { updatePlaying(frameTime); diff --git a/src/game.h b/src/game.h index 5f13672..839be67 100644 --- a/src/game.h +++ b/src/game.h @@ -12,6 +12,7 @@ #include "interpolation.h" #include "consts.h" #include "sign.h" +#include "menu.h" class Renderer; @@ -23,7 +24,8 @@ enum class LoseState { None, PoppingLamps, PoppingPlayer, - Outro + Outro, + Done }; struct Input { @@ -60,8 +62,10 @@ public: std::mt19937& rng; Muxer& muxer; + Renderer& renderer; bool quit = false; + bool quitting = false; // <- whether we will quit after losing, vs starting a new game LoseState losing = LoseState::None; Map map; @@ -110,6 +114,8 @@ public: Interpolation signFade; Sign sign; + Menu menu; + private: void tickDirty(bool onlyDark); diff --git a/src/main.cpp b/src/main.cpp index 2bd8f3c..ae64079 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "util.h" #include "game.h" #include "renderer.h" @@ -21,7 +22,7 @@ int main(int, char**) Renderer renderer; Muxer muxer; - Game game(rng, muxer, renderer); + std::unique_ptr game = std::make_unique(rng, muxer, renderer); constexpr int titleFadeLen = 2000; bool doneTitles = false; @@ -32,22 +33,26 @@ int main(int, char**) titleFade.start(titleFadeLen); size_t lastTime = SDL_GetTicks(); - while (!game.quit) + while (!game->quit) { size_t currentTime = SDL_GetTicks(); size_t frameTime = currentTime - lastTime; lastTime = currentTime; if (doneTitles) { - game.update(frameTime); - renderer.renderGame(game, true); + game->update(frameTime); + renderer.renderGame(*game, true); muxer.update(); + + if (game->losing == LoseState::Done) { + game = std::make_unique(rng, muxer, renderer); + } } else { SDL_Event e; while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) { - game.quit = true; + game->quit = true; } } diff --git a/src/menu.cpp b/src/menu.cpp new file mode 100644 index 0000000..b9a3ace --- /dev/null +++ b/src/menu.cpp @@ -0,0 +1,109 @@ +#include "menu.h" +#include "game.h" +#include "renderer.h" + +Menu::Menu() { + items.push_back({.text = "New Game", .activationFunction = [this] (Game& game) { + close(); + game.losing = LoseState::PoppingLamps; + game.muxer.stopMusic(); + }}); + items.push_back({.text = "Toggle Fullscreen", .activationFunction = [] (Game& game) { + game.renderer.toggleFullscreen(); + }}); + items.push_back({.text = "Quit", .activationFunction = [this] (Game& game) { + close(); + game.losing = LoseState::PoppingLamps; + game.quitting = true; + game.muxer.stopMusic(); + }}); +} + +void Menu::open(Game& game) { + menuState = MenuState::Opening; + menuDisplayProgress.start(200); + game.muxer.playSound("openmenu"); +} + +void Menu::close() { + menuState = MenuState::Closing; + menuDisplayProgress.start(200); +} + +void Menu::update(size_t dt, Game& game) { + if (menuState != MenuState::Open) { + SDL_Event e; + + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + game.quit = true; + } + } + } + + switch (menuState) { + case MenuState::Closed: { + // Shouldn't happen. + break; + } + case MenuState::Opening: { + menuDisplayProgress.tick(dt); + if (menuDisplayProgress.isComplete()) { + menuState = MenuState::Open; + } + + break; + } + case MenuState::Closing: { + menuDisplayProgress.tick(dt); + if (menuDisplayProgress.isComplete()) { + menuState = MenuState::Closed; + } + + break; + } + case MenuState::Open: { + SDL_Event e; + + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + game.quit = true; + } else if (e.type == SDL_KEYDOWN) { + switch (e.key.keysym.sym) + { + case SDLK_ESCAPE: { + close(); + game.muxer.playSound("closemenu"); + break; + } + case SDLK_SPACE: { + items[cursor].activationFunction(game); + game.muxer.playSound("menuselect"); + break; + } + case SDLK_UP: + case SDLK_w: { + cursor--; + if (cursor < 0) { + cursor = items.size() - 1; + } + game.muxer.playSound("menucursor"); + break; + } + case SDLK_DOWN: + case SDLK_s: { + cursor++; + if (cursor >= items.size()) { + cursor = 0; + } + game.muxer.playSound("menucursor"); + break; + } + } + } + } + + break; + } + } +} diff --git a/src/menu.h b/src/menu.h new file mode 100644 index 0000000..ac72999 --- /dev/null +++ b/src/menu.h @@ -0,0 +1,42 @@ +#ifndef MENU_H_61DBBF6A +#define MENU_H_61DBBF6A + +#include +#include +#include +#include "interpolation.h" + +class Game; + +enum class MenuState { + Closed, + Opening, + Open, + Closing +}; + +struct MenuItem { + std::string text; + std::function activationFunction; +}; + +class Menu { +public: + + Menu(); + + void update(size_t dt, Game& game); + + void open(Game& game); + + MenuState menuState = MenuState::Closed; + Interpolation menuDisplayProgress; + std::vector items; + int cursor = 0; + +private: + + void close(); +}; + +#endif /* end of include guard: MENU_H_61DBBF6A */ diff --git a/src/renderer.cpp b/src/renderer.cpp index b90372e..1aa5cbf 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -16,8 +16,6 @@ Renderer::Renderer() GAME_HEIGHT, SDL_WINDOW_SHOWN)); - //SDL_SetWindowFullscreen(win_.get(), SDL_WINDOW_FULLSCREEN_DESKTOP); - if (!win_) { throw sdl_error(); @@ -111,6 +109,7 @@ Renderer::Renderer() loadTextureFromFile("../res/runninbloods.png", tileset_); loadTextureFromFile("../res/lamp.png", lamp_); loadTextureFromFile("../res/read_instruction.png", readInstruction_); + loadTextureFromFile("../res/menu.png", menuBg_); loadTextureFromFile("../res/title0.png", titles_[0]); SDL_QueryTexture(titles_[0].get(), nullptr, nullptr, &titleWidths_[0], &titleHeights_[0]); @@ -394,6 +393,20 @@ void Renderer::renderGame( SDL_RenderCopy(ren_.get(), canvas.get(), &zoomRect, nullptr); + texture_ptr overlays( + SDL_CreateTexture( + ren_.get(), + SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_TARGET, + GAME_WIDTH, + GAME_HEIGHT)); + + SDL_SetTextureBlendMode(overlays.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderTarget(ren_.get(), overlays.get()); + SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); + SDL_RenderClear(ren_.get()); + if (game.signInstructionState != SignInstructionState::Hidden) { int instOpacity = 255; if (game.signInstructionState == SignInstructionState::FadingIn) { @@ -444,7 +457,7 @@ void Renderer::renderGame( srcRect.w, srcRect.h }; - SDL_SetRenderTarget(ren_.get(), nullptr); + SDL_SetRenderTarget(ren_.get(), overlays.get()); SDL_RenderCopy(ren_.get(), messageLines_[lineIndex].renderedTex.get(), &srcRect, &destRect); //std::cout << line.charsRevealed << " (" << messageLines_[lineIndex].charIndexToWidth[line.charsRevealed] << "): " << messageLines_[lineIndex].line.substr(0,line.charsRevealed) << std::endl; } @@ -455,6 +468,34 @@ void Renderer::renderGame( } } + if (game.menu.menuState != MenuState::Closed) { + if (!menu_ || menuSelected_ != game.menu.cursor) { + renderMenu(game); + } + + SDL_Rect rect { + 0, 0, GAME_WIDTH, GAME_HEIGHT + }; + + int opacity = 128; + if (game.menu.menuState == MenuState::Opening) { + opacity = game.menu.menuDisplayProgress.getProgress(0, 128); + rect.y = game.menu.menuDisplayProgress.getProgress(-GAME_HEIGHT, 0); + } else if (game.menu.menuState == MenuState::Closing) { + opacity = game.menu.menuDisplayProgress.getProgress(128, 0); + rect.y = game.menu.menuDisplayProgress.getProgress(0, -GAME_HEIGHT); + } + + SDL_SetRenderTarget(ren_.get(), overlays.get()); + SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, opacity); + SDL_RenderFillRect(ren_.get(), nullptr); + + SDL_RenderCopy(ren_.get(), menu_.get(), nullptr, &rect); + } + + SDL_SetRenderTarget(ren_.get(), nullptr); + SDL_RenderCopy(ren_.get(), overlays.get(), nullptr, nullptr); SDL_RenderPresent(ren_.get()); } @@ -550,3 +591,89 @@ void Renderer::renderMessageLine(MessageCache& line, const std::string& text, co SDL_RenderCopy(ren_.get(), lineTex.get(), nullptr, &rect); } +void Renderer::renderMenu(const Game& game) { + int maxTextWidth = 0; + for (const MenuItem& item : game.menu.items) { + int width = 0; + TTF_SizeText(font_.get(), item.text.c_str(), &width, nullptr); + if (width > maxTextWidth) { + maxTextWidth = width; + } + } + + menuWidth_ = maxTextWidth + MENU_PADDING * 2; + menuHeight_ = (TTF_FontHeight(font_.get()) + 1) * game.menu.items.size() - 1 + MENU_PADDING * 2; + + menu_.reset(SDL_CreateTexture( + ren_.get(), + SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_TARGET, + GAME_WIDTH, + GAME_HEIGHT)); + + SDL_SetRenderTarget(ren_.get(), menu_.get()); + SDL_SetTextureBlendMode(menu_.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); + SDL_RenderClear(ren_.get()); + + SDL_Color bgColor {.r = 55, .g = 55, .b = 87, .a = 255}; + SDL_Color fgColor {.r = 146, .g = 146, .b = 146, .a = 255}; + SDL_Color selectedColor {.r = 255, .g = 255, .b = 255, .a = 255}; + + /*{ + SDL_Rect drawRect { + (GAME_WIDTH - menuWidth_) / 2 - MENU_BORDER, + (GAME_HEIGHT - menuHeight_) / 2 - MENU_BORDER, + menuWidth_ + MENU_BORDER * 2, + menuHeight_ + MENU_BORDER * 2 + }; + + SDL_SetRenderDrawColor(ren_.get(), 200, 200, 200, 255); + SDL_RenderFillRect(ren_.get(), &drawRect); + } + + { + SDL_Rect drawRect { + (GAME_WIDTH - menuWidth_) / 2, + (GAME_HEIGHT - menuHeight_) / 2, + menuWidth_, + menuHeight_ + }; + + SDL_SetRenderDrawColor(ren_.get(), bgColor.r, bgColor.g, bgColor.b, 255); + SDL_RenderFillRect(ren_.get(), &drawRect); + + std::cout << "(" << drawRect.x << "," << drawRect.y << "),(" << drawRect.w << "," << drawRect.h << ")" << std::endl; + }*/ + + SDL_RenderCopy(ren_.get(), menuBg_.get(), nullptr, nullptr); + + for (int i=0; i < game.menu.items.size(); i++) { + const MenuItem& item = game.menu.items.at(i); + int width = 0; + TTF_SizeText(font_.get(), item.text.c_str(), &width, nullptr); + + surface_ptr lineSurf = surface_ptr(TTF_RenderText_Shaded(font_.get(), item.text.c_str(), game.menu.cursor == i ? selectedColor : fgColor, bgColor)); + texture_ptr lineTex = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), lineSurf.get())); + + SDL_Rect drawRect { + (GAME_WIDTH - width) / 2, + (GAME_HEIGHT - menuHeight_) / 2 + MENU_PADDING + (TTF_FontHeight(font_.get()) + 1) * i, + width, + TTF_FontHeight(font_.get()) + }; + SDL_RenderCopy(ren_.get(), lineTex.get(), nullptr, &drawRect); + } + + menuSelected_ = game.menu.cursor; +} + +void Renderer::toggleFullscreen() { + isFullscreen = !isFullscreen; + if (isFullscreen) { + SDL_SetWindowFullscreen(win_.get(), SDL_WINDOW_FULLSCREEN_DESKTOP); + } else { + SDL_SetWindowFullscreen(win_.get(), 0); + } +} diff --git a/src/renderer.h b/src/renderer.h index ce2e7e1..8a74c3c 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -165,6 +165,8 @@ public: TTF_Font* getFont() { return font_.get(); } + void toggleFullscreen(); + private: void loadTextureFromFile(std::string_view path, texture_ptr& texture); @@ -183,6 +185,7 @@ private: texture_ptr tileset_; texture_ptr lamp_; texture_ptr readInstruction_; + texture_ptr menuBg_; std::array titles_; std::array titleWidths_; @@ -199,6 +202,15 @@ private: void renderMessageLine(MessageCache& line, const std::string& text, const Game& game); MessageCache messageLines_[2]; + + bool isFullscreen = false; + + void renderMenu(const Game& game); + + texture_ptr menu_; + int menuWidth_ = 0; + int menuHeight_ = 0; + int menuSelected_ = 0; }; #endif /* end of include guard: RENDERER_H_6A58EC30 */ -- cgit 1.4.1