#include "renderer.h" #include #include #include "consts.h" #include "game.h" #include "map.h" #include "menu.h" #include "transform_system.h" #include "camera_system.h" #include "message_system.h" #include "effect_system.h" #include "input_system.h" #include "menu_system.h" Renderer::Renderer() { win_ = window_ptr( SDL_CreateWindow( "Tanetane", 100, 100, GAME_WIDTH, GAME_HEIGHT, SDL_WINDOW_SHOWN)); if (!win_) { throw sdl_error(); } ren_ = renderer_ptr( SDL_CreateRenderer( win_.get(), -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)); if (!ren_) { throw sdl_error(); } } texture_ptr Renderer::renderMapLayer(const Map& map, bool above) { if (cachedTilesetName_ != map.getTilesetFilename()) { surface_ptr pfs(IMG_Load(map.getTilesetFilename().c_str())); if (!pfs) { throw img_error(); } tilesetTex_ = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), pfs.get())); cachedTilesetName_ = map.getTilesetFilename(); } vec2i mapBounds = map.getMapSize() * map.getTileSize(); texture_ptr canvas( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mapBounds.w(), mapBounds.h())); SDL_SetTextureBlendMode(canvas.get(), SDL_BLENDMODE_BLEND); SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(ren_.get(), 255, 255, 255, 0); SDL_RenderClear(ren_.get()); const std::vector>* layers = nullptr; if (above) { layers = &map.getUpperLayers(); } else { layers = &map.getLowerLayers(); } for (const std::vector& tiles : *layers) { for (int y = 0; y < map.getMapSize().h(); y++) { for (int x = 0; x < map.getMapSize().w(); x++) { const Tile& tile = tiles.at(x + y * map.getMapSize().w()); SDL_Rect srcRect { static_cast((tile.id % map.getTilesetColumns()) * map.getTileSize().w()), static_cast((tile.id / map.getTilesetColumns()) * map.getTileSize().h()), map.getTileSize().w(), map.getTileSize().h() }; SDL_Rect destRect { x * map.getTileSize().w(), y * map.getTileSize().h(), map.getTileSize().w(), map.getTileSize().h() }; SDL_RendererFlip flip = SDL_FLIP_NONE; if (tile.flipHorizontal && tile.flipVertical) { flip = static_cast(SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL); } else if (tile.flipHorizontal) { flip = SDL_FLIP_HORIZONTAL; } else if (tile.flipVertical) { flip = SDL_FLIP_VERTICAL; } SDL_RenderCopyEx(ren_.get(), tilesetTex_.get(), &srcRect, &destRect, 0, nullptr, flip); } } } return canvas; } void Renderer::renderSprite(const Sprite& sprite) { if (sprite.isAnimated) { if (sprite.hasShadow) { int shadowTexId = loadImageFromFile("../res/shadow.png"); const SDL_Rect shadowDest { sprite.loc.x() - 8, sprite.loc.y() - 8, 16, 8 }; SDL_Texture* shadowTex = textures_.at(shadowTexId).get(); SDL_SetTextureAlphaMod(shadowTex, sprite.opacity * 255); SDL_RenderCopy(ren_.get(), shadowTex, nullptr, &shadowDest); } const SpriteFrame& frame = sprite.frames.at(sprite.animations.at(sprite.animationId).frameIndices.at(sprite.animationFrame)); const SDL_Rect& src = frame.srcRect; SDL_Rect dest { sprite.loc.x() - frame.center.x(), sprite.loc.y() - frame.center.y(), frame.size.w(), frame.size.h() }; if (sprite.bobbing) { dest.y -= sprite.bobAmount; } SDL_Texture* textureToRender = textures_.at(loadImageFromFile(sprite.spritesheet)).get(); SDL_SetTextureAlphaMod(textureToRender, sprite.opacity * 255); SDL_RenderCopy(ren_.get(), textureToRender, &src, &dest); } } void Renderer::render(Game& game) { auto& menus = game.getSystem(); texture_ptr canvas( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, CANVAS_WIDTH, CANVAS_HEIGHT)); if (menus.getPauseAnimationProgress() < 1.0) { renderGame(game); SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_RenderCopy(ren_.get(), cameraTex_.get(), nullptr, nullptr); } if (menus.getPauseAnimationProgress() > 0.0) { renderMenu(game, menus.getMenu()); SDL_SetRenderTarget(ren_.get(), canvas.get()); if (menus.getPauseAnimationProgress() == 1.0) { if (menus.isMenuChanging()) { SDL_Rect dest { static_cast(CANVAS_WIDTH * (1.0 - menus.getMenuChangeAnimationProgress())), 0, CANVAS_WIDTH, CANVAS_HEIGHT }; SDL_RenderCopy(ren_.get(), menuTex_.get(), nullptr, &dest); renderMenu(game, menus.getParentMenu()); dest.x -= CANVAS_WIDTH; SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_RenderCopy(ren_.get(), menuTex_.get(), nullptr, &dest); } else { SDL_RenderCopy(ren_.get(), menuTex_.get(), nullptr, nullptr); } } else { int barHeight = CANVAS_HEIGHT / 2 * menus.getPauseAnimationProgress(); SDL_Rect topHalf { 0, 0, CANVAS_WIDTH, barHeight }; SDL_Rect bottomHalf { 0, CANVAS_HEIGHT - barHeight, CANVAS_WIDTH, barHeight }; SDL_RenderCopy(ren_.get(), menuTex_.get(), &topHalf, &topHalf); SDL_RenderCopy(ren_.get(), menuTex_.get(), &bottomHalf, &bottomHalf); } } SDL_SetRenderTarget(ren_.get(), nullptr); SDL_RenderCopy(ren_.get(), canvas.get(), nullptr, nullptr); SDL_RenderPresent(ren_.get()); } void Renderer::renderGame(Game& game) { auto& effects = game.getSystem(); if (cachedMapName_ != game.getMap().getName()) { cachedMapName_ = game.getMap().getName(); renLowerLayer_ = renderMapLayer(game.getMap(), false); renUpperLayer_ = renderMapLayer(game.getMap(), true); vec2i mapBounds = game.getMap().getMapSize() * game.getMap().getTileSize(); mapSwapTex_.reset(SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mapBounds.w(), mapBounds.h())); SDL_SetTextureBlendMode(mapSwapTex_.get(), SDL_BLENDMODE_BLEND); } vec2i mapBounds = game.getMap().getMapSize() * game.getMap().getTileSize(); texture_ptr canvas( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mapBounds.w(), mapBounds.h())); if (!canvas) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 255, 255, 255, 255); SDL_RenderClear(ren_.get()); // Render lower map layer SDL_RenderCopy(ren_.get(), renLowerLayer_.get(), nullptr, nullptr); if (effects.isMapFaded()) { SDL_SetRenderTarget(ren_.get(), mapSwapTex_.get()); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); SDL_RenderClear(ren_.get()); SDL_RenderCopy(ren_.get(), renLowerLayer_.get(), nullptr, nullptr); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_MOD); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255); SDL_RenderFillRect(ren_.get(), nullptr); SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_SetTextureAlphaMod(mapSwapTex_.get(), effects.getMapFadeProgress() * 255); SDL_RenderCopy(ren_.get(), mapSwapTex_.get(), nullptr, nullptr); } // Render mask sprite layer if (!game.getMap().getMaskZone().empty()) { texture_ptr spritesTex( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, mapBounds.w(), mapBounds.h())); if (!spritesTex) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), spritesTex.get()); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); SDL_RenderClear(ren_.get()); for (const Sprite& sprite : game.getSystem().getSpritesByY(SpriteLayer::Mask) | game.spriteView()) { renderSprite(sprite); } const Zone& zone = game.getMap().getZone(game.getMap().getMaskZone()); SDL_Rect zoneArea { zone.ul.x(), zone.ul.y(), zone.dr.x() - zone.ul.x(), zone.dr.y() - zone.ul.y() }; SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_SetTextureBlendMode(spritesTex.get(), SDL_BLENDMODE_BLEND); SDL_RenderCopy(ren_.get(), spritesTex.get(), &zoneArea, &zoneArea); } // Render normal sprite layer for (const Sprite& sprite : game.getSystem().getSpritesByY(SpriteLayer::Normal) | game.spriteView()) { renderSprite(sprite); } // Render upper map layer SDL_RenderCopy(ren_.get(), renUpperLayer_.get(), nullptr, nullptr); if (effects.isMapFaded()) { SDL_SetRenderTarget(ren_.get(), mapSwapTex_.get()); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); SDL_RenderClear(ren_.get()); SDL_RenderCopy(ren_.get(), renUpperLayer_.get(), nullptr, nullptr); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_MOD); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255); SDL_RenderFillRect(ren_.get(), nullptr); SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_SetTextureAlphaMod(mapSwapTex_.get(), effects.getMapFadeProgress() * 255); SDL_RenderCopy(ren_.get(), mapSwapTex_.get(), nullptr, nullptr); } // Render above sprite layer for (const Sprite& sprite : game.getSystem().getSpritesByY(SpriteLayer::Above) | game.spriteView()) { renderSprite(sprite); } vec2i viewport = game.getSystem().getCameraPosition() + effects.getCameraShakeOffset(); SDL_Rect cameraField { viewport.x(), viewport.y(), game.getSystem().getFieldOfView().w(), game.getSystem().getFieldOfView().h() }; if (!cameraTex_) { cameraTex_.reset( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, CANVAS_WIDTH, CANVAS_HEIGHT)); if (!cameraTex_) { throw sdl_error(); } } SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_RenderCopy(ren_.get(), canvas.get(), &cameraField, nullptr); if (game.getSystem().getCutsceneBarsProgress() > 0) { SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255); int topBarHeight = 16.0 * game.getSystem().getCutsceneBarsProgress(); SDL_Rect topBar { 0, 0, CANVAS_WIDTH, topBarHeight }; SDL_RenderFillRect(ren_.get(), &topBar); 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()) { if (!game.getSystem().getSpeaker().empty()) { if (speakerHeaderTex_ == -1) { speakerHeaderTex_ = loadImageFromFile("../res/speaker_header.png"); } { SDL_Rect destRect { 0, 111, 57, 13 }; SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_RenderCopy(ren_.get(), textures_.at(speakerHeaderTex_).get(), nullptr, &destRect); } if (speakerHeaderLine_.line != game.getSystem().getSpeaker()) { renderMessageLine(speakerHeaderLine_, game.getSystem().getSpeaker(), game); } { SDL_Rect destRect { 4, 112, MESSAGE_TEXT_WIDTH, game.getFont().getCharacterHeight() }; SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_RenderCopy(ren_.get(), speakerHeaderLine_.renderedTex.get(), nullptr, &destRect); } } int lineIndex = 0; for (const MessageLine& line : game.getSystem().getLines()) { if (messageLines_[lineIndex].line != line.text) { renderMessageLine(messageLines_[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_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_RenderCopy(ren_.get(), messageLines_[lineIndex].renderedTex.get(), &srcRect, &destRect); } if (line.bulleted) { vec2i charLoc = game.getFont().getCharacterLocation('^'); vec2i charSize = game.getFont().getCharacterSize('^'); SDL_Rect srcRect { charLoc.x(), charLoc.y(), charSize.w(), charSize.h() }; SDL_Rect destRect { 13, 127 + 16 * lineIndex, charSize.w(), charSize.h() }; SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_RenderCopy(ren_.get(), textures_.at(game.getFont().getTextureId()).get(), &srcRect, &destRect); } } lineIndex++; } // 216, 138, 142 if (game.getSystem().isNextArrowShowing()) { if (game.getSystem().isChoiceActive()) { if (choiceArrowTex_ == -1) { choiceArrowTex_ = loadImageFromFile("../res/select_choice_arrow.png"); } int selection = game.getSystem().getChoiceSelection(); int charIndex = game.getSystem().getLines().back().choicePos[selection]; int baseX = messageLines_[1].charIndexToWidth[charIndex]; SDL_Rect destRect { 18 + baseX - game.getSystem().getNextArrowBob()-8, 144, 8, 8 }; SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_RenderCopy(ren_.get(), textures_.at(choiceArrowTex_).get(), nullptr, &destRect); } else { if (advMsgArrowTex_ == -1) { advMsgArrowTex_ = loadImageFromFile("../res/advance_text_arrow.png"); } SDL_Rect destRect { 220, 142 + game.getSystem().getNextArrowBob(), 8, 8 }; SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_RenderCopy(ren_.get(), textures_.at(advMsgArrowTex_).get(), nullptr, &destRect); } } } } if (effects.isScreenFaded()) { SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, effects.getScreenFadeProgress() * 255); SDL_RenderFillRect(ren_.get(), nullptr); } if (effects.isCircleTransitionActive()) { if (effects.getCircleTransitionProgress() == 1.0) { SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255); SDL_RenderClear(ren_.get()); } else { texture_ptr circleEffectTex( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, CANVAS_WIDTH, CANVAS_HEIGHT)); if (!circleEffectTex) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), circleEffectTex.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255); SDL_RenderClear(ren_.get()); int circleDiameter = 290 * (1.0 - effects.getCircleTransitionProgress()); SDL_Rect circleSize { CANVAS_WIDTH / 2 - circleDiameter / 2, CANVAS_HEIGHT / 2 - circleDiameter / 2, circleDiameter, circleDiameter }; SDL_Rect copyFromCamera = circleSize; if (circleDiameter > CANVAS_WIDTH) { copyFromCamera.x = 0; copyFromCamera.w = CANVAS_WIDTH; } if (circleDiameter > CANVAS_HEIGHT) { copyFromCamera.y = 0; copyFromCamera.h = CANVAS_HEIGHT; } SDL_RenderCopy(ren_.get(), cameraTex_.get(), ©FromCamera, ©FromCamera); int pinholeTexId = loadImageFromFile("../res/pinhole.png"); SDL_SetTextureBlendMode(textures_[pinholeTexId].get(), SDL_BLENDMODE_MOD); SDL_RenderCopy(ren_.get(), textures_[pinholeTexId].get(), nullptr, &circleSize); SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_SetTextureBlendMode(circleEffectTex.get(), SDL_BLENDMODE_NONE); SDL_RenderCopy(ren_.get(), circleEffectTex.get(), nullptr, nullptr); } } if (game.getSystem().isDebugConsoleOpen()) { // Not sure why I'm supposed to copy the cached texture to the screen // BEFORE rendering it, but we get flickering if we don't, so. SDL_SetRenderTarget(ren_.get(), cameraTex_.get()); SDL_RenderCopy(ren_.get(), debugConsoleTex_.get(), nullptr, &debugDestRect_); if (!debugConsoleTex_ || cachedDebugText_ != game.getSystem().getDebugText()) { cachedDebugText_ = game.getSystem().getDebugText(); std::vector textLines; std::string remaining = "`" + cachedDebugText_; while (!remaining.empty()) { MessageCache output; renderMessageLine(output, remaining, game); textLines.push_back(std::move(output.renderedTex)); remaining = output.overflow; if (!remaining.empty()) { remaining = " " + remaining; } } int height = 16 * textLines.size() + 4 + 4; debugConsoleTex_.reset( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, CANVAS_WIDTH, height)); SDL_SetRenderTarget(ren_.get(), debugConsoleTex_.get()); SDL_SetTextureBlendMode(debugConsoleTex_.get(), SDL_BLENDMODE_BLEND); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(ren_.get(), 30, 0, 110, 127); //SDL_RenderFillRect(ren_.get(), nullptr); SDL_RenderClear(ren_.get()); for (int i=0; i(); if (!menuTex_) { menuTex_.reset(SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, CANVAS_WIDTH, CANVAS_HEIGHT)); if (!menuTex_) { throw sdl_error(); } } SDL_SetRenderTarget(ren_.get(), menuTex_.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255); SDL_RenderFillRect(ren_.get(), nullptr); const int lineHeight = 16; const int sliderBarWidth = 9; int totalHeight = menu.getItems().size() * lineHeight; std::vector positions; std::deque sliderText; int maxSliderLineWidth = 0; int sliderAlignX = 0; // First, find all of the sliders so we can figure out how to align them. for (const MenuItem& menuItem : menu.getItems()) { if (menuItem.type == MenuType::Slider) { MessageCache output; renderMessageLine(output, menuItem.text, game); int width = output.charIndexToWidth.back() + sliderBarWidth * menuItem.maxValue; if (width > maxSliderLineWidth) { maxSliderLineWidth = width; sliderAlignX = (CANVAS_WIDTH - maxSliderLineWidth) / 2 + output.charIndexToWidth.back(); } sliderText.push_back(std::move(output)); } } // Now, render all of the items. int index = 0; for (const MenuItem& menuItem : menu.getItems()) { int lineY = (CANVAS_HEIGHT - totalHeight) / 2 + lineHeight * index; switch (menuItem.type) { case MenuType::Command: { MessageCache output; renderMessageLine(output, menuItem.text, game); SDL_Rect dest { (CANVAS_WIDTH - output.charIndexToWidth.back()) / 2, lineY, MESSAGE_TEXT_WIDTH, game.getFont().getCharacterHeight() }; SDL_SetRenderTarget(ren_.get(), menuTex_.get()); SDL_RenderCopy(ren_.get(), output.renderedTex.get(), nullptr, &dest); positions.emplace_back(dest.x, dest.y); break; } case MenuType::Slider: { MessageCache& output = sliderText.front(); SDL_Rect dest { sliderAlignX - output.charIndexToWidth.back(), lineY, MESSAGE_TEXT_WIDTH, game.getFont().getCharacterHeight() }; SDL_SetRenderTarget(ren_.get(), menuTex_.get()); SDL_RenderCopy(ren_.get(), output.renderedTex.get(), nullptr, &dest); positions.emplace_back(dest.x, dest.y); for (int j = 0; j < menuItem.maxValue; j++) { int boxTexId = menuItem.value > j ? loadImageFromFile("../res/slider_on.png") : loadImageFromFile("../res/slider_off.png"); const SDL_Rect boxDest { sliderAlignX + j * sliderBarWidth, lineY + 2, 8, 8 }; SDL_SetRenderTarget(ren_.get(), menuTex_.get()); SDL_RenderCopy(ren_.get(), textures_.at(boxTexId).get(), nullptr, &boxDest); } sliderText.pop_front(); break; } } index++; } int cursorTexId = loadImageFromFile("../res/feather_pen.png"); const SDL_Rect cursorDest { positions[menu.getCursorPosition()].x() - 2 - 16 - menus.getCursorBob(), positions[menu.getCursorPosition()].y() - 8 - menus.getCursorBob(), 16, 16 }; SDL_RenderCopy(ren_.get(), textures_.at(cursorTexId).get(), nullptr, &cursorDest); } int Renderer::loadImageFromFile(std::string filename) { if (!filenameToTexId_.count(filename)) { surface_ptr pfs(IMG_Load(filename.data())); if (!pfs) { throw img_error(); } texture_ptr tex = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), pfs.get())); //SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); int texId = textures_.size(); textures_.push_back(std::move(tex)); filenameToTexId_[filename] = texId; } return filenameToTexId_[filename]; } void Renderer::renderMessageLine(MessageCache& line, const std::string& text, Game& game) { 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 MESSAGE_TEXT_WIDTH) { line.overflow = line.line.substr(i); break; } vec2i charLoc = game.getFont().getCharacterLocation(line.line[i]); vec2i charSize = game.getFont().getCharacterSize(line.line[i]); SDL_Rect srcRect { charLoc.x(), charLoc.y(), charSize.w(), charSize.h() }; SDL_Rect destRect { length, 0, charSize.w(), charSize.h() }; SDL_RenderCopy(ren_.get(), textures_.at(game.getFont().getTextureId()).get(), &srcRect, &destRect); length++; } length += game.getFont().getCharacterWidth(line.line[i]); line.charIndexToWidth.push_back(length); } line.charIndexToWidth.back()--; }