@@ -14,7 +14,8 @@ set(assets | |||
assets/tiles/stone.png | |||
assets/tiles/dirt.png | |||
assets/tiles/leaves.png | |||
assets/tiles/tree-trunk.png) | |||
assets/tiles/tree-trunk.png | |||
assets/misc/background-cave.png) | |||
foreach(a ${assets}) | |||
configure_file("${a}" "${a}" COPYONLY) | |||
endforeach(a) |
@@ -1,5 +1,7 @@ | |||
#include "WGDefault.h" | |||
#include <algorithm> | |||
static int grassLevel(const siv::PerlinNoise &perlin, int x) { | |||
return (int)(perlin.noise(x / 50.0, 0) * 13); | |||
} | |||
@@ -8,6 +10,43 @@ static int stoneLevel(const siv::PerlinNoise &perlin, int x) { | |||
return (int)(perlin.noise(x / 50.0, 10) * 10) + 10; | |||
} | |||
void WGDefault::drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) { | |||
int texmin = 10; | |||
int texmax = 20; | |||
if (pos.y > texmin) { | |||
SDL_Texture *tex = bgCave_.texture_.get(); | |||
Uint8 alpha = std::clamp( | |||
(pos.y - texmin) / (texmax - texmin), 0.0f, 1.0f) * 255; | |||
Swan::TexAlphaMod amod(tex, alpha); | |||
Swan::Draw::parallaxBackground( | |||
win, tex, std::nullopt, std::nullopt, | |||
pos.x * Swan::TILE_SIZE, pos.y * Swan::TILE_SIZE, 0.7); | |||
} | |||
} | |||
SDL_Color WGDefault::backgroundColor(Swan::Vec2 pos) { | |||
float y = pos.y; | |||
float deep = 20; | |||
float deeper = deep + 100; | |||
if (y < deep) { | |||
return Swan::Draw::linearColor( | |||
{ 128, 220, 250, 255 }, | |||
{ 107, 87, 5, 255 }, | |||
y / deep); | |||
} else if (y < deeper) { | |||
return Swan::Draw::linearColor( | |||
{ 107, 87, 5, 255 }, | |||
{ 15, 3, 3, 255 }, | |||
(y - deep) / (deeper - deep)); | |||
} else { | |||
return { 15, 3, 3, 255 }; | |||
} | |||
} | |||
Swan::Tile::ID WGDefault::genTile(Swan::TilePos pos) { | |||
int grass_level = grassLevel(perlin_, pos.x); | |||
int stone_level = stoneLevel(perlin_, pos.x); |
@@ -12,15 +12,22 @@ public: | |||
}; | |||
WGDefault(Swan::World &world): | |||
tGrass_(world.getTileID("core::grass")), tDirt_(world.getTileID("core::dirt")), | |||
tStone_(world.getTileID("core::stone")), tAir_(world.getTileID("core::air")), | |||
tTreeTrunk_(world.getTileID("core::tree-trunk")), tLeaves_(world.getTileID("core::leaves")) {} | |||
tGrass_(world.getTileID("core::grass")), | |||
tDirt_(world.getTileID("core::dirt")), | |||
tStone_(world.getTileID("core::stone")), | |||
tAir_(world.getTileID("core::air")), | |||
tTreeTrunk_(world.getTileID("core::tree-trunk")), | |||
tLeaves_(world.getTileID("core::leaves")), | |||
bgCave_(world.resources_.getImage("core::background-cave")) {} | |||
void drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) override; | |||
SDL_Color backgroundColor(Swan::Vec2 pos) override; | |||
void genChunk(Swan::WorldPlane &plane, Swan::Chunk &chunk) override; | |||
Swan::BodyTrait::HasBody *spawnPlayer(Swan::WorldPlane &plane) override; | |||
private: | |||
Swan::Tile::ID genTile(Swan::TilePos pos); | |||
Swan::Tile::ID tGrass_, tDirt_, tStone_, tAir_, tTreeTrunk_, tLeaves_; | |||
Swan::ImageResource &bgCave_; | |||
siv::PerlinNoise perlin_ = siv::PerlinNoise(100); | |||
}; |
@@ -15,6 +15,8 @@ extern "C" void mod_init(Swan::Mod &mod) { | |||
mod.registerImage({ "leaves", "tiles/leaves.png" }); | |||
mod.registerImage({ "player-running", "entities/player-running.png", 64 }); | |||
mod.registerImage({ "player-still", "entities/player-still.png", 64 }); | |||
mod.registerImage({ "background-cave", "misc/background-cave.png" }); | |||
mod.registerImage({ "background-cave-2", "misc/background-cave-2.png" }); | |||
mod.registerTile({ | |||
.name = "air", |
@@ -3,6 +3,7 @@ add_library(libswan SHARED | |||
src/Animation.cc | |||
src/Chunk.cc | |||
src/Clock.cc | |||
src/drawutil.cc | |||
src/Game.cc | |||
src/gfxutil.cc | |||
src/Item.cc |
@@ -3,6 +3,7 @@ | |||
#include <bitset> | |||
#include <map> | |||
#include <string> | |||
#include <SDL.h> | |||
#include "common.h" | |||
#include "Resource.h" | |||
@@ -61,6 +62,7 @@ public: | |||
TilePos getMouseTile(); | |||
SDL_Color backgroundColor(); | |||
void draw(); | |||
void update(float dt); | |||
void tick(float dt); |
@@ -4,6 +4,7 @@ | |||
#include <vector> | |||
#include <string> | |||
#include <random> | |||
#include <SDL.h> | |||
#include "common.h" | |||
#include "Item.h" | |||
@@ -35,6 +36,7 @@ public: | |||
Tile &getTile(const std::string &name); | |||
Item &getItem(const std::string &name); | |||
SDL_Color backgroundColor(); | |||
void draw(Win &win); | |||
void update(float dt); | |||
void tick(float dt); |
@@ -1,15 +1,19 @@ | |||
#pragma once | |||
#include <memory> | |||
#include <SDL.h> | |||
#include "common.h" | |||
#include "Chunk.h" | |||
#include "Entity.h" | |||
#include "traits/BodyTrait.h" | |||
#include "Vector2.h" | |||
namespace Swan { | |||
class World; | |||
class WorldPlane; | |||
class ImageResource; | |||
class WorldGen { | |||
public: | |||
@@ -22,8 +26,18 @@ public: | |||
virtual ~WorldGen() = default; | |||
virtual void drawBackground(const Context &ctx, Win &win, Vec2 pos) = 0; | |||
virtual SDL_Color backgroundColor(Vec2 pos) = 0; | |||
virtual void genChunk(WorldPlane &plane, Chunk &chunk) = 0; | |||
virtual BodyTrait::HasBody *spawnPlayer(WorldPlane &plane) = 0; | |||
}; | |||
class WorldGenStructure { | |||
public: | |||
virtual ~WorldGenStructure() = 0; | |||
virtual bool isBase(TilePos pos); | |||
}; | |||
} |
@@ -53,6 +53,7 @@ public: | |||
BodyTrait::HasBody *spawnPlayer(); | |||
void breakBlock(TilePos pos); | |||
SDL_Color backgroundColor(); | |||
void draw(Win &win); | |||
void update(float dt); | |||
void tick(float dt); |
@@ -0,0 +1,19 @@ | |||
#pragma once | |||
#include <SDL.h> | |||
#include <optional> | |||
#include "Win.h" | |||
namespace Swan { | |||
namespace Draw { | |||
SDL_Color linearColor(SDL_Color from, SDL_Color to, float frac); | |||
void parallaxBackground( | |||
Win &win, SDL_Texture *tex, | |||
std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect, | |||
float x, float y, float factor); | |||
} | |||
} |
@@ -3,6 +3,8 @@ | |||
#include "util.h" | |||
#include "log.h" | |||
namespace Swan { | |||
inline std::ostream &operator<<(std::ostream &os, const SDL_Rect &rect) { | |||
@@ -12,6 +14,59 @@ inline std::ostream &operator<<(std::ostream &os, const SDL_Rect &rect) { | |||
return os; | |||
} | |||
class RenderBlendMode: NonCopyable { | |||
public: | |||
RenderBlendMode(SDL_Renderer *rnd, SDL_BlendMode mode): rnd_(rnd) { | |||
SDL_GetRenderDrawBlendMode(rnd_, &mode_); | |||
SDL_SetRenderDrawBlendMode(rnd_, mode); | |||
} | |||
~RenderBlendMode() { | |||
SDL_SetRenderDrawBlendMode(rnd_, mode_); | |||
} | |||
private: | |||
SDL_Renderer *rnd_; | |||
SDL_BlendMode mode_; | |||
}; | |||
class RenderDrawColor: NonCopyable { | |||
public: | |||
RenderDrawColor(SDL_Renderer *rnd, Uint8 r, Uint8 g, Uint8 b, Uint8 a = 255): rnd_(rnd) { | |||
SDL_GetRenderDrawColor(rnd_, &r_, &g_, &b_, &a_); | |||
SDL_SetRenderDrawColor(rnd_, r, g, b, a); | |||
} | |||
~RenderDrawColor() { | |||
SDL_SetRenderDrawColor(rnd_, r_, g_, b_, a_); | |||
} | |||
private: | |||
SDL_Renderer *rnd_; | |||
Uint8 r_, g_, b_, a_; | |||
}; | |||
class RenderClipRect: NonCopyable { | |||
public: | |||
RenderClipRect(SDL_Renderer *rnd, SDL_Rect *rect): rnd_(rnd) { | |||
enabled_ = SDL_RenderIsClipEnabled(rnd_); | |||
SDL_RenderGetClipRect(rnd_, &rect_); | |||
SDL_RenderSetClipRect(rnd_, rect); | |||
} | |||
~RenderClipRect() { | |||
if (enabled_) | |||
SDL_RenderSetClipRect(rnd_, &rect_); | |||
else | |||
SDL_RenderSetClipRect(rnd_, nullptr); | |||
} | |||
private: | |||
SDL_Renderer *rnd_; | |||
bool enabled_; | |||
SDL_Rect rect_; | |||
}; | |||
class RenderTarget: NonCopyable { | |||
public: | |||
RenderTarget(SDL_Renderer *rnd, SDL_Texture *tex): rnd_(rnd) { | |||
@@ -44,7 +99,7 @@ private: | |||
class TexColorMod: NonCopyable { | |||
public: | |||
TexColorMod(SDL_Texture *tex, uint8_t r, uint8_t g, uint8_t b): tex_(tex) { | |||
TexColorMod(SDL_Texture *tex, Uint8 r, Uint8 g, Uint8 b): tex_(tex) { | |||
SDL_GetTextureColorMod(tex_, &r_, &g_, &b_); | |||
SDL_SetTextureColorMod(tex_, r, g, b); | |||
} | |||
@@ -55,12 +110,12 @@ public: | |||
private: | |||
SDL_Texture *tex_; | |||
uint8_t r_, g_, b_; | |||
Uint8 r_, g_, b_; | |||
}; | |||
class TexAlphaMod: NonCopyable { | |||
public: | |||
TexAlphaMod(SDL_Texture *tex, uint8_t alpha): tex_(tex) { | |||
TexAlphaMod(SDL_Texture *tex, Uint8 alpha): tex_(tex) { | |||
SDL_GetTextureAlphaMod(tex_, &alpha_); | |||
SDL_SetTextureAlphaMod(tex_, alpha); | |||
} | |||
@@ -71,7 +126,7 @@ public: | |||
private: | |||
SDL_Texture *tex_; | |||
uint8_t alpha_; | |||
Uint8 alpha_; | |||
}; | |||
} |
@@ -22,5 +22,6 @@ | |||
#include <swan/WorldPlane.h> | |||
#include <swan/common.h> | |||
#include <swan/log.h> | |||
#include <swan/drawutil.h> | |||
#include <swan/gfxutil.h> | |||
#include <swan/util.h> |
@@ -99,6 +99,7 @@ void Chunk::render(const Context &ctx, SDL_Renderer *rnd) { | |||
texture_.reset(SDL_CreateTexture( | |||
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, | |||
CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE)); | |||
SDL_SetTextureBlendMode(texture_.get(), SDL_BLENDMODE_BLEND); | |||
} | |||
// We wanna render directly to the texture | |||
@@ -129,8 +130,16 @@ void Chunk::renderList(SDL_Renderer *rnd) { | |||
// We still wanna render directly to the target texture | |||
RenderTarget target(rnd, texture_.get()); | |||
// We must make sure the blend mode is NONE, because we want transparent | |||
// pixels to actually overwrite non-transparent pixels | |||
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE); | |||
// When we FillRect, we must fill transparency. | |||
RenderDrawColor color(rnd, 0, 0, 0, 0); | |||
for (auto &[pos, tex]: draw_list_) { | |||
SDL_Rect dest{pos.x * TILE_SIZE, pos.y * TILE_SIZE, TILE_SIZE, TILE_SIZE}; | |||
SDL_RenderFillRect(rnd, &dest); | |||
SDL_RenderCopy(rnd, tex, nullptr, &dest); | |||
} | |||
} |
@@ -43,6 +43,10 @@ TilePos Game::getMouseTile() { | |||
(int)floor(win_.cam_.y + mousePos.y / (Swan::TILE_SIZE * win_.zoom_))); | |||
} | |||
SDL_Color Game::backgroundColor() { | |||
return world_->backgroundColor(); | |||
} | |||
void Game::draw() { | |||
world_->draw(win_); | |||
} |
@@ -1,5 +1,7 @@ | |||
#include "World.h" | |||
#include <algorithm> | |||
#include "log.h" | |||
#include "Game.h" | |||
#include "Win.h" | |||
@@ -123,6 +125,10 @@ Tile &World::getTile(const std::string &name) { | |||
return getTileByID(id); | |||
} | |||
SDL_Color World::backgroundColor() { | |||
return planes_[current_plane_].backgroundColor(); | |||
} | |||
void World::draw(Win &win) { | |||
auto bounds = player_->getBody().getBounds(); | |||
win.cam_ = bounds.pos - (win.getSize() / 2) + (bounds.size / 2); |
@@ -162,8 +162,15 @@ void WorldPlane::breakBlock(TilePos pos) { | |||
} | |||
} | |||
SDL_Color WorldPlane::backgroundColor() { | |||
return gen_->backgroundColor(world_->player_->getBody().getBounds().pos); | |||
} | |||
void WorldPlane::draw(Win &win) { | |||
auto pbounds = world_->player_->getBody().getBounds(); | |||
gen_->drawBackground(getContext(), win, pbounds.pos); | |||
ChunkPos pcpos = ChunkPos( | |||
(int)floor(pbounds.pos.x / CHUNK_WIDTH), | |||
(int)floor(pbounds.pos.y / CHUNK_HEIGHT)); |
@@ -0,0 +1,74 @@ | |||
#include "drawutil.h" | |||
#include <algorithm> | |||
#include <cmath> | |||
#include "gfxutil.h" | |||
namespace Swan { | |||
namespace Draw { | |||
static Uint8 linearLine(float from, float to, float frac) { | |||
return (Uint8)std::clamp(to * frac + from * (1 - frac), 0.0f, 255.0f); | |||
} | |||
SDL_Color linearColor(SDL_Color from, SDL_Color to, float frac) { | |||
return { | |||
.r = linearLine(from.r, to.r, frac), | |||
.g = linearLine(from.g, to.g, frac), | |||
.b = linearLine(from.b, to.b, frac), | |||
.a = linearLine(from.a, to.a, frac), | |||
}; | |||
} | |||
void parallaxBackground( | |||
Win &win, SDL_Texture *tex, | |||
std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect, | |||
float x, float y, float factor) { | |||
SDL_Renderer *rnd = win.renderer_; | |||
// We only need to set a clip rect if we have a destrect | |||
std::optional<RenderClipRect> clip; | |||
if (!srcrect) { | |||
Uint32 fmt; | |||
int access, w, h; | |||
SDL_QueryTexture(tex, &fmt, &access, &w, &h); | |||
srcrect = SDL_Rect{ 0, 0, w, h }; | |||
} | |||
if (destrect) { | |||
clip.emplace(rnd, &*destrect); | |||
} else { | |||
int w, h; | |||
SDL_RenderGetLogicalSize(rnd, &w, &h); | |||
destrect = SDL_Rect{ 0, 0, w, h }; | |||
} | |||
x = (x * win.zoom_) * -factor; | |||
y = (y * win.zoom_) * -factor; | |||
SDL_Rect rect{ | |||
0, 0, | |||
(int)(srcrect->w * win.zoom_), | |||
(int)(srcrect->h * win.zoom_), | |||
}; | |||
rect.x = (int)std::floor((int)x % rect.w); | |||
if (rect.x > 0) rect.x -= rect.w; | |||
rect.y = (int)std::floor((int)y % rect.h); | |||
if (rect.y > 0) rect.y -= rect.h; | |||
int numx = destrect->w / rect.w + 2; | |||
int numy = destrect->h / rect.h + 2; | |||
for (int x = 0; x < numx; ++x) { | |||
for (int y = 0; y < numy; ++y) { | |||
SDL_Rect r{ rect.x + x * rect.w, rect.y + y * rect.h, rect.w, rect.h }; | |||
SDL_RenderCopy(rnd, tex, &*srcrect, &r); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -91,6 +91,8 @@ int main(int argc, char **argv) { | |||
CPtr<SDL_Renderer, SDL_DestroyRenderer> renderer( | |||
SDL_CreateRenderer(window.get(), -1, renderflags)); | |||
sdlassert(renderer, "Could not create renderer"); | |||
SDL_SetRenderDrawBlendMode(renderer.get(), SDL_BLENDMODE_BLEND); | |||
SDL_SetRenderDrawColor(renderer.get(), 0, 0, 0, 255); | |||
Win win(window.get(), renderer.get(), gui_scale); | |||
@@ -230,7 +232,11 @@ int main(int argc, char **argv) { | |||
pcounter.countGameTick(tick_clock.duration()); | |||
} | |||
SDL_RenderClear(renderer.get()); | |||
{ | |||
auto [r, g, b, a] = game.backgroundColor(); | |||
RenderDrawColor c(renderer.get(), r, g, b, a); | |||
SDL_RenderClear(renderer.get()); | |||
} | |||
// ImGUI | |||
imgui_io.DeltaTime = dt; |