Skip to main content

The Zen of UMG

· 2 min read

Some quick guidelines that summarize the current UMG design philosophy.


NOTE: all of this is subject to nuance!

If you disagree with something, or have a counterexample, let me know. I would love to discuss it.
This file basically seeks to clarify UMG's current ideology.




UMG High-Level Philosophy:

  • Follow the 80/20 rule

  • Less code > more code

  • Readibility and Elegance > efficiency (Unless it's really bad)

  • Simple and assumptionless > Complex and flexible

  • Bloat is the greatest evil

  • State is an evil neccessity:

    • Try to keep the program state inside of components.
    • If that's not possible, immutable and short-lived state is 2nd-best.
    • State should ideally have a SSOT
  • Regular data and functions > Objects and classes





Systems and Emergence:

  • DO embrace chaotic, interacting systems

  • DONT fight existing systems to achieve something. Instead:

    • Find another way to achieve a similar result
    • ALTERNATIVELY; go back, and ask what the user is gaining from this feature
  • If something is unnatural to implement, try avoid implementing it





Mod Cohesion:

  • The purpose of base-mods is to provide cohesion/connections within the ecosystem;

    • NOT to reuse code!
  • If mod-A and mod-B know nothing about each another, they should be able to exist cohesively;

    • (UNLESS they are both playable-mods.)

Hackiness:

Its OK for playable/addon mods to be hacky/poorly designed.
But its NOT OKAY for base mods to be hacky/poorly designed.





Component design:

Unifying components:

Components should never have co-dependencies.
Example:

ent.uiElement = Element()
ent.uiRegion = {x,y,w,h}
--[[
When an entity has these two components,
they are rendered as a UI element on screen.
]]

But... this is dumb.
Why not just have a singular component?

ent.ui = {
element = Element(),
region = {x,y,w,h}
}

This is so much better, because:

  • It tells the modder that they need BOTH region and element
  • It is less bloated

Splitting up components:

Ideally, components should have ONE well-defined purpose.

If we think a component is doing too much, we might want to decouple/split it up.

For example:

ent.playerControl = {
clientId = "30943434343043",
type =
}