Entity and Component Interaction

Explore component communication, dependency management, and specialized entity types within your ECS
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

Now that we have our basic Entity-Component System structure, let's focus on making the pieces work together. This lesson explores how components can access their owning entity and interact with sibling components.

We'll implement mechanisms for handling dependencies between components and see how traditional inheritance can still be combined with composition by creating specialized entity subtypes.

Entity and Component Interaction

Components rarely exist in isolation. They often need to know about the Entity they belong to (e.g., to access its other components or trigger entity-level actions) or interact with sibling components (like our ImageComponent needing position data from a TransformComponent).

To enable this communication, we'll give every Component a way to access its owning Entity. We can achieve this by passing a reference or pointer to the Entity into the Component's constructor and storing it as a member variable.

We'll use a raw pointer here, as the Entity owns the Component, not the other way around:

// Component.h
#pragma once
#include <SDL.h>

// Forward declaration to avoid circular includes
class Entity;

class Component {
public:
  // Constructor now takes a pointer to the
  // owning Entity
  Component(Entity* Owner) : Owner(Owner) {} 

  // Getter for the owning entity
  Entity* GetOwner() const { 
    return Owner; 
  } 

  // ...

private:
  // Store a pointer to the owner
  Entity* Owner{nullptr}; 
};

We’ll update our TransformComponent and ImageComponent to inherit this constructor:

// TransformComponent.h
#pragma once
#include "Vec2.h"
#include "Component.h"

class TransformComponent : public Component {
public:
  // Inherit the constructor from the base class
  using Component::Component;
  
  TransformComponent() { // <d>
    std::cout << "TransformComponent created\n"; // <d>
  } // <d>

  // ...
};
// ImageComponent.h
#pragma once
#include "Component.h"

class ImageComponent : public Component {
 public:
  // Inherit the constructor from the base class
  using Component::Component;

  ImageComponent() { 
     std::cout << "ImageComponent created\\n"; 
   } 
   
   // ...
};

Let’s update our AddTransformComponent() and AddImageComponent() to pass a pointer to the Entity that is constructing the component:

// Entity.h
// ...

class Entity {
public:
  // ...

  TransformComponent* AddTransformComponent() {
    if (GetTransformComponent()) {
      std::cout << "Error: Cannot have "
        "multiple transform components";
      return nullptr;
    }

    ComponentPtr& NewComponent{
      Components.emplace_back(
        std::make_unique<
          // Pass 'this' (the Entity*)
          TransformComponent>(this))}; 

    return static_cast<TransformComponent*>(
      NewComponent.get());
  }

  ImageComponent* AddImageComponent() {
    ComponentPtr& NewComponent{
      Components.emplace_back(
        // Pass 'this' (the Entity*)
        std::make_unique<ImageComponent>(this))}; 

    return static_cast<ImageComponent*>(
      NewComponent.get());
  }

  // ...
};

We can see our inter-component communication in action by having our ImageComponent retrieve the position of the Entity they’re attached to by accessing its TransformComponent.

Note that the following code assumes that the entity the ImageComponent is attached to always has a TransformComponent. Later in the lesson, we’ll add logic to ensure that is the case:

// ImageComponent.h
#pragma once
#include "Component.h"

class ImageComponent : public Component {
 public:
  using Component::Component;
  // ...
  void Render(SDL_Surface* Surface) override;
};
// ImageComponent.cpp
#include <iostream>
#include "ImageComponent.h"
#include "Entity.h"

void ImageComponent::Render(
  SDL_Surface* Surface
) {
  // Assume we have an owner, and the owner
  // has a TransformComponent
  TransformComponent* Transform{
    GetOwner()->GetTransformComponent() 
  };

  std::cout << "ImageComponent rendering at: "
    << Transform->GetPosition() << '\n';
}
TransformComponent ticking
ImageComponent rendering at: { x = 0, y = 0 }
TransformComponent ticking
ImageComponent rendering at: { x = 0, y = 0 }
TransformComponent ticking
ImageComponent rendering at: { x = 0, y = 0 }
...

Inter-Component Dependencies

Since our ImageComponent needs a TransformComponent to know where to render, it's a good idea to take steps to enforce this dependency. We can add a check within the ImageComponent itself.

A reasonable place is right after it's constructed and added to the entity, perhaps via an Initialize() method, or even directly within the constructor (though modifying the owner's component list during construction can be tricky).

Most systems handle this by having a separate Initialize() function which is called after the object is constructed. Let’s add that as a virtual function to our Component base class:

// Component.h
// ...

class Component {
 public:
  // ...
  virtual void Initialize() {} 
  // ...
};

We’ll call it at the appropriate times within our AddTransformComponent() and AddImageComponent() functions:

// Entity.h
// ...

class Entity {
public:
  // ...

