Welcome to the Zenith Framework! Based on Clean Architecture, Clean Code, Game Programming Patterns and industry experience, this Unity framework is designed to help new and experienced developers start their projects faster while ensuring scalability and reliability as the codebase grows.
When developing medium-to-large-scale Unity projects, maintaining clean, modular, and scalable code becomes challenging. The Zenith Framework is built on principles derived from Clean Architecture to solve this problem, ensuring:
- Rapid Development: Start your projects with a solid architecture and ready-to-use systems, reducing the setup time.
- Maintainability: Easily locate and fix bugs.
- Scalability: Expand your game systems and add new features without breaking existing functionality.
- Reusability: Create reusable components that can speed up future projects.
The framework utilizes Component and ServiceLocator architectures to keep your code decoupled and flexible. It emphasizes creating modular systems, enabling developers to replace or extend functionality without widespread code changes.
Zenith Framework is grounded in these three architectural principles:
-
Common Closure Principle:
- Group classes that change for the same reason to simplify maintenance.
-
Common Reuse Principle:
- Avoid dependencies on classes you don't use.
-
Reuse/Release Equivalence Principle:
- Group classes to maximize reusability and minimize unnecessary coupling.
These principles ensure that your code remains clean, adaptable, and aligned with industry standards.
Reference: "Clean Architecture: A Craftsman's Guide to Software Structure and Design" by Robert C. Martin.
Treat the Zenith Framework like a tool—test it thoroughly before fully integrating it into your project. A long-term relationship starts with a few good dates! 😉
A Service is a core system that provides functionality widely used across your game. Examples include:
- Object Pooling
- VFX Spawner
- Time Manager
- Screen Manager
Why Services? Services are accessed via the ServiceLocator and use interfaces to enforce limited, controlled access. This design ensures that:
- Complex logic can be refactored without affecting dependent code.
- Systems remain decoupled for easier maintainability, testing and bugfixing.
- Replacement services can be easily swapped in with minimal changes.
Example: Switching from Asset Bundles to Addressables in your Object Pooling system requires only creating a new implementation of the same interface.
Before refactor:
After refactor:
A Entity is a MonoBehaviour that manages events, data, and states within a specific context. It serves as the bridge between the Services and Components.
Entitys should:
- Hold state information relevant to a feature or system.
- Act as a hub for other Components to update or retrieve state data.
General application example:
A Component implements the primary logic of a feature while interacting with Services or Entitys. Components are designed to:
- Focus on a single feature per class.
- Be reusable across different systems or contexts.
Relationships with Entitys:
- Update the Entity's state when necessary.
- Implement the features associated with the Entity.
This separation ensures that changes to a specific feature only affect the associated Component, leaving Entitys and Services intact.
Example: A HealthComponent manages the health logic, while the HealthEntity tracks and communicates health states to other systems.
The Event Service enables decoupled communication between Components and Entitys, ensuring they remain independent and reusable.
- Create Your Event Class:
- Create a script with the name of your event (e.g.,
EnableInputsEvent) in yourEventsfolder. - Make this script inherit from
GameEventBase.
- Create a script with the name of your event (e.g.,
public class EnableInputsEvent : GameEventBase
{
public bool Enable { get; private set; }
public EnableInputsEvent(bool enable)
{
Enable = enable;
}
}-
Subscribe to the event:
- Use the IEventService.AddEventListener method to subscribe to your event (in this case: EnableInputsEvent). Ensure to pass your method and a unique hash code.
public class CombatentAIComponent : MonoBehaviour
{
[SerializeField] private InputActionAsset _inputActions;
private bool _isEnabled = false;
private IEventService _eventService;
private async void Awake()
{
_eventService = await ServiceLocator.GetService<IEventService>();
_eventService.AddListener<EnableInputsEvent>(HandleEnableInputs, GetHashCode());
}
private void HandleEnableInputs(EnableInputsEvent inputEvent)
{
if (inputEvent.Enable)
{
// Implementation here...
}
else
{
// Different implementation here...
}
}
}- Invoke the event with:
- IEventService.TryInvokeEvent(new YourEvent(data));
public class GameSystemComponent : MonoBehaviour
{
private bool _fightIsOn = false;
private IEventService _eventService;
private void Awake()
{
StartMatch();
}
private async void StartMatch()
{
await Task.Delay(1000);
_eventService = await ServiceLocator.GetService<IEventService>();
_eventService.TryInvokeEvent(new EnableInputsEvent(true));
_fightIsOn = true;
}
}-
You can unsubscribe at any time.
- To prevent memory leaks or unexpected behavior, unsubscribe from events when they're no longer needed.
public class CombatentAiComponent : MonoBehaviour ... private void OnDestroy() { _eventService.RemoveListener<EnableInputsEvent>(GetHashCode()); }
Example Use Case: Notify all relevant systems when the game starts without creating direct dependencies between them.
How to Use the Screen Service
Loads and manage screens in Unity projects. Follow these steps to understand and implement it effectively in your projects.
- Understanding the Screen Service
The ScreenService implementation allows you to load a prefab dynamically, in this case, from the Unity Resources folder. The prefab is identified by appending the suffix Screen to the name of the provided GameScreen enum value. The service ensures decoupled and maintainable screen management in your project, with no need for serializable references.
Key Method:
void LoadScreen<T>(GameScreen gameScreen, Action<T> openedCallback) where T : ScreenControllerBase;- Using the Screen Service
Here’s an example of how to load and use the Screen Service.
Example Implementation:
public class ScreenServiceTest : MonoBehaviour
{
private void Start()
{
Initialize();
}
private async void Initialize()
{
IScreenService screenService = await ServiceLocator.GetService<IScreenService>();
screenService.LoadScreen<ScreenControllerTest>(GameScreen.EXAMPLE, OnLoadScreen);
}
private async void OnLoadScreen(ScreenControllerTest screen)
{
...
}
}-
Steps to Use the Screen Service
1.Define Your Screens: Create an enum for all your screens. For example:
public enum GameScreen
{
EXAMPLE,
MAIN_MENU,
GAMEPLAY
}2.Create a Prefab and place it in the Resources folder. Name it after the GameScreen enum value with the suffix Screen (e.g., ExampleScreen.prefab).
3.Implement a Controller script extending ScreenControllerBase for your custom screen behavior.
public class ExampleScreenController : ScreenControllerBase
{
public override void Open()
{
base.Open();
//...
}
public override void Close()
{
base.Close();
//...
Destroy(gameObject);
}
}4.Load the Screen using LoadScreen(GameScreen screen, Action callback) to dynamically load and manage the screen, using the callback provided to modify the screen or perform some logic after it is loaded and opened.
public class Example : MonoBehaviour
{
private async void Start()
{
OpenScreen();
}
private async void OpenScreen()
{
IScreenService screenService = await ServiceLocator.GetService<IScreenService>();
screenService.LoadScreen<ExampleScreenController>(GameScreen.EXAMPLE, OnLoadScreen);
}
private async void OnLoadScreen(ExampleScreenController screen)
{
//...
screen.Close();
}
}The Zenith Framework includes additional ready-to-use services such as:
Object Pooling: Efficiently reuse objects to reduce memory allocations and improve performance. More Services Coming Soon! (Stay tuned for additional services and documentation.)
- Open the Unity Package Manager.
- Select the 'Add package from Git URL' option.
- Paste https://github.com/gabrielgborges/zenith-framework.git
- Navigate to the Packages folder in your Unity project directory and open the manifest.json file using a text editor or IDE.
- Insert "gabi.zenith.framework": "https://github.com/gabrielgborges/zenith-framework.git" in the "dependencies" section of the manifest.json file.
- Save the changes to the manifest.json file and return to Unity, it will automatically download and integrate the Zenith Framework package.
Additional Notes: Ensure that Git is installed on your system because Unity requires Git to fetch packages directly from GitHub.
Follow these steps to integrate the Zenith Framework into your Unity project:
-
Download the Framework:
Clone the repository and paste in your project or include it using the UPM method above.
-
Set Up Services:
Use the ServiceLocator to register and access your custom services.
-
Use Entitys and Components:
Leverage Entitys for managing state and Components for implementing features.
-
Integrate Event-Driven Architecture:
Utilize the Event Service to enable decoupled communication between systems.
The Zenith Framework depends on the UniTask library for asynchronous operations. If you do not have UniTask installed, it will automatically be included as part of this framework's installation.
UniTask Repository: https://github.com/Cysharp/UniTask
If your project already includes the UniTask package, you might encounter dependency conflicts. Here’s how to resolve them:
OR
Contributions to the Zenith Framework are welcome! If you encounter issues or have ideas for new features, feel free to:
Submit a pull request. Open an issue on GitHub.
This project is licensed under the MIT License. See the LICENSE file for details.







