#include "renderer.h" #include #include "game.h" Renderer::Renderer() { SDL_DisplayMode displayMode; SDL_GetDesktopDisplayMode(0, &displayMode); win_ = window_ptr( SDL_CreateWindow( "Ether", displayMode.w / 2 - GAME_WIDTH / 2, displayMode.h / 2 - GAME_HEIGHT / 2, GAME_WIDTH, GAME_HEIGHT, SDL_WINDOW_SHOWN)); //SDL_SetWindowFullscreen(win_.get(), SDL_WINDOW_FULLSCREEN_DESKTOP); 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 origFade; loadTextureFromFile("../res/lighting.png", origFade); playerFade_ = texture_ptr( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA4444, SDL_TEXTUREACCESS_TARGET, 144, 144)); if (!playerFade_) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), playerFade_.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); SDL_RenderClear(ren_.get()); SDL_RenderCopy(ren_.get(), origFade.get(), nullptr, nullptr); lampFade_ = texture_ptr( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA4444, SDL_TEXTUREACCESS_TARGET, 144, 144)); if (!lampFade_) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), lampFade_.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); SDL_RenderClear(ren_.get()); SDL_RenderCopy(ren_.get(), origFade.get(), nullptr, nullptr); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_MOD); SDL_SetRenderDrawColor(ren_.get(), 255, 204, 58, 255); SDL_RenderFillRect(ren_.get(), nullptr); dustFade_ = texture_ptr( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA4444, SDL_TEXTUREACCESS_TARGET, 144, 144)); if (!dustFade_) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), dustFade_.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); SDL_RenderClear(ren_.get()); SDL_RenderCopy(ren_.get(), origFade.get(), nullptr, nullptr); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_MOD); SDL_SetRenderDrawColor(ren_.get(), 255, 150, 255, 255); SDL_RenderFillRect(ren_.get(), nullptr); SDL_SetRenderDrawColor(ren_.get(), 100, 100, 100, 255); SDL_RenderFillRect(ren_.get(), nullptr); loadTextureFromFile("../res/player.png", playerSheet_); loadTextureFromFile("../res/runninbloods.png", tileset_); loadTextureFromFile("../res/lamp.png", lamp_); loadTextureFromFile("../res/read_instruction.png", readInstruction_); 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( const Game& game, bool drawDark) { int windowTileWidth = game.curZoom * ZOOM_X_FACTOR + 2; int windowTileHeight = game.curZoom * ZOOM_Y_FACTOR + 2; texture_ptr canvas( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, windowTileWidth * TILE_WIDTH, windowTileHeight * TILE_HEIGHT)); if (!canvas) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), rand() % 255, rand() % 255, rand() % 255, 255); SDL_RenderClear(ren_.get()); int leftmost = game.player_x - game.curZoom * ZOOM_X_FACTOR / 2 - 1; int topmost = game.player_y - game.curZoom * ZOOM_Y_FACTOR / 2 - 1; for (int y = topmost; y < topmost + windowTileHeight; y++) { for (int x = leftmost; x < leftmost + windowTileWidth; x++) { bool draw = true; bool drawColour = false; SDL_Rect rect { (x - leftmost) * TILE_WIDTH, (y - topmost) * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT}; if (!game.map.at(x,y).lit) { if (drawDark) { SDL_SetRenderDrawColor(ren_.get(), 40, 40, 40, 255); drawColour = true; } else { draw = false; } } if (draw) { if (game.map.at(x,y).tile != Tile::Wall) { SDL_Rect tileRect {17 * 16, 15 * 16, 16, 16}; SDL_RenderCopy(ren_.get(), tileset_.get(), &tileRect, &rect); if (game.map.at(x,y).renderId != -1) { tileRect.x = game.map.at(x,y).renderId % 25 * 16; tileRect.y = game.map.at(x,y).renderId / 25 * 16; SDL_RenderCopy(ren_.get(), tileset_.get(), &tileRect, &rect); } } else { SDL_Rect tileRect { game.map.at(x,y).renderId % 25 * 16, game.map.at(x,y).renderId / 25 * 16, 16, 16}; SDL_RenderCopy(ren_.get(), tileset_.get(), &tileRect, &rect); } if (game.map.at(x,y).tile == Tile::Lamp) { SDL_RenderCopy(ren_.get(), lamp_.get(), nullptr, &rect); } if (drawColour) { SDL_RenderFillRect(ren_.get(), &rect); } } } } if (game.renderPlayer) { SDL_Rect rect { (game.player_x - leftmost) * TILE_WIDTH, (game.player_y - topmost) * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT}; if (game.moving) { rect.x = game.moveProgress.getProgress((game.player_oldx - leftmost) * TILE_WIDTH, rect.x); rect.y = game.moveProgress.getProgress((game.player_oldy - topmost) * TILE_HEIGHT, rect.y); } SDL_RenderCopy(ren_.get(), playerSheet_.get(), &game.playerAnim.getRenderRect(), &rect); } texture_ptr mask( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, windowTileWidth * TILE_WIDTH, windowTileHeight * TILE_HEIGHT)); if (!mask) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), mask.get()); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); SDL_RenderClear(ren_.get()); for (int y = topmost - RADIUS; y < topmost + windowTileHeight + RADIUS; y++) { for (int x = leftmost - RADIUS; x < leftmost + windowTileWidth + RADIUS; x++) { if (game.map.at(x,y).lightType != Source::None) { texture_ptr sourceMask( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, windowTileWidth * TILE_WIDTH, windowTileHeight * TILE_HEIGHT)); if (!sourceMask) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), sourceMask.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); SDL_RenderClear(ren_.get()); int posToUseX = x - leftmost; int posToUseY = y - topmost; int xInterp = 0; int yInterp = 0; if (game.map.at(x,y).lightType == Source::Player && game.moving) { if (game.player_x > game.player_oldx) { xInterp = game.moveProgress.getProgress(-TILE_WIDTH, 0); } else if (game.player_x < game.player_oldx) { xInterp = game.moveProgress.getProgress(TILE_WIDTH, 0); } if (game.player_y > game.player_oldy) { yInterp = game.moveProgress.getProgress(-TILE_HEIGHT, 0); } else if (game.player_y < game.player_oldy) { yInterp = game.moveProgress.getProgress(TILE_HEIGHT, 0); } } int fadeX = posToUseX - game.map.at(x,y).lightRadius; int fadeY = posToUseY - game.map.at(x,y).lightRadius; int fadeRight = posToUseX + game.map.at(x,y).lightRadius; int fadeBottom = posToUseY + game.map.at(x,y).lightRadius; SDL_Rect fadeRect { fadeX * TILE_WIDTH + xInterp, fadeY * TILE_HEIGHT + yInterp, (game.map.at(x,y).lightRadius * 2 + 1) * TILE_WIDTH, (game.map.at(x,y).lightRadius * 2 + 1) * TILE_HEIGHT}; if (game.map.at(x,y).lightType == Source::Lamp) { SDL_SetTextureBlendMode(lampFade_.get(), SDL_BLENDMODE_NONE); SDL_RenderCopy(ren_.get(), lampFade_.get(), nullptr, &fadeRect); } else if (game.map.at(x,y).lightType == Source::Player) { SDL_SetTextureBlendMode(playerFade_.get(), SDL_BLENDMODE_NONE); SDL_RenderCopy(ren_.get(), playerFade_.get(), nullptr, &fadeRect); } else if (game.map.at(x,y).lightType == Source::Dust) { SDL_SetTextureBlendMode(dustFade_.get(), SDL_BLENDMODE_NONE); SDL_RenderCopy(ren_.get(), dustFade_.get(), nullptr, &fadeRect); } SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0); for (int sy = fadeY; sy < fadeBottom; sy++) { for (int sx = fadeX; sx < fadeRight; sx++) { if (!game.map.at(x,y).litTiles.count({sx, sy})) { SDL_Rect rect { (sx - leftmost) * TILE_WIDTH, (sy - topmost) * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT}; SDL_RenderFillRect(ren_.get(), &rect); } } } SDL_SetRenderTarget(ren_.get(), mask.get()); SDL_SetTextureBlendMode(sourceMask.get(), SDL_BLENDMODE_ADD); SDL_RenderCopy(ren_.get(), sourceMask.get(), nullptr, nullptr); } } } SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_SetTextureBlendMode(mask.get(), SDL_BLENDMODE_MOD); SDL_RenderCopy(ren_.get(), mask.get(), nullptr, nullptr); SDL_SetRenderTarget(ren_.get(), nullptr); SDL_Rect zoomRect { TILE_WIDTH, TILE_HEIGHT, (windowTileWidth - 2) * TILE_WIDTH, (windowTileHeight - 2) * TILE_HEIGHT }; if (game.moving) { if (game.player_x > game.player_oldx) { zoomRect.x = game.moveProgress.getProgress(0, TILE_WIDTH); } else if (game.player_x < game.player_oldx) { zoomRect.x = game.moveProgress.getProgress(2*TILE_WIDTH, TILE_WIDTH); } if (game.player_y > game.player_oldy) { zoomRect.y = game.moveProgress.getProgress(0, TILE_HEIGHT); } else if (game.player_y < game.player_oldy) { zoomRect.y = game.moveProgress.getProgress(TILE_HEIGHT*2, TILE_HEIGHT); } } if (game.zooming) { int oldWidth = game.oldZoom * ZOOM_X_FACTOR * TILE_WIDTH; int oldHeight = game.oldZoom * ZOOM_Y_FACTOR * TILE_HEIGHT; SDL_Rect oldRect { zoomRect.w / 2 - oldWidth / 2, zoomRect.h / 2 - oldHeight / 2, oldWidth, oldHeight }; if (game.moving) { if (game.player_x > game.player_oldx) { oldRect.x += game.moveProgress.getProgress(0, TILE_WIDTH); } else if (game.player_x < game.player_oldx) { oldRect.x -= game.moveProgress.getProgress(TILE_WIDTH, 0); } if (game.player_y > game.player_oldy) { oldRect.y += game.moveProgress.getProgress(0, TILE_HEIGHT); } else if (game.player_y < game.player_oldy) { oldRect.y -= game.moveProgress.getProgress(TILE_HEIGHT, 0); } } zoomRect.x = game.zoomProgress.getProgress(oldRect.x, zoomRect.x); zoomRect.y = game.zoomProgress.getProgress(oldRect.y, zoomRect.y); zoomRect.w = game.zoomProgress.getProgress(oldRect.w, zoomRect.w); zoomRect.h = game.zoomProgress.getProgress(oldRect.h, zoomRect.h); } SDL_RenderCopy(ren_.get(), canvas.get(), &zoomRect, nullptr); if (game.signInstructionState != SignInstructionState::Hidden) { int instOpacity = 255; if (game.signInstructionState == SignInstructionState::FadingIn) { instOpacity = game.signFade.getProgress(0, 255); } else if (game.signInstructionState == SignInstructionState::FadingOut) { instOpacity = game.signFade.getProgress(255, 0); } SDL_SetTextureAlphaMod(readInstruction_.get(), instOpacity); 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()); } void Renderer::renderTitle(int num, double fade) { texture_ptr canvas( SDL_CreateTexture( ren_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAME_WIDTH, GAME_HEIGHT)); if (!canvas) { throw sdl_error(); } SDL_SetRenderTarget(ren_.get(), canvas.get()); SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 255); SDL_RenderClear(ren_.get()); if (fade > 0) { SDL_Rect rect { (GAME_WIDTH - titleWidths_[num]) / 2, (GAME_HEIGHT - titleHeights_[num]) / 2, titleWidths_[num], titleHeights_[num] }; SDL_RenderCopy(ren_.get(), titles_[num].get(), nullptr, &rect); if (fade < 1) { SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, (1.0 - fade) * 255); SDL_RenderFillRect(ren_.get(), nullptr); } } SDL_SetRenderTarget(ren_.get(), nullptr); SDL_RenderCopy(ren_.get(), canvas.get(), nullptr, nullptr); SDL_RenderPresent(ren_.get()); } void Renderer::loadTextureFromFile(std::string_view path, texture_ptr& texture) { surface_ptr pfs(IMG_Load(path.data())); if (!pfs) { throw img_error(); } 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