Skip to main content

What is component-projection?

· 4 min read

What is component-projection in Untitled Mod Game?

"Component-projection" is a special feature that I use for my ECS in UMG.

It's (kinda?) similar to inheritance in OOP; except, it occurs on components, as opposed to objects.

At it's most basic level, component-projection is when one component forces another component to exist, by creating the target component automatically.

Lets say we have a generic drawable component, that is attached to any entity that is drawable.

With this, we could have a few different components:

-- all things that can be drawn!
ent.image = "monkey.png"
ent.particles = Particles() = {radius=5}

With component-projection, we would have each of these components "project" to the drawable component:

How this works in practice, is that entities are automatically granted a drawable component IF they have ANY of the listed components.

So when you do this:

ent.image = "bat.png"

It's effectively the same as doing:

ent.image = "bat.png"
ent.drawable = true

This probably smells very similar to OOP- which makes sense, because it is. Essentially, this is the exact same as an interface in OOP, but in reverse:
With an OOP interface, the interface FORCES methods/fields to be implemented.
With component-projection, the methods/fields FORCE the interface to exist.
It's the same thing, but in reverse.

(This is a bit off-topic, but in UMG, we can also use groups as component-projection mediums; which allows us to project in more complex ways.)
That is, we can say stuff like:

  • "If ent has eyes component AND ears component ->"
    • "give entity sensory component"

Anyway, whats AWESOME about all this, is that we can now query entities without knowing about their particular implementation.

For example:

-- Draw System (pseudocode)
local drawGroup ="drawable", "x", "y")

umg.on("@draw", function() {
-- draw all entities!
for ent in drawGroup {

And internally, drawEntity would emit some event through some event bus, like this:

function drawEntity(ent) {
-- dispatch `drawEntity` event"drawEntity", ent)
-- Other systems listen for `drawEntity` event:
umg.on("drawEntity", function(ent) {
if then
drawCircle(ent.x, ent.y,

umg.on("drawEntity", function(ent) {
if ent.image then
drawImage(ent.x, ent.y, ent.image)

What's also amazing about this setup, is that entities can have ALL of the above components, and they will work perfectly fine.
Is that not beautiful?

Taking it a step further- Values-projection:

We can take it a step further, by providing values for the projected component.
Here, we see nametag --> text projection.

components.project("nametag", "text", function(ent)
local nametag = ent.nametag
-- this returned table will be the VALUE
return {
scale = SCALE,
default = DEFAULT,
component = "controller",
oy = nametag.oy or EXTRA_OY,
background = BACKGROUND_COLOR,
disableScaling = true

Here is a code snippet yoinked straight from the umg ecosystem.
What's special, is that the projection is giving an actual value to the target component.
In this example, the nametag component "passes" a bunch of default values to the text component; most notably, it tells the text component to take the value of the controller component; (which, notably, will be the username of the player.)


INCONSISTENCY: If multiple components project to the same component, and they both try to provide a value, only one will succeed. (This is kinda similar to the diamond-problem in OOP.)

MEMORY EFFICIENCY: For rcomp projection, its a bit inefficient, since we need to keep track of who is currently projecting to what component in the event of removal. This involves storing an entity in a SSet per projection.

COUPLING: Creates coupling between 2 components.

Thankz for readin!