Building the Actor Menu

This lesson focuses on creating the UI panel for Actors and adding the first concrete Actor type.
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 a video game map editor
Level Editor: Part 2
Ryan McCombe
Ryan McCombe
Posted

In the previous lesson, we set up the foundational classes for our level editor: the main window, the scene manager, and helper classes for images, text, buttons, and assets. We now have a blank window ready to be populated.

This lesson builds upon that foundation by adding the first major UI component: the Actor Menu. This menu will serve as a palette on the right side of the editor, displaying all the available objects (Actors) that users can place into their levels.

We'll create a dedicated ActorMenu class to manage this area's rendering and logic. We will also define a base Actor class, which will represent any object that can exist in our level, like blocks, enemies, or power-ups.

Finally, we'll implement our first concrete Actor type – a simple blue block – and add an instance of it to the ActorMenu, making it visible and ready for interaction in later steps.

Screenshot showing our completed actor menu

Adding an ActorMenu Class

The right side of our editor will contain a list of available "Actors" (game objects) that can be placed in the level. Let's create a dedicated class, ActorMenu, to manage this UI element.

As with our Scene and other core components, it will follow the standard pattern of having HandleEvent(), Tick(), and Render() methods. It also needs access to the main Scene it belongs to, so we'll store a reference to the ParentScene.

// Editor/ActorMenu.h
#pragma once
#include <SDL.h>

namespace Editor {
class Scene;

class ActorMenu {
 public:
  ActorMenu(Scene& ParentScene)
  : ParentScene{ParentScene}
  {}

  void HandleEvent(const SDL_Event& E) {}
  void Tick(float DeltaTime) {}
  void Render(SDL_Surface* Surface) {}

  Scene& GetScene() const {
    return ParentScene;
  }

 private:
  Scene& ParentScene;
};
}

Now that we have the ActorMenu class defined, we need to create an instance of it within our Editor::Scene. The Scene will own and manage the ActorMenu.

We'll add an ActorMenu member variable to the Scene class, passing *this (a reference to the Scene itself) to the ActorMenu constructor.

We also need to update the Scene's HandleEvent(), Tick(), and Render() methods to delegate calls to the corresponding methods in the ActorMenu instance.

// Editor/Scene.h
#pragma once
#include <SDL.h>
#include "ActorMenu.h"
#include "AssetManager.h"
#include "Window.h"

namespace Editor{
class Scene {
 public:
  Scene(Window& ParentWindow)
  : ParentWindow{ParentWindow}
  {}

  void HandleEvent(const SDL_Event& E) {
    ActorShelf.HandleEvent(E);
  }

  void Tick(float DeltaTime) {
    ActorShelf.Tick(DeltaTime);
  }

  void Render(SDL_Surface* Surface) {
    ActorShelf.Render(Surface);
  }
  
  // ...

 private:
  ActorMenu ActorShelf{*this};
  // ...
};
}

Rendering ActorMenu

With the ActorMenu integrated into the Scene, let's make it visible. We need to define its appearance and position within the editor window.

We'll add some constants to our Config.h file, specifically within the Config::Editor namespace. These will define the menu's width, its X-coordinate (anchored to the right edge of the window), its background color, and some padding for spacing elements inside it later.

// Config.h
#pragma once
// ...

#ifdef WITH_EDITOR
namespace Config::Editor {
// Window
// ...

// ActorMenu
inline const int ACTOR_MENU_WIDTH{70};
inline const int ACTOR_MENU_POSITION_X{
  WINDOW_WIDTH - ACTOR_MENU_WIDTH};
inline const SDL_Color ACTOR_MENU_BACKGROUND{
  15, 15, 15, 255};
inline const int PADDING{10};
}
#endif

// ...

Time to implement ActorMenu::Render(). This function needs to draw the visual representation of the menu panel. We'll start with just the background.

Using the ACTOR_MENU_BACKGROUND color and the position/size defined by ACTOR_MENU_POSITION_X, ACTOR_MENU_WIDTH, and WINDOW_HEIGHT from Config::Editor, we call SDL_FillRect(). To avoid recalculating the menu's rectangle every frame, we'll store it in a private SDL_Rect member variable initialized once.

// Editor/ActorMenu.h
#pragma once
#include <SDL.h>
#include "Config.h"

namespace Editor {
// ...
class ActorMenu {
 public:
  // ...

  void Render(SDL_Surface* Surface) {
    using namespace Config::Editor;
    auto [r, g, b, a]{ACTOR_MENU_BACKGROUND};

    SDL_FillRect(
      Surface,
      &Rect,
      SDL_MapRGB(Surface->format, r, g, b)
    );
  }

