Skip to main content


This overview is meant to be as concise as possible.
I'm not going to waste your time.

You must be a programmer with some experience in lua to understand this.
Also, experience in love2d helps.

Table of Contents:

Mod folder structure:

Each mod has a folder structure as follows:

sounds/ -- sounds and images go here.
images/ -- they are automatically loaded on clientside.
-- images are automatically put in the texture atlas (graphics.atlas) for auto-batching.

file1.lua -- these files are automatically loaded
blah.lua -- on serverside

cl_file.lua -- these files are auto-loaded on clientside.
abc.lua -- nested directories work fine. `abc.lua` is loaded.

-- these files are auto-loaded on BOTH serverside and clientside

-- entities are defined here.
-- entities are loaded on both client and server.
ball.lua -- ball entity definition file

mod_config.json -- config file

All Files and folders starting with _ are ignored.

ECS Architecture:

This project uses something that resembles an Entity Component System.
(Almost) Everything in the world is just an entity.


An entity is just a glorified lua table, pretty much.
They contain "components", which are just key-values in the table.

We can give entitys components:

ent.myComponent = {}
ent.other = 2 -- can be any lua type.

And we access components normally too:


To define an entity type, we create a new file in entities/ folder.

To create an entity, we use server.entities.

local ent = server.entities.my_entity()

Read a more in-depth explanation here.


Groups are what contain entities.

If you are starting out, you likely won't need to use these.
Read more about them here.


Event buses and Question buses are the heart of UMG.
Without them, UMG would be worthless.

Event buses:

  • Dispatch events with
  • When we dispatch information, we don't care who responds
  • Respond to events with umg.on
  • (Similar to pub-sub design pattern)

Question buses:

  • Request information with umg.ask
  • When we gather information, we don't care who gives it
  • Provide information with umg.answer
  • (Similar to pub-sub design pattern, but in reverse)

Read more about them here.

Client-Server communication

In UMG, there isn't automatic syncing; lots has to be done by mods.

What IS synced:

  • creation of entities, (and their component values directly upon creation)
  • deletion of entities
  • joining / leaving of players

What ISN'T synced:

  • entity component values
  • local events

It's important to note, though, that a lot of syncing is done by base mods. For example, the inventory mod automatically syncs entity inventories.
Likewise, the xy mod will automatically sync entity positions/velocities

Client-server communication also uses callbacks:

Server side:

-- broadcasts `message1` to all clients.
-- you can send any lua data you want, even tables!
server.broadcast("message1", ent, 2, 3, "blah data")
-- entities are sent over by id, efficiently.

-- Same as broadcast, but it only sends to the client called `playerUsername`.
server.unicast(playerUsername, "message1", 1.0545, 2.9, -5, "data :)")

-- listens to `moveTo` messages sent by clients
server.on("moveTo", {
arguments = {checkString}, -- add typecheckers here.
handler = function(sender_username, msg, ...)
print("message from", sender_username)
print("data:", msg, ...)

Client side:

-- send a message to the server.  
-- (same as server.broadcast, but clientside)
client.send("moveTo", x, y)

-- listens to `message1` from the server
client.on("message1", function(x, y, z, blah)
print("received message1 from server: ", x, y, z, blah)

Any data can be sent across the network, except for functions and userdata. If a table is sent across, all nested tables will be serialized and sent across. If an entity is sent across, it will be efficiently serialized by id. Sending tables across is the most expensive. Try to only send numbers, strings, and entities across the network for best performance.

If you need to send a metatable across, take a look at the register function. (Remember; functions can't be serialized!)