EntityManager | A modular and reusable entity-component system

entitymanager-banner

A modular and reusable entity-component system



Overview of this resource

EntityManager is a flexible module that allows you to create reusable entities from any instance while attaching components and properties that enhance their functionality. Entities act as data containers, while components provide methods and logic that can be accessed globally anywhere, anytime.




Key features

  • Create and manage entities of any type.
  • Attach components that add reusable behaviors and functionality.
  • Retrieve entities and their components from anywhere in your game.
  • Simplify game logic by modularizing features like leaderstats, health systems, AI behaviors, and more, with the use of custom components.
  • Unlike your typical ECS module, EntityManager provides a simple and intuitive framework that you can use to manage your entities and create custom components.
  • Out of the box experience with annotations and guides.


Why should I use entity-component systems over OOP?

Entity-component systems are often chosen over object-oriented programming in performance-critical applications. Here are some key reasons why ECS might be preferred:

  • ECS structures data in a way that benefits cache locality, leading to faster processing.
  • OOP uses deep inheritance hierarchies, which can become rigid and hard to maintain.
  • ECS stores components in contiguous memory, improving data-oriented design and reducing cache misses.
  • ECS decouples behavior from data, making it easier to add, remove, or modify functionality without breaking existing code.


How does EntityManager target these points?

EntityManager has a strong foundation and effectively implements ECS principles, key strengths that make EntityManager stand out:

  • EntityManager is performance-oriented, entities store components in a modular way, reducing inheritance issues.
  • Cached entities improve lookup efficiency, avoiding unnecessary recreation.
  • EntityManager uses custom annotations to improve clarity and helps developers understand expected types.
  • Entities can add and remove components dynamically, allowing flexibility.
  • Provides a modular framework that allows developers to mix and match components without rigid dependencies. This makes it easy to add new features, replace systems, or optimize specific parts of the game without rewriting entire sections of code.
  • An intuitive framework that provides clear structure, readable API, and predictable behavior, this reduces development time and makes debugging much easier.

Key features EntityManager fails to cover as of now:

  • Components are stored in a dictionary but lack a system for batch updates or global iteration over specific components (e.g., physics, AI). A future update aims to address this.
  • There’s no event-driven system for components to react dynamically (e.g., OnUpdate(), OnDestroy()).
  • Client-side integration is untested, while it may work, features like server-locked entities and network synchronization are planned for future updates.


Getting started with EntityManager

Installation

  1. Get the module from the Roblox Creator Marketplace
  2. Manual Installation
    • If you prefer, you can manually source the code from the GitHub repository (coming soon)
    • After sourcing the code, place each module in its appropriate location within your game

Basic Usage

Here’s a quick example of how to use EntityManager in your game to automatically load player’s data when they join the game using the pre-packed MultiStats component:

First, let’s create a new server script inside of ServerScriptService, now let’s import our globals:

-- Globals
local ServerScriptService = game:GetService("ServerScriptService")
local Players = game:GetService("Players")

-- Require necessary modules
local Entity = require(ServerScriptService.Entity)
local Components = require(ServerScriptService.Entity.Components)

Additionally, if you want to include component-specific annotations, you can also require your component:

local MultiStats = require(ServerScriptService.Server.Modules.Entity.Components.MultiStats)

Now, let’s create our PlayerAdded listener:

Players.PlayerAdded:Connect(function(player)
	-- PlayerAdded listener
end)

Let’s create our first entity, using the Entity.new() function:

