From 871943d6a90bdb92b3cc495d4d927199611f8c6b Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Thu, 4 Feb 2021 20:45:18 -0500 Subject: Added text boxes Text now reveals itself and scrolls! Yay! It even plays speaker beeps. TODO: the arror indicating an A press is needed. Bullets on lines that need bullets. The header that says who the speaker is, if relevant. --- src/consts.h | 2 + src/font.cpp | 63 ++++++++++++++++++++ src/font.h | 33 +++++++++++ src/game.h | 8 +++ src/input_system.cpp | 9 ++- src/main.cpp | 3 +- src/message_system.cpp | 153 +++++++++++++++++++++++++++++++++++++++++++++++++ src/message_system.h | 28 +++++++++ src/renderer.cpp | 71 +++++++++++++++++++++++ src/renderer.h | 11 ++++ 10 files changed, 377 insertions(+), 4 deletions(-) create mode 100644 src/font.cpp create mode 100644 src/font.h (limited to 'src') diff --git a/src/consts.h b/src/consts.h index 5ef2c21..12b9e8f 100644 --- a/src/consts.h +++ b/src/consts.h @@ -10,4 +10,6 @@ const int CANVAS_HEIGHT = 160; const int MOVEMENT_SPEED = 2; const int PARTY_FRAME_DELAY = 10;// / MOVEMENT_SPEED; +const int MESSAGE_TEXT_WIDTH = 196; + #endif /* end of include guard: CONSTS_H_9561E49C */ diff --git a/src/font.cpp b/src/font.cpp new file mode 100644 index 0000000..cfaad24 --- /dev/null +++ b/src/font.cpp @@ -0,0 +1,63 @@ +#include "font.h" +#include +#include +#include "renderer.h" + +inline int charToIndex(char ch) { + return static_cast(ch) - static_cast('!'); +} + +Font::Font(std::string_view filename, Renderer& renderer) { + std::ifstream fontfile(filename.data()); + if (!fontfile.is_open()) { + throw std::invalid_argument("Can't find font file: " + std::string(filename)); + } + + std::string imagefilename; + fontfile >> imagefilename; + textureId_ = renderer.loadImageFromFile(imagefilename); + + std::string line; + char ch; + + int numChars; + fontfile >> numChars; + std::getline(fontfile, line); // characters + + fontfile >> columns_; + std::getline(fontfile, line); // characters per row + + fontfile >> tileSize_.x(); + fontfile >> ch; // x + fontfile >> tileSize_.y(); + std::getline(fontfile, line); // tile size + + std::getline(fontfile, line); // ignore the baseline location + std::getline(fontfile, line); // blank + + for (int i=0; i> ch; // the character + + int width; + fontfile >> width; + + widths_.push_back(width); + } +} + +vec2i Font::getCharacterLocation(char ch) const { + int index = charToIndex(ch); + return (vec2i { index % columns_, index / columns_ }) * tileSize_; +} + +vec2i Font::getCharacterSize(char ch) const { + int index = charToIndex(ch); + return { widths_.at(index), tileSize_.h() }; +} + +int Font::getCharacterWidth(char ch) const { + if (ch == ' ') { + return 3; + } + return widths_.at(charToIndex(ch)); +} diff --git a/src/font.h b/src/font.h new file mode 100644 index 0000000..fd24830 --- /dev/null +++ b/src/font.h @@ -0,0 +1,33 @@ +#ifndef FONT_H_C80183A7 +#define FONT_H_C80183A7 + +#include +#include +#include "vector.h" + +class Renderer; + +class Font { +public: + + Font(std::string_view filename, Renderer& renderer); + + int getTextureId() const { return textureId_; } + + vec2i getCharacterLocation(char ch) const; + + vec2i getCharacterSize(char ch) const; + + int getCharacterWidth(char ch) const; + + int getCharacterHeight() const { return tileSize_.h(); } + +private: + + int textureId_; + int columns_; + vec2i tileSize_; + std::vector widths_; +}; + +#endif /* end of include guard: FONT_H_C80183A7 */ diff --git a/src/game.h b/src/game.h index 36398bc..2f1149d 100644 --- a/src/game.h +++ b/src/game.h @@ -11,10 +11,15 @@ #include "consts.h" #include "system.h" #include "mixer.h" +#include "font.h" + +class Renderer; class Game { public: + explicit Game(Renderer& renderer) : font_("../res/font.txt", renderer) {} + Mixer& getMixer() { return mixer_; } bool shouldQuit() const { return shouldQuit_; } @@ -68,6 +73,8 @@ public: const Map& getMap() const { return *map_; } + const Font& getFont() const { return font_; } + private: Mixer mixer_; @@ -79,6 +86,7 @@ private: std::vector spriteIds_; std::vector sprites_; std::unique_ptr map_; + Font font_; }; #endif /* end of include guard: GAME_H_E6F1396E */ diff --git a/src/input_system.cpp b/src/input_system.cpp index 7bd605c..a0acc5e 100644 --- a/src/input_system.cpp +++ b/src/input_system.cpp @@ -27,11 +27,16 @@ void InputSystem::tick(double dt) { } } else if (e.key.keysym.sym == SDLK_a) { // TODO: Remove this, it's just for testing. - if (game_.getSystem().getCutsceneBarsProgress() == 1.0) { + /*if (game_.getSystem().getCutsceneBarsProgress() == 1.0) { game_.getSystem().hideCutsceneBars(); } else { game_.getSystem().displayCutsceneBars(); - } + }*/ + //game_.getSystem().displayMessage("Some people always try to avoid fighting when there are enemies around. You know the type, right? They use the dash ability to zoom right by. I guess you could say they're followers of \"peace at any price\".", SpeakerType::Woman); + game_.getSystem().displayMessage("Go to sleep.\nIn the absolute darkness.\nDon't do aaaanything.\nDon't see anyone.\nJust sleep.\nIt'll be oh, so much fun......!\nOhohohoho!", SpeakerType::Woman); + } else if (e.key.keysym.sym == SDLK_b) { + // TODO: Remove this, it's just for testing. + game_.getSystem().advanceText(); } } else if (e.type == SDL_KEYUP && (e.key.keysym.sym == SDLK_LSHIFT || e.key.keysym.sym == SDLK_RSHIFT)) { for (int spriteId : game_.getSprites()) { diff --git a/src/main.cpp b/src/main.cpp index 6674c0c..935fc78 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,7 +4,6 @@ #include "game.h" #include "timer.h" #include "map.h" -#include "mixer.h" #include "transform_system.h" #include "camera_system.h" #include "animation_system.h" @@ -13,7 +12,7 @@ #include "message_system.h" void loop(Renderer& renderer) { - Game game; + Game game(renderer); game.emplaceSystem(); game.emplaceSystem(); game.emplaceSystem(); diff --git a/src/message_system.cpp b/src/message_system.cpp index 71e8a5a..5abb4b3 100644 --- a/src/message_system.cpp +++ b/src/message_system.cpp @@ -1,4 +1,9 @@ #include "message_system.h" +#include "game.h" +#include "util.h" + +const int CHARS_TO_REVEAL = 1; +const int CHARS_PER_BEEP = 10; void MessageSystem::tick(double dt) { if (barsState_ == BarsState::Opening || barsState_ == BarsState::Closing) { @@ -11,6 +16,53 @@ void MessageSystem::tick(double dt) { barsState_ = BarsState::Closed; } } + } else if (barsState_ == BarsState::Open) { + if (!linesToShow_.empty()) { + textAdvTimer_.accumulate(dt); + while (textAdvTimer_.step()) { + // Try to advance text on the first line that isn't totally revealed yet. + bool advancedChars = false; + for (MessageLine& line : linesToShow_) { + if (line.charsRevealed < line.text.size()) { + // Every so often play a beep. + if (line.charsRevealed % CHARS_PER_BEEP == 0) { + if (speaker_ == SpeakerType::Man) { + game_.getMixer().playSound("../res/speaking_man.wav"); + } else if (speaker_ == SpeakerType::Woman) { + game_.getMixer().playSound("../res/speaking_woman.wav"); + } else if (speaker_ == SpeakerType::Boy) { + game_.getMixer().playSound("../res/speaking_boy.wav"); + } else if (speaker_ == SpeakerType::Girl) { + game_.getMixer().playSound("../res/speaking_girl.wav"); + } else if (speaker_ == SpeakerType::Nonhuman) { + game_.getMixer().playSound("../res/speaking_nonhuman.wav"); + } + } + + line.charsRevealed += CHARS_TO_REVEAL; + if (line.charsRevealed > line.text.size()) { + line.charsRevealed = line.text.size(); + } + advancedChars = true; + break; + } + } + + if (!advancedChars) { + // If both lines are totally revealed, see if we can scroll up a line. + // This is doable as long as the next line isn't the sentinel value that + // means an A press is required. + if (!lines_.empty() && lines_.front() != "\n") { + if (linesToShow_.size() == 2) { + linesToShow_.pop_front(); + } + + linesToShow_.push_back(MessageLine { .text = lines_.front() }); + lines_.pop_front(); + } + } + } + } } } @@ -24,6 +76,107 @@ void MessageSystem::hideCutsceneBars() { barsState_ = BarsState::Closing; } +void MessageSystem::displayMessage(std::string_view msg, SpeakerType speaker) { + if (!(barsState_ == BarsState::Opening || barsState_ == BarsState::Open)) { + displayCutsceneBars(); + } + + speaker_ = speaker; + + auto lineChunks = splitStr>(std::string(msg), "\n"); + for (const std::string& text : lineChunks) { + auto words = splitStr>(text, " "); + + std::string curLine; + int curWidth = 0; + bool firstWord = true; + bool shouldAddBlank = false; + + // I'm gonna be frank and admit it: I'm not gonna take hyphenation into + // consideration. Please don't write any words that are wider than the + // textbox. + for (const std::string& word : words) { + int wordWidth = 0; + bool firstChar = true; + for (int i=0; i MESSAGE_TEXT_WIDTH) { + lines_.push_back(curLine); + curLine = word; + curWidth = wordWidth + game_.getFont().getCharacterWidth(' '); + + if (shouldAddBlank) { + shouldAddBlank = false; + lines_.push_back("\n"); + } else { + shouldAddBlank = true; + } + } else { + curWidth = nextWidth; + if (!firstWord) { + curLine.append(" "); + } + curLine.append(word); + } + + firstWord = false; + } + + lines_.push_back(curLine); + lines_.push_back("\n"); + } + + if (linesToShow_.empty()) { + linesToShow_.push_back(MessageLine { .text = lines_.front() }); + lines_.pop_front(); + + if (lines_.front() != "\n") { + linesToShow_.push_back(MessageLine { .text = lines_.front() }); + lines_.pop_front(); + } + } +} + +void MessageSystem::advanceText() { + if (barsState_ != BarsState::Open) { + return; + } + + for (const MessageLine& line : linesToShow_) { + if (line.charsRevealed != line.text.size()) { + return; + } + } + + if (lines_.empty()) { + linesToShow_.clear(); + return; + } + + if (lines_.front() != "\n") { + return; + } + + lines_.pop_front(); + + if (lines_.empty()) { + linesToShow_.clear(); + } +} + double MessageSystem::getCutsceneBarsProgress() const { switch (barsState_) { case BarsState::Closed: return 0.0; diff --git a/src/message_system.h b/src/message_system.h index 4dd0166..153805a 100644 --- a/src/message_system.h +++ b/src/message_system.h @@ -1,10 +1,23 @@ #ifndef MESSAGE_SYSTEM_H_DE10D011 #define MESSAGE_SYSTEM_H_DE10D011 +#include +#include +#include #include "system.h" +#include "timer.h" class Game; +enum class SpeakerType { + None, + Man, + Woman, + Boy, + Girl, + Nonhuman +}; + class MessageSystem : public System { public: @@ -20,10 +33,21 @@ public: void hideCutsceneBars(); + void displayMessage(std::string_view msg, SpeakerType speaker); + + void advanceText(); + // Info double getCutsceneBarsProgress() const; + struct MessageLine { + std::string text; + int charsRevealed = 0; + }; + + const std::list& getLines() const { return linesToShow_; } + private: enum class BarsState { @@ -37,6 +61,10 @@ private: BarsState barsState_ = BarsState::Closed; double accum_ = 0.0; double length_ = 1000.0/8; + SpeakerType speaker_ = SpeakerType::None; + std::list lines_; + std::list linesToShow_; + Timer textAdvTimer_ { 10 }; }; #endif /* end of include guard: MESSAGE_SYSTEM_H_DE10D011 */ diff --git a/src/renderer.cpp b/src/renderer.cpp index 2c6cf5c..b22c316 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,4 +1,5 @@ #include "renderer.h" +#include #include "consts.h" #include "game.h" #include "map.h" @@ -153,6 +154,33 @@ void Renderer::render(Game& game) { int bottomBarHeight = 36.0 * game.getSystem().getCutsceneBarsProgress(); SDL_Rect bottomBar { 0, CANVAS_HEIGHT - bottomBarHeight, CANVAS_WIDTH, bottomBarHeight }; SDL_RenderFillRect(ren_.get(), &bottomBar); + + if (game.getSystem().getCutsceneBarsProgress() == 1.0 && + !game.getSystem().getLines().empty()) { + int lineIndex = 0; + for (const MessageSystem::MessageLine& line : game.getSystem().getLines()) { + if (messageLines_[lineIndex].line != line.text) { + renderMessageLine(lineIndex, line.text, game); + } + + // 18x, 127y1, 143y2 + if (line.charsRevealed > 0) { + SDL_Rect srcRect { + 0, 0, messageLines_[lineIndex].charIndexToWidth[line.charsRevealed], + game.getFont().getCharacterHeight() + }; + SDL_Rect destRect { + 18, + 127 + 16 * lineIndex, + srcRect.w, + srcRect.h }; + + SDL_RenderCopy(ren_.get(), messageLines_[lineIndex].renderedTex.get(), &srcRect, &destRect); + } + + lineIndex++; + } + } } SDL_SetRenderTarget(ren_.get(), nullptr); @@ -175,3 +203,46 @@ int Renderer::loadImageFromFile(std::string_view filename) { return texId; } + +void Renderer::renderMessageLine(int lineIndex, const std::string& text, Game& game) { + MessageCache& line = messageLines_[lineIndex]; + line.line = text; + + line.renderedTex.reset( + SDL_CreateTexture( + ren_.get(), + SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_TARGET, + MESSAGE_TEXT_WIDTH, + game.getFont().getCharacterHeight())); + + 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); + + int length = 0; + + for (int i=0; i #include #include +#include #include class Game; @@ -124,6 +125,8 @@ private: texture_ptr renderMapLayer(const Map& map, int layer); + void renderMessageLine(int lineIndex, const std::string& text, Game& game); + sdl_wrapper sdl_; img_wrapper img_; window_ptr win_; @@ -132,6 +135,14 @@ private: std::vector textures_; texture_ptr renLay0_; texture_ptr renLay1_; + + struct MessageCache { + texture_ptr renderedTex; + std::vector charIndexToWidth; + std::string line; + }; + + MessageCache messageLines_[2]; }; #endif /* end of include guard: RENDERER_H_6A58EC30 */ -- cgit 1.4.1