 private:
  Scene& ParentScene;
  SDL_Rect Rect{
    Config::Editor::ACTOR_MENU_POSITION_X, 0,
    Config::Editor::ACTOR_MENU_WIDTH,
    Config::Editor::WINDOW_HEIGHT
  };
};
}

We should now see our menu rendered at the side of our application, using the width and color defined within our configuration file:

Screenshot showing our actor menu background

Actor Classes

Our editor will handle various types of objects that can be placed in the level – blocks, enemies, items, etc. We'll call these "Actors". To manage them effectively, we need a common base class, Actor.

This Actor class will define the fundamental properties and interface shared by all actor types, such as position (SDL_Rect), visual representation (Image&), and core methods like HandleEvent(), Tick(), and Render().

Because Actor needs a reference to the Scene but Scene will eventually hold Actors (via ActorMenu), we must forward-declare Scene in Actor.h to prevent a circular include dependency.

// Editor/Actor.h
#pragma once
#include <SDL.h>
#include "Image.h"

namespace Editor {
class Scene;
class Actor {
 public:
  Actor(
    Scene& ParentScene,
    const SDL_Rect& Rect,
    Image& Image
  ) : ParentScene{ParentScene},
      Rect{Rect},
      Art{Image}
  {}

  virtual void HandleEvent(const SDL_Event& E){}
  void Tick(float DeltaTime) {}

  void Render(SDL_Surface* Surface) {
    Art.Render(Surface, Rect);
  }

protected:
  Scene& ParentScene;
  SDL_Rect Rect;
  Image& Art;
};
}

Let's create our first usable Actor. We'll define a BlueBlock class in Editor/Blocks.h that inherits from our base Actor class.

We’ll store this in a file called "Blocks" rather than something like "BlueBlock", as we’ll have many block types in the future, and their classes will be quite small so we can just define them all in a single file. However, it would be equally valid to separate our block types into their own files - it’s just a question of personal preference.

This BlueBlock class represents a simple blue brick. We'll give it fixed dimensions using constexpr static members. The constructor needs to initialize the base Actor part, including fetching the "BlueBlock" image from the AssetManager via the Scene.

This interaction necessitates the full Scene definition. To avoid issues with include order and dependencies, we declare the BlueBlock constructor in the .h file but defer its implementation to Editor/Source/Blocks.cpp, where including Editor/Scene.h is straightforward.

// Editor/Blocks.h
#pragma once
#include "Actor.h"

namespace Editor{
class BlueBlock : public Actor {
public:
  static constexpr int WIDTH{50};
  static constexpr int HEIGHT{25};

  BlueBlock(
    Scene& ParentScene, SDL_Rect Rect
  );
};
}

In the new Editor/Source/Blocks.cpp file, we provide the definition for BlueBlock::BlueBlock (that is, the constructor). Make sure to include Editor/Scene.h here.

The constructor's main task is to initialize the base Actor class correctly, passing along the scene, setting the block's rectangle dimensions using its static constants, and getting the corresponding Image reference from the AssetManager.

// Editor/Source/Blocks.cpp
#include "Editor/Blocks.h"
#include "Editor/Scene.h"

using namespace Editor;

BlueBlock::BlueBlock(
  Scene& ParentScene, SDL_Rect Rect
) : Actor{
    ParentScene,
    SDL_Rect{Rect.x, Rect.y, WIDTH, HEIGHT},
    ParentScene.GetAssets().BlueBlock
  }
{}

Our ActorMenu needs to store the different Actor types that the user can select. Since these will be instances of classes derived from Actor (like BlueBlock), we need to store them in a way that handles polymorphism.

We'll use a std::vector to hold pointers to Actor objects. To manage memory automatically and correctly handle ownership, we'll use std::unique_ptr<Actor>. We add using aliases (ActorPtr, ActorPtrs) for clarity and declare a std::vector<ActorPtr> named Actors as a member of ActorMenu.

// Editor/ActorMenu.h
// ...
#include <vector>
#include <memory>
#include "Actor.h"

namespace Editor {
class Scene;
using ActorPtr = std::unique_ptr<Actor>;
using ActorPtrs = std::vector<ActorPtr>;

class ActorMenu {
 public:
  // ...

 private:
  ActorPtrs Actors;
  // ...
};
}

Now that ActorMenu has a container for Actors, we need to make sure these actors participate in the application loop. We must iterate through the Actors vector in the ActorMenu's HandleEvent(), Tick(), and Render() methods.

In each of these methods, we add a range-based for loop that goes through each ActorPtr in the Actors vector and calls the corresponding method - HandleEvent(), Tick(), and Render() - on the pointed-to Actor object.

Note that rendering actors should happen after rendering the menu's background, to ensure they get rendered on top of the background.

// Editor/ActorMenu.h
// ...

