diff --git a/CMakeLists.txt b/CMakeLists.txt index 90a8521..aa74e70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,8 @@ set(UTILITIES Utilities/timer.cpp Utilities/route.cpp Utilities/wave.cpp - wave_manager.cpp) + wave_manager.cpp + Utilities/animation.cpp) add_executable(Game ${RESOURCES} @@ -49,6 +50,7 @@ add_executable(Game main_window.cpp Controller/controller.cpp game_view.cpp - constants.cpp game_scene.cpp game_scene.h) + constants.cpp + game_scene.cpp) target_link_libraries(Game Qt::Core Qt::Gui Qt::Widgets) \ No newline at end of file diff --git a/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.cpp b/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.cpp index 7b75d16..65d3a29 100644 --- a/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.cpp +++ b/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.cpp @@ -1,22 +1,29 @@ #include "mob.h" -#include + #include -std::vector - routes{Route({QPointF(50, 50), QPointF(150, 300), - QPointF(-100, -50)})}; // TODO(parfen01): move in level +std::vector routes{Route({ + QPointF(50, 50), + QPointF(300, 400), + QPointF(-100, -50)})}; // TODO(parfen01): move in level Mob::Mob(const VectorF& coordinates, - QPixmap* pixmap, + Animation* animation, int health, qreal speed) - : Entity(coordinates, pixmap, health), + : Entity(coordinates, animation, health), speed_(speed) { route_ = &routes[0]; route_->AddEntity(this); Entity::setPos(route_->GetStart()); } +Mob::Mob(const VectorF& coordinates, + QPixmap* pixmap, + int health, + qreal speed) + : Mob(coordinates, new Animation(pixmap), health, speed) {} + qreal Mob::GetSpeed() const { return speed_; } diff --git a/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h b/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h index df39368..9a13d04 100644 --- a/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h +++ b/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h @@ -12,6 +12,12 @@ class Mob : public Entity { QPixmap* pixmap, int health, qreal speed = 0); + + Mob(const VectorF& coordinates, + Animation* animation, + int health, + qreal speed = 0); + [[nodiscard]] qreal GetSpeed() const; void SetSpeed(qreal speed); diff --git a/GameObjects/BasicObjects/Entities/Mobs/test_mob.cpp b/GameObjects/BasicObjects/Entities/Mobs/test_mob.cpp index 70086f2..583ffc0 100644 --- a/GameObjects/BasicObjects/Entities/Mobs/test_mob.cpp +++ b/GameObjects/BasicObjects/Entities/Mobs/test_mob.cpp @@ -1,12 +1,20 @@ #include "test_mob.h" -#include - #include "Utilities/Resources/pixmap_loader.h" #include "constants.h" void TestMob::Tick(Time delta) { - if (!Mob::route_->isEnd(this)) { + Mob::Tick(delta); + if (is_creating_ && animation_->WasEndedDuringPreviousUpdate()) { + is_creating_ = false; + animation_ = idle_animation_; + } + if (is_destroying_ && animation_->WasEndedDuringPreviousUpdate()) { + animation_->SetIndex(animation_->FrameCount() - 1); + deleteLater(); + } + + if (!is_creating_ && !Mob::route_->isEnd(this)) { Mob::route_->Move(this, Mob::speed_ * delta.seconds()); } } @@ -23,26 +31,49 @@ void TestMob::keyPressEvent(QKeyEvent* event) { setPos(pos() + velocity_vector); } else if (event->key() == Qt::Key::Key_Down) { setPos(pos() - velocity_vector); + } else if (event->key() == Qt::Key::Key_Space && !is_destroying_) { + health_ = 0; + is_destroying_ = true; + disappearing_animation_->Reset(); + animation_ = disappearing_animation_; } } void TestMob::mousePressEvent(QGraphicsSceneMouseEvent* event) { - scene()->addItem(new TestMob(pos() + VectorF{10, 30})); + scene()->addItem(new TestMob(pos() + VectorF{100, 100})); } TestMob::TestMob(const VectorF& coordinates) : Mob( - coordinates, - PixmapLoader::Pixmaps::kTestMob, - Entities::TestMob::kHealth) { + coordinates, + // TODO(jansenin): Было бы лучше инициализировать анимации отдельно + // от моба + new Animation(PixmapLoader::Pixmaps::kFireTotemAppearing, 50_ms), + Entities::TestMob::kHealth, + 100), + is_destroying_(false), + idle_animation_( + new Animation(PixmapLoader::Pixmaps::kFireTotemIdle, 50_ms)), + disappearing_animation_( + new Animation(PixmapLoader::Pixmaps::kFireTotemDisappear, 50_ms)), + appearing_animation_(animation_), + is_creating_(true) { setFlag(QGraphicsItem::ItemIsFocusable, true); + setScale(1.4); } -void TestMob::paint(QPainter* painter, - const QStyleOptionGraphicsItem* option, - QWidget* widget) { - Entity::paint(painter, option, widget); - if (health_ == 0) { - painter->drawLine(-50, -50, 50, 50); - painter->drawLine(50, -50, -50, 50); + +TestMob::~TestMob() { + delete idle_animation_; + delete disappearing_animation_; + delete appearing_animation_; +} + +void TestMob::ApplyDamage(Damage damage) { + Damageable::ApplyDamage(damage); + + if (health_ <= 0 && !is_destroying_) { + disappearing_animation_->Reset(); + is_destroying_ = true; + animation_ = disappearing_animation_; } } diff --git a/GameObjects/BasicObjects/Entities/Mobs/test_mob.h b/GameObjects/BasicObjects/Entities/Mobs/test_mob.h index 3f483bf..2cec260 100644 --- a/GameObjects/BasicObjects/Entities/Mobs/test_mob.h +++ b/GameObjects/BasicObjects/Entities/Mobs/test_mob.h @@ -10,12 +10,17 @@ class TestMob : public Mob { explicit TestMob(const VectorF& coordinates = VectorF{0, 0}); void Tick(Time delta) override; + void ApplyDamage(Damage damage) override; - void paint(QPainter* painter, - const QStyleOptionGraphicsItem* option, - QWidget* widget) override; + ~TestMob() override; protected: void keyPressEvent(QKeyEvent* event) override; void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + bool is_destroying_; + bool is_creating_; + Animation* idle_animation_; + Animation* disappearing_animation_; + Animation* appearing_animation_; }; diff --git a/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.cpp b/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.cpp index fff2f2a..0788072 100644 --- a/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.cpp +++ b/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.cpp @@ -1,6 +1,20 @@ #include #include "autoguided_projectile.h" +#include "GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h" + +AutoguidedProjectile::AutoguidedProjectile( + const VectorF& coordinates, + Animation* animation, + Entity* target, + qreal speed, + Damage damage) + : Projectile(coordinates, animation), + target_(target), speed_(speed), damage_(damage) { + connect(target_, &Entity::destroyed, this, + &AutoguidedProjectile::FindNewTargetOrDie); +} + AutoguidedProjectile::AutoguidedProjectile( const VectorF& coordinates, @@ -8,10 +22,21 @@ AutoguidedProjectile::AutoguidedProjectile( Entity* target, qreal speed, Damage damage) - : Projectile(coordinates, pixmap), - target_(target), speed_(speed), damage_(damage) {} + : AutoguidedProjectile( + coordinates, new Animation(pixmap), target, speed, damage) {} void AutoguidedProjectile::Tick(Time delta) { + Projectile::Tick(delta); + if (target_ == nullptr) { + return; + } + if (target_->GetHealth() <= 0) { + FindNewTargetOrDie(); + } + if (target_ == nullptr) { + return; + } + Move(delta); if (target_->collidesWithItem(this)) { @@ -21,9 +46,49 @@ void AutoguidedProjectile::Tick(Time delta) { } void AutoguidedProjectile::Move(Time delta) { + if (target_ == nullptr) { + return; + } VectorF target_point = target_->scenePos(); VectorF delta_pos = target_point - scenePos(); VectorF velocity = delta_pos.normalized() * speed_; MoveBy(velocity * delta.seconds()); } + +void AutoguidedProjectile::FindNewTargetOrDie() { + if (scene()->Mobs().empty()) { + SetTarget(nullptr); + deleteLater(); + } else { + auto old_target = target_; + for (auto new_target : scene()->Mobs()) { + if (new_target != target_ && + VectorF(new_target->scenePos() - scenePos()).length() < 100) { + SetTarget(new_target); + break; + } + } + if (old_target == target_) { + SetTarget(nullptr); + deleteLater(); + } + } +} + +void AutoguidedProjectile::SetTarget(Entity* target) { + // TODO(jansenin): what if target_ is destroyed? + disconnect( + target_, + &Entity::destroyed, + this, + &AutoguidedProjectile::FindNewTargetOrDie); + target_ = target; + if (target_ != nullptr) { + connect( + target_, + &Entity::destroyed, + this, + &AutoguidedProjectile::FindNewTargetOrDie); + } +} diff --git a/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.h b/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.h index 96b23dd..75c2b9a 100644 --- a/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.h +++ b/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.h @@ -8,10 +8,16 @@ class AutoguidedProjectile : public Projectile { QPixmap* pixmap, Entity* target, qreal speed, Damage damage); + AutoguidedProjectile(const VectorF& coordinates, + Animation* animation, + Entity* target, qreal speed, Damage damage); + void SetTarget(Entity* target); + void Tick(Time delta) override; protected: void Move(Time delta); + void FindNewTargetOrDie(); Entity* target_; qreal speed_; diff --git a/GameObjects/BasicObjects/Entities/Projectiles/projectile.cpp b/GameObjects/BasicObjects/Entities/Projectiles/projectile.cpp index d4bd53b..6e3aa13 100644 --- a/GameObjects/BasicObjects/Entities/Projectiles/projectile.cpp +++ b/GameObjects/BasicObjects/Entities/Projectiles/projectile.cpp @@ -1,4 +1,7 @@ #include "projectile.h" Projectile::Projectile(const VectorF& coordinates, QPixmap* pixmap) - : Entity(coordinates, pixmap) {} + : Projectile(coordinates, new Animation(pixmap)) {} + +Projectile::Projectile(const VectorF& coordinates, Animation* animation) + : Entity(coordinates, animation) {} diff --git a/GameObjects/BasicObjects/Entities/Projectiles/projectile.h b/GameObjects/BasicObjects/Entities/Projectiles/projectile.h index e0299e5..64e1870 100644 --- a/GameObjects/BasicObjects/Entities/Projectiles/projectile.h +++ b/GameObjects/BasicObjects/Entities/Projectiles/projectile.h @@ -5,6 +5,7 @@ class Projectile : public Entity { public: Projectile(const VectorF& coordinates, QPixmap* pixmap); + Projectile(const VectorF& coordinates, Animation* animation); }; diff --git a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.cpp b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.cpp index 2706df1..621fea4 100644 --- a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.cpp +++ b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.cpp @@ -1,7 +1,5 @@ #include "test_tower_slot.h" -#include - #include "GameObjects/BasicObjects/Entities/Towers/test_tower.h" #include diff --git a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.cpp b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.cpp index 4169abb..5f158a8 100644 --- a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.cpp +++ b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.cpp @@ -13,9 +13,14 @@ void TowerSlot::ClearArea() { } TowerSlot::TowerSlot(const VectorF& coordinates, QPixmap* pixmap) - : Entity(coordinates, pixmap), tower_(nullptr) {} + : TowerSlot(coordinates, new Animation(pixmap)) {} + +TowerSlot::TowerSlot(const VectorF& coordinates, Animation* animation) + : Entity(coordinates, animation), tower_(nullptr) {} void TowerSlot::Tick(Time time) { + Entity::Tick(time); + if (tower_ != nullptr) { tower_->Tick(time); } diff --git a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.h b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.h index e6dff12..d281e4f 100644 --- a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.h +++ b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.h @@ -6,6 +6,8 @@ class TowerSlot : public Entity { public: TowerSlot(const VectorF& coordinates, QPixmap* pixmap); + TowerSlot(const VectorF& coordinates, Animation* animation); + [[nodiscard]] bool IsTakenUp() const; void TakeUpArea(Tower* tower); void ClearArea(); diff --git a/GameObjects/BasicObjects/Entities/Towers/test_tower.cpp b/GameObjects/BasicObjects/Entities/Towers/test_tower.cpp index 5865452..df287c7 100644 --- a/GameObjects/BasicObjects/Entities/Towers/test_tower.cpp +++ b/GameObjects/BasicObjects/Entities/Towers/test_tower.cpp @@ -1,6 +1,5 @@ #include "test_tower.h" -#include #include #include "GameObjects/BasicObjects/Entities/Projectiles/test_projectile.h" @@ -30,6 +29,8 @@ TestTower::TestTower(const VectorF& coordinates) } void TestTower::Tick(Time delta) { + Tower::Tick(delta); + attack_timer_.Tick(delta); if (attack_timer_.IsExpired()) { @@ -37,6 +38,9 @@ void TestTower::Tick(Time delta) { scene()->items(scene_attack_area_); for (QGraphicsItem* item : items_in_attack_area) { if (Mob* mob = dynamic_cast(item)) { + if (mob->GetHealth() <= 0) { + continue; + } scene()->addItem(new TestProjectile(scenePos(), mob)); attack_timer_.Start(Entities::TestTower::kAttackCooldown); break; diff --git a/GameObjects/BasicObjects/Entities/Towers/tower.cpp b/GameObjects/BasicObjects/Entities/Towers/tower.cpp index a5269ef..f92c0af 100644 --- a/GameObjects/BasicObjects/Entities/Towers/tower.cpp +++ b/GameObjects/BasicObjects/Entities/Towers/tower.cpp @@ -1,4 +1,7 @@ #include "tower.h" Tower::Tower(const VectorF& coordinates, QPixmap* pixmap, int health) - : Entity(coordinates, pixmap, health) {} + : Tower(coordinates, new Animation(pixmap), health) {} + +Tower::Tower(const VectorF& coordinates, Animation* animation, int health) + : Entity(coordinates, animation, health) {} diff --git a/GameObjects/BasicObjects/Entities/Towers/tower.h b/GameObjects/BasicObjects/Entities/Towers/tower.h index 81f2dd2..4a41492 100644 --- a/GameObjects/BasicObjects/Entities/Towers/tower.h +++ b/GameObjects/BasicObjects/Entities/Towers/tower.h @@ -5,4 +5,5 @@ class Tower : public Entity { public: Tower(const VectorF& coordinates, QPixmap* pixmap, int health = 0); + Tower(const VectorF& coordinates, Animation* animation, int health = 0); }; diff --git a/GameObjects/BasicObjects/Interface/damageable.cpp b/GameObjects/BasicObjects/Interface/damageable.cpp index 739c492..ebfbd77 100644 --- a/GameObjects/BasicObjects/Interface/damageable.cpp +++ b/GameObjects/BasicObjects/Interface/damageable.cpp @@ -14,3 +14,7 @@ void Damageable::ApplyDamage(Damage damage) { void Damageable::SetHealth(int health) { health_ = health; } + +int Damageable::GetHealth() const { + return health_; +} diff --git a/GameObjects/BasicObjects/Interface/damageable.h b/GameObjects/BasicObjects/Interface/damageable.h index 53527b3..2b581d9 100644 --- a/GameObjects/BasicObjects/Interface/damageable.h +++ b/GameObjects/BasicObjects/Interface/damageable.h @@ -6,6 +6,7 @@ class Damageable { public: explicit Damageable(int health); virtual void ApplyDamage(Damage damage); + [[nodiscard]] int GetHealth() const; protected: int health_; diff --git a/GameObjects/BasicObjects/Interface/entity.cpp b/GameObjects/BasicObjects/Interface/entity.cpp index 11a3f85..2376443 100644 --- a/GameObjects/BasicObjects/Interface/entity.cpp +++ b/GameObjects/BasicObjects/Interface/entity.cpp @@ -1,25 +1,44 @@ #include "entity.h" +#include + Entity::Entity( const VectorF& coordinates, QPixmap* pixmap, int health) - : Damageable(health), GraphicsItem(), - pixmap(pixmap) { + : Entity(coordinates, new Animation(pixmap), health) {} + +Entity::Entity(const VectorF& coordinates, Animation* animation, int health) + : Damageable(health), + GraphicsItem(), + animation_(animation) { + animation->Frame(); setPos(coordinates); setFlag(ItemSendsGeometryChanges); } QRectF Entity::boundingRect() const { + const QPixmap* frame = animation_->Frame(); return QRectF( - pixmap->rect().translated( - QPoint{ -pixmap->width()/2, -pixmap->height()/2 })); + frame->rect().translated( + QPoint{ -frame->width()/2, -frame->height()/2 })); } void Entity::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { painter->save(); - painter->drawPixmap(Entity::boundingRect().toRect(), *pixmap); + static QPen pen(QColor(0, 0, 255, 50)); + painter->setPen(pen); + // painter->drawRect(boundingRect()); + painter->drawPixmap(Entity::boundingRect().toRect(), *animation_->Frame()); painter->restore(); } + +void Entity::Tick(Time delta) { + QPixmap* previous_frame = animation_->Frame(); + animation_->Tick(delta); + if (previous_frame != animation_->Frame()) { + update(); + } +} diff --git a/GameObjects/BasicObjects/Interface/entity.h b/GameObjects/BasicObjects/Interface/entity.h index e0fb9d6..d6a8bc5 100644 --- a/GameObjects/BasicObjects/Interface/entity.h +++ b/GameObjects/BasicObjects/Interface/entity.h @@ -11,6 +11,7 @@ #include "damageable.h" #include "Utilities/vector_f.h" #include "GameObjects/BasicObjects/Interface/graphics_item.h" +#include "Utilities/animation.h" class Entity : public QObject, @@ -24,11 +25,17 @@ class Entity QPixmap* pixmap, int health = 0); + Entity( + const VectorF& coordinates, + Animation* animation, + int health = 0); + [[nodiscard]] QRectF boundingRect() const override; void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + void Tick(Time delta) override; protected: - QPixmap* pixmap; + Animation* animation_; }; diff --git a/Resources/images/fire_totem.png b/Resources/images/fire_totem.png new file mode 100644 index 0000000..d0dc40a Binary files /dev/null and b/Resources/images/fire_totem.png differ diff --git a/Resources/images/fire_totem2.png b/Resources/images/fire_totem2.png new file mode 100644 index 0000000..014f8f2 Binary files /dev/null and b/Resources/images/fire_totem2.png differ diff --git a/Resources/resources.qrc b/Resources/resources.qrc index 40a92fc..4b777b9 100644 --- a/Resources/resources.qrc +++ b/Resources/resources.qrc @@ -6,5 +6,6 @@ images/test_tower.png images/test_tower_gun.png images/test_tower_slot.png + images/fire_totem.png \ No newline at end of file diff --git a/Utilities/Resources/pixmap_loader.cpp b/Utilities/Resources/pixmap_loader.cpp index 15c8207..50f393d 100644 --- a/Utilities/Resources/pixmap_loader.cpp +++ b/Utilities/Resources/pixmap_loader.cpp @@ -1,19 +1,90 @@ #include "pixmap_loader.h" -QPixmap* PixmapLoader::Pixmaps::kBackground; -QPixmap* PixmapLoader::Pixmaps::kTestBullet; -QPixmap* PixmapLoader::Pixmaps::kTestMob; -QPixmap* PixmapLoader::Pixmaps::kTestTower; -QPixmap* PixmapLoader::Pixmaps::kTestTowerGun; -QPixmap* PixmapLoader::Pixmaps::kTestTowerSlot; +#include -void PixmapLoader::LoadPixmaps() { - using P = PixmapLoader::Pixmaps; +using P = PixmapLoader::Pixmaps; + +QPixmap* P::kBackground; +QPixmap* P::kTestBullet; +QPixmap* P::kTestMob; +QPixmap* P::kTestTower; +QPixmap* P::kTestTowerGun; +QPixmap* P::kTestTowerSlot; +QPixmap* P::kFireTotemAnimations; +std::vector P::kFireTotemIdle; +std::vector P::kFireTotemDisappear; +std::vector P::kFireTotemAppearing; + +void PixmapLoader::LoadPixmaps() { P::kBackground = new QPixmap(":images/background.png"); P::kTestBullet = new QPixmap(":images/test_bullet.png"); P::kTestMob = new QPixmap(":images/test_mob.png"); P::kTestTower = new QPixmap(":images/test_tower.png"); P::kTestTowerGun = new QPixmap(":images/test_tower_gun.png"); P::kTestTowerSlot = new QPixmap(":images/test_tower_slot.png"); + + LoadFireTotemAnimations(); +} + +std::vector PixmapLoader::CreateHorizontalFramesVector( + QPixmap* source, + int frame_width, + int frame_height, + int frames_count, + int start_x, + int y) { + std::vector result; + + for (int i = 0 ; i < frames_count ; ++i) { + int x = i * frame_width + start_x; + QPixmap* frame = new QPixmap(std::move(source->copy( + QRect(x, y, frame_width, frame_height)))); + result.push_back(frame); + } + + return result; +} + +void PixmapLoader::LoadFireTotemAnimations() { + // file size - 896x480 + // 5 frame rows, 14 frame columns + const int frame_width = 896 / 14; + const int frame_height = 480 / 5; + const int idle_animation_frames_count = 7; + const int disappear_animation_frames_count = 14; + const int appear_animation_frames_count = 8; + // row and column start from 0 + const int idle_animation_row = 1; + const int idle_animation_column = 0; + const int disappear_animation_row = 4; + const int disappear_animation_column = 0; + const int appear_animation_row = 0; + const int appear_animation_column = 0; + + P::kFireTotemAnimations = new QPixmap(":images/fire_totem.png"); + + P::kFireTotemIdle = CreateHorizontalFramesVector( + P::kFireTotemAnimations, + frame_width, + frame_height, + idle_animation_frames_count, + idle_animation_column * frame_width, + idle_animation_row * frame_height); + + P::kFireTotemDisappear = CreateHorizontalFramesVector( + P::kFireTotemAnimations, + frame_width, + frame_height, + disappear_animation_frames_count, + disappear_animation_column * frame_width, + disappear_animation_row * frame_height); + + P::kFireTotemAppearing = CreateHorizontalFramesVector( + P::kFireTotemAnimations, + frame_width, + frame_height, + appear_animation_frames_count, + appear_animation_column * frame_width, + appear_animation_row * frame_height); } diff --git a/Utilities/Resources/pixmap_loader.h b/Utilities/Resources/pixmap_loader.h index d1f3a8d..dd8eb75 100644 --- a/Utilities/Resources/pixmap_loader.h +++ b/Utilities/Resources/pixmap_loader.h @@ -1,18 +1,40 @@ #pragma once +#include + #include +#include "Utilities/animation.h" + class PixmapLoader { public: class Pixmaps { public: + // TODO(jansenin): maybe make this readonly static QPixmap* kBackground; static QPixmap* kTestBullet; static QPixmap* kTestMob; static QPixmap* kTestTower; static QPixmap* kTestTowerGun; static QPixmap* kTestTowerSlot; + + // For animations test + static QPixmap* kFireTotemAnimations; + static std::vector kFireTotemIdle; + static std::vector kFireTotemDisappear; + static std::vector kFireTotemAppearing; }; static void LoadPixmaps(); + + private: + static std::vector CreateHorizontalFramesVector( + QPixmap* source, + int frame_width, + int frame_height, + int frames_count, + int start_x, + int y); + + static void LoadFireTotemAnimations(); }; diff --git a/Utilities/animation.cpp b/Utilities/animation.cpp new file mode 100644 index 0000000..55b7a4c --- /dev/null +++ b/Utilities/animation.cpp @@ -0,0 +1,82 @@ +#include "animation.h" + +#include + +Animation::Animation(std::vector frames, Time time_between_frames) + : frames_(std::move(frames)), + time_between_frames_(time_between_frames), + time_to_next_frame_(time_between_frames), + frame_index_(0), + was_ended_during_previous_update(false) { + assert(!frames_.empty()); +} + +Animation::Animation(QPixmap* pixmap) + // doesn't matter what time between frames is, it just must be non-zero + : Animation(std::vector{pixmap}, 1_ms) {} + +void Animation::Tick(Time delta) { + time_to_next_frame_ -= delta; + if (time_to_next_frame_ < 0_ms) { + int frames_passed = + abs(time_to_next_frame_.ms() / time_between_frames_.ms()); + ++frames_passed; + time_to_next_frame_ += time_between_frames_ * frames_passed; + bool animation_ended = false; + if (frame_index_ + frames_passed >= frames_.size()) { + animation_ended = true; + } + SetIndex(frame_index_ + frames_passed); + was_ended_during_previous_update = animation_ended; + } +} + +const QPixmap* Animation::Frame() const { + return frames_.at(frame_index_); +} + +QPixmap* Animation::Frame() { + return frames_.at(frame_index_); +} + +void Animation::SetIndex(int index) { + was_ended_during_previous_update = false; + if (index >= frames_.size()) { + was_ended_during_previous_update = true; + } + frame_index_ = ((index % frames_.size()) + frames_.size()) % frames_.size(); +} + +void Animation::SetTimeBetweenFrames(Time time) { + time_between_frames_ = time; +} + +Time Animation::TimeBetweenFrames() const { + return time_between_frames_; +} + +Time Animation::TimeToNextFrame() const { + return time_to_next_frame_; +} + +int Animation::FrameIndex() const { + return frame_index_; +} + +void Animation::NextFrame() { + SetIndex(frame_index_ + 1); +} + +void Animation::Reset() { + time_to_next_frame_ = time_between_frames_; + SetIndex(0); + was_ended_during_previous_update = false; +} + +bool Animation::WasEndedDuringPreviousUpdate() const { + return was_ended_during_previous_update; +} + +int Animation::FrameCount() const { + return frames_.size(); +} diff --git a/Utilities/animation.h b/Utilities/animation.h new file mode 100644 index 0000000..b4c624e --- /dev/null +++ b/Utilities/animation.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +#include "Utilities/time.h" +#include "GameObjects/BasicObjects/Interface/tickable.h" + +class Animation : public Tickable { + public: + Animation(std::vector frames, Time time_between_frames); + explicit Animation(QPixmap* pixmap); + + void Tick(Time delta) override; + QPixmap* Frame(); + [[nodiscard]] const QPixmap* Frame() const; + void SetIndex(int index); + void SetTimeBetweenFrames(Time time); + void NextFrame(); + void Reset(); + + [[nodiscard]] bool WasEndedDuringPreviousUpdate() const; + [[nodiscard]] int FrameCount() const; + [[nodiscard]] Time TimeBetweenFrames() const; + [[nodiscard]] Time TimeToNextFrame() const; + [[nodiscard]] int FrameIndex() const; + + protected: + std::vector frames_; + Time time_between_frames_; + Time time_to_next_frame_; + int frame_index_; + bool was_ended_during_previous_update; +}; diff --git a/Utilities/route.cpp b/Utilities/route.cpp index ec22f25..e0f491c 100644 --- a/Utilities/route.cpp +++ b/Utilities/route.cpp @@ -1,4 +1,5 @@ #include "route.h" + #include bool Route::isEnd(Entity* entity) { diff --git a/Utilities/time.cpp b/Utilities/time.cpp index 513c71f..d0c7b9a 100644 --- a/Utilities/time.cpp +++ b/Utilities/time.cpp @@ -48,3 +48,20 @@ Time& Time::operator-() { ms_ = -ms_; return *this; } + +Time Time::operator*(int rhs) const { + return Time(ms() * rhs); +} + +Time& Time::operator*=(int rhs) { + ms_ *= rhs; + return *this; +} + +Time operator*(int lhs, const Time& rhs) { + return rhs * lhs; +} + +Time operator "" _ms(unsigned long long int ms) { // NOLINT + return Time(ms); +} diff --git a/Utilities/time.h b/Utilities/time.h index bbc0a9e..dd2ddec 100644 --- a/Utilities/time.h +++ b/Utilities/time.h @@ -17,12 +17,24 @@ class Time { Time operator+(const Time& rhs) const; // there is no check for negative time Time operator-(const Time& rhs) const; + Time operator*(int rhs) const; Time& operator+=(const Time& rhs); Time& operator-=(const Time& rhs); + Time& operator*=(int rhs); + friend Time operator*(int lhs, const Time& rhs); Time& operator-(); private: int ms_; }; + +Time operator*(int lhs, const Time& rhs); + +// cpplint говорит, что unsigned long long int(Тип из си) нужно поменять на +// int16/int32/int64...(тип из C++), но в документации к пользовательским +// литералам сказано, что в них могут использоваться только определённые +// типы(и записи unsigned long long int в C++ я не нашёл, а это единственный +// целочисленный тип, который можно использовать для литерала) +Time operator "" _ms(unsigned long long int ms); // NOLINT diff --git a/constants.cpp b/constants.cpp index 844d05d..f8b336c 100644 --- a/constants.cpp +++ b/constants.cpp @@ -12,8 +12,8 @@ namespace Entities { const int kCircleAttackAreaApproximationPointsCount = 10; namespace TestTower { -const qreal kAttackRange = 200; -const Time kAttackCooldown = Time(1000); +const qreal kAttackRange = 300; +const Time kAttackCooldown = Time(100); } namespace TestMob { @@ -24,7 +24,7 @@ const int kHealth = 30; } namespace TestProjectile { -extern const Damage kDamage = Damage(10); -extern const qreal kSpeed = 100; +extern const Damage kDamage = Damage(3); +extern const qreal kSpeed = 500; } } // namespace Entities diff --git a/game_scene.cpp b/game_scene.cpp index 5d28dcd..a17305b 100644 --- a/game_scene.cpp +++ b/game_scene.cpp @@ -8,11 +8,7 @@ #include "GameObjects/BasicObjects/Entities/Projectiles/projectile.h" GameScene::GameScene(const QRectF& scene_rect, QObject* parent) - : QGraphicsScene(scene_rect, parent), - mobs_(std::set()), - towers_(std::set()), - tower_slots_(std::set()), - projectiles_(std::set()) {} + : QGraphicsScene(scene_rect, parent) {} GameView* GameScene::view() { auto result = dynamic_cast(QGraphicsScene::views().at(0)); @@ -20,75 +16,43 @@ GameView* GameScene::view() { return result; } -void GameScene::removeItem(GraphicsItem* item) { - auto mob = dynamic_cast(item); - if (mob != nullptr) { - mobs_.erase(mob); +std::vector GameScene::Mobs() const { + std::vector result; + for (auto item : items()) { + if (auto mob = dynamic_cast(item)) { + result.push_back(mob); + } } - - auto tower = dynamic_cast(item); - if (tower != nullptr) { - towers_.erase(tower); - } - - auto tower_slot = dynamic_cast(item); - if (tower_slot != nullptr) { - tower_slots_.erase(tower_slot); - } - - auto projectile = dynamic_cast(item); - if (projectile != nullptr) { - projectiles_.erase(projectile); - } - - QGraphicsScene::removeItem(item); -} - -void GameScene::clear() { - mobs_.clear(); - towers_.clear(); - tower_slots_.clear(); - projectiles_.clear(); - - QGraphicsScene::clear(); + return result; } -void GameScene::addItem(GraphicsItem* item) { - auto mob = dynamic_cast(item); - if (mob != nullptr) { - mobs_.erase(mob); - } - - auto tower = dynamic_cast(item); - if (tower != nullptr) { - towers_.erase(tower); +std::vector GameScene::Towers() const { + std::vector result; + for (auto item : items()) { + if (auto tower = dynamic_cast(item)) { + result.push_back(tower); + } } - - auto tower_slot = dynamic_cast(item); - if (tower_slot != nullptr) { - tower_slots_.erase(tower_slot); - } - - auto projectile = dynamic_cast(item); - if (projectile != nullptr) { - projectiles_.erase(projectile); - } - - QGraphicsScene::addItem(item); -} - -const std::set& GameScene::Mobs() const { - return mobs_; + return result; } -const std::set& GameScene::Towers() const { - return towers_; +std::vector GameScene::TowerSlots() const { + std::vector result; + for (auto item : items()) { + if (auto tower_slot = dynamic_cast(item)) { + result.push_back(tower_slot); + } + } + return result; } -const std::set& GameScene::TowerSlots() const { - return tower_slots_; +std::vector GameScene::Projectiles() const { + std::vector result; + for (auto item : items()) { + if (auto projectile = dynamic_cast(item)) { + result.push_back(projectile); + } + } + return result; } -const std::set& GameScene::Projectiles() const { - return projectiles_; -} diff --git a/game_scene.h b/game_scene.h index 28cbbd4..55adcde 100644 --- a/game_scene.h +++ b/game_scene.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -11,6 +11,7 @@ class Mob; class Tower; class TowerSlot; class Projectile; +class Entity; class GameScene : public QGraphicsScene { public: @@ -18,18 +19,8 @@ class GameScene : public QGraphicsScene { GameView* view(); - void removeItem(GraphicsItem* item); - void clear(); - void addItem(GraphicsItem* item); - - [[nodiscard]] const std::set& Mobs() const; - [[nodiscard]] const std::set& Towers() const; - [[nodiscard]] const std::set& TowerSlots() const; - [[nodiscard]] const std::set& Projectiles() const; - - private: - std::set mobs_; - std::set towers_; - std::set tower_slots_; - std::set projectiles_; + [[nodiscard]] std::vector Mobs() const; + [[nodiscard]] std::vector Towers() const; + [[nodiscard]] std::vector TowerSlots() const; + [[nodiscard]] std::vector Projectiles() const; };