|
#include <iostream> |
|
#include <vector> |
|
#include <string> |
|
|
|
// === FORWARD DECLARATIONS === |
|
class Entity; |
|
|
|
// === COMPONENT BASE CLASSES === |
|
|
|
class TransformComponent { |
|
public: |
|
float x, y; |
|
TransformComponent(float px, float py) : x(px), y(py) {} |
|
}; |
|
|
|
class RenderComponent { |
|
public: |
|
virtual ~RenderComponent() {} |
|
virtual void render(TransformComponent* transform) = 0; |
|
}; |
|
|
|
class HealthComponent { |
|
public: |
|
int health; |
|
int maxHealth; |
|
|
|
HealthComponent(int max) : health(max), maxHealth(max) {} |
|
|
|
void takeDamage(int amount) { |
|
health -= amount; |
|
std::cout << " Took " << amount << " damage. HP: " << health << "/" |
|
<< maxHealth << "\n"; |
|
} |
|
|
|
bool isAlive() const { return health > 0; } |
|
}; |
|
|
|
class MovementComponent { |
|
public: |
|
virtual ~MovementComponent() {} |
|
virtual void update(TransformComponent* transform, float deltaTime) = 0; |
|
}; |
|
|
|
class CollectibleComponent { |
|
public: |
|
virtual ~CollectibleComponent() {} |
|
virtual void onCollect(Entity* collector) = 0; |
|
}; |
|
|
|
// === CONCRETE RENDER COMPONENTS === |
|
|
|
class SpriteRender : public RenderComponent { |
|
private: |
|
std::string spriteName; |
|
|
|
public: |
|
SpriteRender(const std::string& name) : spriteName(name) {} |
|
|
|
void render(TransformComponent* transform) override { |
|
std::cout << " [Sprite: " << spriteName << "] at (" |
|
<< transform->x << ", " << transform->y << ")\n"; |
|
} |
|
}; |
|
|
|
// === CONCRETE MOVEMENT COMPONENTS === |
|
|
|
class UserMovement : public MovementComponent { |
|
private: |
|
float speed; |
|
float inputX = 0, inputY = 0; |
|
|
|
public: |
|
UserMovement(float spd) : speed(spd) {} |
|
|
|
void setInput(float x, float y) { |
|
inputX = x; |
|
inputY = y; |
|
} |
|
|
|
void update(TransformComponent* transform, float deltaTime) override { |
|
transform->x += inputX * speed * deltaTime; |
|
transform->y += inputY * speed * deltaTime; |
|
std::cout << " [UserMovement] Moving based on input\n"; |
|
} |
|
}; |
|
|
|
class PatrolMovement : public MovementComponent { |
|
private: |
|
float speed; |
|
float direction = 1.0f; |
|
float patrolRange = 100.0f; |
|
float startX; |
|
|
|
public: |
|
PatrolMovement(float spd, float startPos) |
|
: speed(spd), startX(startPos) {} |
|
|
|
void update(TransformComponent* transform, float deltaTime) override { |
|
transform->x += direction * speed * deltaTime; |
|
|
|
// Reverse at patrol boundaries |
|
if (transform->x > startX + patrolRange) { |
|
direction = -1.0f; |
|
} else if (transform->x < startX - patrolRange) { |
|
direction = 1.0f; |
|
} |
|
|
|
std::cout << " [PatrolMovement] Patrolling...\n"; |
|
} |
|
}; |
|
|
|
class ChaseMovement : public MovementComponent { |
|
private: |
|
float speed; |
|
Entity* target; |
|
|
|
public: |
|
ChaseMovement(float spd, Entity* tgt) : speed(spd), target(tgt) {} |
|
|
|
void update(TransformComponent* transform, float deltaTime) override; |
|
}; |
|
|
|
// === CONCRETE COLLECTIBLE COMPONENTS === |
|
|
|
class HealthPotion : public CollectibleComponent { |
|
private: |
|
int healAmount; |
|
|
|
public: |
|
HealthPotion(int amount) : healAmount(amount) {} |
|
|
|
void onCollect(Entity* collector) override; |
|
}; |
|
|
|
// === ENTITY CLASS === |
|
|
|
class Entity { |
|
private: |
|
std::string name; |
|
|
|
public: |
|
// Fixed component slots - can be null |
|
TransformComponent* transform; |
|
RenderComponent* render; |
|
HealthComponent* health; |
|
MovementComponent* movement; |
|
CollectibleComponent* collectible; |
|
|
|
Entity(const std::string& n) |
|
: name(n) |
|
, transform(nullptr) |
|
, render(nullptr) |
|
, health(nullptr) |
|
, movement(nullptr) |
|
, collectible(nullptr) |
|
{} |
|
|
|
~Entity() { |
|
delete transform; |
|
delete render; |
|
delete health; |
|
delete movement; |
|
delete collectible; |
|
} |
|
|
|
std::string getName() const { return name; } |
|
|
|
void update(float deltaTime) { |
|
// Only update components that exist |
|
if (movement && transform) { |
|
movement->update(transform, deltaTime); |
|
} |
|
} |
|
|
|
void doRender() { |
|
if (render && transform) { |
|
render->render(transform); |
|
} |
|
} |
|
}; |
|
|
|
// === IMPLEMENT DEFERRED METHODS === |
|
|
|
void ChaseMovement::update(TransformComponent* transform, float deltaTime) { |
|
if (target && target->transform) { |
|
float dx = target->transform->x - transform->x; |
|
float dy = target->transform->y - transform->y; |
|
|
|
// Normalize and move towards target |
|
float dist = sqrt(dx*dx + dy*dy); |
|
if (dist > 0.1f) { |
|
transform->x += (dx/dist) * speed * deltaTime; |
|
transform->y += (dy/dist) * speed * deltaTime; |
|
std::cout << " [ChaseMovement] Chasing target!\n"; |
|
} |
|
} |
|
} |
|
|
|
void HealthPotion::onCollect(Entity* collector) { |
|
std::cout << " [HealthPotion] Collected by " << collector->getName() |
|
<< "!\n"; |
|
if (collector->health) { |
|
collector->health->health += healAmount; |
|
if (collector->health->health > collector->health->maxHealth) { |
|
collector->health->health = collector->health->maxHealth; |
|
} |
|
std::cout << " Healed " << healAmount << " HP. New HP: " |
|
<< collector->health->health << "\n"; |
|
} |
|
} |
|
|
|
// === ENTITY FACTORY FUNCTIONS === |
|
|
|
Entity* createPlayer(float x, float y) { |
|
Entity* player = new Entity("Player"); |
|
|
|
player->transform = new TransformComponent(x, y); |
|
player->render = new SpriteRender("player.png"); |
|
player->health = new HealthComponent(100); |
|
player->movement = new UserMovement(50.0f); |
|
player->collectible = nullptr; // Player can't be collected |
|
|
|
return player; |
|
} |
|
|
|
Entity* createPatrolEnemy(float x, float y) { |
|
Entity* enemy = new Entity("Patrol Enemy"); |
|
|
|
enemy->transform = new TransformComponent(x, y); |
|
enemy->render = new SpriteRender("enemy_patrol.png"); |
|
enemy->health = new HealthComponent(50); |
|
enemy->movement = new PatrolMovement(30.0f, x); |
|
enemy->collectible = nullptr; |
|
|
|
return enemy; |
|
} |
|
|
|
Entity* createChaseEnemy(float x, float y, Entity* target) { |
|
Entity* enemy = new Entity("Chase Enemy"); |
|
|
|
enemy->transform = new TransformComponent(x, y); |
|
enemy->render = new SpriteRender("enemy_chase.png"); |
|
enemy->health = new HealthComponent(75); |
|
enemy->movement = new ChaseMovement(40.0f, target); |
|
enemy->collectible = nullptr; |
|
|
|
return enemy; |
|
} |
|
|
|
Entity* createPotion(float x, float y) { |
|
Entity* potion = new Entity("Health Potion"); |
|
|
|
potion->transform = new TransformComponent(x, y); |
|
potion->render = new SpriteRender("potion.png"); |
|
potion->health = nullptr; // Can't be damaged |
|
potion->movement = nullptr; // Doesn't move |
|
potion->collectible = new HealthPotion(25); |
|
|
|
return potion; |
|
} |
|
|
|
Entity* createWall(float x, float y) { |
|
Entity* wall = new Entity("Wall"); |
|
|
|
wall->transform = new TransformComponent(x, y); |
|
wall->render = new SpriteRender("wall.png"); |
|
wall->health = nullptr; // Indestructible |
|
wall->movement = nullptr; // Static |
|
wall->collectible = nullptr; |
|
|
|
return wall; |
|
} |
|
|
|
// === GAME SYSTEMS === |
|
|
|
void renderSystem(std::vector<Entity*>& entities) { |
|
std::cout << "\n=== RENDER ===\n"; |
|
for (Entity* entity : entities) { |
|
std::cout << entity->getName() << ":\n"; |
|
entity->doRender(); |
|
} |
|
} |
|
|
|
void updateMovement(std::vector<Entity*>& entities, float deltaTime) { |
|
std::cout << "\n=== MOVEMENT UPDATE ===\n"; |
|
for (Entity* entity : entities) { |
|
if (entity->movement) { |
|
std::cout << entity->getName() << ":\n"; |
|
entity->update(deltaTime); |
|
} |
|
} |
|
} |
|
|
|
void dealDamage(Entity* target, int damage) { |
|
std::cout << "\nAttacking " << target->getName() << ":\n"; |
|
if (target->health) { |
|
target->health->takeDamage(damage); |
|
} else { |
|
std::cout << " No health component - can't be damaged!\n"; |
|
} |
|
} |
|
|
|
void collectItem(Entity* collector, Entity* item) { |
|
std::cout << "\n" << collector->getName() << " collecting " |
|
<< item->getName() << ":\n"; |
|
if (item->collectible) { |
|
item->collectible->onCollect(collector); |
|
} else { |
|
std::cout << " Not collectible!\n"; |
|
} |
|
} |
|
|
|
// === MAIN === |
|
|
|
int main() { |
|
// Create entities |
|
Entity* player = createPlayer(0, 0); |
|
Entity* patrolEnemy = createPatrolEnemy(100, 0); |
|
Entity* chaseEnemy = createChaseEnemy(150, 50, player); |
|
Entity* potion = createPotion(50, 0); |
|
Entity* wall = createWall(200, 0); |
|
|
|
std::vector<Entity*> entities = { |
|
player, patrolEnemy, chaseEnemy, potion, wall |
|
}; |
|
|
|
// Initial render |
|
renderSystem(entities); |
|
|
|
// Player takes input and moves |
|
std::cout << "\n=== PLAYER MOVES ===\n"; |
|
if (UserMovement* userMove = |
|
dynamic_cast<UserMovement*>(player->movement)) { |
|
userMove->setInput(1, 0); // Move right |
|
} |
|
|
|
// Update everything |
|
updateMovement(entities, 1.0f); |
|
|
|
// Combat |
|
dealDamage(player, 20); |
|
dealDamage(patrolEnemy, 30); |
|
dealDamage(wall, 50); // Wall has no health - won't work |
|
|
|
// Collect potion |
|
collectItem(player, potion); |
|
collectItem(player, wall); // Wall not collectible |
|
|
|
// Final render |
|
renderSystem(entities); |
|
|
|
// Cleanup |
|
for (Entity* entity : entities) { |
|
delete entity; |
|
} |
|
|
|
return 0; |
|
} |