Players.PlayerAdded:Connect(function(player)
	-- Initialize the entity with latest id and player's identifier
	local entityManager = Entity.new(#Entity:GetCached(), player.Name)
end)

Now, let’s give our entity an identity, by changing it’s properties:

Players.PlayerAdded:Connect(function(player)
	-- Initialize the entity with latest id and player's identifier
	local entityManager = Entity.new(#Entity:GetCached(), player.Name)
	entityManager.Properties.Instance = player
end)

It’s about time we load our first component using the Component:Load() method, we will be using local component: MultiStats.Component to let our script know which annotations to use for type-checking:

Players.PlayerAdded:Connect(function(player)
	-- Initialize the entity with latest id and player's identifier
	local entityManager = Entity.new(#Entity:GetCached(), player.Name)
	entityManager.Properties.Instance = player
	
	-- Load the pre-packed MultiStats component
	local component: MultiStats.Component = Components:Load(entityManager, "MultiStats")
end)

Now let’s use our component’s AddCoins() method:

Players.PlayerAdded:Connect(function(player)
	-- Initialize the entity with latest id and player's identifier
	local entityManager = Entity.new(#Entity:GetCached(), player.Name)
	entityManager.Properties.Instance = player
	
	-- Load the pre-packed MultiStats component
	local component: MultiStats.Component = Components:Load(entityManager, "MultiStats")
	component:AddCoins(100)
end)

If we run the game, now we should have 100 coins!

image

Now, let’s use our component’s CanAfford() method to check if our player can afford a 500 coin item:

Players.PlayerAdded:Connect(function(player)
	-- Initialize the entity with latest id and player's identifier
	local entityManager = Entity.new(#Entity:GetCached(), player.Name)
	entityManager.Properties.Instance = player
	
	-- Load the pre-packed MultiStats component
	local component: MultiStats.Component = Components:Load(entityManager, "MultiStats")
	component:AddCoins(100)
	
	-- Check if player can afford a 500 coins priced item
	print(component:CanAfford(500))
end)

As expected, the output is false, our player only has 100 coins, so how would they be able to afford a 500 coin item?

Let’s continue by creating a new part, and adding a new script inside of it to see how we would retrieve entities and their components:

We’ll start off by requiring our modules and services:

-- Globals
local ServerScriptService = game:GetService("ServerScriptService")
local Players = game:GetService("Players")

-- Require necessary modules
local Entity = require(ServerScriptService.Server.Modules.Entity)

This time, we won’t be using the Components module, as we don’t need to load any components, just retrieve an existing one on the entity.

Let’s create our touch listener:

-- Touched event
script.Parent.Touched:Connect(function(part)
	-- Touch listener
end)

Let’s find our player:

-- Touched event
script.Parent.Touched:Connect(function(part)
	if part.Parent and part.Parent:FindFirstChild("Humanoid") then
		local character = part.Parent
		local player = Players:GetPlayerFromCharacter(character)
		
		if player then
			-- We have a player!
		end
	end
end)

Now let’s get this player’s entity object using the Entity:Get() method:

-- Touched event
script.Parent.Touched:Connect(function(part)
	if part.Parent and part.Parent:FindFirstChild("Humanoid") then
		local character = part.Parent
		local player = Players:GetPlayerFromCharacter(character)
		
		if player then
			local entity = Entity:Get(player)
		end
	end
end)

Let’s retrieve our MultiStats component using the entity:GetComponent() method:

-- Touched event
script.Parent.Touched:Connect(function(part)
	if part.Parent and part.Parent:FindFirstChild("Humanoid") then
		local character = part.Parent
		local player = Players:GetPlayerFromCharacter(character)
		
		if player then
			local entity = Entity:Get(player)
			local component = entity:GetComponent("MultiStats")
		end
	end
end)

Now we can use our component the same as before!


Next Steps

What happens next is up to you! You can create your own components using the pre-packed component for reference, I’m curious to see what you will make!


Feedback

If you encounter any issues or errors, or you need further assistance or guides on this project, do let me know in the replies below, I’m open to help! Also, let me know what you will create using my module, or if you plan on using it in the future, thank you for using EntityManager!


Do you plan on using EntityManager in the near future?

  • Yes
  • No
  • Not sure

0 voters

Do you believe this module could help developers?

  • Yes
  • No
  • Not sure

0 voters

Should I continue this project?

  • Yes
  • No
  • Not sure

0 voters

Have you tried it? How do you rate it?

  • 1
  • 2
  • 3
  • 4
  • 5
  • Not sure

0 voters



Changelog

Version v1.0.0-rc1* (release candidate, pre-release)

  • Created main modules and component support
  • Added component loaders
  • Created entity classes

Version v1.0.0-rc2* (release candidate, pre-release)

  • First public release
  • Added a pre-packed version of my module MultiStats translated to a component
  • Added annotations & type checking support
  • Minor bug fixes

Version v1.0.0-rc2.1* (release candidate, pre-release)

  • Included MIT license inside the project
  • Created a read me script inside the main module

Version v1.0.0 (stable release, latest)

  • Same as v1.0.0-rc2.1, now officially marked as stable
  • No major code changes, just confirming stability

*a release candidate is a version of a resource that is nearly ready for release but may still have some minor bugs, it is a beta version with the potential to be a stable product, which is ready to release unless significant bugs emerge



This resource is licensed under the MIT License. You are free to use, modify, and distribute it, but please include the original copyright and license notice in any copy of the resource that you distribute.

MIT License

Copyright (c) 2025 iAmDany (@Danielhoteaale)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.




entitymanager-banner-wide

A modular and reusable entity-component system

14 Likes

Is this an ECS or OOP composition?

ECS, however if you tweak some things you can probably use it as an OOP framework too.