diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c9698b..15f832f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable(PlatScifi src/worldobjects/player.h src/worldobjects/staticcollider.h src/worldobjects/staticdeadly.h + src/worldobjects/leveltp.h src/worldobjects/spike.h ) target_link_libraries(PlatScifi PRIVATE sfml-graphics) diff --git a/src/assets/level_0.csv b/src/assets/level_0.csv index 0a64f07..057cd61 100644 --- a/src/assets/level_0.csv +++ b/src/assets/level_0.csv @@ -1,4 +1,5 @@ -// Example level (level_0.csv) +// Example level (level_0) +nextLevelName level_1 // Bottom colliders StaticCollider, 3, 25, 6, 2 StaticCollider, 9, 27, 10, 2 @@ -11,4 +12,6 @@ StaticCollider, 23, 29, 44, 19 // Spike Spike, 28, 28 // Static deadly section -StaticDeadly, 33, 28, 5, 2 \ No newline at end of file +StaticDeadly, 33, 28, 5, 2 +// Ending teleporter +LevelTp, 55, 28 \ No newline at end of file diff --git a/src/assets/level_1.csv b/src/assets/level_1.csv new file mode 100644 index 0000000..aa9feb1 --- /dev/null +++ b/src/assets/level_1.csv @@ -0,0 +1,7 @@ +// Example level (level_1) +// Cycle back to the start +nextLevelName level_0 +// Bottom collider +StaticCollider, 2, 30, 30, 2 +// Next level +LevelTp, 50, 29 \ No newline at end of file diff --git a/src/gamestate.cpp b/src/gamestate.cpp index 5005a41..314b7e9 100644 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -4,10 +4,28 @@ void GameState::spawnObject(WorldObject* object) { objects.push_back(object); } -void GameState::update() { +UpdateResult GameState::update() { for (WorldObject* object : objects) { - object->update(worldState, objects); + UpdateResult updateResult = object->update(worldState, objects); + if (updateResult == UpdateResult::NextLevel) { + // TODO: improve this + return UpdateResult::NextLevel; + } } + return UpdateResult::None; +} + +std::string GameState::getLevelName() { + return levelName; +} + +std::string GameState::getNextLevelName() { + return nextLevelName; +} + +void GameState::updateLevelNames(std::string newLevelName, std::string newNextLevelName) { + levelName = newLevelName; + nextLevelName = newNextLevelName; } std::vector::iterator GameState::objectsBegin() { @@ -19,10 +37,16 @@ std::vector::iterator GameState::objectsEnd() { } void GameState::clear() { - while (!objects.empty()) { - // TODO: good memory freeing? - delete objects.back(); - objects.pop_back(); + auto it = objects.begin(); + while (it != objects.end()) { + if ((*it)->hasAttribute(ObjectAttribute::Persist)) { + // Do not delete + it++; + } else { + // TODO: good memory freeing? + delete *it; + it = objects.erase(it); + } } } diff --git a/src/gamestate.h b/src/gamestate.h index 618282f..822c4a8 100644 --- a/src/gamestate.h +++ b/src/gamestate.h @@ -8,17 +8,25 @@ class GameState { private: WorldState worldState; + std::string levelName; + std::string nextLevelName; std::vector objects; public: // Spawn an object void spawnObject(WorldObject* object); // Update - void update(); + UpdateResult update(); + // Get the current level name + std::string getLevelName(); + // Get the next level name + std::string getNextLevelName(); + // Update the current level name + void updateLevelNames(std::string newLevelName, std::string newNextLevelName); // Get all objects std::vector::iterator objectsBegin(); std::vector::iterator objectsEnd(); - // Clear all items + // Clear all objects from the world, aside from Persisting objects void clear(); // Destructor to free memory ~GameState(); diff --git a/src/main.cpp b/src/main.cpp index a88b0e0..264eac6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,7 @@ int main() { gameState.spawnObject(player); // Debug: load a basic level from a txt file - worldSpawner.spawnWorld(gameState, "assets/level_0.csv"); + worldSpawner.spawnWorld(gameState, "level_0"); /*// Example level (level_0.csv) // Bottom colliders gameState.spawnObject(new StaticCollider(3, 25, 6, 2)); @@ -83,7 +83,12 @@ int main() { } // Update the game and world - gameState.update(); + UpdateResult updateResult = gameState.update(); + if (updateResult == UpdateResult::NextLevel) { + // Load the next level, if possible + // TODO: impl better + worldSpawner.spawnWorld(gameState, gameState.getNextLevelName()); + } // Render the game and world window.clear(sf::Color(111, 201, 252)); diff --git a/src/worldobject.h b/src/worldobject.h index 8da75d9..6d60783 100644 --- a/src/worldobject.h +++ b/src/worldobject.h @@ -8,13 +8,17 @@ // The result of an update function enum class UpdateResult { None, - Destroy + Destroy, + NextLevel // TODO: better naming }; // The attributes that an object can have enum class ObjectAttribute { + OverlapDetect, Collision, - Deadly + Deadly, + LevelTeleport, + Persist // Persist across levels }; // Stores a world object diff --git a/src/worldobjectincludes.h b/src/worldobjectincludes.h index b71c2e1..3ffc0f1 100644 --- a/src/worldobjectincludes.h +++ b/src/worldobjectincludes.h @@ -7,3 +7,4 @@ #include "worldobjects/staticcollider.h" #include "worldobjects/spike.h" #include "worldobjects/staticdeadly.h" +#include "worldobjects/leveltp.h" diff --git a/src/worldobjects/leveltp.h b/src/worldobjects/leveltp.h new file mode 100644 index 0000000..46b0597 --- /dev/null +++ b/src/worldobjects/leveltp.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../worldobject.h" + +// An object that teleports the player to another level +class LevelTp : public WorldObject { +private: + long frameCount = 0; + +public: + // Constructor + LevelTp(double spawnx, double spawny) : WorldObject() { + locx = spawnx; + locy = spawny; + this->width = 1; + this->height = 1; + this->objectAttributes.insert(ObjectAttribute::OverlapDetect); + this->objectAttributes.insert(ObjectAttribute::LevelTeleport); + } + + // Override update: check with player + UpdateResult update(WorldState& worldState, std::vector& objects) { + // TODO: stuff + return UpdateResult::None; + } + + // Override rendering + RenderData getRenderData() { + frameCount++; + // TODO: image? + return { + RenderType::Rectangle, + coordType, + locx + sin(frameCount / 20.0) * 0.3, + locy + cos(frameCount / 20.0) * 0.3, + width, + height, + { 66, 135, 245 }, + "" + }; + } +}; diff --git a/src/worldobjects/player.h b/src/worldobjects/player.h index 411fe08..eb5e3e5 100644 --- a/src/worldobjects/player.h +++ b/src/worldobjects/player.h @@ -14,6 +14,7 @@ class Player : public WorldObject { Player(double spawnx, double spawny) : WorldObject() { locx = spawnx; locy = spawny; + objectAttributes.insert(ObjectAttribute::Persist); } // Input: acceleration @@ -58,6 +59,7 @@ class Player : public WorldObject { for (WorldObject* object : objects) { if (object == this) continue; bool collided = false; + bool overlapped = false; if (object->hasAttribute(ObjectAttribute::Collision)) { // X movement pushout // todo: better way of doing the 0.1 thing @@ -68,6 +70,7 @@ class Player : public WorldObject { locx -= velx; velx = 0; collided = true; + overlapped = true; } } if (locx + width >= object->getLocx() && locx < object->getLocx() + object->getWidth()) { @@ -77,17 +80,33 @@ class Player : public WorldObject { locy -= vely; vely = 0; collided = true; + overlapped = true; // todo: should not be on ground on a bottom corner onGround = true; } } } + if (object->hasAttribute(ObjectAttribute::OverlapDetect)) { + // Check overlap only + if (locy + height >= object->getLocy() && locy < object->getLocy() + object->getHeight()) { + if (locx + width > object->getLocx() && locx < object->getLocx() + object->getWidth()) { + // Hit + overlapped = true; + } + } + } // Collision stuff if (collided && object->hasAttribute(ObjectAttribute::Deadly)) { // Die // todo: impl teleport(5, 10); } + if (overlapped && object->hasAttribute(ObjectAttribute::LevelTeleport)) { + // Teleport to the next level + // TODO: impl better + teleport(5, 10); + return UpdateResult::NextLevel; + } } return UpdateResult::None; } diff --git a/src/worldobjects/spike.h b/src/worldobjects/spike.h index cf8a9c1..e88acfe 100644 --- a/src/worldobjects/spike.h +++ b/src/worldobjects/spike.h @@ -31,9 +31,9 @@ class Spike : public WorldObject { RenderType::Image, coordType, locx, - locy - sin(frameCount / 400) * 0.3, + locy - sin(frameCount / 80.0) * 0.3, width, - height + sin(frameCount / 400) * 0.3, + height + sin(frameCount / 80.0) * 0.3, { 255, 0, 0 }, "assets/spike.png" }; diff --git a/src/worldobjects/staticdeadly.h b/src/worldobjects/staticdeadly.h index b0a4b06..6258048 100644 --- a/src/worldobjects/staticdeadly.h +++ b/src/worldobjects/staticdeadly.h @@ -16,7 +16,7 @@ class StaticDeadly : public WorldObject { objectAttributes.insert(ObjectAttribute::Deadly); } - // Override update: gravity + // Override update UpdateResult update(WorldState& worldState, std::vector& objects) { return UpdateResult::None; } diff --git a/src/worldspawner.cpp b/src/worldspawner.cpp index e69de29..6f7f6fd 100644 --- a/src/worldspawner.cpp +++ b/src/worldspawner.cpp @@ -0,0 +1,51 @@ +#include "worldspawner.h" + +void WorldSpawner::spawnWorld(GameState& gameState, std::string levelName) { + // Load the file based on the level name + // todo: error handling + std::ifstream file("assets/" + levelName + ".csv"); + std::string line; + std::string newNextLevelName = "level_0"; + // Clear the world + gameState.clear(); + // Update + while (std::getline(file, line)) { + // Parse line + std::vector parsed; + std::string thisitem = ""; + for (char c : line) { + if (c == ',') { + parsed.push_back(thisitem); + thisitem = ""; + } else if (c != ' ' && c != '\n') { + thisitem += c; + } + } + if (thisitem != "") parsed.push_back(thisitem); + // Process arguments + // todo: error handling with try catch + if (parsed[0].rfind("//", 0) == 0) { + // Comment: skip + } else if (parsed[0] == "nextLevelName") { + newNextLevelName = parsed[1]; + } else if (parsed[0] == "StaticCollider") { + gameState.spawnObject(new StaticCollider( + std::stoi(parsed[1]), std::stoi(parsed[2]), std::stoi(parsed[3]), std::stoi(parsed[4]) + )); + } else if (parsed[0] == "Spike") { + gameState.spawnObject(new Spike( + std::stoi(parsed[1]), std::stoi(parsed[2]) + )); + } else if (parsed[0] == "StaticDeadly") { + gameState.spawnObject(new StaticDeadly( + std::stoi(parsed[1]), std::stoi(parsed[2]), std::stoi(parsed[3]), std::stoi(parsed[4]) + )); + } else if (parsed[0] == "LevelTp") { + gameState.spawnObject(new LevelTp( + std::stoi(parsed[1]), std::stoi(parsed[2]) + )); + } + } + // Successful load: update the game state + gameState.updateLevelNames(levelName, newNextLevelName); +} diff --git a/src/worldspawner.h b/src/worldspawner.h index b250df5..a7d3d34 100644 --- a/src/worldspawner.h +++ b/src/worldspawner.h @@ -16,41 +16,5 @@ class WorldSpawner { StaticCollider, 9, 27, 10, 2 StaticCollider, 19, 25, 6, 2 */ - void spawnWorld(GameState& gameState, std::string worldfile) { - // Load the file - // todo: error handling - std::ifstream file(worldfile); - std::string line; - while (std::getline(file, line)) { - // Parse line - std::vector parsed; - std::string thisitem = ""; - for (char c : line) { - if (c == ',') { - parsed.push_back(thisitem); - thisitem = ""; - } else if (c != ' ' && c != '\n') { - thisitem += c; - } - } - if (thisitem != "") parsed.push_back(thisitem); - // Process arguments - // todo: error handling with try catch - if (parsed[0].rfind("//", 0) == 0) { - // Comment: skip - } else if (parsed[0] == "StaticCollider") { - gameState.spawnObject(new StaticCollider( - std::stoi(parsed[1]), std::stoi(parsed[2]), std::stoi(parsed[3]), std::stoi(parsed[4]) - )); - } else if (parsed[0] == "Spike") { - gameState.spawnObject(new Spike( - std::stoi(parsed[1]), std::stoi(parsed[2]) - )); - } else if (parsed[0] == "StaticDeadly") { - gameState.spawnObject(new StaticDeadly( - std::stoi(parsed[1]), std::stoi(parsed[2]), std::stoi(parsed[3]), std::stoi(parsed[4]) - )); - } - } - } + void spawnWorld(GameState& gameState, std::string levelName); };