  TransformComponent* AddTransformComponent() {
    if (GetTransformComponent()) {
      std::cout << "Error: Cannot have "
        "multiple transform components";
      return nullptr;
    }

    ComponentPtr& NewComponent{
      Components.emplace_back(
        std::make_unique<
          TransformComponent>(this))};

    NewComponent->Initialize(); // <h>

    return static_cast<TransformComponent*>(
      NewComponent.get());
  }

  ImageComponent* AddImageComponent() {
    ComponentPtr& NewComponent{
      Components.emplace_back(
        std::make_unique<ImageComponent>(this))};

    NewComponent->Initialize(); 

    return static_cast<ImageComponent*>(
      NewComponent.get());
  }
  // ...
};

Our ImageComponent can now override this to make sure it’s being added to an entity that has a TransformComponent. If not, the ImageComponent will log an error message and request its own removal:

// ImageComponent.h
// ... 

class ImageComponent : public Component {
 public:
  using Component::Component;
  // Override Initialize
  void Initialize() override; 
  // ...
};
// ImageComponent.cpp
// ...

void ImageComponent::Initialize() {
  Entity* Owner{GetOwner()};
  if (!Owner->GetTransformComponent()) {
    std::cout << "Error: ImageComponent "
      "requires TransformComponent on its Owner\n";

    // Request removal
    Owner->RemoveComponent(this);
  }
}

Edge Cases

Our ImageComponent is checking if its owner has a TransformComponent at the point it’s constructed, but it is possible that the TransformComponent be removed afterwards.

This would leave our ImageComponent in a broken state. We could check for the TransformComponent every time we Render(), but that has a performance cost that may not be worth paying.

If it’s a realistic scenario that a component depends on some other component that might be removed from the entity, we should spend some time expanding our system to support this. For example, any time a component is removed from an entity, that entity could push an SDL_Event onto the event queue.

Alternatively, we could handle this within our Entity and Component infrastructure. An example solution would be to add an OnComponentRemoved() method to our Component base class:

// Component.h
// ...

class Component {
public:
  // ...
  virtual void OnComponentRemoved(Component* C) {} 
  // ...
};

Before an entity removes one of it’s components, it can iterate through all the other components to notify them of what is happening:

// Entity.h
// ...

class Entity {
public:
 
  void RemoveComponent(Component* PtrToRemove) {
    for (size_t i{0}; i < Components.size(); ++i) {
      if (Components[i].get() == PtrToRemove) {
        for (ComponentPtr& C : Components) {
          C->OnComponentRemoved(PtrToRemove);
        }
        Components.erase(Components.begin() + i);
        return;
      }
    }

    std::cout << "Warning: Attempted to remove "
      "a component not found on this entity.\n";
  }

  // ...
};

Then, any component that cares when some other component gets removed can override the OnComponentRemoved() method and react accordingly:

// ImageComponent.h
#pragma once
#include "Component.h"

class ImageComponent : public Component {
 public:
  // ...
  void OnComponentRemoved(Component*) override;
  // ...
};
// ImageComponent.cpp
// ...

void ImageComponent::OnComponentRemoved(
  Component* Ptr
) {
  // Check if the component being removed is a
  // TransformComponent
  if (dynamic_cast<TransformComponent*>(Ptr)) {
    std::cout << "ImageComponent: Owner's "
      "TransformComponent was removed!\n";

    // React to removal...
  }
}

Note that we should be careful about calling Owner->RemoveComponent() here - at this point, there is already an invocation of RemoveComponent() in progress - that’s why OnComponentRemoved() has been called. If we call RemoveComponent() again, that call can reduce the size of our array and change the indices of elements, disrupting the iteration that the previous invocation was in the middle of.

Note that the code in our components also assume that the component has an owner - that is, Owner is not a nullptr or, worse, a dangling pointer.

Our component’s will always have an owner in our examples, but it’s technically possible for someone to write code that violates this. An obvious example is just directly creating and using a component with no Entity involved:

// Unintended usage
ImageComponent BrokenComponent{nullptr}; 

// Likely crash
BrokenComponent.Render(Surface);

We cover techniques that can reduce this risk, such as private constructors and the friend keyword, in our advanced course.

However, we’ll keep things simple in this course. Our components will always have the correct owners, and we won’t remove components that other components depend upon. As such, we won’t need this OnComponentRemoved() notification system but, in more complex projects, it’s worth considering what capabilities you need to have, and then build robust systems to support them.

Entity Subtypes

While the component system provides great flexibility through composition, it doesn't mean we have to abandon inheritance entirely. We can still create specialized Entity subclasses if it makes sense for our game structure, and get all the same benefits of inheritence.

Entity subtypes can customise how they manage their components, too. For instance, all characters in our game might need both a position (TransformComponent) and a visual representation (ImageComponent). We can create a Character class that inherits from Entity and automatically adds these essential components in its constructor.

