{"id":13663534,"url":"https://github.com/lazlo-bonin/gocs","last_synced_at":"2025-04-10T21:22:10.108Z","repository":{"id":51332897,"uuid":"215885004","full_name":"lazlo-bonin/gocs","owner":"lazlo-bonin","description":"GameObject Component System for Unity","archived":false,"fork":false,"pushed_at":"2020-12-21T21:49:53.000Z","size":286,"stargazers_count":115,"open_issues_count":4,"forks_count":11,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-03-24T18:52:25.382Z","etag":null,"topics":["ecs","unity","unity-scripts","unity2d","unity3d"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lazlo-bonin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-17T21:01:30.000Z","updated_at":"2025-02-27T21:04:07.000Z","dependencies_parsed_at":"2022-09-10T23:40:21.740Z","dependency_job_id":null,"html_url":"https://github.com/lazlo-bonin/gocs","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/lazlo-bonin%2Fgocs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lazlo-bonin%2Fgocs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lazlo-bonin%2Fgocs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lazlo-bonin%2Fgocs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lazlo-bonin","download_url":"https://codeload.github.com/lazlo-bonin/gocs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248299024,"owners_count":21080449,"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":["ecs","unity","unity-scripts","unity2d","unity3d"],"created_at":"2024-08-02T05:02:29.934Z","updated_at":"2025-04-10T21:22:10.088Z","avatar_url":"https://github.com/lazlo-bonin.png","language":"C#","funding_links":[],"categories":["C\\#","Open Source Repositories","C#","Open Source Packages"],"sub_categories":["Framework"],"readme":"# GameObject Component System (GoCS) for Unity\n\n💡 GoCS (pronounced ***go-cee-ess***) is a design pattern for Unity. It's inspired by entity component system (ECS), but it works with the existing Unity GameObject + Component architecture, hence the name. It's also more flexible and less strict than ECS. As such, it's easy to get started using the concepts and API you're already familiar with. \n\n🏁 The goal of GoCS is to help you write code that is readable, reusable and maintanable. It elegantly scales to any project size, from small to big. If you ever found yourself asking \"where should I put this code?\", then GoCS is here to help.\n\n🔀 To be clear, GoCS is entirely unrelated to Unity's new [data oriented tech stack (DOTS)](https://unity.com/dots).\n\n🎓 Before you continue, if you're a bit confused with acronyms and concepts like ECS, OOP, IOC or DOTS, you should start with this basic introduction: [Intro: OOP vs ECS in Unity](Documentation~/Manual/Intro.md)\n\n# Why GoCS?\n\nGoCS helps solve some common headaches when developing Unity projects:\n\n- You use OOP, but your component inheritance hierarchy quickly becomes messy\n- You use ECS, but you are constantly writing boilerplate code for simple tasks\n- You need an event system, but you're unsure how to implement them properly\n\nIn tech lingo, GoCS excels at combining encapsulation, composition and inversion of control.\n\n|**Benefit**|**OOP**|**ECS**|**GoCS**|\n|---|---|---|---|\n|Encapsulation|✅|⛔|✅|\n|Scalability|⛔ (Inheritance)|✅ (Composition)|✅ (Both)|\n|Inversion of Control|⛔|⛔|✅|\n|High Performance|⛔|✅ (DOTS)|⛔|\n\nGoCS is **not** for you if you need the high performance benefits of multi-threading, burst compiling, and memory layouting. For those case, you should use [DOTS](https://unity.com/dots).\n\n# Getting Started\n\nGoCS is a very lightweight framework imported from the Unity Package Manager.\n\n### Requirements\n\nGoCS requires **Unity 2018.4** **or newer**.\n\n### Installing\n\nTo import GoCS, open `Packages/manifest.json` and add this line under `dependencies`:\n\n```json\n\"dev.lazlo.gocs\": \"https://github.com/lazlo-bonin/gocs.git\"\n```\n\n### Updating\n\nTo update GoCS, open `Packages/manifest.json` and **remove** the `dev.lazlo.gocs` entry under `lock` at the end of the file.\n\n### Namespace\n\nAll the GoCS API is under the `Lazlo.Gocs` namespace:\n\n```csharp\nusing Lazlo.Gocs;\n```\n\n### License\n\nGoCS is shared under the **MIT License**. This means you're free to use and redistribute it in your games and other projects, even commercially. For the full license, see [LICENSE.md](LICENSE.md).\n\n# GoCS in a nutshell\n\nGoCS is designed to be simple to implement. Once you wrap your head around the core concepts below, you should be able to start using it with only the basics of C# and Unity. \n\nGoCS is **similar** to traditional ECS: it's organized as **E**ntities, **C**omponents and **S**ystems.\n\n|**Concept**|**Purpose**|**Related API**|\n|---|---|---|\n|⛓ Entities|Empty shells that link Components together|`GameObject`\n|🧩 Components|Define data for runtime and authoring for the editor|`IComponent` + `BaseComponent`\n|⚙️ Systems|Perform common logic on batches of components|`BaseSystem`\n\nGoCS is **different** from traditional ECS in two ways:\n\n1. Components are separated in two layers: the **Interface** and the **Class(es)**. Systems only operate on component interfaces. But component classes can add extra logic, data, attributes or even inheritance and couplings if they need to. (Don't worry if that sounds abstract for now, it'll become clearer with examples!)\n2. Systems are in charge of event dispatch. In other words, they decide when to send events back to the components. This is what's called **\"inversion of control\"**: your components don't need to handle their own event *conditions*, they only need to handle their event *reactions*.\n\n# Example\n\nLet's say we have a first person game where the player can interact with objects in the world.\n\nWe want to use GoCS to create a setup where all the code to hover and click objects is handled by a common System, so that our Components only need to implement the reactions to those events.\n\n## Step 1: Create the Component Interface\n\nLet's start by creating a component interface that defines interactable objects. \n\nThe component interface is responsible for declaring:\n\n- 🎛 **Attributes:** Immutable (read-only) properties needed by the system.\n- 📦 **Data:** Mutable (read+write) properties needed by the system.\n- ⚡️ **Events:** Callbacks that the system can trigger or register.\n\n```csharp\n// Component Interfaces must implement the IComponent interface\npublic interface IInteractable : IComponent\n{\n    // Attributes \n    // (immutable, so only { get; })\n    float range { get; }\n\n    // Data \n    // (mutable, so both { get; set; })\n    bool isHovered { get; set; }\n    bool isPressed { get; set; }\n\n    // Events \n    // (only { get; })\n    // (\"Event\" is a minimal event wrapper class included in the GoCS API)\n    Event onHoverEnter { get; }\n    Event onHoverExit { get; }\n    Event onPress { get; }\n    Event onRelease { get; }\n}\n```\n\n## Step 2: Create the Component Class(es)\n\nThe component class is responsible for providing attribute values and handling events.\n\nIn GoCS, you can define more than one component class per component interface. That's very powerful, because they will all reuse the same logic from the system without having to resort to inheritance. Therefore, each of these classes stays simple and focused on a single responsibility. \n\nTo demonstrate that, we'll create two different interactable classes from the same interactable interface.\n\n### 2.1. Grabbable\n\nThe grabbable component allows the player to move an object around by clicking and holding it. It does so by simply changing the object's transform parent to the main camera.\n\n```csharp\n// Component Classes are derived from the BaseComponent class\n// They must also implement their matching Component Interface\npublic class Grabbable : BaseComponent, IInteractable\n{\n    // Attributes\n    // (Tip: use [SerializeField] to expose the range field to the inspector\n    // while also implementing the interface's range property.)\n    [SerializeField] \n    private float _range = 5;\n    public float range \n    {\n        get =\u003e _range;\n        set =\u003e _range = value;\n    }\n    \n    // Data\n    public bool isHovered { get; set; }\n    public bool isPressed { get; set; }\n\n    // Events\n    public Event onHoverEnter { get; private set; }\n    public Event onHoverExit { get; private set; }\n    public Event onPress { get; private set; }\n    public Event onRelease { get; private set; }\n\n    // Initialization\n    protected override void Awake()\n    {\n        base.Awake();\n        \n        // Create events and link them to our handlers\n        onPress = new Event(Grab);\n        onRelease = new Event(LetGo);\n    }\n\n    // Extra Logic\n\n    private void Grab()\n    {\n        transform.parent = Camera.main.transform;\n    }\n\n    private void LetGo()\n    {\n        transform.parent = null;\n    }\n}\n```\n\n### 2.2. Highlightable\n\nThe highlightable component changes the object's color when it gets hovered, and sets it back to its normal color afterwards. \n\nThis component makes use of GoCS' ability to add extra attributes, data and couplings to  component classes when you need to.\n\n```csharp\n[RequireComponent(typeof(Renderer))]\npublic class Highlightable : BaseComponent, IInteractable\n{\n    // Attributes\n    [SerializeField]\n    private float _range = 5;\n    public float range \n    {\n        get =\u003e _range;\n        set =\u003e _range = value;\n    }\n    \n    // Data\n    public bool isHovered { get; set; }\n    public bool isPressed { get; set; }\n\n    // Events\n    public Event onHoverEnter { get; private set; }\n    public Event onHoverExit { get; private set; }\n    public Event onPress { get; private set; }\n    public Event onRelease { get; private set; }\n\n    // Extra Attributes\n    [SerializeField]\n    private Color _color = Color.yellow;\n    public Color color \n    {\n        get =\u003e _color;\n        set =\u003e _color = value;\n    }\n\n    // Extra Data\n    private Color normalColor;\n\n    // Extra Coupling\n    private Renderer renderer;\n\n    // Initialization\n    protected override void Awake()\n    {\n        base.Awake();\n        renderer = GetComponent\u003cRenderer\u003e();\n        onHoverEnter = new Event(Highlight);\n        onHoverExit = new Event(ResetColor);\n    }\n    \n    // Extra Logic\n\n    private void Highlight()\n    {\n        normalColor = renderer.material.color;\n        renderer.material.color = color;\n    }\n\n    private void ResetColor()\n    {\n        renderer.material.color = normalColor;\n    }\n}\n```\n\n## Step 3: Create the System\n\nThe system is a class responsible for implementing the reusable logic and for triggering events.\n\nHere, we'll create a basic system that uses a raycast to find the object below the player's cursor at the center of the screen. \n\nIt stores and compares the currently hovered and pressed objects across frames to determine when they change and trigger the related events accordingly.\n\n```csharp\n// Systems are derived from the BaseSystem class\npublic class InteractionSystem : BaseSystem\n{\n    private IInteractable hovered;\n    private IInteractable pressed;\n\n    private void Update()\n    {\n        if (pressed == null)\n        {\n            var camera = Camera.main;\n            var ray = new Ray(camera.transform.position, camera.transform.forward);\n\n            IInteractable interactable = null;\n\n            if (Physics.Raycast(ray, out var hit, Mathf.Infinity, (LayerMask)~0))\n            {\n                interactable = hit.collider.gameObject?.GetComponentInParent\u003cIInteractable\u003e();\n\n                if (interactable != null \u0026\u0026 hit.distance \u003e interactable.range)\n                {\n                    interactable = null;\n                }\n            }\n\n            if (interactable != hovered)\n            {\n                if (hovered != null)\n                {\n                    hovered.onHoverExit?.Invoke();\n                    hovered.isHovered = false;\n                }\n\n                hovered = interactable;\n\n                if (hovered != null)\n                {\n                    hovered.onHoverEnter?.Invoke();\n                    hovered.isHovered = true;\n                }\n            }\n\n            if (hovered != null \u0026\u0026 Input.GetMouseButtonDown(0))\n            {\n                pressed = hovered;\n                pressed.onPress?.Invoke();\n                pressed.isPressed = true;\n            }\n        }\n\n        if (pressed != null \u0026\u0026 Input.GetMouseButtonUp(0))\n        {\n            pressed.onRelease?.Invoke();\n            pressed.isPressed = false;\n            pressed = null;\n        }\n    }\n}\n```\n\n## 4. (Optional) Base Component Class\n\nBecause GoCS allows inheritance in component classes, you can reduce the amount of boilerplate even further.\n\nFor example, you could create a `BaseInteractable` class from which `Grabbable` and `Highlightable` inherit.\n\n```csharp\n// BaseInteractable.cs\n\npublic abstract class BaseInteractable : BaseComponent, IInteractable\n{\n    protected override void Awake()\n    {\n        base.Awake();\n        onHoverEnter = new Event(OnHoverEnter);\n        onHoverExit = new Event(OnHoverExit);\n        onPress = new Event(OnPress);\n        onRelease = new Event(OnRelease);\n    }\n\n    // Attributes\n    [SerializeField]\n    private float _range = 5;\n    public float range \n    {\n        get =\u003e _range;\n        set =\u003e _range = value;\n    }\n\n    // Data\n    public bool isHovered { get; set; }\n    public bool isPressed { get; set; }\n\n    // Events\n    public Event onHoverEnter { get; private set; }\n    public Event onHoverExit { get; private set; }\n    public Event onPress { get; private set; }\n    public Event onRelease { get; private set; }\n\n    // Event Handlers\n    protected virtual void OnHoverEnter() { }\n    protected virtual void OnHoverExit() { }\n    protected virtual void OnPress() { }\n    protected virtual void OnRelease() { }\n}\n```\n\nThen, the derived classes become extremely simple and eloquent, which is the the ultimate goal of GoCS! \n\nFor example, `Grabbable` now only needs a dozen lines of code:\n\n```csharp\npublic class Grabbable : BaseInteractable\n{\n    protected override void OnPress()\n    {\n        transform.parent = Camera.main.transform;\n    }\n\n    protected override void OnRelease()\n    {\n        transform.parent = null;\n    }\n}\n``` \n\nNeat! ✨\n\n---\n\n# Advanced Use\n\n## Component Queries\n\nIn ECS patterns, you'll typically want systems to operate on GameObjects that share a set of components. \n\nGoCS provides helpers to make that easy. These helpers use `out` parameters and the [tuple deconstruction syntax](https://docs.microsoft.com/en-us/dotnet/csharp/deconstruct) to keep the code readable.\n\nFor the following examples, we'll pretend we want a health regeneration system that slowly regens the health of game objects that have both `IHealth` and `IRegenable` components attached.\n\nOn individual GameObjects, you an use the `Has` and `Get` to fetch sets of components.\n\n### GameObject.Has\n\n`Has` will only return true if all the components are found on the given object:\n\n```csharp\nif (gameObject.Has(out IHealth health, out IRegenable regenable))\n{\n    health.value += regenable.healthPerSecond * Time.deltaTime;\n}\n```\n\n### GameObject.Get\n`Get` will always return a tuple of components, but some of them may be null if they are not on the object. Only use it if you know for certain that all components are present.\n\n```csharp\nvar (health, regenable) = gameObject.Get\u003cIHealth, IRegenable\u003e();\n\nif (health != null \u0026\u0026 regenable != null)\n{\n    health.value += regenable.healthPerSecond * Time.deltaTime;\n}\n```\n\n### World.Query\n\nTypically, systems will want to run a `foreach` loop on all game objects that share a set of components. To do that, you can use `World.Query`. Unlike `FindObjectsOfType`, this method is optimized for speed and allocates zero byte of memory.\n\n```csharp\nclass HealthRegenSystem : BaseSystem\n{\n    void Update()\n    {\n        foreach (var (health, regenable) in World.Query\u003cIHealth, IRegenable\u003e())\n        {\n            health.value += regenable.healthPerSecond * Time.deltaTime;\n        }\n    }\n}\n```\n\nIf you need to use a world query in the while in edit-mode, you must pass `true` to the `forceNative` argument of the `World.Query` method. This will make it revert to the slower `FindObjectsOfType` in the background, which is required because the components are only properly cached during play mode.\n\nFor example, if we wanted to draw a gizmo for the range of each of our interactables, we could add this code to the interaction system:\n\n```csharp\nclass InteractionSystem : BaseSystem\n{\n    // (Previous code...)\n\n    void OnDrawGizmos()\n    {\n        Gizmos.color = Color.magenta;\n\n        foreach (var interactable in World.Query\u003cIInteractable\u003e(forceNative: true))\n        {\n            Gizmos.DrawWireSphere(interactable.transform.position, interactable.range);\n        }\n    }\n}\n```\n\n### SystemComponents\n\nFor even better performance, you can use the `SystemComponents` helper generic class.\n\nThis class will keep a extremely fast and lean registry of components, but you must add and remove components to it manually.\n\nGoCS makes this easy with the `OnCreatedComponent` and `OnDestroyingComponent` callbacks in `BaseSystem`. Let's rewrite our health regen system with this approach:\n\n```csharp\nclass HealthRegenSystem : BaseSystem\n{\n    // Declare and initialize the SystemComponents helper\n   SystemComponents\u003cIHealth, IRegenable\u003e components = new SystemComponents\u003cIHealth, IRegenable\u003e();\n    \n    // OnCreatedComponent is sent to all systems when a new component is created\n    public override void OnCreatedComponent(IComponent component)\n    {\n        // Add the component to our SystemComponents\n        // (The API takes care of making sure it has the right components for us)\n        components.Add(component);\n    }\n    \n    // OnDestroyingComponent is sent to all systems before an existing component is destroyed\n    public override void OnDestroyingComponent(IComponent component)\n    {\n        // Remove the component from our SystemComponents\n        components.Remove(component);\n    }\n        \n    void Update()\n    {\n        // Enumerate over the SystemComponents instead of using World.Query\n        foreach (var (health, regenable) in components)\n        {\n            health.value += regenable.healthPerSecond * Time.deltaTime;\n        }\n    }\n}\n```\n\nYou can also use multiple SystemComponents in a single system if you need to.\n\n### Performance Comparison\n\nGoCS includes a benchmark of the different query methods in the samples. This benchmark tests a two-component query over **10,000 game objects** at every frame.\n\n|Method|API|Time (lower is better)|Allocation (lower is better)|\n|---|---|---|---|\n|Native Query|`World.Query(true)`|🛑 36.28ms|🔴 195.2 KB\n|Managed Query|`World.Query()`|⚠️ 13.58ms|❇️ 0 byte\n|System Query|`SystemComponents`|✅ 0.16ms|❇️ 0 byte\n\nObviously, using system components is the fastest alternative, clocking in at almost 200x faster than Unity's `FindObjectsOfType`.\n\n---\n\n## Component Requirements\n\nSometimes, components only make sense when coupled with other components.\n\nFor example, an `IRegenable` component might always require a `IHealth` component.\n\nThere are a few ways you can go about this.\n\nThe most simple is to require a component to appear with another with Unity's built-in `[RequireComponent]` attribute:\n\n```csharp\n[RequireComponent(typeof(IHealth))]\ninterface IRegenable : IComponent { }\n```\n\nThis will force the component to be added while in the editor. But if you don't need to configure any property on the required component, you might not want to pollute your game objects while authoring in the editor. This is often the case when using GoCS event proxies (see section below). In those cases, you can use GoCS' custom `[RuntimeRequireComponent]` attribute:\n\n```csharp\n[RuntimeRequireComponent(typeof(CollisionEventProxy))]\ninterface IDestructible : IComponent { }\n```\n\nFinally, if you don't want to (or can't) specify dependencies on the components themselves, you can require components directly from the system, during the `OnCreatedComponent ` phase. For example, if `IDestructible` components always need a matching `CollisionEventProxy` component, you could do so using the `GetOrAddComponent` helper:\n\n```csharp\nclass ZoneSystem : BaseSystem\n{\n    public override void OnCreatedComponent(IComponent component)\n    {\n        if (component is IDestructible destructible)\n        {\n            destructible.gameObject.GetOrAddComponent\u003cCollisionProxy\u003e();\n        }\n    }\n}\n```\n\n---\n\n## Event Arguments\n\nIf you need to pass in arguments to GoCS events, you can use the generic version of Event.\n\nFor example, if you need an damage event that passes the amount of damage, you could write:\n\n```csharp\ninterface IDamageable : IComponent\n{\n    Event\u003cint\u003e onDamage { get; }\n}\n```\n\nYour handlers signatures would then need to take that parameter:\n\n```csharp\nclass Damageable : IDamageable\n{\n    public Event\u003cint\u003e onDamage { get; private set; }\n\n    public float health { get; set; } = 100;\n\n    protected override void Awake()\n    {\n        base.Awake();\n        onDamage = new Event(OnDamage);\n    }\n\n    private void OnDamage(int damage)\n    {\n        health -= damage;\n    }\n}\n```\n\nAnd when invoking the event, you'll need to pass the damage argument:\n\n```csharp\ndamageable.onDamage.Invoke(5);\n```\n\nIf you need more than one event argument, you can create an arguments struct (or class).\n\nFor example, if you wanted to add an elemental type to your damage event, you could write:\n\n```csharp\nenum Element\n{\n    Fire,\n    Water,\n    Wind,\n    Earth\n}\n\nstruct DamageEventArgs\n{\n    public int amount { get; }\n    public Element element { get; }\n\n    public DamageEventArgs(int amount, Element element)\n    {\n        this.amount = amount;\n        this.element = element;\n    }\n}\n\ninterface IDamageable : IComponent\n{\n    Event\u003cDamageEventArgs\u003e onDamage { get; }\n}\n```\n\n---\n\n## Event Handlers\n\nEvents can have zero, one or multiple handlers.\n\nUnlike C# events, GoCS events can be triggered from outside their parent class. This is the trick that lets systems manage the event dispatch.\n\nThere are two styles you can use to declare events. These two styles are functionally identical. It's just a matter of preference!\n\nOn one hand, you can make the event property `{ get; private set; }` and use the constructor that specifies the handler in `Awake`. For example:\n\n```csharp\nclass Destructible : BaseComponent\n{\n    public Event onDestroy { get; private set; }\n    \n    protected override void Awake()\n    {\n        base.Awake();\n        onDestroy = new Event(OnDestroyHandler);\n    }\n    \n    private void OnDestroyHandler()\n    {\n        // ...\n    }\n}\n```\n\nOr, if you prefer, you can make the event propery `{ get; }` only and initialize it inline, then use the `AddHandler` method in `Awake`:\n\n```csharp\nclass Destructible : BaseComponent\n{\n    public Event onDestroy { get; } = new Event();\n    \n    protected override void Awake()\n    {\n        base.Awake();\n        onDestroy.AddHandler(OnDestroyHandler);\n    }\n    \n    private void OnDestroyHandler()\n    {\n        // ...\n    }\n}\n```\n\n### Null Events\n\nBy convention, GoCS allows event properties to stay null (uninitialized) if your doesn't need them. To avoid errors, your systems should therefore always use the null-coalesce operator `?.` before invoking events:\n\n```csharp\ndestructible.onDestroy?.Invoke();\n```\n\n### C# 8\n\nWhenever Unity will start supporting C# 8 (currently [planned for the 2020 cycle](https://forum.unity.com/threads/unity-c-8-support.663757/#post-5056769)), you'll be able to use the new [default interface implementations](https://devblogs.microsoft.com/dotnet/default-implementations-in-interfaces/) feature to further reduce the amount of boilerplate for events.\n\nIndeed, the component interface will be able to define a null getter for events by default, meaning they assume their implementation don't care about the event unless they specifically override them.\n\nFor example:\n\n```csharp\ninterface IInteractable : IComponent\n{\n    // Default interface implementation (no event handling)\n    Event onHoverEnter =\u003e null;\n    Event onHoverExit =\u003e null;\n    Event onPress =\u003e null;\n    Event onRelease =\u003e null;\n}\n\nclass Grabbable : BaseComponent, IInteractable\n{\n    public Event onPress { get; } = new Event();\n    public Event onRelease { get; } = new Event();\n    // No need to implement onHoverEnter and onHoverExit!\n}\n```\n\n---\n\n## Event Proxies \u0026 System Events\n\nUnity's event system is limited because it does not allow external objects to add event handlers.\n\nFor example, you cannot listen to a collider's `OnCollisionEnter` event unless you create a MonoBehaviour script on the *same* GameObject.\n\nThis is problematic for GoCS, because your Systems don't live on the same GameObject as your Components, and yet they are responsible for event dispatch!\n\nTo fix that issue, GoCS introduces something called event **proxies**. Proxies are small components packaged with GoCS that just forward the built-in Unity messages like  `OnCollisionEnter`, `OnTriggerEnter`, `OnTransformParentChanged`, etc. to normal GoCS events that we can then use in our systems.\n\nFor example, here is how the collision proxy is implemented behind the scenes:\n\n```csharp\npublic sealed class CollisionProxy : BaseComponent\n{\n    public Event\u003cCollision\u003e onEnter { get; } = new Event\u003cCollision\u003e();\n    public Event\u003cCollision\u003e onStay { get; } = new Event\u003cCollision\u003e();\n    public Event\u003cCollision\u003e onExit { get; } = new Event\u003cCollision\u003e();\n\n    private void OnCollisionEnter(Collision collision)\n    {\n        onEnter.Invoke(collision);\n    }\n\n    private void OnCollisionStay(Collision collision)\n    {\n        onStay.Invoke(collision);\n    }\n\n    private void OnCollisionExit(Collision collision)\n    {\n        onExit.Invoke(collision);\n    }\n}\n```\n\nLet's say you wanted to implement a procedural destruction system with GoCS. When objects with the `IDestructible` component collide with enough force, then the `DestructionSystem` system should break them to pieces and send back an `onDestroy` event. We'll need to to use `CollisionProxy` for that purpose. \n\nFirst, your component must require the proxy:\n\n```csharp\n[RuntimeRequireComponent(typeof(CollisionProxy))]\ninterface IDestructible : IComponent\n{\n    float requiredForce { get; }\n    Event onDestroy { get; }\n}\n```\n\nThen, your system must add and remove listeners to the collision events in its `OnCreatedComponent` and `OnDestroyingComponent` phases. \n\nTo do that, you must use a `SystemEvents` helper.\n\n```csharp\nclass DestructionSystem : BaseSystem\n{   \n    // Helper class to assign event handlers\n    SystemEvents\u003cCollision\u003e collisionEvents = new SystemEvents\u003cCollision\u003e();\n\n    public override void OnCreatedComponent(IComponent c)\n    {\n        if (c.gameObject.Has(out IDestructible destructible, out CollisionProxy collidable))\n        {\n            // Add the system event handler on the proxy\n            collisionEvents[collidable.onEnter] = collision =\u003e OnCollision(destructible, collision);\n        }\n    }\n\n    public override void OnDestroyingComponent(IComponent c)\n    {\n        if (c.gameObject.Has(out IDestructible destructible, out CollisionProxy collidable))\n        {\n            // Remove the system event handler from the proxy\n            collisionEvents[collidable.onEnter] = null;\n        }\n    }\n\n    void OnCollision(IDestructible destructible, Collision collision)\n    {\n        // Do the condition check and send back the event to the component\n        if (collision.impulse.magnitude \u003e= destructible.requiredForce)\n        {\n            destructible.onDestroy?.Invoke();\n            // Here you could also add common code for destruction VFX...\n        }\n    }\n}\n```\n\n### SystemComponents Shorthands\n\nYou'll often also want to use a `SystemComponents` for performance at the same time as registering `SystemEvents`. Because this is a very common pattern with GoCS, there are overloads that combine `Add` and `Has` in one call for convenience.\n\nFor example, if you also had:\n\n```csharp\nSystemComponents\u003cIDestructible, ICollisionProxy\u003e components = // ...\n```\n\nThen you could rewrite:\n\n```csharp\npublic override void OnCreatedComponent(IComponent c)\n{\n    components.Add(c.gameObject);\n    \n    if (c.gameObject.Has(out IDestructible destructible, out CollisionProxy collidable))\n    {\n        collisionEvents[collidable.onEnter] = collision =\u003e OnCollision(destructible, collision);\n    }\n}\n```\n\nTo the single-line:\n\n```csharp\npublic override void OnCreatedComponent(IComponent c)\n{\n    if (components.Add(c.gameObject, out IDestructible destructible, out CollisionProxy collidable))\n    {\n        collisionEvents[collidable.onEnter] = collision =\u003e OnCollision(destructible, collision);\n    }\n}\n```\n\nThanks to these helpers, our system code stays very readable:\n\n```csharp\nclass DestructionSystem : BaseSystem\n{\n    SystemComponents\u003cIDestructible, ICollisionProxy\u003e components = new SystemComponents\u003cIDestructible, ICollisionProxy\u003e();\n    \n    SystemEvents\u003cCollision\u003e collisionEvents = new SystemEvents\u003cCollision\u003e();\n\n    public override void OnCreatedComponent(IComponent c)\n    {\n        if (components.Add(c.gameObject, out IDestructible destructible, out CollisionProxy collidable))\n        {\n            collisionEvents[collidable.onEnter] = collision =\u003e OnCollision(destructible, collision);\n        }\n    }\n\n    public override void OnDestroyingComponent(IComponent c)\n    {\n        if (components.Remove(c.gameObject, out IDestructible destructible, out CollisionProxy collidable))\n        {\n            collisionEvents[collidable.onEnter] = null;\n        }\n    }\n\n    void OnCollision(IDestructible destructible, Collision collision)\n    {\n        if (collision.impulse.magnitude \u003e= destructible.requiredForce)\n        {\n            destructible.onDestroy?.Invoke();\n        }\n    }\n}\n```\n\n---\n\n## Custom Base Classes\n\nGoCS come packaged with `BaseComponent` and `BaseSystem` base classes to get you started quickly.\n\nBoth of these classes are derived from `MonoBehaviour`.\n\nHowever, if you need your components or systems to derive from other root classes, you don't have to use those provided by GoCS. You only have to implement the `IComponent` and `ISystem` interfaces. The API provides a `BaseImplementation` static helper class that makes this process easy and future-proof.\n\n### Custom Base Component\n\nLet's say you are making an online game with UNET and thus need to use `NetworkBehaviour` instead of MonoBehaviour as your base component type.\n\nTo make it GoCS-compatible, you could create your own `BaseNetworkComponent` class:\n\n```csharp\n// Derive from NetworkBehaviour and implement IComponent\npublic abstract class BaseNetworkComponent : NetworkBehaviour, IComponent\n{\n    // Forward to BaseImplementation\n\n    protected virtual void Awake()\n    {\n        BaseImplementation.ComponentAwake(this);\n    }\n\n    protected virtual void OnEnable()\n    {\n        BaseImplementation.ComponentOnEnable(this);\n    }\n\n    protected virtual void OnDisable()\n    {\n        BaseImplementation.ComponentOnDisable(this);\n    }\n\n    protected virtual void OnDestroy()\n    {\n        BaseImplementation.ComponentOnDestroy(this);\n    }\n}\n```\n\n### Custom Base System\n\nSystems follow the same principle: you can implement your own `ISystem` via `BaseImplementation` helpers.\n\nHowever, if you want to receive the `OnCreatedComponent` and `OnDestroyingComponent` callbacks, you also need to implement the `IWorldCallbackReceiver` class. This interface is optional in case you don't need those callbacks in your system and want minimize its initialization cost. `BaseSystem` always implements `IWorldCallbackReceiver`.\n\nFinally, systems also don't theoretically need to inherit from `MonoBehaviour` or even `UnityEngine.Object`. They could live in singletons, scriptable objects, or any other kind of class you have in your architecture. As long as you call Awake + OnEnable when the system \"starts\" and OnDisable + OnDestroy when the system \"stops\", everything should work.\n\n```csharp\n// Implement ISystem as a non-UnityEngine.Object class.\npublic abstract class MyBaseSystem: ISystem, IWorldCallbackReceiver\n{\n    // (Required) Forward lifecycle events to BaseImplementation:\n\n    public void Start()\n    {\n        BaseImplementation.SystemAwake(this);\n        BaseImplementation.SystemOnEnable(this);\n    }\n\n    public void Stop()\n    {\n        BaseImplementation.SystemOnDisable(this);\n        BaseImplementation.SystemOnDestroy(this);\n    }\n    \n    // (Optional) Implement IWorldCallbackReceiver:\n    public virtual void OnCreatedComponent(IComponent component) { }\n    public virtual void OnDestroyingComponent(IComponent component) { }\n}\n```\n\n---\n\n## Mixing GoCS with OOP\n\nIt's perfectly fine to mix GoCS with OOP. In fact, that's one of its main benefits! You should think of GoCS as just another tool in your development toolbox, not as a pattern you *have* to use everywhere.\n\nFor example, if you have a player controller class that works well as a self-contained, encapsulated object, then you should keep it that way. No need to split it into entities, components and systems unless it makes sense to you. As the saying goes, *if it ain't broke, don't fix it*.\n\nLikewise, if you use an entire other architectural pattern for other parts of your codebase, for example model-view-component (MVC) for your GUI code, you don't need to convert that. GoCS can happily coexist in parallel of all your existing codebase.\n\nGoCS is also designed to let you progressively add it to an existing OOP architecture.\n\nThere's no downside and very minimal overhead to having every component in your project derive from BaseComponent, even if you don't use Systems with them. But if for whatever reason you don't want all your components to derive from IComponent, you could create a more flexible base component class that will only use GoCS on derived classes that implement IComponent. This way, you can reuse it as a common base for classes that are GoCS-aware and classes that aren't. For example:\n\n```csharp\n// Don't implement IComponent yet, let the derived classes do that.\npublic abstract class MyBaseComponent: MonoBehaviour\n{\n    // Forward to BaseImplementation if this is a GoCS component.\n\n    protected virtual void Awake()\n    {\n        if (this is IComponent component)\n        {\n            BaseImplementation.ComponentAwake(this);\n        }\n    }\n\n    protected virtual void OnEnable()\n    {\n        if (this is IComponent component)\n        {\n            BaseImplementation.ComponentOnEnable(this);\n        }\n    }\n\n    protected virtual void OnDisable()\n    {\n        if (this is IComponent component)\n        {\n            BaseImplementation.ComponentOnDisable(this);\n        }\n    }\n\n    protected virtual void OnDestroy()\n    {\n        if (this is IComponent component)\n        {\n            BaseImplementation.ComponentOnDestroy(this);\n        }\n    }\n}\n```\n\nYou can then start adding GoCS at any level in your inheritance hierarchy.\n\nLet's say you have the following inheritance hierarchy:\n\n```csharp\npublic class Character : MyBaseComponent { }\n  public class Player : Character { }\n  public class NPC : Character { }\n```\n\nLater on in your development process, you realize that NPCs should be interactable to start a conversation. But you don't want player characters to be interactable, because that wouldn't make sense. Instead of rearchitecting your existing classes (that work just fine as is!), you can tackle on GoCS by simply adding component interfaces like IInteractable lower in the chain:\n\n```csharp\npublic class Character : MyBaseComponent { }\n  public class Player : Character { }\n  public class NPC : Character, IInteractable { } // Minor change, big benefits!\n```\n\nAll NPC would have to implement is the members needed by the IInteractable component interface, for example its interaction range and events. With minimal changes to your existing code, you now benefit from the full `InteractionSystem` we created earlier.\n\n---\n\n## More Examples\n\nThe GoCS package comes bundled with optional commented examples.\n\nIf you're using Unity 2019 or newer, you can import them in your project directly from the Package Manager 2.0 interface. \n\nIf you're on Unity 2018, you can find them under the `Samples~/` directory of the package and copy them to your project manually.\n\n## Forking \u0026 Contributing\n\nSee: [CONTRIBUTING.md](./CONTRIBUTING.md)\n\n## API Reference\n\nSee: [API Reference](./Documentation~/API/Lazlo-Gocs.md)\n\n## Development Status\n\nGoCS is not yet guaranteed to be production ready. There may be breaking API changes in the future. In that case, since GoCS uses [Semantic Versioning](https://semver.org), a new major version number will be used. \n\nI'm open to suggestions and appreciate bug reports. If you have any, just create a new issue on Github!\n\n## Special Thanks\n\n- Alvaro Salvagno for early feedback and testing\n- Tor Vesteergard for various optimization tips","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flazlo-bonin%2Fgocs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flazlo-bonin%2Fgocs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flazlo-bonin%2Fgocs/lists"}