namespace Editor {
// ...

class ActorMenu {
 public:
  // ...

  void HandleEvent(const SDL_Event& E) {
    for (ActorPtr& A : Actors) {
      A->HandleEvent(E);
    }
  }

  void Tick(float DeltaTime) {
    for (ActorPtr& A : Actors) {
      A->Tick(DeltaTime);
    }
  }

  void Render(SDL_Surface* Surface) {
    using namespace Config::Editor;
    auto [r, g, b, a]{ACTOR_MENU_BACKGROUND};

    SDL_FillRect(
      Surface,
      &Rect,
      SDL_MapRGB(Surface->format, r, g, b)
    );

    for (ActorPtr& A : Actors) {
      A->Render(Surface);
    }
  }

 private:
  ActorPtrs Actors;
  // ...
};
}

We have the ActorMenu, the Actor base class, the BlueBlock derived class, and the mechanism to store and process actors. Now, let's actually create an instance of BlueBlock and add it to the ActorMenu.

We'll do this in the ActorMenu's constructor.

Using Actors.emplace_back() and std::make_unique<BlueBlock>(), we create a new BlueBlock in dynamic memory, managed by a unique_ptr. We pass the required Scene reference using GetScene() and an initial SDL_Rect defining its position within the menu, using the PADDING configuration for spacing.

We’ll set the width and height in the SDL_Rect to 0, but the value doesn’t matter as the BlueBlock constructor will set the correct dimensions itself before forwarding it to the Actor base constructor.

// Editor/ActorMenu.h
// ...
#include "Blocks.h"

namespace Editor {
// ...

class ActorMenu {
 public:
  ActorMenu(Scene& ParentScene)
  : ParentScene{ParentScene}
  {
    using namespace Config::Editor;
    Actors.emplace_back(
      std::make_unique<BlueBlock>(
        GetScene(),
        SDL_Rect{
          ACTOR_MENU_POSITION_X + PADDING,
          PADDING,
          0, 0
        }
      )
    );
  }

  // ...
};
}

If you run the program after these changes, the blue block should appear inside the actor menu panel.

This confirms that the ActorMenu is being rendered, the BlueBlock is being created correctly, added to the Actors vector, and its Render() method is being called by loop within the ActorMenu::Render() function.

Screenshot showing our completed actor menu

Complete Code

We've created several new files and modified existing ones to implement the ActorMenu and the first Actor.

Below are the full contents of Editor/ActorMenu.h, Editor/Actor.h, Editor/Blocks.h, and Editor/Source/Blocks.cpp reflecting the changes made in this lesson.

#pragma once
#include <SDL.h>
#include <vector>
#include <memory>
#include "Actor.h"
#include "Blocks.h"
#include "Config.h"

namespace Editor {
class Scene;
using ActorPtr = std::unique_ptr<Actor>;
using ActorPtrs = std::vector<ActorPtr>;

class ActorMenu {
 public:
  ActorMenu(Scene& ParentScene)
  : ParentScene{ParentScene}
  {
    using namespace Config::Editor;
    Actors.emplace_back(
      std::make_unique<BlueBlock>(
        GetScene(),
        SDL_Rect{
          ACTOR_MENU_POSITION_X + PADDING,
          PADDING,
          0, 0
        }
      )
    );
  }

  void HandleEvent(const SDL_Event& E) {
    for (ActorPtr& A : Actors) {
      A->HandleEvent(E);
    }
  }

  void Tick(float DeltaTime) {
    for (ActorPtr& A : Actors) {
      A->Tick(DeltaTime);
    }
  }

  void Render(SDL_Surface* Surface) {
    using namespace Config::Editor;
    auto [r, g, b, a]{ACTOR_MENU_BACKGROUND};

    SDL_FillRect(
      Surface,
      &Rect,
      SDL_MapRGB(Surface->format, r, g, b)
    );

    for (ActorPtr& A : Actors) {
      A->Render(Surface);
    }
  }

  Scene& GetScene() const {
    return ParentScene;
  }

 private:
  Scene& ParentScene;
  ActorPtrs Actors;
  SDL_Rect Rect{
    Config::Editor::ACTOR_MENU_POSITION_X, 0,
    Config::Editor::ACTOR_MENU_WIDTH,
    Config::Editor::WINDOW_HEIGHT
  };
};
}
#pragma once
#include <SDL.h>
#include "Image.h"

namespace Editor {
class Scene;
class Actor {
 public:
  Actor(
    Scene& ParentScene,
    const SDL_Rect& Rect,
    Image& Image
  ) : ParentScene{ParentScene},
      Rect{Rect},
      Art{Image}
  {}

  virtual void HandleEvent(const SDL_Event& E){}
  void Tick(float DeltaTime) {}