// Character.h
#pragma once
#include "Entity.h"
#include "ImageComponent.h"
#include "TransformComponent.h"

class Character : public Entity {
 public:
  Character() {
    // Automatically add required components
    // upon construction. The AddComponent
    // methods already handle passing 'this'
    Transform = AddTransformComponent();
    Image = AddImageComponent();
  }

  // Optionally add Character-specific methods here
  void SayHello() const {
    std::cout << "Character says hello!\n";
  }

 private:
  // Optional: store direct pointers for
  // convenience if frequently accessed.
  TransformComponent* Transform{nullptr};
  ImageComponent* Image{nullptr};
};

Let’s update our Scene to include a Character.

// Scene.h
// ...
#include "Character.h"

class Scene {
public:
  Scene() {
    // Create a Character instead of a generic Entity
    // The Character constructor handles adding
    // its default components
    EntityPtr& NewCharacter{Entities.emplace_back(
      std::make_unique<Character>()
    )};
  }

  // ...
};

The Scene doesn’t need to worry about the character’s components - the Character makes sure it has what it needs:

TransformComponent ticking
ImageComponent rendering at: { x = 0, y = 0 }
TransformComponent ticking
ImageComponent rendering at: { x = 0, y = 0 }
TransformComponent ticking
ImageComponent rendering at: { x = 0, y = 0 }
...

Additionally, just because our Character objects comes with some components as standard, that doesn’t mean our Scene (or any other code) can’t add more. We have the flexibility to do whatever we need:

// Scene.h
// ...

class Scene {
public:
  Scene() {
    EntityPtr& Player{
      Entities.emplace_back(
        std::make_unique<Character>())};

    // The player character needs more components
    Player->AddImageComponent();
    Player->AddImageComponent();
  }
  // ...
};
TransformComponent ticking
ImageComponent rendering at: { x = 0, y = 0 }
ImageComponent rendering at: { x = 0, y = 0 }
ImageComponent rendering at: { x = 0, y = 0 }
// ...

Complete Code

Our complete code, which we’ll build upon throughout this chapter, is available below:

#include <SDL.h>
#include "Window.h"
#include "Scene.h"

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  Scene GameScene;

  Uint64 LastTick{SDL_GetPerformanceCounter()};
  SDL_Event Event;
  while (true) {
    while (SDL_PollEvent(&Event)) {
      GameScene.HandleEvent(Event);
      if (Event.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }

    Uint64 CurrentTick{SDL_GetPerformanceCounter()};
    float DeltaTime{static_cast<float>(
      CurrentTick - LastTick) /
      SDL_GetPerformanceFrequency()
    };
    LastTick = CurrentTick;

    // Tick
    GameScene.Tick(DeltaTime);

    // Render
    GameWindow.Render();
    GameScene.Render(GameWindow.GetSurface());

    // Swap
    GameWindow.Update();
  }

  return 0;
}
#pragma once
#include <iostream>
#include <SDL.h>

class Window {
public:
  Window() {
    SDLWindow = SDL_CreateWindow(
      "Scene",
      SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED,
      700, 300, 0
    );
  }

  ~Window() {
    if (SDLWindow) {
      SDL_DestroyWindow(SDLWindow);
    }
  }

  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;

  void Render() {
    SDL_FillRect(
      GetSurface(), nullptr,
      SDL_MapRGB(GetSurface()->format,
        220, 220, 220));
  }

  void Update() {
    SDL_UpdateWindowSurface(SDLWindow);
  }

  SDL_Surface* GetSurface() {
    return SDL_GetWindowSurface(SDLWindow);
  }

private:
  SDL_Window* SDLWindow;
};
#pragma once
#include <SDL.h>
#include <vector>
#include "Entity.h"

using EntityPtr = std::unique_ptr<Entity>;
using EntityPtrs = std::vector<EntityPtr>;

class Scene {
 public:
  Scene() {
    EntityPtr& Player{Entities.emplace_back(
      std::make_unique<Entity>())};

    Player->AddTransformComponent();
    Player->AddImageComponent();
  }

  void HandleEvent(SDL_Event& E) {
    for (EntityPtr& Entity : Entities) {
      Entity->HandleEvent(E);
    }
  }

  void Tick(float DeltaTime) {
    for (EntityPtr& Entity : Entities) {
      Entity->Tick(DeltaTime);
    }
  }

  void Render(SDL_Surface* Surface) {
    for (EntityPtr& Entity : Entities) {
      Entity->Render(Surface);
    }
  }

 private:
  EntityPtrs Entities;
};
#pragma once
#include <memory>
#include <vector>
#include <SDL.h>
#include "Component.h"
#include "TransformComponent.h"
#include "ImageComponent.h"

using ComponentPtr = std::unique_ptr<Component>;
using ComponentPtrs = std::vector<ComponentPtr>;

