From 57fe8f3c4124819b95164547333a33f4c45eac8d Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sun, 10 Mar 2019 12:07:40 -0400 Subject: Editor now allows tile placement You can scroll through the three layers (map, track, object) with Z/X. You can swap between focusing on the map and the tileset with TAB. You can place tiles with enter or space. Pretty rudimentary, but it's a start. --- src/consts.h | 6 +- src/editor.cpp | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/editor.h | 30 +++++++ src/input.h | 39 +++++++++ src/input_lag.h | 66 ++++++++++++++ src/level.cpp | 41 +++++---- src/level.h | 64 +++++++++++--- src/main.cpp | 20 ++--- src/simulation.cpp | 2 +- src/tileset.h | 38 +++++++++ 10 files changed, 496 insertions(+), 56 deletions(-) create mode 100644 src/input.h create mode 100644 src/input_lag.h (limited to 'src') diff --git a/src/consts.h b/src/consts.h index f3a073c..a6b5094 100644 --- a/src/consts.h +++ b/src/consts.h @@ -4,7 +4,9 @@ #include "vector.h" constexpr vec2s TILE_SIZE { 32, 32 }; -constexpr vec2s LEVEL_SIZE { 16, 16 }; -constexpr vec2s WINDOW_SIZE = TILE_SIZE * LEVEL_SIZE; +constexpr vec2s MAP_SIZE { 16, 16 }; +constexpr vec2s LEVEL_SIZE = TILE_SIZE * MAP_SIZE; +constexpr vec2s BORDER_SIZE = TILE_SIZE; +constexpr vec2s WINDOW_SIZE = LEVEL_SIZE * vec2s { 2, 1 } + BORDER_SIZE * 2; #endif /* end of include guard: CONSTS_H_8019F1B3 */ diff --git a/src/editor.cpp b/src/editor.cpp index 92c6b67..30dde45 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -5,27 +5,150 @@ void Editor::tick( double dt, const Uint8* keystate) { + if (keystate[SDL_SCANCODE_LEFT] || + keystate[SDL_SCANCODE_UP] || + keystate[SDL_SCANCODE_RIGHT] || + keystate[SDL_SCANCODE_DOWN]) + { + // If the player is holding down a directional key, accumulate the time + // since the last tick, and then apply the directional key as many times as + // needed. The input lag class automatically handles the delays necessary + // for key repeat. + inputLag_.accumulate(dt); -} + while (inputLag_.step()) + { + if (mapFocus_) + { + // If we are focused on the map, move the map cursor. + if (keystate[SDL_SCANCODE_LEFT] && cursor_.x() > 0) + { + cursor_.x()--; + } -void Editor::render(SDL_Renderer* ren) -{ - if (!background_) + if (keystate[SDL_SCANCODE_UP] && cursor_.y() > 0) + { + cursor_.y()--; + } + + if (keystate[SDL_SCANCODE_RIGHT] && + cursor_.x() < level_.getSize().w() - 1) + { + cursor_.x()++; + } + + if (keystate[SDL_SCANCODE_DOWN] && + cursor_.y() < level_.getSize().h() - 1) + { + cursor_.y()++; + } + } else { + // If we are focused on the tileset, rotate through the selection of + // tiles. + if (keystate[SDL_SCANCODE_UP]) + { + selectedTile_--; + } else if (keystate[SDL_SCANCODE_DOWN]) + { + selectedTile_++; + } + + selectedTile_ %= level_.getTileset(layer_).getNumTiles(); + } + } + } else if (inputLag_.isActive()) { - surface_ptr bgSurf(IMG_Load("../res/editor_bg.png")); + // If the player is not holding down a directional key, reset the input lag. + inputLag_.reset(); + } + + // Check for keypress changes since the last tick. + input_.tick(keystate); - if (!bgSurf) + if (input_.wasPressed(SDL_SCANCODE_Z)) + { + switch (layer_) { - throw img_error(); + case Layer::map: + { + layer_ = Layer::object; + + break; + } + + case Layer::track: + { + layer_ = Layer::map; + + break; + } + + case Layer::object: + { + layer_ = Layer::track; + + break; + } } - background_.reset(SDL_CreateTextureFromSurface(ren, bgSurf.get())); - bgSize_ = { bgSurf->w, bgSurf->h }; + selectedTile_ = 0; + + // Reset the cached map so that it gets redrawn with the focused layer. + renderedMap_.reset(); + } else if (input_.wasPressed(SDL_SCANCODE_X)) + { + switch (layer_) + { + case Layer::map: + { + layer_ = Layer::track; + + break; + } + + case Layer::track: + { + layer_ = Layer::object; + + break; + } + + case Layer::object: + { + layer_ = Layer::map; + + break; + } + } + + selectedTile_ = 0; + + // Reset the cached map so that it gets redrawn with the focused layer. + renderedMap_.reset(); + } else if (input_.wasPressed(SDL_SCANCODE_TAB)) + { + mapFocus_ = !mapFocus_; } - for (int y = 0; y < WINDOW_SIZE.h() / bgSize_.h(); y++) + if (mapFocus_) { - for (int x = 0; x < WINDOW_SIZE.w() / bgSize_.w(); x++) + if (keystate[SDL_SCANCODE_RETURN] || + keystate[SDL_SCANCODE_SPACE]) + { + level_.at(cursor_.x(), cursor_.y(), layer_) = selectedTile_; + + // Reset the cached map so that it gets redrawn with the new tile. + renderedMap_.reset(); + } + } +} + +void Editor::render(SDL_Renderer* ren) +{ + // Tile the background art. + for (int y = 0; y <= WINDOW_SIZE.h() / bgSize_.h(); y++) + { + for (int x = 0; x <= WINDOW_SIZE.w() / bgSize_.w(); x++) { SDL_Rect rect { x * bgSize_.w(), @@ -37,4 +160,105 @@ void Editor::render(SDL_Renderer* ren) SDL_RenderCopy(ren, background_.get(), nullptr, &rect); } } + + // Render and cache the map if the cache is currently empty. + if (!renderedMap_) + { + renderedMap_.reset( + SDL_CreateTexture( + ren, + SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_TARGET, + LEVEL_SIZE.w(), + LEVEL_SIZE.h())); + + // Render each layer separately, then compose them atop each other. Layers + // above the currently selected one are drawn semi-transparently. + texture_ptr mapRender = level_.render(ren, Layer::map); + texture_ptr trackRender = level_.render(ren, Layer::track); + texture_ptr objectRender = level_.render(ren, Layer::object); + + if (layer_ < Layer::track) + { + SDL_SetTextureBlendMode(trackRender.get(), SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod(trackRender.get(), 127); + } + + if (layer_ < Layer::object) + { + SDL_SetTextureBlendMode(objectRender.get(), SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod(objectRender.get(), 127); + } + + SDL_SetRenderTarget(ren, renderedMap_.get()); + SDL_RenderCopy(ren, mapRender.get(), nullptr, nullptr); + SDL_RenderCopy(ren, trackRender.get(), nullptr, nullptr); + SDL_RenderCopy(ren, objectRender.get(), nullptr, nullptr); + SDL_SetRenderTarget(ren, nullptr); + } + + // Copy the rendered map onto the editor. + SDL_Rect mapLoc { + static_cast(BORDER_SIZE.w()), + static_cast(BORDER_SIZE.h()), + static_cast(LEVEL_SIZE.w()), + static_cast(LEVEL_SIZE.h()) + }; + + SDL_RenderCopy(ren, renderedMap_.get(), nullptr, &mapLoc); + + // If the map is focused, draw an outline around it. + if (mapFocus_) + { + SDL_SetRenderDrawColor(ren, 255, 0, 0, 255); + SDL_RenderDrawRect(ren, &mapLoc); + } + + // Draw a box indicating the position of the map cursor. + vec2s cursorLoc = BORDER_SIZE + TILE_SIZE * cursor_; + + SDL_Rect cursorRect { + static_cast(cursorLoc.x()), + static_cast(cursorLoc.y()), + static_cast(TILE_SIZE.w()), + static_cast(TILE_SIZE.h()) + }; + + SDL_SetRenderDrawColor(ren, 255, 0, 0, 255); + SDL_RenderDrawRect(ren, &cursorRect); + + // Draw the tileset for the currently selected layer. + SDL_Rect tilesetLoc { + static_cast(BORDER_SIZE.w() * 2 + LEVEL_SIZE.w()), + static_cast(BORDER_SIZE.h()), + static_cast(TILE_SIZE.w()), + level_.getTileset(layer_).getSize().h() + }; + + SDL_SetRenderDrawColor(ren, 255, 255, 255, 255); + SDL_RenderFillRect(ren, &tilesetLoc); + + SDL_RenderCopy( + ren, + level_.getTileset(layer_).getImage().get(), + nullptr, + &tilesetLoc); + + // If the tileset is focused, draw an outline around it. + if (!mapFocus_) + { + SDL_SetRenderDrawColor(ren, 255, 0, 0, 255); + SDL_RenderDrawRect(ren, &tilesetLoc); + } + + // Draw a box indicating which tile is currently selected. + SDL_Rect tileCursorRect { + tilesetLoc.x, + static_cast((1 + selectedTile_) * TILE_SIZE.h()), + static_cast(TILE_SIZE.w()), + static_cast(TILE_SIZE.h()) + }; + + SDL_SetRenderDrawColor(ren, 255, 255, 0, 255); + SDL_RenderDrawRect(ren, &tileCursorRect); } diff --git a/src/editor.h b/src/editor.h index dc908b9..dbf50c0 100644 --- a/src/editor.h +++ b/src/editor.h @@ -3,10 +3,26 @@ #include "state.h" #include "vector.h" +#include "level.h" +#include "input.h" +#include "input_lag.h" class Editor : public State { public: + Editor(SDL_Renderer* ren) : level_(ren) + { + surface_ptr bgSurf(IMG_Load("../res/editor_bg.png")); + + if (!bgSurf) + { + throw img_error(); + } + + background_.reset(SDL_CreateTextureFromSurface(ren, bgSurf.get())); + bgSize_ = { bgSurf->w, bgSurf->h }; + } + void tick( double dt, const Uint8* keystate) override; @@ -15,9 +31,23 @@ public: private: + Level level_; + texture_ptr background_; vec2i bgSize_; + texture_ptr renderedMap_; + + bool mapFocus_ = true; + vec2s cursor_; + + size_t selectedTile_ = 0; + + Input input_; + InputLag inputLag_; + + Layer layer_ = Layer::map; + }; #endif /* end of include guard: EDITOR_H_8BB54FE3 */ diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..ad8b761 --- /dev/null +++ b/src/input.h @@ -0,0 +1,39 @@ +#ifndef INPUT_H_0FB34C42 +#define INPUT_H_0FB34C42 + +#include +#include + +/** + * Helper class that detects when a key is newly pressed. + */ +class Input { +public: + + Input() + { + const Uint8* s = SDL_GetKeyboardState(&length_); + + curstate_.assign(s, s + length_); + prevstate_ = curstate_; + } + + void tick(const Uint8* keystate) + { + prevstate_ = std::move(curstate_); + curstate_.assign(keystate, keystate + length_); + } + + bool wasPressed(int scancode) const + { + return curstate_.at(scancode) && !prevstate_.at(scancode); + } + +private: + + int length_; + std::vector curstate_; + std::vector prevstate_; +}; + +#endif /* end of include guard: INPUT_H_0FB34C42 */ diff --git a/src/input_lag.h b/src/input_lag.h new file mode 100644 index 0000000..40bc4a1 --- /dev/null +++ b/src/input_lag.h @@ -0,0 +1,66 @@ +#ifndef INPUT_LAG_H_DF16F381 +#define INPUT_LAG_H_DF16F381 + +/** + * Helper class that handles key repeat. + */ +class InputLag { +public: + + bool isActive() const + { + return active_; + } + + void reset() + { + accum_ = 0.0; + active_ = false; + repeat_ = false; + } + + void accumulate(double dt) + { + accum_ += dt; + } + + bool step() + { + if (!active_) + { + active_ = true; + + return true; + } else if (!repeat_) + { + if (accum_ > delay_) + { + accum_ -= delay_; + repeat_ = true; + + return true; + } + } else { + if (accum_ > tick_) + { + accum_ -= tick_; + + return true; + } + } + + return false; + } + +private: + + double tick_ = 1.0 / 15.0; + double delay_ = tick_ * 3.0; + + double accum_ = 0.0; + bool active_ = false; + bool repeat_ = false; + +}; + +#endif /* end of include guard: INPUT_LAG_H_DF16F381 */ diff --git a/src/level.cpp b/src/level.cpp index b97eb70..e22a43f 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -7,30 +7,37 @@ texture_ptr Level::render(SDL_Renderer* ren, Layer layer) const ren, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, - WINDOW_SIZE.w(), - WINDOW_SIZE.h())); + LEVEL_SIZE.w(), + LEVEL_SIZE.h())); SDL_SetRenderTarget(ren, canvas.get()); + SDL_SetTextureBlendMode(canvas.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(ren, 0, 0, 0, 0); + SDL_RenderFillRect(ren, nullptr); for (size_t y = 0; y < size_.h(); y++) { for (size_t x = 0; x < size_.w(); x++) { - if (layer == Layer::map) - { - int val = 255 - at(x, y) * 10; - - SDL_SetRenderDrawColor(ren, val, val, val, 255); - - SDL_Rect rect { - static_cast(x * TILE_SIZE.w()), - static_cast(y * TILE_SIZE.h()), - TILE_SIZE.w(), - TILE_SIZE.h() - }; - - SDL_RenderFillRect(ren, &rect); - } + SDL_Rect src { + 0, + static_cast(at(x, y, layer) * TILE_SIZE.h()), + TILE_SIZE.w(), + TILE_SIZE.h() + }; + + SDL_Rect dest { + static_cast(x * TILE_SIZE.w()), + static_cast(y * TILE_SIZE.h()), + TILE_SIZE.w(), + TILE_SIZE.h() + }; + + SDL_RenderCopy( + ren, + getTileset(layer).getImage().get(), + &src, + &dest); } } diff --git a/src/level.h b/src/level.h index bc05837..9b01f64 100644 --- a/src/level.h +++ b/src/level.h @@ -6,15 +6,22 @@ #include "vector.h" #include "consts.h" #include "tileset.h" +#include "enums.h" class Level { public: - Level() + Level( + SDL_Renderer* ren) : + mapTileset_(ren, "map.png"), + trackTileset_(ren, "track.png"), + objectTileset_(ren, "object.png") { - size_ = LEVEL_SIZE; + size_ = MAP_SIZE; - tiles_.resize(size_.w() * size_.h()); + mapTiles_.resize(size_.w() * size_.h()); + trackTiles_.resize(size_.w() * size_.h()); + objectTiles_.resize(size_.w() * size_.h()); } const vec2s& getSize() const @@ -22,19 +29,48 @@ public: return size_; } - size_t at(vec2s pos) const + size_t at(vec2s pos, Layer layer = Layer::map) const { - return at(pos.x(), pos.y()); + return at(pos.x(), pos.y(), layer); } - size_t at(size_t x, size_t y) const + size_t& at(vec2s pos, Layer layer = Layer::map) { - return tiles_.at(x + size_.w() * y); + return at(pos.x(), pos.y(), layer); } - const Tileset& getTileset() const + size_t at(size_t x, size_t y, Layer layer = Layer::map) const { - return tileset_; + size_t index = x + size_.w() * y; + + switch (layer) + { + case Layer::map: return mapTiles_.at(index); + case Layer::track: return trackTiles_.at(index); + case Layer::object: return objectTiles_.at(index); + } + } + + size_t& at(size_t x, size_t y, Layer layer = Layer::map) + { + size_t index = x + size_.w() * y; + + switch (layer) + { + case Layer::map: return mapTiles_.at(index); + case Layer::track: return trackTiles_.at(index); + case Layer::object: return objectTiles_.at(index); + } + } + + const Tileset& getTileset(Layer layer) const + { + switch (layer) + { + case Layer::map: return mapTileset_; + case Layer::track: return trackTileset_; + case Layer::object: return objectTileset_; + } } texture_ptr render(SDL_Renderer* ren, Layer layer) const; @@ -42,8 +78,14 @@ public: private: vec2s size_; - std::vector tiles_; - Tileset tileset_; + + std::vector mapTiles_; + std::vector trackTiles_; + std::vector objectTiles_; + + Tileset mapTileset_; + Tileset trackTileset_; + Tileset objectTileset_; }; #endif /* end of include guard: LEVEL_H_678CFCCF */ diff --git a/src/main.cpp b/src/main.cpp index cbac5bb..cd8ee3e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,13 +39,13 @@ int main(int argc, char** argv) throw sdl_error(); } - Level level; + Level level(ren.get()); std::unique_ptr state; if (argc == 2 && !strcmp(argv[1], "--editor")) { - state.reset(new Editor()); + state.reset(new Editor(ren.get())); } else { state.reset(new Simulation(level)); } @@ -63,20 +63,12 @@ int main(int argc, char** argv) while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) + if (e.type == SDL_QUIT || + (e.type == SDL_KEYDOWN && + e.key.keysym.sym == SDLK_ESCAPE)) { quit = true; - } else if (e.type == SDL_KEYDOWN) - { - switch (e.key.keysym.sym) - { - case SDLK_ESCAPE: - { - quit = true; - - break; - } - } + break; } } diff --git a/src/simulation.cpp b/src/simulation.cpp index 4d8ec02..62026a6 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -342,7 +342,7 @@ bool Simulation::moveEntityOnGrid( } } - if (!level_.getTileset().canEntityMoveTo( + if (!level_.getTileset(Layer::map).canEntityMoveTo( entity.colliderType, level_.at(shouldMoveTo))) { diff --git a/src/tileset.h b/src/tileset.h index 8a565bc..745ea1d 100644 --- a/src/tileset.h +++ b/src/tileset.h @@ -1,15 +1,53 @@ #ifndef TILESET_H_B89AE7A1 #define TILESET_H_B89AE7A1 +#include +#include "renderer.h" #include "enums.h" +#include "consts.h" class Tileset { public: + Tileset(SDL_Renderer* ren, std::string filename) + { + surface_ptr surf(IMG_Load(("../res/" + filename).c_str())); + + if (!surf) + { + throw img_error(); + } + + image_.reset(SDL_CreateTextureFromSurface(ren, surf.get())); + size_ = { surf->w, surf->h }; + numTiles_ = size_.h() / TILE_SIZE.h(); + } + bool canEntityMoveTo(ColliderType collider, size_t tile) const { return true; } + + const texture_ptr& getImage() const + { + return image_; + } + + const vec2i& getSize() const + { + return size_; + } + + size_t getNumTiles() const + { + return numTiles_; + } + +private: + + texture_ptr image_; + vec2i size_; + size_t numTiles_; }; #endif /* end of include guard: TILESET_H_B89AE7A1 */ -- cgit 1.4.1