{"id":18622180,"url":"https://github.com/renderheads/unityplugin-modulepattern","last_synced_at":"2025-04-11T03:31:10.804Z","repository":{"id":39317918,"uuid":"306012386","full_name":"RenderHeads/UnityPlugin-ModulePattern","owner":"RenderHeads","description":"A simple module pattern (in the classical sense) for building gameplay systems decoupled from the transform hierarchy (run in c# land and access from monobehaviour-land). ","archived":false,"fork":false,"pushed_at":"2023-03-28T13:31:35.000Z","size":73,"stargazers_count":9,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-25T08:42:30.022Z","etag":null,"topics":["module","pattern","unity"],"latest_commit_sha":null,"homepage":"https://www.renderheads.com","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RenderHeads.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-21T12:09:20.000Z","updated_at":"2024-11-16T09:19:56.000Z","dependencies_parsed_at":"2022-09-18T03:41:08.004Z","dependency_job_id":null,"html_url":"https://github.com/RenderHeads/UnityPlugin-ModulePattern","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RenderHeads%2FUnityPlugin-ModulePattern","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RenderHeads%2FUnityPlugin-ModulePattern/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RenderHeads%2FUnityPlugin-ModulePattern/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RenderHeads%2FUnityPlugin-ModulePattern/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RenderHeads","download_url":"https://codeload.github.com/RenderHeads/UnityPlugin-ModulePattern/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248335413,"owners_count":21086588,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["module","pattern","unity"],"created_at":"2024-11-07T04:15:54.418Z","updated_at":"2025-04-11T03:31:10.442Z","avatar_url":"https://github.com/RenderHeads.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://renderheads-file-share.s3.af-south-1.amazonaws.com/assets/renderheads.svg\" width=50%\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eRenderHeads ©2021\u003c/b\u003e\n\u003c/p\u003e\n\u003cp align =\"center\"\u003e Author / Maintainer: Ross Borchers \u003c/p\u003e\n\n\n# Module pattern for Unity\n\nThe module pattern is a framework for **building gameplay systems decoupled from the transform hierarchy.**\nThe framework provides a very light set of tools to create acyclical, decoupled caller hierarchies in code, and **allows you to access them from MonoBehaviour-land.**\n\n[A module in a classical sense!](https://en.wikipedia.org/wiki/Modular_programming) A decoupled, cohesive and replaceable piece of functionality, hidden behind an interface. Most of the time, modules are plain C# classes, but they could be anything you like. \n\nWe also provide a factory implementation to access modules program or scene wide, so they can improve on the singleton pattern and provide access at different levels of scope.\n\n**Have you experienced one or more of the following symptoms?**\n\n- _**Uncertainty**_ surrounding which components depend on one another?\n- _**Disbelief**_ staring at a mess of duplicated game objects after project hand-over?\n- _**Dread**_ while searching for a component in the hierarchy editor window?\n- _**Trepidation**_ related to moving a component to a different GameObject?\n- _**Concern**_ over linking components together in the hierarchy and whether they will randomly become unlinked?\n- _**Anxiety**_ while debugging, caused by forgetting to assign a reference?\n- _**Unease**_ building the transform hierarchy for systems that have no reason to be represented spatially?\n- _**Confusion**_ when disabled GameObjects erroneously disable an important component?\n\n## The module pattern was built to help avoid these issues\n\n\n## Features\n- Build your module hierarchy in code.\n- Access modules anywhere, globally and on a per scene basis, without overusing singletons.\n- Create modules as plain C# classes which open up the possibility for new design patterns:\n   - Adhere to [RAII](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization) principles.\n   - Use [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control) and DI.\n   - Manual control of module update order.\n\n### The Module Pattern is not\n\n- It is **not** a replacement or in competition with DOTS, Jobs, or Burst. You can use them together.\n- It is **not** a replacement or in competition with MonoBehaviours, MonoBehaviours are still useful for view related logic.\n\n# Installation\nUsing Unity's Package Manager (UPM), select Add New Package from Git URL, and paste the following URL: https://github.com/RenderHeads/UnityPlugin-ModulePattern.git?path=/Packages/RH.ModulePattern\n\n# Usage\nThere are only three useful files in this repository:\n- [IModule](https://github.com/RenderHeads/UnityPlugin-ModulePattern/blob/master/Packages/RH.ModulePattern/Runtime/IModule.cs) \n    - Interface for any module you want to create (A C# class that extends IModule), IModuleFactory requires modules inherit from this.\n- [IModuleFactory](https://github.com/RenderHeads/UnityPlugin-ModulePattern/blob/master/Packages/RH.ModulePattern/Runtime/IModuleFactory.cs)\n    - Interface responsible for storing modules. This is used to get Modules in your game, whether it be in MonoBehaviours, Systems, etc.\n- [DefaultModuleFactory](https://github.com/RenderHeads/UnityPlugin-ModulePattern/blob/master/Packages/RH.ModulePattern/Runtime/DefaultModuleFactory.cs) is an implementation of [IModuleFactory](https://github.com/RenderHeads/UnityPlugin-ModulePattern/blob/master/Packages/RH.ModulePattern/Runtime/IModuleFactory.cs).\n    - It provides a standard way to get modules across your game and should be appropriate for 90% of use cases.\n\nNote that we use interfaces wherever possible as a matter of principle to maintain decoupling. This abstraction is enforced in DefaultModuleFactory, but it is not necessarily required if you define your own module factory. It's often faster to develop without abstraction.\n\n### Example\nYou can use a MonoBehaviour to bootstrap the module system, but there are other ways to bootstrap the module system. You just need a way to create and update the module factory and modules. Using a GameManager/LevelManager's Awake/Start and Update functions suffice most of the time.\n \n- Making a module factory for each scene you want to have a dedicated group of modules:\n```\n//We specify the scene this factory is created in so we can search for modules by scene.\nIModuleFactory moduleFactory = new DefaultModuleFactory(gameObject.scene);\n```\n\n- Initializing modules and dependencies, adding them to the module factory:\n```\nIMyModule1 module1 = moduleFactory.AddModule(new MyModule1());\nIMyModule2 module2 = moduleFactory.AddModule(new MyModule2(module1)); //Module2 depends on module 1.\n```\n\n- After initialization, accessing modules from a MonoBehaviour:\n```\n//Get factory by scene\nif(DefaultModuleFactory.TryFindFactoryInScene(this.gameObject.scene, out IModuleFactory factory))\n{\n   //Get module from factory\n   if(factory.TryGetModule(out IModule1 module1))\n   {\n      module1.FooBar();\n   }\n}\n\n//Get the first module of type in all factories across all scenes.\nif(DefaultModuleFactory.TryFindFirstInAll(out IModule1 module1))\n{\n   module1.FooBar();\n}\n\n//Get all modules of type in all factories across all scenes.\nList\u003cIModule1\u003e modules = DefaultModuleFactory.FindInAll\u003cIModule1\u003e();\n\n//Get all module factories for all scenes\nList\u003cIModuleFactory\u003e factories = DefaultModuleFactory.GetAll();\n\n```\n\n# Rationale\n\nInter-class communication traditionally is achieved by a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern), [FindObjectOfType](https://docs.unity3d.com/ScriptReference/Object.FindObjectOfType.html) or [GetComponent](https://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html), [GetComponentInChildren](https://docs.unity3d.com/ScriptReference/Component.GetComponentsInChildren.html), [GetComponentInParent](https://docs.unity3d.com/ScriptReference/GameObject.GetComponentInParent.html) and related functions. Sometimes you may assign a reference in an inspector field.\n\nAll of these assume some relationship between the transform hierarchy and the code structure. This is not always the case. Systems can contain logic but do not handle rendering and have no reason to be spatial - they are not view components! The relationship does not need to be serialized in the scene or prefab. There may be little relationship between transform hierarchy order and the caller hierarchy. It's convoluted.\n\nScript update is typically controlled by MonoBehaviour Start/Update/FixedUpdate/LateUpdate callbacks. This requires that scripts inherit from MonoBehaviour, which couples script update to the transform hierarchy. There is no reason this needs to be the case. Furthermore, MonoBehaviour update order is notoriously hard to control and understand and can lead to one-frame-late type bugs. [Not to mention the performance implications!](https://blogs.unity3d.com/2015/12/23/1k-update-calls/)\n\nBased on these observations we chose to separate the caller hierarchy for non-view systems from the scene tree hierarchy. In our projects, it has helped us decouple, standardize and re-use code and helped developers jump between projects faster than they otherwise would have been able to.\n\n# Sample Project\n\n## Project Description\nThe project shows a simple use case for the module pattern, where two modules perform and indepedant function button are dependant on each other. In this case, the one module spawns objects (ISpawnerModule), and the other module moves objects (IMoverModule).\n\n## Application Entrypoint\nThe application entrypoint is [GameManager.cs](https://github.com/RenderHeads/UnityPlugin-ModulePattern/blob/master/Assets/RenderHeads/Sample/Scripts/GameManager.cs) and has the responsibility of setting up and modules and updating them.  The setup happens in the *Awake()* method and the update step happens in the *Update()* method\n\nThe Awake method reads as follows\n```\n       void Awake()\n        {\n            //Create a new defaultmodule factory and pass in the active scene as a reference, so this particular module can be scoped to the scene\n            DefaultModuleFactory _factory = new DefaultModuleFactory(gameObject.scene);\n\n            //declare our spawner module for spawning objects in the scene, and set spawn immediately = true\n            _spawnerModule = new ConsistentSpawnModule(_spawnPrefab, true);\n\n            //declare our mover module for moving objects around\n            _moverModule = new MoverModule(_spawnerModule);\n\n            //add our modules to the factory so they can be accessed outside this scope.\n            _factory.AddModule(_spawnerModule);\n            _factory.AddModule(_moverModule);\n\n            SetupViewCallbacks();\n        }\n```\n\nYou will notice we declare two modules, a spawner module, and a mover module.  The Mover module takes the Spawner module in as a constructor, as it uses some of its functionality to perform its function. This clearly defines a dependency of one on the other (so we are ensured that these two modules are initialized in the right order).\n\nFinally we add these modules to the factory so we can utilize them in other places. It is important to note in this sample we declare these types as interfaces so it is easy to swap out the implementation if we wish to do so\n\nThe Update Method reads as follows \n```\n        /// \u003csummary\u003e\n        ///  we will manually call the UpdateModule for each module that we have defined, in the order we want, so we can be guarenteed of update order.\n        /// \u003c/summary\u003e\n        void Update()\n        {\n            _spawnerModule.UpdateModule();\n            //update our move module after the spawning has taken place\n            _moverModule.UpdateModule();\n        }\n```\n\nIn this particular pattern, we have to manually call UpdateModule in an update loop somewhere (typically a global game manager). This ensures that our update order is as we specify it.\n\n\n\n## Module Structure\nThis projects has 2 modules,  ISpawnerModule and IMoverModule\n\nEach module is made up of 2 files: an interface file and an implementation file. \n\nThe interface needs to implement from IModule:\n```\n    public interface IMoverModule : IModule\n```\n\nand the implementation needs to implement the interface:\n```\n    public class MoverModule : IMoverModule\n```\n\n## Accessing Modules\nThe sample also has a file called *GameView* which controls the view of the application (in this case just allows us to toggle on and off spawning and movement).\n\nThe view fetches a references from the DefaultModuleFactory for our two modules in the Start Methods\n```\n        void Start()\n        {\n            if (!DefaultModuleFactory.TryFindFirstInAll(out _spawnerModule))\n            {\n                throw new System.Exception(\"[GameView] Could not find SpawnerModule, was it declared in game manager Awake?\");\n            }\n\n            if (!DefaultModuleFactory.TryFindFirstInAll(out _moverModule))\n            {\n                throw new System.Exception(\"[GameView] Could not find MoverModule, was it declared in game manager Awake?\");\n            }\n\n            _spawnButton.onClick.AddListener(() =\u003e { OnSpawnerClick.Invoke(); });\n            _moverButton.onClick.AddListener(() =\u003e { OnMoverClick.Invoke(); });\n         }\n```\n\nand then utilizes the functions exposed by the interface in the update method\n\n```\n        void Update()\n        {\n            _countLabel.text = $\"Total Spawned: {_spawnerModule.GetSpawnCount()}\";\n            _spawnButtonText.text = $\"{(_spawnerModule.IsSpawning() ? \"Stop\" : \"Start\")} spawning\";\n            _moveButtonText.text = $\"{(_moverModule.IsMoving() ? \"Stop\" : \"Start\")} moving\";\n        }\n```\n\n# Usage and Contribution\n## Usage License\nThe project is licensed under a GPL-3.0 license, which means you can use it for commerical and non commercial use, but the project you use it in also needs to apply the GPL-3.0 license and be open-sourced. If this license is not suitable for you, please contact us at southafrica@renderheads.com and we can discuss an appropriate commercial license.\n\n## Contributors License\nWe are currently working on a Contributors License Agreement, which we will put up here when it's ready. In the meantime, if you would like to contribute, please reach out to us.\n  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenderheads%2Funityplugin-modulepattern","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frenderheads%2Funityplugin-modulepattern","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenderheads%2Funityplugin-modulepattern/lists"}