{"id":13589564,"url":"https://github.com/getnamo/GlobalEventSystem-Unreal","last_synced_at":"2025-04-08T09:33:19.579Z","repository":{"id":44923686,"uuid":"223662255","full_name":"getnamo/GlobalEventSystem-Unreal","owner":"getnamo","description":"Loosely coupled internal event system plugin for the Unreal Engine.","archived":false,"fork":false,"pushed_at":"2024-06-06T00:15:06.000Z","size":326,"stargazers_count":274,"open_issues_count":14,"forks_count":44,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-11-12T18:02:31.477Z","etag":null,"topics":["blueprint","cpp","event-system","loosely-coupled","pinning","ue4","ue5","unreal-engine"],"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/getnamo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-23T22:37:31.000Z","updated_at":"2024-11-08T06:28:53.000Z","dependencies_parsed_at":"2024-06-06T01:30:07.113Z","dependency_job_id":"e80a2472-af6f-4e76-824a-51873c63dfe5","html_url":"https://github.com/getnamo/GlobalEventSystem-Unreal","commit_stats":null,"previous_names":["getnamo/global-event-system-ue4"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getnamo%2FGlobalEventSystem-Unreal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getnamo%2FGlobalEventSystem-Unreal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getnamo%2FGlobalEventSystem-Unreal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getnamo%2FGlobalEventSystem-Unreal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getnamo","download_url":"https://codeload.github.com/getnamo/GlobalEventSystem-Unreal/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247814301,"owners_count":21000540,"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":["blueprint","cpp","event-system","loosely-coupled","pinning","ue4","ue5","unreal-engine"],"created_at":"2024-08-01T16:00:31.642Z","updated_at":"2025-04-08T09:33:19.354Z","avatar_url":"https://github.com/getnamo.png","language":"C++","funding_links":[],"categories":["Utilities"],"sub_categories":[],"readme":"# GlobalEventSystem-Unreal\nA loosely coupled internal global event system (GES) plugin for the Unreal Engine. Aims to solve cross-map and cross-blueprint communication for reliable and inferable event flow. Should enable a publisher-observer pattern.\n\n[![GitHub release](https://img.shields.io/github/release/getnamo/GlobalEventSystem-Unreal.svg)](https://github.com/getnamo/GlobalEventSystem-Unreal/releases)\n[![Github All Releases](https://img.shields.io/github/downloads/getnamo/GlobalEventSystem-Unreal/total.svg)](https://github.com/getnamo/GlobalEventSystem-Unreal/releases)\n\nBecause the events are emitted to a dynamic map of listeners you can loosely link parts of your project without needing to redo boilerplate when you change parts of the code, dynamically change environments, or e.g. load a different submap. Fire something away, and if something is interested in that information, they can do something with it; optional.\n\nQuestions? See https://github.com/getnamo/GlobalEventSystem-Unreal/issues\n\nDiscussions? See [Unreal Thread](https://forums.unrealengine.com/t/plugin-global-event-system/134063)\n\n[Discord Server](https://discord.gg/qfJUyxaW4s)\n\n### Current Important Issue\n\nEmitting a struct from C++ to blueprint receiver will currently not fill properly. All other emit/receive pairs work. Use object wrapper as workaround until fix is found. Issue: https://github.com/getnamo/GlobalEventSystem-Unreal/issues/15\n\n## Quick Install \u0026 Setup ##\n 1. [Download Latest Release](https://github.com/getnamo/GlobalEventSystem-Unreal/releases)\n 2. Create new or choose project.\n 3. Browse to your project folder (typically found at Documents/Unreal Project/{Your Project Root})\n 4. Copy *Plugins* folder into your Project root.\n 5. Plugin should be now ready to use.\n\n## How to use - Basics and API\n\nThere are globally available functions that you can use to emit and bind events. At this time there are two variants for emitting (no parameters and one wildcard parameter) and one for binding events to your local functions. There are also GameplayTag variants of these emitters and receivers for easy dropdown linking.\n\nEach emit is a multi-cast to all valid bound receivers. If the parameters don't match you'll be warned in the log with fairly verbose messages while emitting to all other valid targets. \n\nConsider optionally using Gameplay tagged based emitters/receivers, or extending GESReceiver components to keep messaging organized.\n\n### Emit Event\n\n#### ```GESEmitEvent```\n\n##### Param: Pinned\nWhether the event should trigger for listeners added after the event has fired. Useful to communicate state.\n\n##### Param: Domain\nA string type similar to a namespace with reverse-DNS like structure encouraged, but not enforced. By default there is a ```global.default``` prefilled which can be changed to any valid utf8 string.\n\n##### Param: Event\nThis is an abstract name and is considered unique for that domain. You'll need the same domain and event name to match in your binding function to receive the event.\n\n#### ```GESEmitEventOneParam```\n\n##### Additional Param: Parameter Data\nWildcard Property, will accept any single property type e.g. *int, float, byte, string, name, bool, struct,* and *object*. Wrap arrays and maps in a custom struct to emit more complex data types. \n\nBreak pin to set a new type of value. \n\nKeep in mind that the receiving listeners need to match the property type to receive the data.\n\n![emit](https://i.imgur.com/8nXb5ya.png)\n\n#### ```GESEmitTagEvent```\n\nGameplayTag variant of GESEmitEvent. Instead of ```Domain``` and ```Event``` string you pick an event from a GameplayTag via dropdown\n\n##### Param: Domained Event Tag\n\nA GameplayTag similar to a namespace with reverse-DNS like structure. Select or make one from the drop down list. Any depth tag should be supported and will automatically translate to domain and event under the hood.\n\n##### Param: Pinned\nWhether the event should trigger for listeners added after the event has fired. Useful to communicate state.\n\n![image](https://user-images.githubusercontent.com/542365/113543315-f605a780-959a-11eb-9b70-83208ad2f002.png)\n\n\n#### ```GESEmitTagEventOneParam```\n\n##### Additional Param: Parameter Data\nWildcard Property, will accept any single property type e.g. *int, float, byte, string, name, bool, struct,* and *object*. Wrap arrays and maps in a custom struct to emit more complex data types. \n\nBreak pin to set a new type of value. \n\nKeep in mind that the receiving listeners need to match the property type to receive the data.\n\n![image](https://user-images.githubusercontent.com/542365/113543142-a030ff80-959a-11eb-93bd-7e9d5ec55a79.png)\n\n\n### Bind Event\n\n```GESBindEvent```\n##### Param: Domain\nA string type similar to a namespace with reverse-DNS like structure encouraged, but not enforced. By default there is a ```global.default``` prefilled which can be changed to any valid utf8 string.\n\n##### Param: Event\nThis is an abstract name and is considered unique for that domain. You'll need the same domain and event name to match in your emitting function to receive the event.\n\n##### Param: Receiving Function\nThe final parameter is your local function name. This will be called on the graph context object (owner of graph e.g. the calling actor).\n\n![bind](https://i.imgur.com/WzHhEeG.png)\n\nThen make your custom event or blueprint function with a matching name and matching parameters.\n\n### Bind Event to Wildcard Delegate\n\nInstead of linking via function name, you can connect or make a wildcard property delegate (c++ type _FGESOnePropertySignature_).\n\n![wildcard delegate](https://i.imgur.com/bOX2lve.png)\n\nYou can then convert your received wildcard property to a fixed type with a boolean indicator if the conversion was successful. Below are the available conversion types.\n\n![other conversions](https://i.imgur.com/iOtaJTq.png)\n\nNB: The struct property in the conversion node will appear gray until linked with a local/member variable via e.g. a Set call.\n\n\n### Bind Event via GameplayTag\n\nSimilar to the emit ```GESEmitTagEvent```, you can use the GameplayTag based variants to bind to a delegate or function by name\n\n![image](https://user-images.githubusercontent.com/542365/113543759-e6d32980-959b-11eb-8839-97138b49c1de.png)\n\n\n## Unbinding\n\nEvents automatically unbind on world end, but if you expect your receiver to last shorter than the world, consider unbinding all events attached to receiver on its _EndPlay_ call\n\n![unbind all](https://i.imgur.com/ePryxZ4.png)\n\nor optionally unbind individual events\n\n![unbind](https://i.imgur.com/Qw3znMg.png)\n\n## Examples\n\nKeep in mind that you can start using GES incrementally for specific tasks or parts of large projects instead of replacing too many parts at once. Below are some very basic examples where GES could be useful.\n\n### Cross-map reference pinning\nLet's say you had two actors in two different sub-maps and you wanted one actor to know that it has spawned from e.g. some dynamic process. Delay nodes shown below are only used to show example event delays due to e.g. async processing or waiting on something else to happen; not needed for function.\n\n![actor ready](https://i.imgur.com/BLUFoFs.png)\n\nIn the spawned actor you could emit a reference to itself.\n\n![listen actor](https://i.imgur.com/IP0XTtC.png)\n\nand in the other actor you could bind to that event to do something with that information. Normally even without pinning this event should be received because you bind before you emit. But what if you couldn't control the delay?\n\n![delayed bind](https://i.imgur.com/UfQYsJa.png)\n\nThis is the case where pinning the event would help as now when the receiving actor binds to the event, it will automatically receive the last emit even though it was called after the event was emitted. From a developer perspective you can now just handle the receiving logic and not worry about whether you need to add delays or loop through all actors in the map. By arranging your events to signal selectively and muxing those states you can ensure that the order of your events remains predictable; only start x when part y and z in the map have happened.\n\n### Flow muxing and loose coupling\n\nYou can add a simple actor to the map which listens to various GES events. When for example two of those events have fired you can fire off another event which is a composite logic of the source events e.g. ANDGate or much more complex logic if we decide to use variable state.\n\n![](https://i.imgur.com/ickckJe.png)\n\nBlueprints which would listen to the SAReady event, don't even have to care where the source came from and you could easily swap out this logic actor for maybe another type without changing any other code; an example of the loose coupling enabled by GES. The actor is replaceable, there is no additional boilerplate that needs to be changed if replaced.\n\n## Component Receivers - Optional way of organizing events\n\nIf your receiver is an actor, you can organize your events via _GESBaseReceiverComponent_ sub-classed _ActorComponent_ receivers. These receivers automatically store the last received value and auto-unbind on EndPlay.\n\nThere are a few built-in types available e.g. a float receiver\n\n![float receiver](https://i.imgur.com/eVCxucx.png)\n\nJust add the component to your actor and add the OnFloatReceived Event. Change the BindSettings to match your expected _Domain_ and _Event_ names. Leave receiving function unless you want to specialize this receiver.\n\nBelow are the available built-in receivers.\n\n![convenience receivers](https://i.imgur.com/wcECuDo.png)\n\n#### Customizing your own receiver\n\nStart with adding a new blueprint with _GESBaseReceiverComponent_ base class\n\n![subclass](https://i.imgur.com/W2qvQR8.png)\n\nThen modify your blueprint to store your own data type and forward the GES event to your own Event Dispatcher.\n\n![example customization](https://i.imgur.com/x04WW5c.png)\n\ne.g. a custom struct specialized receiver\n\nYou can then just add this component to all the actors that are interested in this type of event.\n\n## Options\n\nThere are some simple options to toggle some log messages and detailed struct type checking.\n\n![options](https://i.imgur.com/22tC4lI.png)\n\n## C++\n\nTo use GES in C++, add ```\"GlobalEventSystem\"``` to your project Build.cs e.g.\n\n```PublicDependencyModuleNames.AddRange(new string[] { \"Core\", \"CoreUObject\", \"Engine\", \"InputCore\", \"GlobalEventSystem\" });```\n\nthen in your implementation file of choice add the ```#include \"GESHandler.h\"``` header.\n\n### Emit an event\n\nUse _FGESHandler_ class to get static access to a default handler.\n\n```FGESHandler::DefaultHandler()```\n\nCall functions on this handler to both emit and bind events.\n\n#### No param\nTo emit a no-param event you specify an _FGESEmitContext_ struct as the first function parameter\n\n```c++\n//define emit contexts\nFGESEmitContext Context;\nContext.Domain = TEXT(\"global.default\");\nContext.Event = TEXT(\"MyEvent\");\nContext.bPinned = true;      //whether the event state should be available after emit\nContext.WorldContext = this; //all GES events require a WorldContext object, typically this will be an actor or anything with a world.\n\nFGESHandler::DefaultHandler()-\u003eEmitEvent(Context);\n```\n\n#### One param\n\nFor any other emit type with one parameter, you pass the parameter value of choice as the second function parameter.\nMost common types are overloaded in _EmitEvent_. For multi-param and complex data types wrap or use a UStruct or UObject sub-class.\n\n##### FString\n\n```c++\n...\n\nFString MyString = TEXT(\"MyStringData\");\nFGESHandler::DefaultHandler()-\u003eEmitEvent(Context, MyString);\n```\n\nor you can emit string literals via\n\n```c++\n...\nFGESHandler::DefaultHandler()-\u003eEmitEvent(Context, TEXT(\"MyStringData\"));\n```\n\n##### int32\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eEmitEvent(Context, 5);\n```\n\n##### float\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eEmitEvent(Context, 1.3);\n```\n\n##### bool\n\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eEmitEvent(Context, true);\n```\n\n##### FName\n\n```c++\n...\n\nFName MyName = TEXT(\"my name\");\nFGESHandler::DefaultHandler()-\u003eEmitEvent(Context, MyName);\n```\n\n##### UObject*\n```c++\n...\n\nUObject* SomeObject;\n\nFGESHandler::DefaultHandler()-\u003eEmitEvent(Context, SomeObject);\n```\n\n##### Struct\n\n```c++\n\n//Assuming e.g. this custom struct definition\n//NB : blueprint type declaration is optional, but will expose it to bp for easier receiving in that context\nUSTRUCT(BlueprintType)\nstruct FCustomTestData\n{\n    GENERATED_BODY()\n\n    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category= Test)\n    FString Name;\n\n    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Test)\n    int32 Index;\n\n    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Test)\n    TArray\u003cfloat\u003e Data;\n};\n\n...\n\nFCustomTestData EmitStruct;\nEmitStruct.Name = TEXT(\"Testy\");\nEmitStruct.Index = 5;\nEmitStruct.Data = {1.2, 2.3};\n\n\nFGESHandler::DefaultHandler()-\u003eEmitEvent(FCustomTestData::StaticStruct(), \u0026EmitStruct);\n```\n\nNB: v0.7.0 has a bug where c++ struct emits to blueprint receivers do not properly fill. Use object wrappers until a fix is found.\n\n### Receive an event\n\nThe recommended method is using lambda receivers. Define an _FGESEventContext_ struct as the first param, then pass your overloaded lambda as the second type. NB: you can also alternatively organize your receivers with e.g. subclassing a _GESBaseReceiverComponent_, but these are only applicable for actor owners and thus not recommended over lambda receivers in general. \n\n#### No param event\n\nOnly the event context is required. Use 'this' capture context in the lambda to enable calling e.g. member functions (optional).\n\n```c++\nFGESEventContext Context;\nContext.Domain = TEXT(\"global.default\");\nContext.Event = TEXT(\"MyEvent\");\nContext.WorldContext = this;\n \nFGESHandler::DefaultHandler()-\u003eAddLambdaListener(Context, [this]\n{\n    //handle receive\n});\n```\n\n#### FString param event\n\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eAddLambdaListener(Context, [this](const FString\u0026 StringData)\n{\n    //handle receive, e.g. log result\n    UE_LOG(LogTemp, Log, TEXT(\"Received %s\"), *StringData);\n});\n```\n\n#### float param event\n\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eAddLambdaListener(Context, [this](float FloatData)\n{\n    //handle receive, e.g. log result\n    UE_LOG(LogTemp, Log, TEXT(\"Received %1.3f\"), FloatData);\n});\n```\n\n#### int32 param event\n\nNB: name specialization of this bind due to lambda bind ambiguity with float callback\n\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eAddLambdaListenerInt(Context, [this](int32 IntData)\n{\n    //handle receive, e.g. log result\n    UE_LOG(LogTemp, Log, TEXT(\"Received %d\"), IntData);\n});\n```\n\n#### bool param event\n\nNB: name specialization of this bind due to lambda bind ambiguity with float callback\n\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eAddLambdaListenerBool(Context, [this](bool BoolData)\n{\n    //handle receive, e.g. log result\n    UE_LOG(LogTemp, Log, TEXT(\"Received %d\"), BoolData);\n});\n```\n\n#### FName param event\n\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eAddLambdaListener(Context, [this](const FName\u0026 NameData)\n{\n    //handle receive, e.g. log result\n    UE_LOG(LogTemp, Log, TEXT(\"Received %s\"), *NameData.ToString());\n});\n```\n\n#### UObject* param event\n\n```c++\n...\n\nFGESHandler::DefaultHandler()-\u003eAddLambdaListener(Context, [this](UObject* ObjectData)\n{\n    //handle receive, e.g. log result\n    UE_LOG(LogTemp, Log, TEXT(\"Received %s\"), *ObjectData.GetName());\n});\n```\n\n#### Struct param event\n\nStructs need a deep copy to be readable.\n\n```c++\n\n//Assuming this custom struct\nUSTRUCT(BlueprintType)\nstruct FCustomTestData\n{\n\tGENERATED_BODY()\n\n\tUPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Test)\n\tFString Name;\n\n\tUPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Test)\n\tint32 Index;\n\n\tUPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Test)\n\tTArray\u003cfloat\u003e Data;\n};\n\n...\n\nFGESHandler::DefaultHandler()-\u003eAddLambdaListener(Context, [this](UStruct* Struct, void* StructPtr)\n{\n    //Confirm matching struct\n    if (Struct == FCustomTestData::StaticStruct())\n    {\n        //Deep copy your struct to local ref\n\tFCustomTestData TestData;\n\tTestData = *(FCustomTestData*)StructPtr;\n        \n\t//Test data is now usable\n\tUE_LOG(LogTemp, Log, TEXT(\"Struct data: %s %d\"), *TestData.Name, TestData.Data.Num());\n    }\n});\n```\n\n#### Wildcard\nIf you're not sure of the type of data you can receive, try a wildcard lambda and cast to test validity of data types. You'll need to add ```\"#include \"GlobalEventSystemBPLibrary.h\"``` to use the wildcard property conversion functions.\n\n```c++\n...\nFGESHandler::DefaultHandler()-\u003eAddLambdaListener(Context, [this](const FGESWildcardProperty\u0026 WildcardProperty)\n{\n    //Let's try to decode a float\n    float MaybeFloat;\n    bool bDidGetFloat = UGlobalEventSystemBPLibrary::Conv_PropToFloat(WildcardProperty, MaybeFloat);\n    if(bDidGetFloat)\n    {\n        //good to go\n    }\n});\n```\n\n#### Unbinding Events\nEach bound event function should unbind automatically when the world gets removed, but it is recommended to remove your listener if your receiver has a shorter lifetime e.g. on its _EndPlay_ call.\n\nRemove all listeners attached to this owner (where _this_ == world context object).\n\n```c++\nFGESHandler::DefaultHandler()-\u003eRemoveAllListenersForReceiver(this);\n```\n\nOr you unbind each listener via the returned lambda function name you get when you bind the listener to the event.\n\n```c++\n...\n\n//Store a reference to your lambda via string name\nFString LambdaFunctionName = FGESHandler::DefaultHandler()-\u003eAddLambdaListener(Context, [this]\n{\n    //handle receive\n});\n\n...\n\n//let's say we're done listening now\nFGESHandler::DefaultHandler()-\u003eRemoveLambdaListener(Context, LambdaFunctionName);\n\n```\n\nOptionally you can also store the function and pass it instead of the lambda name to unbind it. Name method is preferred due to developers often defining anonymous functions inline when binding.\n\n## When not to use GES\n- There are some performance considerations to keep in mind. While the overall architecture is fairly optimized, it can be more expensive than a simple function call due to function and type checking. Consider it appropriate for signaling more than a hammer to use everywhere.\n\n- If your objects have a tight coupling or it's easily accessible in a tree hierarchy pattern I would use standard methods instead of GES.\n\n- Background threads. Current version is not thread safe and should be called only in your game thread.\n\n## Possible Improvements\nSee https://github.com/getnamo/GlobalEventSystem-Unreal/issues for latest.\nGeneral enhancements:\n- Event with callback (get information from a listener)\n- Add optional logging utility to record event flow with possibly replay (attach middleware function)\n- Trigger limits, e.g. can only trigger n times\n- Add receiver limits (target requires interface/etc)\n- Bind to Interface (binds all events in an interface map to functions in interface)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetnamo%2FGlobalEventSystem-Unreal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetnamo%2FGlobalEventSystem-Unreal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetnamo%2FGlobalEventSystem-Unreal/lists"}