From d55b9527caf43bbf975ac2e41d027dcfe871e60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=87=D0=B5=D0=BD=D0=B8=D0=BD=20=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BB=D0=B0=20=D0=9C=D0=B8=D1=85=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=D0=B8=D1=87?= <dmpechenin@edu.hse.ru> Date: Sat, 22 Mar 2025 17:15:58 +0300 Subject: [PATCH] Support continues moving of entities --- source/Configuration.h | 3 ++ .../DynamicEntities/DynamicEntities.cpp | 42 ++++++++++++++++--- .../DynamicEntities/DynamicEntities.h | 7 +++- source/States/GameState/Entities/IEntity.h | 7 +++- .../GameState/Entities/Pacman/Pacman.cpp | 41 ++++++++++++++++-- .../States/GameState/Entities/Pacman/Pacman.h | 9 +++- .../StaticEntities/StaticEntities.cpp | 4 ++ .../Entities/StaticEntities/StaticEntities.h | 2 + .../GameState/GameBuilder/GameBuilder.cpp | 16 +++++-- source/States/GameState/GameState.cpp | 39 +++++++++++++---- source/States/GameState/GameState.h | 1 + source/States/GameState/Maze/Room/Room.cpp | 2 +- source/States/SelectState/SelectState.cpp | 2 +- 13 files changed, 149 insertions(+), 26 deletions(-) diff --git a/source/Configuration.h b/source/Configuration.h index 7c04e84..f7d0808 100644 --- a/source/Configuration.h +++ b/source/Configuration.h @@ -21,12 +21,15 @@ namespace config { constexpr char EASY_GAME_TITLE[] = "Level: Easy"; constexpr char MEDIUM_GAME_TITLE[] = "Level: Medium"; constexpr char HARD_GAME_TITLE[] = "Level: Hard"; + constexpr char EXTREME_GAME_TITLE[] = "Level: Extreme"; constexpr float EASY_GAME_ENEMY_RATIO = 0.0f; constexpr float MEDIUM_GAME_ENEMY_RATIO = 0.03f; constexpr float HARD_GAME_ENEMY_RATIO = 0.07f; constexpr float ROOM_SIZE = 50; constexpr float GAME_ENEMY_SIZE = ROOM_SIZE * 0.7; constexpr float GAME_FOOD_SIZE = ROOM_SIZE * 0.1; + constexpr float ENEMY_SPEED = 80; + constexpr float PACMAN_SPEED = 300; // Пакмэн: constexpr float GAME_PACMAN_SIZE = ROOM_SIZE * 0.8; constexpr sf::Keyboard::Key KEY_LEFT = sf::Keyboard::A; diff --git a/source/States/GameState/Entities/DynamicEntities/DynamicEntities.cpp b/source/States/GameState/Entities/DynamicEntities/DynamicEntities.cpp index c2f0ffa..1e228fa 100644 --- a/source/States/GameState/Entities/DynamicEntities/DynamicEntities.cpp +++ b/source/States/GameState/Entities/DynamicEntities/DynamicEntities.cpp @@ -4,6 +4,7 @@ Enemy::Enemy() : m_rectangle({config::GAME_ENEMY_SIZE, config::GAME_ENEMY_SIZE}) { m_rectangle.setFillColor(config::GAME_ENEMY_COLOR); m_rectangle.setOrigin({config::GAME_ENEMY_SIZE/2, config::GAME_ENEMY_SIZE/2}); + m_distance_limit = config::ENEMY_SPEED/config::ROOM_SIZE; } std::unique_ptr<IDynamicEntity> Enemy::clone() const { @@ -11,11 +12,34 @@ std::unique_ptr<IDynamicEntity> Enemy::clone() const { } void Enemy::action() { - if (const auto miliseconds = static_cast<size_t>(m_stopwatch.getElapsedTime().asMilliseconds()); - miliseconds < m_dist_milliseconds(m_rng)) - return; - const auto direction = static_cast<Room::Direction>(m_dist_direction(m_rng)); - m_ptr_room->get_side(direction)->enter(this); + if (m_is_moving) { + const sf::Vector2f cur_pos = m_rectangle.getPosition(); + sf::Vector2f displacement = m_estimated_position - cur_pos; + const float distance = std::sqrt(displacement.x * displacement.x + displacement.y * displacement.y); + if (distance < m_distance_limit) { + m_rectangle.setPosition(m_estimated_position); + m_is_moving = false; + return; + } + const float frame_duration = m_stopwatch.getElapsedTime().asSeconds(); + const sf::Vector2f direction = config::ENEMY_SPEED * frame_duration * displacement / distance; + float delta_x, delta_y; + if (direction.x > 0) + delta_x = std::min(direction.x, displacement.x); + else + delta_x = std::max(direction.x, displacement.x); + if (direction.y > 0) + delta_y = std::min(direction.y, displacement.y); + else + delta_y = std::max(direction.y, displacement.y); + m_rectangle.move(delta_x, delta_y); + } + else { + if (static_cast<size_t>(m_stopwatch.getElapsedTime().asMilliseconds()) < m_dist_milliseconds(m_rng)) + return; + const auto direction = static_cast<Room::Direction>(m_dist_direction(m_rng)); + m_ptr_room->get_side(direction)->enter(this); + } m_stopwatch.restart(); } @@ -23,6 +47,12 @@ void Enemy::draw_into(sf::RenderWindow& window) const { window.draw(m_rectangle); } -void Enemy::prepare_for_drawing() { +void Enemy::prepare_for_first_drawing() { m_rectangle.setPosition(m_ptr_room->get_position()); } + +void Enemy::prepare_for_drawing() { + m_estimated_position = m_ptr_room->get_position(); + m_is_moving = true; + action(); +} diff --git a/source/States/GameState/Entities/DynamicEntities/DynamicEntities.h b/source/States/GameState/Entities/DynamicEntities/DynamicEntities.h index d0f1d6e..ceb115f 100644 --- a/source/States/GameState/Entities/DynamicEntities/DynamicEntities.h +++ b/source/States/GameState/Entities/DynamicEntities/DynamicEntities.h @@ -15,12 +15,17 @@ public: [[nodiscard]] std::unique_ptr<IDynamicEntity> clone() const override; void action() override; void draw_into(sf::RenderWindow& window) const override; + void prepare_for_first_drawing() override; void prepare_for_drawing() override; + sf::FloatRect getBounds() const { return m_rectangle.getGlobalBounds(); } std::unique_ptr<IGameEvent> accept(IVisitor* ptr_visitor) override { return ptr_visitor->visit(this); } private: sf::RectangleShape m_rectangle; + sf::Vector2f m_estimated_position; + bool m_is_moving = false; + float m_distance_limit; sf::Clock m_stopwatch; std::mt19937 m_rng{std::random_device{}()}; - std::uniform_int_distribution<size_t> m_dist_milliseconds{0, 9999}; + std::uniform_int_distribution<size_t> m_dist_milliseconds{300, 10000}; std::uniform_int_distribution<> m_dist_direction{0, 3}; }; diff --git a/source/States/GameState/Entities/IEntity.h b/source/States/GameState/Entities/IEntity.h index 6b1e5b2..0188517 100644 --- a/source/States/GameState/Entities/IEntity.h +++ b/source/States/GameState/Entities/IEntity.h @@ -4,7 +4,12 @@ class IEntity : public IPreparable { public: - void set_location(Room* ptr_room) noexcept { + virtual void prepare_for_first_drawing() = 0; + void set_initial_location(Room* ptr_room) { + m_ptr_room = ptr_room; + prepare_for_first_drawing(); + } + void set_location(Room* ptr_room) { m_ptr_room = ptr_room; prepare_for_drawing(); } diff --git a/source/States/GameState/Entities/Pacman/Pacman.cpp b/source/States/GameState/Entities/Pacman/Pacman.cpp index c7b6d5f..5a4fad2 100644 --- a/source/States/GameState/Entities/Pacman/Pacman.cpp +++ b/source/States/GameState/Entities/Pacman/Pacman.cpp @@ -6,28 +6,61 @@ Pacman::Pacman() : m_circle{ config::GAME_PACMAN_SIZE/2 } { m_circle.setFillColor(config::GAME_COLOR_PACMAN); m_circle.setOrigin(config::GAME_PACMAN_SIZE/2, config::GAME_PACMAN_SIZE/2); + m_distance_limit = config::PACMAN_SPEED/config::ROOM_SIZE; } -void Pacman::move(const Room::Direction direction) { +void Pacman::replace(const Room::Direction direction) { + if (m_is_moving) return; m_ptr_room->get_side(direction)->enter(this); } +void Pacman::move() { + const float frame_duration = m_stopwatch.getElapsedTime().asSeconds(); + m_stopwatch.restart(); + if (!m_is_moving) return; + const sf::Vector2f cur_pos = m_circle.getPosition(); + const sf::Vector2f displacement = m_estimated_position - cur_pos; + const float distance = std::sqrt(displacement.x * displacement.x + displacement.y * displacement.y); + if (distance < m_distance_limit) { + m_circle.setPosition(m_estimated_position); + m_is_moving = false; + return; + } + const sf::Vector2f direction = config::PACMAN_SPEED * frame_duration * displacement / distance; + float delta_x, delta_y; + if (direction.x > 0) + delta_x = std::min(direction.x, displacement.x); + else + delta_x = std::max(direction.x, displacement.x); + if (direction.y > 0) + delta_y = std::min(direction.y, displacement.y); + else + delta_y = std::max(direction.y, displacement.y); + m_circle.move(delta_x, delta_y); +} + void Pacman::draw_into(sf::RenderWindow& window) const { window.draw(m_circle); } -void Pacman::prepare_for_drawing() { +void Pacman::prepare_for_first_drawing() { m_circle.setPosition(m_ptr_room->get_position()); } +void Pacman::prepare_for_drawing() { + m_estimated_position = m_ptr_room->get_position(); + m_is_moving = true; + move(); +} + std::unique_ptr<IGameEvent> Pacman::visit(Food* ptr_food) { - if (ptr_food->get_location() != this->get_location()) + if (!this->getBounds().intersects(ptr_food->getBounds())) return {}; return std::make_unique<DeleteStaticEntity>(ptr_food); } std::unique_ptr<IGameEvent> Pacman::visit(Enemy* ptr_enemy) { - if (ptr_enemy->get_location() != this->get_location()) + if (!this->getBounds().intersects(ptr_enemy->getBounds())) return {}; return std::make_unique<LostGame>(); } diff --git a/source/States/GameState/Entities/Pacman/Pacman.h b/source/States/GameState/Entities/Pacman/Pacman.h index c838384..2bd8a83 100644 --- a/source/States/GameState/Entities/Pacman/Pacman.h +++ b/source/States/GameState/Entities/Pacman/Pacman.h @@ -5,11 +5,18 @@ class Pacman final : public IEntity, public IVisitor { public: Pacman(); - void move(Room::Direction direction); + void replace(Room::Direction direction); + void move(); void draw_into(sf::RenderWindow& window) const override; + void prepare_for_first_drawing() override; void prepare_for_drawing() override; + sf::FloatRect getBounds() const { return m_circle.getGlobalBounds(); } std::unique_ptr<IGameEvent> visit(Food* ptr_food) override; std::unique_ptr<IGameEvent> visit(Enemy* ptr_enemy) override; private: sf::CircleShape m_circle; + sf::Vector2f m_estimated_position; + float m_distance_limit; + bool m_is_moving = false; + sf::Clock m_stopwatch; }; \ No newline at end of file diff --git a/source/States/GameState/Entities/StaticEntities/StaticEntities.cpp b/source/States/GameState/Entities/StaticEntities/StaticEntities.cpp index f3e755e..bf11f5c 100644 --- a/source/States/GameState/Entities/StaticEntities/StaticEntities.cpp +++ b/source/States/GameState/Entities/StaticEntities/StaticEntities.cpp @@ -14,6 +14,10 @@ void Food::draw_into(sf::RenderWindow& window) const { window.draw(m_circle); } +void Food::prepare_for_first_drawing() { + prepare_for_drawing(); +} + void Food::prepare_for_drawing() { m_circle.setPosition(m_ptr_room->get_position()); } diff --git a/source/States/GameState/Entities/StaticEntities/StaticEntities.h b/source/States/GameState/Entities/StaticEntities/StaticEntities.h index cf09a19..4c8bcfb 100644 --- a/source/States/GameState/Entities/StaticEntities/StaticEntities.h +++ b/source/States/GameState/Entities/StaticEntities/StaticEntities.h @@ -12,7 +12,9 @@ public: Food(); [[nodiscard]] std::unique_ptr<IStaticEntity> clone() const override; void draw_into(sf::RenderWindow& window) const override; + void prepare_for_first_drawing() override; void prepare_for_drawing() override; + sf::FloatRect getBounds() const { return m_circle.getGlobalBounds(); } std::unique_ptr<IGameEvent> accept(IVisitor* ptr_visitor) override { return ptr_visitor->visit(this); } private: sf::CircleShape m_circle; diff --git a/source/States/GameState/GameBuilder/GameBuilder.cpp b/source/States/GameState/GameBuilder/GameBuilder.cpp index d572ca0..26ab0f6 100644 --- a/source/States/GameState/GameBuilder/GameBuilder.cpp +++ b/source/States/GameState/GameBuilder/GameBuilder.cpp @@ -18,22 +18,30 @@ void CommonBuilder::create_context(const float dynamic_objets_ratio) { std::uniform_int_distribution distrib(0 , static_cast<int>(buf_rooms.size()-1)); auto pos = buf_rooms.begin() + distrib(gen); - m_context.pacman.set_location((*pos)->get()); + const sf::Vector2f pacman_pos = (*pos)->get()->get_position(); + m_context.pacman.set_initial_location((*pos)->get()); buf_rooms.erase(pos); if (dynamic_objets_ratio > 1) throw std::invalid_argument("GAME_ENEMY_RATIO should be <= 1"); const auto number_of_dynamic_entities = static_cast<size_t>(static_cast<float>(buf_rooms.size()) * dynamic_objets_ratio); for (size_t i = 0; i < number_of_dynamic_entities; ++i) { - m_context.dynamic_objects.emplace_back(std::move(std::make_unique<Enemy>())); distrib.param(std::uniform_int_distribution<>::param_type(0, static_cast<int>(buf_rooms.size()) - 1)); pos = buf_rooms.begin() + distrib(gen); - m_context.dynamic_objects.back()->set_location((*pos)->get()); + if (float pos_x = (*pos)->get()->get_position().x, pos_y = (*pos)->get()->get_position().y; + std::sqrt((pos_x-pacman_pos.x) * (pos_x-pacman_pos.x) + (pos_y-pacman_pos.y) * (pos_y-pacman_pos.y)) <= m_room_size * 2) { + m_context.static_objects.emplace_back(std::move(std::make_unique<Food>())); + m_context.static_objects.back()->set_initial_location((*pos)->get()); + } + else { + m_context.dynamic_objects.emplace_back(std::move(std::make_unique<Enemy>())); + m_context.dynamic_objects.back()->set_initial_location((*pos)->get()); + } buf_rooms.erase(pos); } for (const auto& it_room : buf_rooms) { m_context.static_objects.emplace_back(std::move(std::make_unique<Food>())); - m_context.static_objects.back()->set_location(it_room->get()); + m_context.static_objects.back()->set_initial_location(it_room->get()); } } diff --git a/source/States/GameState/GameState.cpp b/source/States/GameState/GameState.cpp index ae6fa45..601d867 100644 --- a/source/States/GameState/GameState.cpp +++ b/source/States/GameState/GameState.cpp @@ -1,3 +1,4 @@ +#include <BasicAbstractions/Font.h> #include <States/GameState/GameState.h> #include <States/SelectState/SelectState.h> @@ -34,27 +35,51 @@ void GameState::update() { events.emplace_back(std::make_unique<WinGame>()); } + m_context_manager.get_current_context().pacman.move(); + for (const auto& event : events) event->handle(m_context_manager.get_current_context()); } void GameState::render() { + sf::Text text; + sf::RectangleShape rect; switch (m_context_manager.get_current_context().state) { case GameContext::INGAME: m_window.clear(config::GAME_COLOR_BACKGROUND_INGAME); break; + case GameContext::LOST: + text.setFont(MyFont::Instance()); + text.setFillColor({255, 0, 0, 220}); + text.setString("LOST!"); + text.setCharacterSize(300); + text.setStyle(sf::Text::Bold); + text.setPosition( + round((m_window.getSize().x - text.getLocalBounds().width) / 2 - text.getLocalBounds().left), + round((m_window.getSize().y - text.getLocalBounds().height) / 2 - text.getLocalBounds().top) + ); + rect.setSize(sf::Vector2f(text.getLocalBounds().width + 100, text.getLocalBounds().height + 100)); + rect.setFillColor(sf::Color(240, 0, 0, 50)); + rect.setOutlineThickness(5); + rect.setOutlineColor(sf::Color::White); + rect.setPosition( + round((m_window.getSize().x - rect.getSize().x) / 2), + round((m_window.getSize().y - rect.getSize().y) / 2) + ); + m_window.clear(config::GAME_COLOR_BACKGROUND_INGAME); + break; case GameContext::WIN: m_window.clear(config::GAME_COLOR_BACKGROUND_WIN); break; - case GameContext::LOST: - m_window.clear(config::GAME_COLOR_BACKGROUND_LOST); } m_maze.draw_into(m_window); for (const auto& obj : m_context_manager.get_current_context().static_objects) obj->draw_into(m_window); + m_context_manager.get_current_context().pacman.draw_into(m_window); for (const auto& obj : m_context_manager.get_current_context().dynamic_objects) obj->draw_into(m_window); - m_context_manager.get_current_context().pacman.draw_into(m_window); + m_window.draw(rect); + m_window.draw(text); m_window.display(); } @@ -62,19 +87,19 @@ void GameState::process_key_pressed(const sf::Keyboard::Key key) { switch (key) { case config::KEY_UP: m_context_manager.save_context(); - m_context_manager.get_current_context().pacman.move(Room::UP); + m_context_manager.get_current_context().pacman.replace(Room::UP); break; case config::KEY_DOWN: m_context_manager.save_context(); - m_context_manager.get_current_context().pacman.move(Room::DOWN); + m_context_manager.get_current_context().pacman.replace(Room::DOWN); break; case config::KEY_LEFT: m_context_manager.save_context(); - m_context_manager.get_current_context().pacman.move(Room::LEFT); + m_context_manager.get_current_context().pacman.replace(Room::LEFT); break; case config::KEY_RIGHT: m_context_manager.save_context(); - m_context_manager.get_current_context().pacman.move(Room::RIGHT); + m_context_manager.get_current_context().pacman.replace(Room::RIGHT); break; default: break; } diff --git a/source/States/GameState/GameState.h b/source/States/GameState/GameState.h index 1a5df43..83cdfbf 100644 --- a/source/States/GameState/GameState.h +++ b/source/States/GameState/GameState.h @@ -10,6 +10,7 @@ public: explicit GameState(IStateManager& state_manager, const sf::VideoMode& video_mode, const sf::String& window_title) : IState(state_manager), IWindowKeeper(video_mode, window_title) { m_window.setFramerateLimit(config::FRAME_RATE_LIMIT); + m_window.setKeyRepeatEnabled(false); } bool do_step() override; void set_maze(Maze&& maze) { m_maze = std::move(maze); } diff --git a/source/States/GameState/Maze/Room/Room.cpp b/source/States/GameState/Maze/Room/Room.cpp index d1a7cc8..da320fe 100644 --- a/source/States/GameState/Maze/Room/Room.cpp +++ b/source/States/GameState/Maze/Room/Room.cpp @@ -6,7 +6,7 @@ Room::Room(float size) : m_rectangle({size, size}) { void Room::set_side(const Direction side, const std::shared_ptr<IRoomSide>& ptr_side) { if (side == INVALID) throw std::invalid_argument("Invalid direction"); - m_sides[side] = std::move(ptr_side); + m_sides[side] = ptr_side; m_sides[side]->prepare_for_drawing(); } diff --git a/source/States/SelectState/SelectState.cpp b/source/States/SelectState/SelectState.cpp index c86ff97..cd5092b 100644 --- a/source/States/SelectState/SelectState.cpp +++ b/source/States/SelectState/SelectState.cpp @@ -36,7 +36,7 @@ Menu::Menu(IStateManager& state_manager) { std::make_unique<GameBuilderDirector>( std::make_unique<ComplexRandomBuilder>(config::GAME_VIDEO_MODE.width, config::GAME_VIDEO_MODE.height, config::ROOM_SIZE, config::GAME_COLOR_ROOM), config::GAME_VIDEO_MODE, - config::HARD_GAME_TITLE, + config::EXTREME_GAME_TITLE, config::HARD_GAME_ENEMY_RATIO)); m_buttons[0].set(sf::Vector2f{pos_left, pos_top}, config::BUTTON_SIZE, config::BUTTON_TEXT_EASY, -- GitLab