#ifndef SHMUP_HPP #define SHMUP_HPP #include #include #include #include #include #include #include #include #include #include enum class GameState { Playing, Paused, GameOver }; class Shmup : public OpenEngine::Layer { public: Shmup() : camera((float)OpenEngine::Application::Get().GetWindow().GetWidth() / OpenEngine::Application::Get().GetWindow().GetHeight(), 1.0f) { } ~Shmup() {}; void OnAttach() override { OE_PROFILE_FUNCTION(); { OE_PROFILE_SCOPE("Texture2D Creation"); ship = OpenEngine::Texture2D::Create("assets/textures/shmup/ship.png"); enemy_ship = OpenEngine::Texture2D::Create("assets/textures/shmup/enemy_ship.png"); bullet_tx = OpenEngine::Texture2D::Create("assets/textures/shmup/bullet.png"); } LoadMap(); } void OnDetach() override { } struct Bounds { glm::vec2 min; // Bottom-Left glm::vec2 max; // Top-Right }; Bounds GetBounds(glm::vec2 position, glm::vec2 scale) { Bounds b; // Because the original vertices are -0.5 to 0.5, // the distance from center to edge is exactly scale * 0.5 glm::vec2 halfScale = scale * 0.5f; b.min = position - halfScale; b.max = position + halfScale; return b; } bool CheckCollision(const Bounds& a, const Bounds& b) { bool overlapX = a.max.x >= b.min.x && b.max.x >= a.min.x; bool overlapY = a.max.y >= b.min.y && b.max.y >= a.min.y; return overlapX && overlapY; } void MovePlayerShip() { double delta = OpenEngine::Time::DeltaTime(); if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Up)) { tr1.position.y += 1 * delta; } if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Down)) { tr1.position.y -= 1 * delta; } if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Right)) { tr1.position.x += 1 * delta; } if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Left)) { tr1.position.x -= 1 * delta; } if (tr1.position.y <= -0.9) tr1.position.y = -0.9; if (tr1.position.y >= -0.3) tr1.position.y = -0.3; if (tr1.position.x <= -0.8) tr1.position.x = -0.8; if (tr1.position.x >= 0.8) tr1.position.x = 0.8; } bool MovePlayerShipDiscrete(OpenEngine::KeyPressedEvent& event) { if (state == GameState::Playing) { double delta = OpenEngine::Time::DeltaTime(); if (event.GetKeyCode() == OpenEngine::KeyCode::Right) { tr1.position.x += 0.1f; return true; } if (event.GetKeyCode() == OpenEngine::KeyCode::Left) { tr1.position.x -= 0.1f; return true; } if (tr1.position.y <= -0.9) tr1.position.y = -0.9; if (tr1.position.y >= -0.3) tr1.position.y = -0.3; if (tr1.position.x <= -0.8) tr1.position.x = -0.8; if (tr1.position.x >= 0.8) tr1.position.x = 0.8; } return false; } void MoveEntities(double time_step) { double delta = time_step; // Updating enemy positions for (auto& enemy : enemies) { enemy.position.y -= 0.3 * delta; if (state == GameState::Playing && (enemy.position.y <= -1.1 || CheckCollision( GetBounds(enemy.position, enemy.size), GetBounds(tr1.position, tr1.size)))) lives--; } // Updating friendly bullets positions for (auto& bullet : bullets) { bullet.position.y += 1.1 * delta; } } void UpdateEntity() { double delta = OpenEngine::Time::DeltaTime(); MoveEntities(delta); // Deletes enemies offscreen std::erase_if(enemies, [](const auto& enemy) { return enemy.position.y <= -1.1f; }); // Deletes bullets offscreen std::erase_if(bullets, [](const auto& bullet) { return bullet.position.y >= 1.2f; }); // Lowers lives upon collision between player and enemy for (auto& enemy : enemies) { if (state == GameState::Playing) { if (enemy.position.y <= -1.1) lives--; if (CheckCollision( GetBounds(enemy.position, enemy.size), GetBounds(tr1.position, tr1.size))) lives = 0; } } // Deletes enemy and bullet and increases score after collision for (auto& bullet : bullets) { for (auto& enemy : enemies) { Bounds bullet_bounds = GetBounds(bullet.position, bullet.size); Bounds enemy_bounds = GetBounds(enemy.position, enemy.size); if (CheckCollision(bullet_bounds, enemy_bounds)) { std::erase_if(bullets, [&](const OpenEngine::Transform& _bullet) { return _bullet.position == bullet.position; }); std::erase_if(enemies, [&](const auto& _enemy) { return _enemy.position == enemy.position; }); score++; } } } } void Render() { OE_PROFILE_FUNCTION() { OE_PROFILE_SCOPE("Setting up Rendering"); OpenEngine::RenderCommand::SetClearColor({0.11f, 0.11f, 0.15f, 1.0f}); //OpenEngine::RenderCommand::SetClearColor({1.0f, 0.11f, 0.15f, 1.0f}); OpenEngine::RenderCommand::Clear(); OpenEngine::Renderer2D::BeginScene(camera.GetCamera()); } { OE_PROFILE_SCOPE("Drawing Quads"); OpenEngine::Renderer2D::DrawQuad(tr1, ship, -1); for (auto& enemy : enemies) OpenEngine::Renderer2D::DrawQuad(enemy, enemy_ship, -1); for (auto& bullet : bullets) OpenEngine::Renderer2D::DrawQuad(bullet, bullet_tx, -1); } OpenEngine::Renderer2D::EndScene(); } void OnUpdate() override { float delta = OpenEngine::Time::DeltaTime(); Render(); UpdateEntity(); if (state == GameState::Playing) { if (spawn_cooldown >= spawn_min_time) { //SpawnEnemyRandom(); ReadMap(); spawn_cooldown = 0; if (spawn_min_time > 0.5) spawn_min_time -= 0.01; } //MovePlayerShip(); if (shooting) SpawnBullet(); shot_cooldown += delta; spawn_cooldown += delta; } if (lives <= 0) state = GameState::GameOver; } void Reset() { tr1 = {glm::vec3(0.0f, -0.9f, -0.1f), glm::vec3(0.1f), 0.0f}; score = 0; lives = 5; bullets.clear(); enemies.clear(); state = GameState::Playing; } void SpawnEnemyRandom() { static std::uniform_int_distribution dist{-8, 8}; SpawnEnemy(dist(rd)); }; void SpawnEnemy(int x) { OpenEngine::Transform enemy_tr({{x/10.0f, 1.2f, 0.1f}, glm::vec3(0.1f), 180.0f}); enemies.emplace_back(enemy_tr); }; void LoadMap(const char* path = "assets/maps/lvl1.txt") { std::string buffer; std::ifstream map_file(path); while (std::getline(map_file, buffer)) { map.emplace_back(buffer); } }; void ReadMap() { static int line_n = 0; if (line_n >= map.size()) return; auto line = map[line_n]; int position = -8; for (int i = 0; i < line.length(); i++) { if (line[i] == '#') SpawnEnemy(position); position++; } line_n++; }; void SpawnBullet() { if (shot_cooldown >= 0.2) { OpenEngine::Transform bullet_tr; bullet_tr.size = glm::vec3(0.03f); bullet_tr.position.x = tr1.position.x; bullet_tr.position.y = tr1.position.y + 0.1; bullets.emplace_back(bullet_tr); shot_cooldown = 0; } } bool ProcessKeyPressedEvents(OpenEngine::KeyPressedEvent& event) { if (event.GetKeyCode() == OpenEngine::KeyCode::Space) { shooting = true; return true; } if (state == GameState::GameOver && event.GetKeyCode() == OpenEngine::KeyCode::Enter) { Reset(); return true; } return false; }; bool ProcessKeyReleased(OpenEngine::KeyReleasedEvent& event) { if (event.GetKeyCode() == OpenEngine::KeyCode::Space) shooting = false; return false; } void OnEvent(OpenEngine::Event& event) override { OE_PROFILE_FUNCTION(); OpenEngine::EventDispatcher dispatcher(event); dispatcher.Dispatch(BIND_EVENT_FN(Shmup::MovePlayerShipDiscrete)); dispatcher.Dispatch(BIND_EVENT_FN(Shmup::ProcessKeyPressedEvents)); dispatcher.Dispatch(BIND_EVENT_FN(Shmup::ProcessKeyReleased)); { OE_PROFILE_SCOPE("Camera OnEvent"); camera.OnEvent(event); } } //void OnImGuiRender() override //{ // ImGuiIO io = ImGui::GetIO(); // static auto m_Font = io.Fonts->AddFontFromFileTTF("/usr/share/fonts/TTF/JetBrainsMono-Regular.ttf", 120.0f); // ImGui::GetForegroundDrawList()->AddText(m_Font, 48.0f, {5.0f, 5.0f}, 0xffffffff, "test"); //} GameState GetGameState() { return state; }; int GetScore() { return score; }; int GetLives() { return lives; }; private: OpenEngine::Ref ship; OpenEngine::Ref enemy_ship; OpenEngine::Ref bullet_tx; OpenEngine::Transform tr1 = {glm::vec3(0.0f, -0.9f, -0.1f), glm::vec3(0.1f), 0.0f}; std::vector enemies, bullets; int score = 0; int lives = 5; bool shooting = false; float spawn_cooldown = 0, shot_cooldown = 0; float spawn_min_time = 1.0f; std::vector map; std::random_device rd; std::mt19937 e{rd()}; // or std::default_random_engine e{rd()}; OpenEngine::OrthographicCameraController camera; GameState state = GameState::Playing; //OpenEngine::Ref overlay; }; #endif // SHMUP_HPP