  void Render(SDL_Surface* Surface) {
    Art.Render(Surface, Rect);
  }

protected:
  Scene& ParentScene;
  SDL_Rect Rect;
  Image& Art;
};
}
#pragma once
#include "Actor.h"

namespace Editor{
class BlueBlock : public Actor {
public:
  static constexpr int WIDTH{50};
  static constexpr int HEIGHT{25};

  BlueBlock(
    Scene& ParentScene, SDL_Rect Rect
  );
};
}
#include "Editor/Blocks.h"
#include "Editor/Scene.h"

using namespace Editor;

BlueBlock::BlueBlock(
  Scene& ParentScene, SDL_Rect Rect
) : Actor{
  ParentScene,
  SDL_Rect{Rect.x, Rect.y, WIDTH, HEIGHT},
  ParentScene.GetAssets().BlueBlock
} {}

We also made modifications to Editor/Scene.h to include and manage the ActorMenu, and to Config.h to add layout constants. The updated versions of these files are shown below.

#pragma once
#include <SDL.h>
#include "ActorMenu.h"
#include "AssetManager.h"
#include "Window.h"

namespace Editor{
class Scene {
 public:
  Scene(Window& ParentWindow)
  : ParentWindow{ParentWindow}
  {}

  void HandleEvent(const SDL_Event& E) {
    ActorShelf.HandleEvent(E);
  }

  void Tick(float DeltaTime) {
    ActorShelf.Tick(DeltaTime);
  }

  void Render(SDL_Surface* Surface) {
    ActorShelf.Render(Surface);
  }

  AssetManager& GetAssets() {
    return Assets;
  }

  bool HasMouseFocus() const {
    return ParentWindow.HasMouseFocus();
  }

  Window& GetWindow() const {
    return ParentWindow;
  }

 private:
  ActorMenu ActorShelf{*this};
  Window& ParentWindow;
  AssetManager Assets;
};
}
#pragma once
#include <iostream>
#include <SDL.h>
#include <string>
#include <vector>

namespace Config {
inline const std::vector BUTTON_COLORS{
  SDL_Color{15, 15, 15, 255},  // Normal
  SDL_Color{15, 155, 15, 255}, // Hover
  SDL_Color{225, 15, 15, 255}, // Active
  SDL_Color{60, 60, 60, 255}   // Disabled
};

inline constexpr SDL_Color FONT_COLOR{
  255, 255, 255, 255};

inline const std::string FONT{
  "Assets/Rubik-SemiBold.ttf"};
}

#ifdef WITH_EDITOR
namespace Config::Editor {
// Window
inline const std::string WINDOW_TITLE{"Editor"};
inline const int WINDOW_WIDTH{730};
inline const int WINDOW_HEIGHT{300};
inline const SDL_Color WINDOW_BACKGROUND{35, 35, 35, 255};

// ActorMenu
inline const int ACTOR_MENU_WIDTH{70};
inline const int ACTOR_MENU_POSITION_X{
  WINDOW_WIDTH - ACTOR_MENU_WIDTH};
inline const SDL_Color ACTOR_MENU_BACKGROUND{
  15, 15, 15, 255};
inline const int PADDING{10};
}
#endif

inline void CheckSDLError(
  const std::string& Msg) {
#ifdef CHECK_ERRORS
  const char* error = SDL_GetError();
  if (*error != '\0') {
    std::cerr << Msg << " Error: "
      << error << '\n';
    SDL_ClearError();
  }
#endif
}

Summary

We've added the first major piece of the editor's user interface: the Actor Menu. Here's what we did:

  • ActorMenu Class: Created a new class to manage the menu panel, including its rendering and event handling. Added an instance of it to the Scene.
  • Configuration: Defined constants in Config.h for the menu's width, position, background color, and padding.
  • Rendering: Implemented ActorMenu::Render() to draw the background rectangle.
  • Actor Hierarchy: Introduced a base Actor class for all placeable items, using forward declaration for Scene to prevent include cycles.
  • BlueBlock Actor: Created the first concrete Actor subclass, BlueBlock, defining its dimensions and implementing its constructor in a separate .cpp file to handle dependencies correctly.
  • Actor Storage: Used std::vector<std::unique_ptr<Actor>> within ActorMenu to store and manage actor instances polymorphically and safely.
  • Integration: Updated ActorMenu's methods to process the contained actors and added a BlueBlock instance to the menu in the constructor.

The editor now displays the menu on the right with a blue block ready to be used in future lessons.

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
Posted
Lesson Contents

Building the Actor Menu

This lesson focuses on creating the UI panel for Actors and adding the first concrete Actor type.

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
Project: Level Editor
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:

  • 118 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