Skip to content

Instantly share code, notes, and snippets.

@kenpower
Last active January 28, 2026 10:56
Show Gist options
  • Select an option

  • Save kenpower/0dcca01bcdfbf46dce30ac9d0cd35af7 to your computer and use it in GitHub Desktop.

Select an option

Save kenpower/0dcca01bcdfbf46dce30ac9d0cd35af7 to your computer and use it in GitHub Desktop.
Using interfaces for component slot

Key Points

Fixed Component Slots:

class Entity {
  TransformComponent* transform;  // Always same slots
  RenderComponent* render;
  HealthComponent* health;
  MovementComponent* movement;
  CollectibleComponent* collectible;
};

Null When Not Needed:

// Wall has no movement or health
wall->health = nullptr;
wall->movement = nullptr;

Different Implementations:

player->movement = new UserMovement(50.0f);     // Player uses keyboard
enemy->movement = new PatrolMovement(30.0f);    // Enemy patrols
potion->movement = nullptr;                      // Potion doesn't move

Check Before Using:

void update(float deltaTime) {
  if (movement && transform) {  // Check for null!
    movement->update(transform, deltaTime);
  }
}

This approach is simpler than dynamic collections while still flexible!

#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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment