diff --git a/.gitignore b/.gitignore index f147edf..170b6f4 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,7 @@ compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* + + + +/.idea/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c6fcf02 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.20) +project(Game) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 COMPONENTS + Core + Gui + Widgets + REQUIRED) + +set(RESOURCES + Resources/resources.qrc) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(GAME_OBJECTS + GameObjects/BasicObjects/Interface/damageable.cpp + GameObjects/BasicObjects/Interface/entity.cpp + GameObjects/BasicObjects/Entities/Mobs/Basis/mob.cpp + GameObjects/BasicObjects/Entities/Mobs/test_mob.cpp + GameObjects/BasicObjects/Entities/Towers/tower.cpp + GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.cpp + GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.cpp + GameObjects/BasicObjects/Entities/Towers/test_tower.cpp + GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.cpp + GameObjects/BasicObjects/Entities/Projectiles/test_projectile.cpp) + +set(UTILITIES + Utilities/damage.cpp + Utilities/time.cpp + Utilities/vector_f.cpp + Utilities/Resources/pixmap_loader.cpp + Utilities/timer.cpp) + +add_executable(Game + ${RESOURCES} + ${GAME_OBJECTS} + ${UTILITIES} + main.cpp + main_window.cpp + Controller/controller.cpp game_view.cpp game_view.h constants.cpp) + +target_link_libraries(Game Qt::Core Qt::Gui Qt::Widgets) \ No newline at end of file diff --git a/Controller/controller.cpp b/Controller/controller.cpp new file mode 100644 index 0000000..e8b5b76 --- /dev/null +++ b/Controller/controller.cpp @@ -0,0 +1,94 @@ +#include "Controller/controller.h" + +#include +#include +#include +#include + +#include "GameObjects/BasicObjects/Entities/Mobs/test_mob.h" +#include "GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.h" +#include "constants.h" + +Controller* Controller::instance; + +Controller::Controller() : + scene_(new QGraphicsScene(kSceneRect)), + view_(new GameView(scene_)), + tick_timer_(new QTimer(this)) { + SetupScene(); + LaunchTickTimer(); +} + +GameView* Controller::GetView() const { + return view_; +} + +void Controller::SetupScene() { + { // temporary code + QPushButton* close_button = new QPushButton(); + QGraphicsProxyWidget* close_button_proxy = scene_->addWidget(close_button); + close_button_proxy->setGeometry(QRectF( + scene_->sceneRect().topRight() - VectorF{100, 0}, + scene_->sceneRect().topRight() + VectorF{0, 100})); + + close_button->setText("Close"); + QObject::connect(close_button, &QPushButton::clicked, &QApplication::exit); + + Entity* entity = new TestMob(); + scene_->addItem(entity); + scene_->setFocusItem(entity); + + TestTowerSlot* test_tower_slot = new TestTowerSlot(VectorF{400, 400}); + scene_->addItem(test_tower_slot); + + QRectF sceneRect = scene_->sceneRect(); + qreal x = sceneRect.x(); + qreal y = sceneRect.y(); + qreal width = sceneRect.width(); + qreal height = sceneRect.height(); + + scene_->addLine( + x + width / 2, + y, + x + width / 2, + y + height, + QPen(Qt::blue)); + + scene_->addLine( + x, + y + 1, + x + width, + y + 1, + QPen(Qt::blue)); + + scene_->addLine( + x, + y + height / 2, + x + width, + y + height / 2, + QPen(Qt::blue)); + } // temporary code end +} + +void Controller::LaunchTickTimer() { + tick_timer_->setInterval(1000 / 30); + tick_timer_->start(); + connect(tick_timer_, &QTimer::timeout, this, &Controller::TickAllTickables); +} + +Controller* Controller::Instance() { + if (instance == nullptr) { + instance = new Controller(); + } + return instance; +} + +void Controller::TickAllTickables() { + for (QGraphicsItem* graphics_item : scene_->items()) { + if (Tickable* tickable = dynamic_cast(graphics_item)) { + // TODO(jansenin): make time dependency(it + // could have been more than 1000/30 ms) + tickable->Tick(Time(1000 / 30)); + } + } +} diff --git a/Controller/controller.h b/Controller/controller.h new file mode 100644 index 0000000..662a639 --- /dev/null +++ b/Controller/controller.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +#include "GameObjects/BasicObjects/Interface/entity.h" +#include "game_view.h" + +class Controller : public QObject { + Q_OBJECT + public: + static Controller* Instance(); + + [[nodiscard]] GameView* GetView() const; + + public slots: + void TickAllTickables(); + + private: + static Controller* instance; + + Controller(); + + void SetupScene(); + void LaunchTickTimer(); + + QGraphicsScene* scene_; + GameView* view_; + QTimer* tick_timer_; +}; diff --git a/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.cpp b/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.cpp new file mode 100644 index 0000000..cc15906 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.cpp @@ -0,0 +1,4 @@ +#include "mob.h" + +Mob::Mob(const VectorF& coordinates, QPixmap* pixmap, int health) + : Entity(coordinates, pixmap, health) {} diff --git a/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h b/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h new file mode 100644 index 0000000..b9cee7f --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h @@ -0,0 +1,12 @@ +#pragma once + +#include "GameObjects/BasicObjects/Interface/entity.h" + +#include + +#include "Utilities/vector_f.h" + +class Mob : public Entity { + public: + Mob(const VectorF& coordinates, QPixmap* pixmap, int health = 0); +}; diff --git a/GameObjects/BasicObjects/Entities/Mobs/test_mob.cpp b/GameObjects/BasicObjects/Entities/Mobs/test_mob.cpp new file mode 100644 index 0000000..bdcfc76 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Mobs/test_mob.cpp @@ -0,0 +1,46 @@ +#include "test_mob.h" + +#include + +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +void TestMob::Tick(Time delta) { + MoveBy(VectorF{delta.seconds() * Entities::TestMob::kPassiveMoveSpeed, 0}); +} + +void TestMob::keyPressEvent(QKeyEvent* event) { + if (health_ == 0) return; + QPointF velocity_vector = mapToParent(pos() + + VectorF{0, -Entities::TestMob::kActiveMoveSpeed}) - mapToParent(pos()); + if (event->key() == Qt::Key::Key_Left) { + setRotation(rotation() - Entities::TestMob::kRotationSpeed); + } else if (event->key() == Qt::Key::Key_Right) { + setRotation(rotation() + Entities::TestMob::kRotationSpeed); + } else if (event->key() == Qt::Key::Key_Up) { + setPos(pos() + velocity_vector); + } else if (event->key() == Qt::Key::Key_Down) { + setPos(pos() - velocity_vector); + } +} + +void TestMob::mousePressEvent(QGraphicsSceneMouseEvent* event) { + scene()->addItem(new TestMob(pos() + VectorF{10, 30})); +} + +TestMob::TestMob(const VectorF& coordinates) + : Mob( + coordinates, + PixmapLoader::Pixmaps::kTestMob, + Entities::TestMob::kHealth) { + setFlag(QGraphicsItem::ItemIsFocusable, true); +} +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); + } +} diff --git a/GameObjects/BasicObjects/Entities/Mobs/test_mob.h b/GameObjects/BasicObjects/Entities/Mobs/test_mob.h new file mode 100644 index 0000000..3f483bf --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Mobs/test_mob.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "Basis/mob.h" +#include "Utilities/vector_f.h" + +class TestMob : public Mob { + public: + explicit TestMob(const VectorF& coordinates = VectorF{0, 0}); + + void Tick(Time delta) override; + + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + + protected: + void keyPressEvent(QKeyEvent* event) override; + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; +}; diff --git a/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.cpp b/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.cpp new file mode 100644 index 0000000..da0a0e7 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.cpp @@ -0,0 +1,29 @@ +#include + +#include "autoguided_projectile.h" + +AutoguidedProjectile::AutoguidedProjectile( + const VectorF& coordinates, + QPixmap* pixmap, + Entity* target, + qreal speed, + Damage damage) + : Entity(coordinates, pixmap), + target_(target), speed_(speed), damage_(damage) {} + +void AutoguidedProjectile::Tick(Time delta) { + Move(delta); + + if (target_->collidesWithItem(this)) { + target_->ApplyDamage(damage_); + deleteLater(); + } +} + +void AutoguidedProjectile::Move(Time delta) { + VectorF target_point = target_->scenePos(); + VectorF delta_pos = target_point - scenePos(); + VectorF velocity = delta_pos.normalized() * speed_; + + MoveBy(velocity * delta.seconds()); +} diff --git a/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.h b/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.h new file mode 100644 index 0000000..0a70022 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Projectiles/autoguided_projectile.h @@ -0,0 +1,19 @@ +#pragma once + +#include "GameObjects/BasicObjects/Interface/entity.h" + +class AutoguidedProjectile : public Entity { + public: + AutoguidedProjectile(const VectorF& coordinates, + QPixmap* pixmap, + Entity* target, qreal speed, Damage damage); + + void Tick(Time delta) override; + + protected: + void Move(Time delta); + + Entity* target_; + qreal speed_; + Damage damage_; +}; diff --git a/GameObjects/BasicObjects/Entities/Projectiles/test_projectile.cpp b/GameObjects/BasicObjects/Entities/Projectiles/test_projectile.cpp new file mode 100644 index 0000000..5037d31 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Projectiles/test_projectile.cpp @@ -0,0 +1,16 @@ +#include "test_projectile.h" + +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +TestProjectile::TestProjectile(const VectorF& coordinates, Entity* target) + : AutoguidedProjectile( + coordinates, + PixmapLoader::Pixmaps::kTestBullet, + target, + Entities::TestProjectile::kSpeed, + Entities::TestProjectile::kDamage) {} + +void TestProjectile::Tick(Time delta) { + AutoguidedProjectile::Tick(delta); +} diff --git a/GameObjects/BasicObjects/Entities/Projectiles/test_projectile.h b/GameObjects/BasicObjects/Entities/Projectiles/test_projectile.h new file mode 100644 index 0000000..d510285 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Projectiles/test_projectile.h @@ -0,0 +1,10 @@ +#pragma once + +#include "autoguided_projectile.h" + +class TestProjectile : public AutoguidedProjectile { + public: + TestProjectile(const VectorF& coordinates, Entity* target); + + void Tick(Time delta) override; +}; diff --git a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.cpp b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.cpp new file mode 100644 index 0000000..2706df1 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.cpp @@ -0,0 +1,29 @@ +#include "test_tower_slot.h" + +#include + +#include "GameObjects/BasicObjects/Entities/Towers/test_tower.h" +#include + +TestTowerSlot::TestTowerSlot(const VectorF& coordinates) : TowerSlot( + coordinates, PixmapLoader::Pixmaps::kTestTowerSlot) {} + +void TestTowerSlot::mousePressEvent(QGraphicsSceneMouseEvent* event) { + if (event->button() != Qt::MouseButton::LeftButton) { + return TowerSlot::mousePressEvent(event); + } + if (!IsTakenUp()) { + TestTower* tower = new TestTower(scenePos()); + scene()->addItem(tower); + TakeUpArea(tower); + } + QGraphicsItem::mousePressEvent(event); +} + +void TestTowerSlot::paint( + QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + if (IsTakenUp()) return; + Entity::paint(painter, option, widget); +} diff --git a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.h b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.h new file mode 100644 index 0000000..a09a135 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/test_tower_slot.h @@ -0,0 +1,16 @@ +#pragma once + +#include "tower_slot.h" + +class TestTowerSlot : public TowerSlot { + public: + explicit TestTowerSlot(const VectorF& coordinates); + + protected: + void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + public: + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; +}; diff --git a/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.cpp b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.cpp new file mode 100644 index 0000000..4169abb --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.cpp @@ -0,0 +1,22 @@ +#include "tower_slot.h" + +bool TowerSlot::IsTakenUp() const { + return tower_ != nullptr; +} + +void TowerSlot::TakeUpArea(Tower* tower) { + tower_ = tower; +} + +void TowerSlot::ClearArea() { + tower_ = nullptr; +} + +TowerSlot::TowerSlot(const VectorF& coordinates, QPixmap* pixmap) + : Entity(coordinates, pixmap), tower_(nullptr) {} + +void TowerSlot::Tick(Time 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 new file mode 100644 index 0000000..e6dff12 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Towers/TowerSlots/tower_slot.h @@ -0,0 +1,16 @@ +#pragma once + +#include "GameObjects/BasicObjects/Entities/Towers/tower.h" +#include "GameObjects/BasicObjects/Interface/entity.h" + +class TowerSlot : public Entity { + public: + TowerSlot(const VectorF& coordinates, QPixmap* pixmap); + [[nodiscard]] bool IsTakenUp() const; + void TakeUpArea(Tower* tower); + void ClearArea(); + void Tick(Time time) override; + + protected: + Tower* tower_; +}; diff --git a/GameObjects/BasicObjects/Entities/Towers/test_tower.cpp b/GameObjects/BasicObjects/Entities/Towers/test_tower.cpp new file mode 100644 index 0000000..360398c --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Towers/test_tower.cpp @@ -0,0 +1,45 @@ +#include "test_tower.h" + +#include + +#include "GameObjects/BasicObjects/Entities/Projectiles/test_projectile.h" +#include "GameObjects/BasicObjects/Entities/Mobs/Basis/mob.h" +#include "Utilities/Resources/pixmap_loader.h" +#include "constants.h" + +namespace { +QPolygonF CreateAttackArea(qreal range) { + const int points_count = Entities::kCircleAttackAreaApproximationPointsCount; + QList points; + for (int i = 0 ; i < points_count; ++i) { + qreal angle = i * 2 * M_PI / points_count; + points.push_back(QPointF { cos(angle), sin(angle) } * range); + } + return QPolygonF(points); +} +} // namespace + +TestTower::TestTower(const VectorF& coordinates) + : Tower(coordinates, PixmapLoader::Pixmaps::kTestTower), + attack_timer_(Time(0)), + range_(Entities::TestTower::kAttackRange), + local_attack_area_(CreateAttackArea(range_)) { + // TODO(jansenin): change it when coordinates are changed + scene_attack_area_ = local_attack_area_.translated(scenePos()); +} + +void TestTower::Tick(Time delta) { + attack_timer_.Tick(delta); + + if (attack_timer_.IsExpired()) { + QList items_in_attack_area = + scene()->items(scene_attack_area_); + for (QGraphicsItem* item : items_in_attack_area) { + if (Mob* mob = dynamic_cast(item)) { + scene()->addItem(new TestProjectile(scenePos(), mob)); + attack_timer_.Start(Entities::TestTower::kAttackCooldown); + break; + } + } + } +} diff --git a/GameObjects/BasicObjects/Entities/Towers/test_tower.h b/GameObjects/BasicObjects/Entities/Towers/test_tower.h new file mode 100644 index 0000000..fa33d7f --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Towers/test_tower.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Utilities/timer.h" +#include + +#include "tower.h" + +class TestTower : public Tower { + public: + explicit TestTower(const VectorF& coordinates); + + void Tick(Time delta) override; + + protected: + qreal range_; + Timer attack_timer_; + QPolygonF local_attack_area_; + QPolygonF scene_attack_area_; +}; diff --git a/GameObjects/BasicObjects/Entities/Towers/tower.cpp b/GameObjects/BasicObjects/Entities/Towers/tower.cpp new file mode 100644 index 0000000..a5269ef --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Towers/tower.cpp @@ -0,0 +1,4 @@ +#include "tower.h" + +Tower::Tower(const VectorF& coordinates, QPixmap* pixmap, int health) + : Entity(coordinates, pixmap, health) {} diff --git a/GameObjects/BasicObjects/Entities/Towers/tower.h b/GameObjects/BasicObjects/Entities/Towers/tower.h new file mode 100644 index 0000000..81f2dd2 --- /dev/null +++ b/GameObjects/BasicObjects/Entities/Towers/tower.h @@ -0,0 +1,8 @@ +#pragma once + +#include "GameObjects/BasicObjects/Interface/entity.h" + +class Tower : public Entity { + public: + Tower(const VectorF& coordinates, QPixmap* pixmap, int health = 0); +}; diff --git a/GameObjects/BasicObjects/Interface/damageable.cpp b/GameObjects/BasicObjects/Interface/damageable.cpp new file mode 100644 index 0000000..739c492 --- /dev/null +++ b/GameObjects/BasicObjects/Interface/damageable.cpp @@ -0,0 +1,16 @@ +#include "damageable.h" + +#include +#include + +Damageable::Damageable(int health) { + health_ = health; +} + +void Damageable::ApplyDamage(Damage damage) { + SetHealth(std::max(health_ - damage.GetDamage(), 0)); +} + +void Damageable::SetHealth(int health) { + health_ = health; +} diff --git a/GameObjects/BasicObjects/Interface/damageable.h b/GameObjects/BasicObjects/Interface/damageable.h new file mode 100644 index 0000000..53527b3 --- /dev/null +++ b/GameObjects/BasicObjects/Interface/damageable.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Utilities/damage.h" + +class Damageable { + public: + explicit Damageable(int health); + virtual void ApplyDamage(Damage damage); + + protected: + int health_; + virtual void SetHealth(int health); +}; diff --git a/GameObjects/BasicObjects/Interface/entity.cpp b/GameObjects/BasicObjects/Interface/entity.cpp new file mode 100644 index 0000000..0a48275 --- /dev/null +++ b/GameObjects/BasicObjects/Interface/entity.cpp @@ -0,0 +1,29 @@ +#include "entity.h" + +Entity::Entity( + const VectorF& coordinates, + QPixmap* pixmap, + int health) + : Damageable(health), QGraphicsItem(), + pixmap(pixmap) { + setPos(coordinates); + setFlag(ItemSendsGeometryChanges); +} + +QRectF Entity::boundingRect() const { + return QRectF( + pixmap->rect().translated( + QPoint{ -pixmap->width()/2, -pixmap->height()/2 })); +} + +void Entity::paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) { + painter->save(); + painter->drawPixmap(Entity::boundingRect().toRect(), *pixmap); + painter->restore(); +} + +void Entity::MoveBy(const VectorF& delta) { + setPos(pos() + delta); +} diff --git a/GameObjects/BasicObjects/Interface/entity.h b/GameObjects/BasicObjects/Interface/entity.h new file mode 100644 index 0000000..a0efcda --- /dev/null +++ b/GameObjects/BasicObjects/Interface/entity.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "tickable.h" +#include "damageable.h" +#include "Utilities/vector_f.h" + +class Entity + : public QObject, + public Tickable, + public Damageable, + public QGraphicsItem { + Q_OBJECT + public: + Entity( + const VectorF& coordinates, + QPixmap* pixmap, + int health = 0); + + [[nodiscard]] QRectF boundingRect() const override; + void paint(QPainter* painter, + const QStyleOptionGraphicsItem* option, + QWidget* widget) override; + + void MoveBy(const VectorF& delta); + + protected: + QPixmap* pixmap; +}; diff --git a/GameObjects/BasicObjects/Interface/tickable.h b/GameObjects/BasicObjects/Interface/tickable.h new file mode 100644 index 0000000..22c9f6f --- /dev/null +++ b/GameObjects/BasicObjects/Interface/tickable.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Utilities/time.h" + +class Tickable { + public: + virtual void Tick(Time delta) = 0; +}; diff --git a/Resources/images/background.png b/Resources/images/background.png new file mode 100644 index 0000000..06f192a Binary files /dev/null and b/Resources/images/background.png differ diff --git a/Resources/images/test_bullet.png b/Resources/images/test_bullet.png new file mode 100644 index 0000000..a65e54b Binary files /dev/null and b/Resources/images/test_bullet.png differ diff --git a/Resources/images/test_mob.png b/Resources/images/test_mob.png new file mode 100644 index 0000000..cc1c2c5 Binary files /dev/null and b/Resources/images/test_mob.png differ diff --git a/Resources/images/test_tower.png b/Resources/images/test_tower.png new file mode 100644 index 0000000..7362ce7 Binary files /dev/null and b/Resources/images/test_tower.png differ diff --git a/Resources/images/test_tower_gun.png b/Resources/images/test_tower_gun.png new file mode 100644 index 0000000..9e7fd2a Binary files /dev/null and b/Resources/images/test_tower_gun.png differ diff --git a/Resources/images/test_tower_slot.png b/Resources/images/test_tower_slot.png new file mode 100644 index 0000000..6789676 Binary files /dev/null and b/Resources/images/test_tower_slot.png differ diff --git a/Resources/resources.qrc b/Resources/resources.qrc new file mode 100644 index 0000000..40a92fc --- /dev/null +++ b/Resources/resources.qrc @@ -0,0 +1,10 @@ + + + images/background.png + images/test_bullet.png + images/test_mob.png + images/test_tower.png + images/test_tower_gun.png + images/test_tower_slot.png + + \ No newline at end of file diff --git a/Utilities/Resources/pixmap_loader.cpp b/Utilities/Resources/pixmap_loader.cpp new file mode 100644 index 0000000..15c8207 --- /dev/null +++ b/Utilities/Resources/pixmap_loader.cpp @@ -0,0 +1,19 @@ +#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; + +void PixmapLoader::LoadPixmaps() { + using P = PixmapLoader::Pixmaps; + + 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"); +} diff --git a/Utilities/Resources/pixmap_loader.h b/Utilities/Resources/pixmap_loader.h new file mode 100644 index 0000000..d1f3a8d --- /dev/null +++ b/Utilities/Resources/pixmap_loader.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class PixmapLoader { + public: + class Pixmaps { + public: + static QPixmap* kBackground; + static QPixmap* kTestBullet; + static QPixmap* kTestMob; + static QPixmap* kTestTower; + static QPixmap* kTestTowerGun; + static QPixmap* kTestTowerSlot; + }; + + static void LoadPixmaps(); +}; diff --git a/Utilities/damage.cpp b/Utilities/damage.cpp new file mode 100644 index 0000000..2826a1e --- /dev/null +++ b/Utilities/damage.cpp @@ -0,0 +1,9 @@ +#include "damage.h" + +Damage::Damage(int damage) { + damage_ = damage; +} + +int Damage::GetDamage() const { + return damage_; +} diff --git a/Utilities/damage.h b/Utilities/damage.h new file mode 100644 index 0000000..515b421 --- /dev/null +++ b/Utilities/damage.h @@ -0,0 +1,10 @@ +#pragma once + +class Damage { + public: + explicit Damage(int damage); + [[nodiscard]] int GetDamage() const; + + private: + int damage_ = 0; +}; diff --git a/Utilities/time.cpp b/Utilities/time.cpp new file mode 100644 index 0000000..513c71f --- /dev/null +++ b/Utilities/time.cpp @@ -0,0 +1,50 @@ +#include "time.h" + +int Time::ms() const { + return ms_; +} + +qreal Time::seconds() const { + return ms_ / 1000.0; +} + +Time::Time(int ms) : ms_(ms) {} + +bool Time::operator<(const Time& rhs) const { + return ms_ < rhs.ms_; +} + +bool Time::operator>(const Time& rhs) const { + return rhs < *this; +} + +bool Time::operator<=(const Time& rhs) const { + return !(rhs < *this); +} + +bool Time::operator>=(const Time& rhs) const { + return !(*this < rhs); +} + +Time Time::operator+(const Time& rhs) const { + return Time(ms_ + rhs.ms_); +} + +Time Time::operator-(const Time& rhs) const { + return Time(ms_ - rhs.ms_); +} + +Time& Time::operator+=(const Time& rhs) { + ms_ += rhs.ms_; + return *this; +} + +Time& Time::operator-=(const Time& rhs) { + ms_ -= rhs.ms_; + return *this; +} + +Time& Time::operator-() { + ms_ = -ms_; + return *this; +} diff --git a/Utilities/time.h b/Utilities/time.h new file mode 100644 index 0000000..bbc0a9e --- /dev/null +++ b/Utilities/time.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +class Time { + public: + explicit Time(int ms); + + [[nodiscard]] int ms() const; + [[nodiscard]] qreal seconds() const; + + bool operator<(const Time& rhs) const; + bool operator>(const Time& rhs) const; + bool operator<=(const Time& rhs) const; + bool operator>=(const Time& rhs) const; + + Time operator+(const Time& rhs) const; + // there is no check for negative time + Time operator-(const Time& rhs) const; + + Time& operator+=(const Time& rhs); + Time& operator-=(const Time& rhs); + + Time& operator-(); + + private: + int ms_; +}; diff --git a/Utilities/timer.cpp b/Utilities/timer.cpp new file mode 100644 index 0000000..83e96f3 --- /dev/null +++ b/Utilities/timer.cpp @@ -0,0 +1,23 @@ +#include "timer.h" + +Timer::Timer(const Time& remaining_time) : remaining_time_(remaining_time) {} + +void Timer::Tick(const Time& delta_time) { + remaining_time_ -= delta_time; +} + +Time Timer::RemainingTime() { + return remaining_time_; +} + +bool Timer::IsExpired() { + return remaining_time_ <= Time(0); +} + +void Timer::Start(const Time& remaining_time) { + remaining_time_ = remaining_time; +} + +Time Timer::HowMuchExpired() { + return -remaining_time_; +} diff --git a/Utilities/timer.h b/Utilities/timer.h new file mode 100644 index 0000000..7e6393a --- /dev/null +++ b/Utilities/timer.h @@ -0,0 +1,17 @@ +#pragma once + +#include "time.h" + +class Timer { + public: + explicit Timer(const Time& remaining_time); + + void Tick(const Time& delta_time); + Time RemainingTime(); + bool IsExpired(); + void Start(const Time& remaining_time); + Time HowMuchExpired(); + + private: + Time remaining_time_; +}; diff --git a/Utilities/vector_f.cpp b/Utilities/vector_f.cpp new file mode 100644 index 0000000..4bcec90 --- /dev/null +++ b/Utilities/vector_f.cpp @@ -0,0 +1,22 @@ +#include "vector_f.h" + +VectorF::VectorF() {} +VectorF::VectorF(const QPoint& p) : QPointF(p) {} +VectorF::VectorF(qreal x, qreal y) : QPointF(x, y) {} +VectorF::VectorF(const QPointF& p) : QPointF(p) {} + +qreal VectorF::lengthSquared() { + return x() * x() + y() * y(); +} + +qreal VectorF::length() { + return sqrt(lengthSquared()); +} + +void VectorF::normalize() { + *this /= length(); +} + +VectorF VectorF::normalized() { + return *this / length(); +} diff --git a/Utilities/vector_f.h b/Utilities/vector_f.h new file mode 100644 index 0000000..de85928 --- /dev/null +++ b/Utilities/vector_f.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class VectorF : public QPointF { + public: + VectorF(); + explicit VectorF(const QPoint& p); + VectorF(const QPointF& p); // NOLINT + VectorF(qreal x, qreal y); + + qreal lengthSquared(); + qreal length(); + + void normalize(); + VectorF normalized(); +}; diff --git a/constants.cpp b/constants.cpp new file mode 100644 index 0000000..844d05d --- /dev/null +++ b/constants.cpp @@ -0,0 +1,30 @@ +#include "constants.h" + +const qreal kSceneWidth = 1920; +const qreal kSceneHeight = 1080; +const QRectF kSceneRect{ + -kSceneWidth/2, + -kSceneHeight/2, + kSceneWidth, + kSceneHeight}; + +namespace Entities { +const int kCircleAttackAreaApproximationPointsCount = 10; + +namespace TestTower { +const qreal kAttackRange = 200; +const Time kAttackCooldown = Time(1000); +} + +namespace TestMob { +const qreal kPassiveMoveSpeed = 5; +const qreal kActiveMoveSpeed = 100; +const qreal kRotationSpeed = 10; +const int kHealth = 30; +} + +namespace TestProjectile { +extern const Damage kDamage = Damage(10); +extern const qreal kSpeed = 100; +} +} // namespace Entities diff --git a/constants.h b/constants.h new file mode 100644 index 0000000..3fe716e --- /dev/null +++ b/constants.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "Utilities/time.h" +#include + +extern const qreal kSceneWidth; +extern const qreal kSceneHeight; +extern const QRectF kSceneRect; + +namespace Entities { +extern const int kCircleAttackAreaApproximationPointsCount; + +namespace TestTower { +extern const qreal kAttackRange; +extern const Time kAttackCooldown; +} + +namespace TestMob { +extern const qreal kPassiveMoveSpeed; +extern const qreal kActiveMoveSpeed; +extern const qreal kRotationSpeed; +extern const int kHealth; +} + +namespace TestProjectile { +extern const Damage kDamage; +extern const qreal kSpeed; +} +} // namespace Entities diff --git a/game_view.cpp b/game_view.cpp new file mode 100644 index 0000000..cc91884 --- /dev/null +++ b/game_view.cpp @@ -0,0 +1,20 @@ +#include "game_view.h" + +GameView::GameView(QGraphicsScene* scene, QWidget* parent) + : QGraphicsView(scene, parent) { + scale(1/devicePixelRatio(), 1/devicePixelRatio()); + setInteractive(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setRenderHint(QPainter::RenderHint::Antialiasing); + setOptimizationFlag( + QGraphicsView::OptimizationFlag::DontSavePainterState); + setViewportUpdateMode( + QGraphicsView::ViewportUpdateMode::FullViewportUpdate); + + setViewportMargins(0, 0, 0, 0); + setContentsMargins(0, 0, 0, 0); + setFrameStyle(QFrame::NoFrame); + + centerOn(0, 0); +} diff --git a/game_view.h b/game_view.h new file mode 100644 index 0000000..e3bc675 --- /dev/null +++ b/game_view.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +class GameView : public QGraphicsView { + public: + explicit GameView(QGraphicsScene* scene, QWidget* parent = nullptr); +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..f8a1f51 --- /dev/null +++ b/main.cpp @@ -0,0 +1,12 @@ +#include + +#include "main_window.h" +#include "Utilities/Resources/pixmap_loader.h" + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + PixmapLoader::LoadPixmaps(); + MainWindow window; + window.show(); + return QApplication::exec(); +} diff --git a/main_window.cpp b/main_window.cpp new file mode 100644 index 0000000..39242de --- /dev/null +++ b/main_window.cpp @@ -0,0 +1,9 @@ +#include "main_window.h" + +#include "Controller/controller.h" + +MainWindow::MainWindow() : + QMainWindow(nullptr) { + setCentralWidget(Controller::Instance()->GetView()); + showFullScreen(); +} diff --git a/main_window.h b/main_window.h new file mode 100644 index 0000000..f71d8ef --- /dev/null +++ b/main_window.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +class MainWindow : public QMainWindow { + Q_OBJECT + public: + MainWindow(); +}; diff --git a/project description/Tower defence.drawio b/project description/Tower defence.drawio index 860c26e..cf0025a 100644 --- a/project description/Tower defence.drawio +++ b/project description/Tower defence.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/project description/Tower defence.drawio.png b/project description/Tower defence.drawio.png new file mode 100644 index 0000000..7128100 Binary files /dev/null and b/project description/Tower defence.drawio.png differ