From 2c75d95ddf849996bfc18267a9eecb4d0f4e1916 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 16 Mar 2022 15:30:37 -0400 Subject: signs can be read now! --- src/consts.h | 3 ++ src/game.cpp | 33 +++++++++++--- src/game.h | 15 +++---- src/main.cpp | 2 +- src/muxer.cpp | 12 +++++ src/muxer.h | 2 + src/renderer.cpp | 96 ++++++++++++++++++++++++++++++++++++++++ src/renderer.h | 57 ++++++++++++++++++++++++ src/sign.cpp | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/sign.h | 45 +++++++++++++++++++ 10 files changed, 381 insertions(+), 14 deletions(-) create mode 100644 src/sign.cpp create mode 100644 src/sign.h (limited to 'src') diff --git a/src/consts.h b/src/consts.h index 78312c0..ecb2306 100644 --- a/src/consts.h +++ b/src/consts.h @@ -12,5 +12,8 @@ constexpr int CHUNK_WIDTH = 80; constexpr int CHUNK_HEIGHT = 80; constexpr int RADIUS = 8; 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; #endif /* end of include guard: CONSTS_H_152EBF56 */ diff --git a/src/game.cpp b/src/game.cpp index 301447f..5838528 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -7,9 +7,10 @@ #include "renderer.h" #include "consts.h" -Game::Game(std::mt19937& rng, Muxer& muxer) : +Game::Game(std::mt19937& rng, Muxer& muxer, Renderer& renderer) : rng(rng), - muxer(muxer) + muxer(muxer), + sign(renderer.getFont()) { losePopLampTimer.accumulate(losePopLampTimer.getDt()); @@ -631,7 +632,7 @@ void Game::performDash() { } } -void Game::update(size_t frameTime) { +void Game::updatePlaying(size_t frameTime) { SDL_Event e; while (SDL_PollEvent(&e)) @@ -666,8 +667,22 @@ void Game::update(size_t frameTime) { { if (losing == LoseState::None) { + auto [lookX, lookY] = coordInDirection(player_x, player_y, playerAnim.getDirection()); + MapData& lookTile = map.at(lookX, lookY); if (moving) { - queueDash = true; + if (!lookTile.sign) { + queueDash = true; + } + } else if (lookTile.sign) { + if (lookTile.text.empty()) { + int lineToRead = nextSignIndex++; + if (nextSignIndex >= signTexts.size()) { + nextSignIndex = 0; + } + lookTile.text = signTexts[lineToRead]; + } + + sign.displayMessage(lookTile.text); } else { performDash(); } @@ -815,7 +830,7 @@ void Game::update(size_t frameTime) { switch (signInstructionState) { case SignInstructionState::Hidden: { auto [lookX, lookY] = coordInDirection(player_x, player_y, playerAnim.getDirection()); - if (map.at(lookX, lookY).sign) { + if (losing == LoseState::None && map.at(lookX, lookY).sign) { signInstructionState = SignInstructionState::FadingIn; signFade.start(1000); } @@ -913,3 +928,11 @@ void Game::update(size_t frameTime) { playerAnim.update(frameTime); } + +void Game::update(size_t frameTime) { + if (sign.signDisplayState != SignInstructionState::Hidden) { + sign.update(frameTime, *this); + } else { + updatePlaying(frameTime); + } +} diff --git a/src/game.h b/src/game.h index 71685e6..637a033 100644 --- a/src/game.h +++ b/src/game.h @@ -11,6 +11,9 @@ #include "animation.h" #include "interpolation.h" #include "consts.h" +#include "sign.h" + +class Renderer; constexpr int TilesetIndex(int x, int y) { return x + y * 25; @@ -23,13 +26,6 @@ enum class LoseState { Outro }; -enum class SignInstructionState { - Hidden, - FadingIn, - Visible, - FadingOut -}; - struct Input { bool left = false; bool right = false; @@ -58,7 +54,7 @@ struct Kickup { class Game { public: - Game(std::mt19937& rng, Muxer& muxer); + Game(std::mt19937& rng, Muxer& muxer, Renderer& render); void update(size_t dt); @@ -111,6 +107,7 @@ public: int nextSignIndex = 0; SignInstructionState signInstructionState = SignInstructionState::Hidden; Interpolation signFade; + Sign sign; private: @@ -144,6 +141,8 @@ private: void performDash(); + void updatePlaying(size_t frameTime); + }; #endif /* end of include guard: GAME_H_7D2B65AE */ diff --git a/src/main.cpp b/src/main.cpp index 49f5ff2..2bd8f3c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,7 +21,7 @@ int main(int, char**) Renderer renderer; Muxer muxer; - Game game(rng, muxer); + Game game(rng, muxer, renderer); constexpr int titleFadeLen = 2000; bool doneTitles = false; diff --git a/src/muxer.cpp b/src/muxer.cpp index 3450187..c93c45a 100644 --- a/src/muxer.cpp +++ b/src/muxer.cpp @@ -42,6 +42,18 @@ void Muxer::setPlayerLoc(int x, int y) { ERRCHECK(system_->setListenerAttributes(0, &attributes)); } +void Muxer::playSound(std::string name) { + std::string eventPath = std::string("event:/") + name; + + FMOD::Studio::EventDescription* eventDescription = nullptr; + ERRCHECK(system_->getEvent(eventPath.c_str(), &eventDescription)); + + FMOD::Studio::EventInstance* eventInstance = nullptr; + ERRCHECK(eventDescription->createInstance(&eventInstance)); + ERRCHECK(eventInstance->start()); + ERRCHECK(eventInstance->release()); +} + void Muxer::playSoundAtPosition(std::string name, float x, float y) { std::string eventPath = std::string("event:/") + name; diff --git a/src/muxer.h b/src/muxer.h index b1a5b26..9750808 100644 --- a/src/muxer.h +++ b/src/muxer.h @@ -28,6 +28,8 @@ public: void setPlayerLoc(int x, int y); + void playSound(std::string name); + void playSoundAtPosition(std::string name, float x, float y); void setMusicLevel(int level); diff --git a/src/renderer.cpp b/src/renderer.cpp index 2be36ae..c8c1746 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,4 +1,5 @@ #include "renderer.h" +#include #include "game.h" Renderer::Renderer() @@ -113,6 +114,11 @@ Renderer::Renderer() loadTextureFromFile("../res/title0.png", titles_[0]); SDL_QueryTexture(titles_[0].get(), nullptr, nullptr, &titleWidths_[0], &titleHeights_[0]); + + font_ = font_ptr(TTF_OpenFont("../res/softsquare.ttf", 45)); + if (!font_) { + throw ttf_error(); + } } void Renderer::renderGame( @@ -389,6 +395,55 @@ void Renderer::renderGame( SDL_RenderCopy(ren_.get(), readInstruction_.get(), nullptr, nullptr); } + if (game.sign.signDisplayState != SignInstructionState::Hidden) { + int opacity = 255; + if (game.sign.signDisplayState == SignInstructionState::FadingIn) { + opacity = game.sign.signDisplayFade.getProgress(0, 255); + } else if (game.sign.signDisplayState == SignInstructionState::FadingOut) { + opacity = game.sign.signDisplayFade.getProgress(255, 0); + } + + SDL_Rect signRect { + 0, + GAME_HEIGHT / 2 - (45 * 2 + 1 + MESSAGE_MARGIN * 2) / 2, + GAME_WIDTH, + 45 * 2 + 1 + MESSAGE_MARGIN * 2 + }; + + SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, opacity); + SDL_RenderFillRect(ren_.get(), &signRect); + + if (game.sign.signDisplayState == SignInstructionState::Visible) { + int lineIndex = 0; + for (const SignLine& line : game.sign.linesToShow) { + if (messageLines_[lineIndex].line != line.text) { + renderMessageLine(messageLines_[lineIndex], line.text, game); + } + + if (line.charsRevealed > 0) { + { + SDL_Rect srcRect { + 0, 0, messageLines_[lineIndex].charIndexToWidth[line.charsRevealed], + TTF_FontHeight(font_.get()) + }; + SDL_Rect destRect { + MESSAGE_MARGIN, + GAME_HEIGHT / 2 - (45 * 2 + 1 + MESSAGE_MARGIN * 2) / 2 + MESSAGE_MARGIN + (45 + 1) * lineIndex, + srcRect.w, + srcRect.h }; + + SDL_SetRenderTarget(ren_.get(), nullptr); + 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; + } + } + + lineIndex++; + } + } + } + SDL_RenderPresent(ren_.get()); } @@ -443,3 +498,44 @@ void Renderer::loadTextureFromFile(std::string_view path, texture_ptr& texture) texture = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), pfs.get())); SDL_SetTextureBlendMode(texture.get(), SDL_BLENDMODE_BLEND); } + +void Renderer::renderMessageLine(MessageCache& line, const std::string& text, const Game& game) { + line.line = text; + + line.renderedTex.reset( + SDL_CreateTexture( + ren_.get(), + SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_TARGET, + MESSAGE_TEXT_WIDTH, + TTF_FontHeight(font_.get()))); + + SDL_SetTextureBlendMode(line.renderedTex.get(), SDL_BLENDMODE_BLEND); + + SDL_SetRenderTarget(ren_.get(), line.renderedTex.get()); + SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); + SDL_RenderClear(ren_.get()); + + line.charIndexToWidth.clear(); + line.charIndexToWidth.push_back(0); + + for (int i=0; i #include +#include #include #include #include #include +#include +#include #include "consts.h" class Game; @@ -27,6 +30,14 @@ public: } }; +class ttf_error : public std::logic_error { +public: + + ttf_error() : std::logic_error(TTF_GetError()) + { + } +}; + class sdl_wrapper { public: @@ -67,6 +78,26 @@ public: } }; +class ttf_wrapper { +public: + + ttf_wrapper() + { + if (TTF_Init() != 0) + { + ttf_error ex; + TTF_Quit(); + + throw ex; + } + } + + ~ttf_wrapper() + { + TTF_Quit(); + } +}; + class window_deleter { public: @@ -111,6 +142,16 @@ public: using texture_ptr = std::unique_ptr; +class font_deleter { +public: + + void operator()(TTF_Font* ptr) { + TTF_CloseFont(ptr); + } +}; + +using font_ptr = std::unique_ptr; + class Renderer { public: @@ -122,14 +163,18 @@ public: void renderTitle(int num, double fade); + TTF_Font* getFont() { return font_.get(); } + private: void loadTextureFromFile(std::string_view path, texture_ptr& texture); sdl_wrapper sdl_; img_wrapper img_; + ttf_wrapper ttf_; window_ptr win_; renderer_ptr ren_; + font_ptr font_; texture_ptr playerFade_; texture_ptr lampFade_; @@ -142,6 +187,18 @@ private: std::array titles_; std::array titleWidths_; std::array titleHeights_; + + // Text rendering + struct MessageCache { + texture_ptr renderedTex; + std::vector charIndexToWidth; + std::string line; + std::string overflow; + }; + + void renderMessageLine(MessageCache& line, const std::string& text, const Game& game); + + MessageCache messageLines_[2]; }; #endif /* end of include guard: RENDERER_H_6A58EC30 */ diff --git a/src/sign.cpp b/src/sign.cpp new file mode 100644 index 0000000..992ac3d --- /dev/null +++ b/src/sign.cpp @@ -0,0 +1,130 @@ +#include "sign.h" +#include +#include "util.h" +#include "consts.h" +#include "game.h" + +void Sign::displayMessage(std::string text) { + signDisplayState = SignInstructionState::FadingIn; + lines.clear(); + + auto lineChunks = splitStr>(text, "\\n"); + for (std::string lineChunk : lineChunks) { + auto words = splitStr>(lineChunk, " "); + std::string prev = words.front(); + words.pop_front(); + std::string cur = prev; + bool pauseLine = false; + + while (!words.empty()) { + cur = prev + " " + words.front(); + + int width = 0; + TTF_SizeText(font_, cur.c_str(), &width, nullptr); + if (width > MESSAGE_TEXT_WIDTH) { + lines.push_back({.text = prev, .pause = pauseLine}); + pauseLine = !pauseLine; + cur = words.front(); + } else if (words.size() == 1) { + lines.push_back({.text = cur, .pause = true}); + } + + prev = cur; + words.pop_front(); + } + } + + lines.back().pause = true; + linesToShow.push_back(lines.front()); + lines.pop_front(); + + if (!linesToShow.back().pause) { + linesToShow.push_back(lines.front()); + lines.pop_front(); + } +} + +void Sign::update(size_t dt, Game& game) { + SDL_Event e; + + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + game.quit = true; + } + } + + switch (signDisplayState) { + case SignInstructionState::Hidden: { + // Shouldn't happen. + break; + } + case SignInstructionState::FadingIn: { + signDisplayFade.tick(dt); + if (signDisplayFade.isComplete()) { + signDisplayState = SignInstructionState::Visible; + } + + break; + } + case SignInstructionState::FadingOut: { + signDisplayFade.tick(dt); + if (signDisplayFade.isComplete()) { + signDisplayState = SignInstructionState::Hidden; + } + + break; + } + case SignInstructionState::Visible: { + const Uint8* state = SDL_GetKeyboardState(NULL); + if (state[SDL_SCANCODE_SPACE]) { + bool fullyRevealed = true; + for (const SignLine& line : linesToShow) { + if (line.charsRevealed != line.text.size()) { + fullyRevealed = false; + break; + } + } + + if (fullyRevealed) { + if (linesToShow.back().pause) { + linesToShow.back().pause = false; + // Play a sound + } + if (lines.empty()) { + linesToShow.clear(); + signDisplayState = SignInstructionState::FadingOut; + signDisplayFade.start(1000); + break; + } + } + } + + textAdvTimer_.accumulate(dt); + while (textAdvTimer_.step()) { + bool advancedChars = false; + for (SignLine& line : linesToShow) { + if (line.charsRevealed < line.text.size()) { + if (line.charsRevealed % CHARS_PER_BEEP == 0) { + // Play a sound + game.muxer.playSound("textbeep"); + } + line.charsRevealed++; + advancedChars = true; + break; + } + } + if (!advancedChars) { + if (!lines.empty() && !linesToShow.back().pause) { + if (linesToShow.size() == 2) { + linesToShow.pop_front(); + } + linesToShow.push_back(lines.front()); + lines.pop_front(); + } + } + } + + break; + } + } +} diff --git a/src/sign.h b/src/sign.h new file mode 100644 index 0000000..c90a8fd --- /dev/null +++ b/src/sign.h @@ -0,0 +1,45 @@ +#ifndef SIGN_H_B0491849 +#define SIGN_H_B0491849 + +#include +#include +#include +#include "interpolation.h" +#include "timer.h" + +class Game; + +enum class SignInstructionState { + Hidden, + FadingIn, + Visible, + FadingOut +}; + +struct SignLine { + std::string text; + bool pause = false; + int charsRevealed = 0; +}; + +class Sign { +public: + + explicit Sign(TTF_Font* font) : font_(font) {} + + void displayMessage(std::string text); + + void update(size_t dt, Game& game); + + SignInstructionState signDisplayState = SignInstructionState::Hidden; + Interpolation signDisplayFade; + std::list lines; + std::list linesToShow; + +private: + + TTF_Font* font_; + Timer textAdvTimer_ { 15 }; +}; + +#endif /* end of include guard: SIGN_H_B0491849 */ -- cgit 1.4.1