class Entity {
public:
  virtual void HandleEvent(const SDL_Event& E) {
    for (ComponentPtr& C : Components) {
      C->HandleEvent(E);
    }
  }

  virtual void Tick(float DeltaTime) {
    for (ComponentPtr& C : Components) {
      C->Tick(DeltaTime);
    }
  }

  virtual void Render(SDL_Surface* Surface) {
    for (ComponentPtr& C : Components) {
      C->Render(Surface);
    }
  }

  virtual ~Entity() = default;

  TransformComponent* AddTransformComponent() {
    if (GetTransformComponent()) {
      std::cout << "Error: Cannot have "
        "multiple transform components";
      return nullptr;
    }

    ComponentPtr& NewComponent{
      Components.emplace_back(
        std::make_unique<
          TransformComponent>(this))};

    NewComponent->Initialize();

    return static_cast<TransformComponent*>(
      NewComponent.get());
  }

  TransformComponent* GetTransformComponent() const {
    for (const ComponentPtr& C : Components) {
      if (auto Ptr{
        dynamic_cast<TransformComponent*>(C.get())
      }) {
        return Ptr;
      }
    }
    return nullptr;
  }

  ImageComponent* AddImageComponent() {
    ComponentPtr& NewComponent{
      Components.emplace_back(
        std::make_unique<ImageComponent>(this))};

    NewComponent->Initialize();

    return static_cast<ImageComponent*>(
      NewComponent.get());
  }

  using ImageComponents =
    std::vector<ImageComponent*>;
  ImageComponents GetImageComponents() const {
    ImageComponents Result;
    for (const ComponentPtr& C : Components) {
      if (auto Ptr{dynamic_cast<
        ImageComponent*>(C.get())}
      ) {
        Result.push_back(Ptr);
      }
    }
    return Result;
  }

  void RemoveComponent(Component* PtrToRemove) {
    for (size_t i{0}; i < Components.size(); ++i) {
      if (Components[i].get() == PtrToRemove) {
        Components.erase(Components.begin() + i);
        return;
      }
    }

    std::cout << "Warning: Attempted to remove "
      "a component not found on this entity.\n";
  }

private:
  ComponentPtrs Components;
};
#pragma once
#include <SDL.h>

class Entity;

class Component {
 public:
  Component(Entity* Owner) : Owner(Owner) {}
  virtual void Initialize() {}
  virtual void HandleEvent(const SDL_Event& E) {}
  virtual void Tick(float DeltaTime) {}
  virtual void Render(SDL_Surface* Surface) {}
  virtual ~Component() = default;

  Entity* GetOwner() const { return Owner; }

private:
  Entity* Owner{nullptr};
};
#pragma once
#include "Vec2.h"
#include "Component.h"

class TransformComponent : public Component {
public:
  using Component::Component;

  Vec2 GetPosition() const {
    return Position;
  }

private:
  Vec2 Position{0, 0};
};
#pragma once
#include "Component.h"

class ImageComponent : public Component {
 public:
  using Component::Component;
  void Initialize() override;
  void Render(SDL_Surface* Surface) override;
};
#include <iostream>
#include "ImageComponent.h"
#include "Entity.h"

void ImageComponent::Render(
  SDL_Surface* Surface
) {
  TransformComponent* Transform{
    GetOwner()->GetTransformComponent()
  };

  std::cout << "ImageComponent rendering at: "
    << Transform->GetPosition() << '\n';
}

void ImageComponent::Initialize() {
  Entity* Owner{GetOwner()};
  if (!Owner->GetTransformComponent()) {
    std::cout << "Error: ImageComponent requires"
      " TransformComponent on its Owner\n";

    // React to removal...
  }
}

Summary

This lesson enhanced our ECS by enabling communication and dependency management. We gave components access to their owning entity via an Owner pointer, allowing sibling component interaction.

We introduced an Initialize() step to enforce dependencies (like ImageComponent requiring TransformComponent) and demonstrated how an OnComponentRemoved() notification system could be added for robust dependency handling.

Finally, we saw how entity subtypes (Character) can combine inheritance with composition for convenient setup of standard entities.

Key takeaways:

  • Components can access their Entity via an Owner pointer passed during construction.
  • Sibling components can be found using GetOwner()->GetComponent()-style functions.
  • An Initialize() method allows components to verify dependencies after being added.
  • Components can request their own removal if dependencies aren't met.
  • An OnComponentRemoved() notification helps components react if a depended-upon component is removed later.
  • Entity subclasses (like Character) can inherit from Entity to provide default component setups.
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Entity and Component Interaction

Explore component communication, dependency management, and specialized entity types within your ECS

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Components and Composition
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

This course includes:

  • 105 Lessons
  • 92% Positive Reviews
  • Regularly Updated
  • Help and FAQs
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved