├── .gitignore ├── GameStateSubsystem.uplugin ├── LICENSE ├── README.md └── Source ├── Editor ├── GameStateSubsystemEd.build.cs └── Private │ ├── BlueprintGraph │ ├── K2Node_GetGameStateSubsystem.cpp │ └── K2Node_GetGameStateSubsystem.h │ ├── GameStateSubsystemBlueprintLibrary.cpp │ ├── GameStateSubsystemBlueprintLibrary.h │ ├── GameStateSubsystemEdModule.cpp │ └── GameStateSubsystemEdModule.h └── Runtime ├── GameStateSubsystem.build.cs ├── Private ├── ExampleGameStateSubsystem.cpp ├── ExtendableGameStateBase.cpp ├── GameStateSubsystem.cpp ├── GameStateSubsystemModule.cpp └── GameStateSubsystemModule.h └── Public ├── ExampleGameStateSubsystem.h ├── ExtendableGameStateBase.h └── GameStateSubsystem.h /.gitignore: -------------------------------------------------------------------------------- 1 | /.DS_Store 2 | /.idea 3 | /.vs 4 | /_ReSharper.Caches 5 | /Binaries 6 | /Intermediate 7 | -------------------------------------------------------------------------------- /GameStateSubsystem.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | "Version" : 1, 4 | "FriendlyName" : "GameState Subsystems", 5 | "Description" : "Replicated Subsystems for Modular GameState.", 6 | "Category" : "Gameplay", 7 | "CreatedBy" : "Daft Software.", 8 | "CreatedByURL" : "https://github.com/daftsoftware", 9 | "EnabledByDefault" : true, 10 | "CanContainContent" : false, 11 | "IsBetaVersion" : false, 12 | "Installed" : false, 13 | "Modules" : 14 | [ 15 | { 16 | "Name" : "GameStateSubsystem", 17 | "Type" : "Runtime", 18 | "LoadingPhase" : "PreDefault" 19 | }, 20 | { 21 | "Name" : "GameStateSubsystemEd", 22 | "Type" : "UncookedOnly", 23 | "LoadingPhase" : "Default" 24 | } 25 | ], 26 | "Plugins": [ 27 | { 28 | "Name": "ModularGameplay", 29 | "Enabled": true 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Daft Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## GameState Subsystems 3 | 4 | Replicated Subsystems - Extensible GameState via subsystems rather than GameState Components. 5 | To setup please enable the plugin and reparent your GameState to AExtensibleGameStateBase, then just create a UGameStateSubsystem and it will be replicated and initialized automatically. 6 | 7 | You can find an example of the functionality in UExampleGameStateSubsystem, it's disabled by default, to turn it on just change CreateExampleSubsystem = true in ShouldCreateSubsystem. 8 | 9 | Hope you find a good usage for them in your projects <3 10 | 11 | ## Why didn't you just replicate world subsystems? 12 | 13 | Because World Subsystems have bizarre and awkward lifetimes that highly complicate replication and networking. It is completely possible with Iris, but in practice it was actually very awkward to use and it didn't really offer many considerable benefits over GameState Subsystems other than a few niche usages. 14 | 15 | The other more technical reason is because a subsystem is a singleton, by it's nature the patterns end up matching that of the GameState when it's replicated anyway - you can't really meaningfully implement a Subsystem that behaves like the Player Controller because the Server would require an authoritative version for each player with individual remotes having their local proxy of said subsystem. However that doesn't work because it breaks the singleton pattern on the server due to requiring multiple instances. 16 | 17 | ## Why is this useful? 18 | 19 | I am not the biggest fan of the Lyra Experience system, however I do see it's value - especially in GameState Components. However for some cases I think that it would be nice if they sort of just registered themselves and just worked, rather than needing to be manually added as a default subobject or added dynamically like Lyra Experience does. So I made this Subsystem type, and I find this solution to be a bit of a best of both worlds. 20 | 21 | It can make it more straightforward to add code in auxiliary modules that require a GameState or replication without having to do any extra work other than reparenting the top-level GameState class. You can achieve the same with GameState Components with the right setup of course, especially if you're using Lyra, but it can be relatively easy to do it with these Subsystems. 22 | 23 | ## When isn't it useful? 24 | 25 | It is still prone to the same limitations as the GameState and GameState Components - These subsystems do not exist on clients until the GameState has replicated. Therefore if you are looking for a solution that is immediately available on clients upon a World being created, this Subsystem type doesn't offer that. 26 | 27 | When you want logic that can be dynamically added or removed based on specific GameStates you are probably better off just using a GameState Component for that as it's much better suited and designed for that purpose. 28 | 29 | ## Support 30 | 31 | This code was designed to work on a version > 5.3 but it's likely possible to backport the code. It doesn't necessarily do any black magic or special things that are new to the engine. If something is broken, you can ping me at my discord handle "snaps" and i'l try push a fix asap. 32 | 33 | ## Contributions 34 | 35 | Big thanks to Vori for helping me battle test it. PRs are welcome! Feel free to contibute <3 36 | -------------------------------------------------------------------------------- /Source/Editor/GameStateSubsystemEd.build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | using UnrealBuildTool; 11 | 12 | public class GameStateSubsystemEd : ModuleRules 13 | { 14 | public GameStateSubsystemEd(ReadOnlyTargetRules Target) : base(Target) 15 | { 16 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 17 | 18 | PrivateDependencyModuleNames.AddRange(new string[] { 19 | "Core", 20 | "CoreUObject", 21 | "Engine", 22 | "UnrealEd", 23 | "BlueprintGraph", 24 | "KismetCompiler", 25 | "GameStateSubsystem" 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Source/Editor/Private/BlueprintGraph/K2Node_GetGameStateSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "K2Node_GetGameStateSubsystem.h" 11 | #include "ExtendableGameStateBase.h" 12 | #include "GameStateSubsystemBlueprintLibrary.h" 13 | #include "GameStateSubsystem.h" 14 | #include "BlueprintActionDatabaseRegistrar.h" 15 | #include "BlueprintNodeSpawner.h" 16 | #include "K2Node_CallFunction.h" 17 | #include "EdGraphSchema_K2.h" 18 | #include "KismetCompiler.h" 19 | 20 | #include UE_INLINE_GENERATED_CPP_BY_NAME(K2Node_GetGameStateSubsystem) 21 | 22 | // ************************************************************************************ 23 | // UK2Node_GetGameStateSubsystem 24 | // ************************************************************************************ 25 | 26 | void UK2Node_GetGameStateSubsystem::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) 27 | { 28 | // Skip the UK2Node_GetSubsystem implementation 29 | UK2Node::ExpandNode(CompilerContext, SourceGraph); 30 | 31 | static const FName WorldContextObject_ParamName(TEXT("ContextObject")); 32 | static const FName Class_ParamName(TEXT("Class")); 33 | 34 | UK2Node_GetSubsystem* GetSubsystemNode = this; 35 | UEdGraphPin* SpawnWorldContextPin = GetSubsystemNode->GetWorldContextPin(); 36 | UEdGraphPin* SpawnClassPin = GetSubsystemNode->GetClassPin(); 37 | UEdGraphPin* SpawnNodeResult = GetSubsystemNode->GetResultPin(); 38 | 39 | UClass* SpawnClass = (SpawnClassPin != nullptr) ? Cast(SpawnClassPin->DefaultObject) : nullptr; 40 | if (SpawnClassPin && (SpawnClassPin->LinkedTo.Num() == 0) && !SpawnClass) 41 | { 42 | CompilerContext.MessageLog.Error(*NSLOCTEXT("K2Node", "GetSubsystem_Error", "Node @@ must have a class specified.").ToString(), GetSubsystemNode); 43 | GetSubsystemNode->BreakAllNodeLinks(); 44 | return; 45 | } 46 | 47 | // Choose appropriate underlying Getter 48 | FName Get_FunctionName; 49 | if (CustomClass->IsChildOf()) 50 | { 51 | Get_FunctionName = GET_FUNCTION_NAME_CHECKED(UGameStateSubsystemBlueprintLibrary, GetGameStateSubsystem); 52 | } 53 | else 54 | { 55 | CompilerContext.MessageLog.Error(*NSLOCTEXT("K2Node", "GetSubsystem_Error", "Node @@ must have a class specified.").ToString(), GetSubsystemNode); 56 | GetSubsystemNode->BreakAllNodeLinks(); 57 | return; 58 | } 59 | 60 | ////////////////////////////////////////////////////////////////////////// 61 | // create 'UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystem' call node 62 | UK2Node_CallFunction* CallGetNode = CompilerContext.SpawnIntermediateNode(GetSubsystemNode, SourceGraph); 63 | CallGetNode->FunctionReference.SetExternalMember(Get_FunctionName, UGameStateSubsystemBlueprintLibrary::StaticClass()); 64 | CallGetNode->AllocateDefaultPins(); 65 | 66 | UEdGraphPin* CallCreateWorldContextPin = CallGetNode->FindPinChecked(WorldContextObject_ParamName); 67 | UEdGraphPin* CallCreateClassTypePin = CallGetNode->FindPinChecked(Class_ParamName); 68 | UEdGraphPin* CallCreateResult = CallGetNode->GetReturnValuePin(); 69 | 70 | if (SpawnClassPin && SpawnClassPin->LinkedTo.Num() > 0) 71 | { 72 | // Copy the 'class' connection from the spawn node to 'UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystem' 73 | CompilerContext.MovePinLinksToIntermediate(*SpawnClassPin, *CallCreateClassTypePin); 74 | } 75 | else 76 | { 77 | // Copy class literal onto 'UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystem' call 78 | CallCreateClassTypePin->DefaultObject = *CustomClass; 79 | } 80 | 81 | // Copy the world context connection from the spawn node to 'UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystem' if necessary 82 | if (SpawnWorldContextPin) 83 | { 84 | CompilerContext.MovePinLinksToIntermediate(*SpawnWorldContextPin, *CallCreateWorldContextPin); 85 | } 86 | 87 | // Move result connection from spawn node to 'UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystem' 88 | CallCreateResult->PinType = SpawnNodeResult->PinType; 89 | CompilerContext.MovePinLinksToIntermediate(*SpawnNodeResult, *CallCreateResult); 90 | 91 | ////////////////////////////////////////////////////////////////////////// 92 | 93 | // Break any links to the expanded node 94 | GetSubsystemNode->BreakAllNodeLinks(); 95 | } 96 | 97 | FText UK2Node_GetGameStateSubsystem::GetTooltipText() const 98 | { 99 | if (CustomClass) 100 | { 101 | FText SubsystemTypeText; 102 | if (CustomClass->IsChildOf()) 103 | { 104 | SubsystemTypeText = NSLOCTEXT("K2Node", "GetSubsystem_GameStateSubsystemTooltip", "GameState Subsystem"); 105 | } 106 | return FText::FormatNamed(NSLOCTEXT("K2Node", "GetSubsystem_TooltipFormat", "Get {ClassName} ({SubsystemType})\n\n{ClassTooltip}"), 107 | TEXT("ClassName"), CustomClass->GetDisplayNameText(), 108 | TEXT("SubsystemType"), SubsystemTypeText, 109 | TEXT("ClassTooltip"), CustomClass->GetToolTipText(/*bShortTooltip=*/ true)); 110 | } 111 | 112 | return NSLOCTEXT("K2Node", "GetSubsystem_InvalidSubsystemTypeTooltip", "Invalid Subsystem Type"); 113 | } 114 | 115 | void UK2Node_GetGameStateSubsystem::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const 116 | { 117 | static TArray Subclasses; 118 | Subclasses.Reset(); 119 | GetDerivedClasses(UGameStateSubsystem::StaticClass(), Subclasses); 120 | 121 | auto CustomizeCallback = [](UEdGraphNode* Node, bool bIsTemplateNode, UClass* Subclass) 122 | { 123 | auto TypedNode = CastChecked(Node); 124 | TypedNode->Initialize(Subclass); 125 | }; 126 | 127 | UClass* ActionKey = GetClass(); 128 | if (ActionRegistrar.IsOpenForRegistration(ActionKey)) 129 | { 130 | for (UClass* Iter : Subclasses) 131 | { 132 | if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Iter, true)) 133 | { 134 | continue; 135 | } 136 | 137 | UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(ActionKey); 138 | check(Spawner); 139 | 140 | Spawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeCallback, Iter); 141 | ActionRegistrar.AddBlueprintAction(ActionKey, Spawner); 142 | } 143 | } 144 | } 145 | 146 | FText UK2Node_GetGameStateSubsystem::GetMenuCategory() const 147 | { 148 | return NSLOCTEXT("K2Node", "GetGameStateSubsystem_MenuCategory", "GameState|GameState Subsystems"); 149 | } 150 | 151 | void UK2Node_GetSubsystemFromGS::AllocateDefaultPins() 152 | { 153 | // If required add the world context pin 154 | CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, AExtendableGameStateBase::StaticClass(), TEXT("GameState")); 155 | 156 | // Add blueprint pin 157 | if (!CustomClass) 158 | { 159 | CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Class, USubsystem::StaticClass(), TEXT("Class")); 160 | } 161 | 162 | // Result pin 163 | CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, (CustomClass ? (UClass*)CustomClass : UGameStateSubsystem::StaticClass()), UEdGraphSchema_K2::PN_ReturnValue); 164 | 165 | // Skip the UK2Node_GetSubsystem implementation 166 | UK2Node::AllocateDefaultPins(); 167 | } 168 | 169 | // ************************************************************************************ 170 | // UK2Node_GetSubsystemFromGS 171 | // ************************************************************************************ 172 | 173 | void UK2Node_GetSubsystemFromGS::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) 174 | { 175 | // Skip the UK2Node_GetGameStateSubsystem implementation 176 | UK2Node::ExpandNode(CompilerContext, SourceGraph); 177 | 178 | static const FName GameState_ParamName(TEXT("GameState")); 179 | static const FName Class_ParamName(TEXT("Class")); 180 | 181 | UK2Node_GetSubsystemFromGS* GetSubsystemFromGSNode = this; 182 | UEdGraphPin* SpawnGameStatePin = GetSubsystemFromGSNode->GetGameStatePin(); 183 | UEdGraphPin* SpawnClassPin = GetSubsystemFromGSNode->GetClassPin(); 184 | UEdGraphPin* SpawnNodeResult = GetSubsystemFromGSNode->GetResultPin(); 185 | 186 | UClass* SpawnClass = (SpawnClassPin != nullptr) ? Cast(SpawnClassPin->DefaultObject) : nullptr; 187 | if (SpawnClassPin && (SpawnClassPin->LinkedTo.Num() == 0) && !SpawnClass) 188 | { 189 | CompilerContext.MessageLog.Error(*NSLOCTEXT("K2Node", "GetSubsystem_Error", "Node @@ must have a class specified.").ToString(), GetSubsystemFromGSNode); 190 | GetSubsystemFromGSNode->BreakAllNodeLinks(); 191 | return; 192 | } 193 | 194 | // Choose appropriate underlying Getter 195 | FName Get_FunctionName; 196 | if (CustomClass->IsChildOf()) 197 | { 198 | Get_FunctionName = GET_FUNCTION_NAME_CHECKED(UGameStateSubsystemBlueprintLibrary, GetGameStateSubsystemFromGameState); 199 | } 200 | else 201 | { 202 | CompilerContext.MessageLog.Error(*NSLOCTEXT("K2Node", "GetSubsystem_Error", "Node @@ must have a class specified.").ToString(), GetSubsystemFromGSNode); 203 | GetSubsystemFromGSNode->BreakAllNodeLinks(); 204 | return; 205 | } 206 | 207 | ////////////////////////////////////////////////////////////////////////// 208 | // create 'UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystemFromGameState' call node 209 | UK2Node_CallFunction* CallGetNode = CompilerContext.SpawnIntermediateNode(GetSubsystemFromGSNode, SourceGraph); 210 | CallGetNode->FunctionReference.SetExternalMember(Get_FunctionName, UGameStateSubsystemBlueprintLibrary::StaticClass()); 211 | CallGetNode->AllocateDefaultPins(); 212 | 213 | UEdGraphPin* CallCreateGameStatePin = CallGetNode->FindPinChecked(GameState_ParamName); 214 | UEdGraphPin* CallCreateClassTypePin = CallGetNode->FindPinChecked(Class_ParamName); 215 | UEdGraphPin* CallCreateResult = CallGetNode->GetReturnValuePin(); 216 | 217 | if (SpawnClassPin && SpawnClassPin->LinkedTo.Num() > 0) 218 | { 219 | // Copy the 'class' connection from the spawn node to 'UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystemFromGameState' 220 | CompilerContext.MovePinLinksToIntermediate(*SpawnClassPin, *CallCreateClassTypePin); 221 | } 222 | else 223 | { 224 | // Copy class literal onto 'USubsystemBlueprintLibrary::GetLocalPlayerSubSystemFromPlayerController' call 225 | CallCreateClassTypePin->DefaultObject = *CustomClass; 226 | } 227 | 228 | // Copy the world context connection from the spawn node to 'USubsystemBlueprintLibrary::GetLocalPlayerSubSystemFromPlayerController' if necessary 229 | if (SpawnGameStatePin) 230 | { 231 | CompilerContext.MovePinLinksToIntermediate(*SpawnGameStatePin, *CallCreateGameStatePin); 232 | } 233 | 234 | // Move result connection from spawn node to 'UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystem' 235 | CallCreateResult->PinType = SpawnNodeResult->PinType; 236 | CompilerContext.MovePinLinksToIntermediate(*SpawnNodeResult, *CallCreateResult); 237 | 238 | ////////////////////////////////////////////////////////////////////////// 239 | 240 | // Break any links to the expanded node 241 | GetSubsystemFromGSNode->BreakAllNodeLinks(); 242 | } 243 | 244 | FText UK2Node_GetSubsystemFromGS::GetTooltipText() const 245 | { 246 | if (CustomClass) 247 | { 248 | return FText::FormatNamed(NSLOCTEXT("K2Node", "GetSubsystemFromGS_TooltipFormat", "Get {ClassName} from GameState"), TEXT("ClassName"), CustomClass->GetDisplayNameText()); 249 | } 250 | 251 | return NSLOCTEXT("K2Node", "GetSubsystemFromGS_InvalidSubsystemTypeTooltip", "Invalid Subsystem Type"); 252 | } 253 | 254 | void UK2Node_GetSubsystemFromGS::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const 255 | { 256 | static TArray Subclasses; 257 | Subclasses.Reset(); 258 | GetDerivedClasses(UGameStateSubsystem::StaticClass(), Subclasses); 259 | 260 | auto CustomizeCallback = [](UEdGraphNode* Node, bool bIsTemplateNode, UClass* Subclass) 261 | { 262 | auto TypedNode = CastChecked(Node); 263 | TypedNode->Initialize(Subclass); 264 | }; 265 | 266 | UClass* ActionKey = GetClass(); 267 | if (ActionRegistrar.IsOpenForRegistration(ActionKey)) 268 | { 269 | for (UClass* Iter : Subclasses) 270 | { 271 | if (!UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Iter, true)) 272 | { 273 | continue; 274 | } 275 | 276 | UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(ActionKey); 277 | check(Spawner); 278 | 279 | Spawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeCallback, Iter); 280 | ActionRegistrar.AddBlueprintAction(ActionKey, Spawner); 281 | } 282 | } 283 | } 284 | 285 | FText UK2Node_GetSubsystemFromGS::GetMenuCategory() const 286 | { 287 | return NSLOCTEXT("K2Node", "GetSubsystemFromGS_MenuCategory", "GameState|GameState Subsystems"); 288 | } 289 | 290 | UEdGraphPin* UK2Node_GetSubsystemFromGS::GetGameStatePin() const 291 | { 292 | UEdGraphPin* Pin = FindPin(TEXT("GameState")); 293 | check(Pin == NULL || Pin->Direction == EGPD_Input); 294 | return Pin; 295 | } -------------------------------------------------------------------------------- /Source/Editor/Private/BlueprintGraph/K2Node_GetGameStateSubsystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "K2Node_GetSubsystem.h" 13 | #include "K2Node_GetGameStateSubsystem.generated.h" 14 | 15 | UCLASS() 16 | class UK2Node_GetGameStateSubsystem : public UK2Node_GetSubsystem 17 | { 18 | GENERATED_BODY() 19 | public: 20 | 21 | //~Begin UEdGraphNode interface. 22 | virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; 23 | virtual FText GetTooltipText() const override; 24 | //~End UEdGraphNode interface. 25 | 26 | virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; 27 | virtual FText GetMenuCategory() const override; 28 | }; 29 | 30 | UCLASS() 31 | class UK2Node_GetSubsystemFromGS : public UK2Node_GetGameStateSubsystem 32 | { 33 | GENERATED_BODY() 34 | public: 35 | 36 | //~Begin UEdGraphNode interface. 37 | virtual void AllocateDefaultPins() override; 38 | virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; 39 | virtual FText GetTooltipText() const override; 40 | //~End UEdGraphNode interface. 41 | 42 | virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; 43 | virtual FText GetMenuCategory() const override; 44 | UEdGraphPin* GetGameStatePin() const; 45 | }; -------------------------------------------------------------------------------- /Source/Editor/Private/GameStateSubsystemBlueprintLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "GameStateSubsystemBlueprintLibrary.h" 11 | #include "ExtendableGameStateBase.h" 12 | 13 | #include UE_INLINE_GENERATED_CPP_BY_NAME(GameStateSubsystemBlueprintLibrary) 14 | 15 | UGameStateSubsystem* UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystem(UObject* ContextObject, TSubclassOf Class) 16 | { 17 | if(const auto* GameState = Cast(ContextObject)) 18 | { 19 | return GameState->GetSubsystemBase(Class); 20 | } 21 | return nullptr; 22 | } 23 | 24 | UGameStateSubsystem* UGameStateSubsystemBlueprintLibrary::GetGameStateSubsystemFromGameState(AExtendableGameStateBase* GameState, TSubclassOf Class) 25 | { 26 | return GameState ? GameState->GetSubsystemBase(Class) : nullptr; 27 | } 28 | -------------------------------------------------------------------------------- /Source/Editor/Private/GameStateSubsystemBlueprintLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "GameStateSubsystemBlueprintLibrary.generated.h" 13 | 14 | class AExtendableGameStateBase; 15 | class UGameStateSubsystem; 16 | 17 | UCLASS(MinimalAPI) 18 | class UGameStateSubsystemBlueprintLibrary : public UBlueprintFunctionLibrary 19 | { 20 | GENERATED_BODY() 21 | public: 22 | 23 | /** Get a GameState Subsystem from the GameState associated with the provided context */ 24 | UFUNCTION(BlueprintPure, Category = "GameState Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true")) 25 | static GAMESTATESUBSYSTEMED_API UGameStateSubsystem* GetGameStateSubsystem(UObject* ContextObject, TSubclassOf Class); 26 | 27 | /** 28 | * Get a GameState Subsystem from the GameState associated with the provided context 29 | * If the player GameState isn't valid a nullptr is returned 30 | */ 31 | UFUNCTION(BlueprintPure, Category = "GameState Subsystems", meta = (BlueprintInternalUseOnly = "true")) 32 | static GAMESTATESUBSYSTEMED_API UGameStateSubsystem* GetGameStateSubsystemFromGameState(AExtendableGameStateBase* GameState, TSubclassOf Class); 33 | }; -------------------------------------------------------------------------------- /Source/Editor/Private/GameStateSubsystemEdModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "GameStateSubsystemEdModule.h" 11 | #include "Modules/ModuleManager.h" 12 | 13 | void FGameStateSubsystemEdModule::StartupModule() 14 | { 15 | } 16 | 17 | void FGameStateSubsystemEdModule::ShutdownModule() 18 | { 19 | } 20 | 21 | IMPLEMENT_MODULE(FGameStateSubsystemEdModule, GameStateSubsystemEd) -------------------------------------------------------------------------------- /Source/Editor/Private/GameStateSubsystemEdModule.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "Modules/ModuleInterface.h" 13 | 14 | class FGameStateSubsystemEdModule : public IModuleInterface 15 | { 16 | void StartupModule() override; 17 | void ShutdownModule() override; 18 | }; 19 | -------------------------------------------------------------------------------- /Source/Runtime/GameStateSubsystem.build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | using UnrealBuildTool; 11 | 12 | public class GameStateSubsystem : ModuleRules 13 | { 14 | public GameStateSubsystem(ReadOnlyTargetRules Target) : base(Target) 15 | { 16 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 17 | 18 | PublicDependencyModuleNames.AddRange(new string[] 19 | { 20 | }); 21 | 22 | PrivateDependencyModuleNames.AddRange(new string[] { 23 | "Core", 24 | "CoreUObject", 25 | "Engine", 26 | "IrisCore", 27 | "NetCore" 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/Runtime/Private/ExampleGameStateSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "ExampleGameStateSubsystem.h" 11 | #include "Logging/StructuredLog.h" 12 | #include "Net/UnrealNetwork.h" 13 | 14 | #include UE_INLINE_GENERATED_CPP_BY_NAME(ExampleGameStateSubsystem) 15 | 16 | static FString NetModeToString(ENetMode InNetMode) 17 | { 18 | switch(InNetMode) 19 | { 20 | case NM_Standalone: 21 | return TEXT("Standalone"); 22 | case NM_DedicatedServer: 23 | return TEXT("DedicatedServer"); 24 | case NM_ListenServer: 25 | return TEXT("ListenServer"); 26 | case NM_Client: 27 | return TEXT("Client"); 28 | default: 29 | checkf(false, TEXT("Invalid NetMode")); 30 | return TEXT("Invalid"); 31 | } 32 | } 33 | 34 | void UExampleGameStateSubsystem::Initialize(FSubsystemCollectionBase& Collection) 35 | { 36 | Super::Initialize(Collection); 37 | 38 | checkf(GetOuter(), TEXT("No Outer.")); 39 | 40 | GetWorldTimerManager().SetTimer(RPCTestTimer, FTimerDelegate::CreateWeakLambda(this, [this]() 41 | { 42 | if(HasAuthority()) 43 | { 44 | ServerDoSomething(); 45 | } 46 | else 47 | { 48 | ClientDoSomething(); 49 | } 50 | }), 5.0f, true); 51 | 52 | UE_LOGFMT(LogNet, Display, "Initialized Example Gamestate Subsystem, NetMode - {0}", NetModeToString(GetWorld()->GetNetMode())); 53 | } 54 | 55 | void UExampleGameStateSubsystem::Deinitialize() 56 | { 57 | Super::Deinitialize(); 58 | 59 | RPCTestTimer.Invalidate(); 60 | 61 | UE_LOGFMT(LogNet, Display, "Deinitialized Example Gamestate Subsystem, NetMode - {0}", NetModeToString(GetWorld()->GetNetMode())); 62 | } 63 | 64 | bool UExampleGameStateSubsystem::ShouldCreateSubsystem(UObject* Outer) const 65 | { 66 | constexpr bool CreateExampleSubsystem = false; 67 | return Super::ShouldCreateSubsystem(Outer) && CreateExampleSubsystem; 68 | } 69 | 70 | void UExampleGameStateSubsystem::Tick(float DeltaTime) 71 | { 72 | Super::Tick(DeltaTime); 73 | 74 | if(HasAuthority()) 75 | { 76 | RepVar2 += 1; 77 | } 78 | } 79 | 80 | void UExampleGameStateSubsystem::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const 81 | { 82 | Super::GetLifetimeReplicatedProps(OutLifetimeProps); 83 | 84 | DOREPLIFETIME_CONDITION(UExampleGameStateSubsystem, RepVar1, COND_OwnerOnly); 85 | DOREPLIFETIME(UExampleGameStateSubsystem, RepVar2); 86 | } 87 | 88 | void UExampleGameStateSubsystem::ServerDoSomething_Implementation() 89 | { 90 | RepVar1 = FMath::Rand(); 91 | UE_LOGFMT(LogNet, Display, "Logging Something on Server! {0}", NetModeToString(GetWorld()->GetNetMode())); 92 | } 93 | 94 | void UExampleGameStateSubsystem::ClientDoSomething_Implementation() 95 | { 96 | UE_LOGFMT(LogNet, Display, "Logging Something on Client! {0}", NetModeToString(GetWorld()->GetNetMode())); 97 | } 98 | 99 | void UExampleGameStateSubsystem::OnRep_RepVar2(int32 NewVar2) 100 | { 101 | if(!HasAuthority()) 102 | { 103 | UE_LOGFMT(LogNet, Display, "RepVar2 Changed on Example GameState Subsystem! {0}", NewVar2); 104 | } 105 | } 106 | 107 | void UExampleGameStateSubsystem::BlueprintDoAThing() 108 | { 109 | UE_LOGFMT(LogTemp, Display, "Do a thing called from Blueprint."); 110 | } 111 | -------------------------------------------------------------------------------- /Source/Runtime/Private/ExtendableGameStateBase.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "ExtendableGameStateBase.h" 11 | 12 | #include UE_INLINE_GENERATED_CPP_BY_NAME(ExtendableGameStateBase) 13 | 14 | AExtendableGameStateBase::AExtendableGameStateBase() 15 | { 16 | bReplicateUsingRegisteredSubObjectList = true; 17 | } 18 | 19 | void AExtendableGameStateBase::PostInitializeComponents() 20 | { 21 | Super::PostInitializeComponents(); 22 | 23 | check(!SubsystemCollection.IsInitialized()); 24 | SubsystemCollection.Initialize(this); 25 | } 26 | 27 | void AExtendableGameStateBase::BeginPlay() 28 | { 29 | Super::BeginPlay(); 30 | 31 | for(UGameStateSubsystem* Subsystem : GetSubsystemArray()) 32 | { 33 | AddReplicatedSubObject(Subsystem); 34 | Subsystem->BeginPlay(); 35 | } 36 | } 37 | 38 | void AExtendableGameStateBase::EndPlay(const EEndPlayReason::Type EndPlayReason) 39 | { 40 | for(UGameStateSubsystem* Subsystem : GetSubsystemArray()) 41 | { 42 | RemoveReplicatedSubObject(Subsystem); 43 | } 44 | 45 | SubsystemCollection.Deinitialize(); 46 | 47 | Super::EndPlay(EndPlayReason); 48 | } 49 | 50 | void AExtendableGameStateBase::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) 51 | { 52 | AExtendableGameStateBase* This = CastChecked(InThis); 53 | This->SubsystemCollection.AddReferencedObjects(InThis, Collector); 54 | Super::AddReferencedObjects(InThis, Collector); 55 | } 56 | -------------------------------------------------------------------------------- /Source/Runtime/Private/GameStateSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "GameStateSubsystem.h" 11 | #include "ExtendableGameStateBase.h" 12 | #if UE_WITH_IRIS 13 | #include "Iris/ReplicationSystem/ReplicationFragmentUtil.h" 14 | #endif 15 | 16 | #include UE_INLINE_GENERATED_CPP_BY_NAME(GameStateSubsystem) 17 | 18 | AExtendableGameStateBase* UGameStateSubsystem::GetGameState() const 19 | { 20 | return CastChecked(GetOuter()); 21 | } 22 | 23 | ENetRole UGameStateSubsystem::GetLocalRole() const 24 | { 25 | return GetGameState()->GetLocalRole(); 26 | } 27 | 28 | bool UGameStateSubsystem::HasAuthority() const 29 | { 30 | return (GetLocalRole() == ROLE_Authority); 31 | } 32 | 33 | bool UGameStateSubsystem::ShouldCreateSubsystem(UObject* Outer) const 34 | { 35 | const UWorld* World = Outer->GetWorld(); 36 | return World && World->IsGameWorld(); 37 | } 38 | 39 | UWorld& UGameStateSubsystem::GetWorldRef() const 40 | { 41 | checkf(GetOuter()->GetWorld(), TEXT("Invalid world on outer.")); 42 | return *GetOuter()->GetWorld(); 43 | } 44 | 45 | UWorld* UGameStateSubsystem::GetWorld() const 46 | { 47 | return GetOuter()->GetWorld(); 48 | } 49 | 50 | bool UGameStateSubsystem::IsNameStableForNetworking() const 51 | { 52 | return true; 53 | } 54 | 55 | bool UGameStateSubsystem::IsFullNameStableForNetworking() const 56 | { 57 | return true; 58 | } 59 | 60 | bool UGameStateSubsystem::IsSupportedForNetworking() const 61 | { 62 | return true; 63 | } 64 | 65 | int32 UGameStateSubsystem::GetFunctionCallspace(UFunction* Function, FFrame* Stack) 66 | { 67 | return GetGameState()->GetFunctionCallspace(Function, Stack); 68 | } 69 | 70 | bool UGameStateSubsystem::CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack) 71 | { 72 | if(UNetDriver* NetDriver = GetGameState()->GetNetDriver()) 73 | { 74 | NetDriver->ProcessRemoteFunction(GetGameState(), Function, Parms, OutParms, Stack, this); 75 | return true; 76 | } 77 | return false; 78 | } 79 | 80 | #if UE_WITH_IRIS 81 | void UGameStateSubsystem::RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags) 82 | { 83 | Super::RegisterReplicationFragments(Context, RegistrationFlags); 84 | 85 | UE::Net::FReplicationFragmentUtil::CreateAndRegisterFragmentsForObject(this, Context, RegistrationFlags); 86 | } 87 | #endif 88 | 89 | ETickableTickType UTickableGameStateSubsystem::GetTickableTickType() const 90 | { 91 | // By default (if the child class doesn't override GetTickableTickType), don't let CDOs ever tick: 92 | return IsTemplate() ? ETickableTickType::Never : FTickableGameObject::GetTickableTickType(); 93 | } 94 | 95 | bool UTickableGameStateSubsystem::IsAllowedToTick() const 96 | { 97 | // No matter what IsTickable says, don't let CDOs or uninitialized subsystems tick. 98 | // Note: even if GetTickableTickType was overridden by the child class and returns something else than ETickableTickType::Never for CDOs, 99 | // it's probably a mistake, so by default, don't allow ticking. If the child class really intends its CDO to tick, it can always override IsAllowedToTick... 100 | return !IsTemplate() && bInitialized; 101 | } 102 | 103 | void UTickableGameStateSubsystem::Tick(float DeltaTime) 104 | { 105 | checkf(IsInitialized(), TEXT("Ticking should have been disabled for an uninitialized subsystem : remember to call IsInitialized in the subsystem's IsTickable, IsTickableInEditor and/or IsTickableWhenPaused implementation")); 106 | } 107 | 108 | void UTickableGameStateSubsystem::Initialize(FSubsystemCollectionBase& Collection) 109 | { 110 | check(!bInitialized); 111 | bInitialized = true; 112 | } 113 | 114 | void UTickableGameStateSubsystem::Deinitialize() 115 | { 116 | check(bInitialized); 117 | bInitialized = false; 118 | } 119 | -------------------------------------------------------------------------------- /Source/Runtime/Private/GameStateSubsystemModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #include "GameStateSubsystemModule.h" 11 | #include "Modules/ModuleManager.h" 12 | 13 | void FGameStateSubsystemModule::StartupModule() 14 | { 15 | } 16 | 17 | void FGameStateSubsystemModule::ShutdownModule() 18 | { 19 | } 20 | 21 | IMPLEMENT_MODULE(FGameStateSubsystemModule, GameStateSubsystem) -------------------------------------------------------------------------------- /Source/Runtime/Private/GameStateSubsystemModule.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "Modules/ModuleInterface.h" 13 | 14 | class FGameStateSubsystemModule : public IModuleInterface 15 | { 16 | void StartupModule() override; 17 | void ShutdownModule() override; 18 | }; -------------------------------------------------------------------------------- /Source/Runtime/Public/ExampleGameStateSubsystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "GameStateSubsystem.h" 13 | #include "ExampleGameStateSubsystem.generated.h" 14 | 15 | UCLASS(MinimalAPI) 16 | class UExampleGameStateSubsystem : public UTickableGameStateSubsystem 17 | { 18 | GENERATED_BODY() 19 | public: 20 | 21 | virtual void Initialize(FSubsystemCollectionBase& Collection) override; 22 | virtual void Deinitialize() override; 23 | virtual bool ShouldCreateSubsystem(UObject* Outer) const override; 24 | virtual void Tick(float DeltaTime) override; 25 | TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UExampleGameStateSubsystem, STATGROUP_Tickables); } 26 | 27 | virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; 28 | 29 | UFUNCTION(Server, Reliable) 30 | void ServerDoSomething(); 31 | 32 | UFUNCTION(Client, Reliable) 33 | void ClientDoSomething(); 34 | 35 | UPROPERTY(Transient, Replicated) 36 | int32 RepVar1 = 0; 37 | 38 | UPROPERTY(Transient, ReplicatedUsing=OnRep_RepVar2) 39 | int32 RepVar2 = 0; 40 | 41 | UFUNCTION() 42 | void OnRep_RepVar2(int32 NewVar2); 43 | 44 | UFUNCTION(BlueprintCallable) 45 | void BlueprintDoAThing(); 46 | 47 | private: 48 | 49 | FTimerHandle RPCTestTimer; 50 | }; 51 | -------------------------------------------------------------------------------- /Source/Runtime/Public/ExtendableGameStateBase.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "GameStateSubsystem.h" 13 | #include "GameFramework/GameStateBase.h" 14 | #include "ExtendableGameStateBase.generated.h" 15 | 16 | UCLASS() 17 | class GAMESTATESUBSYSTEM_API AExtendableGameStateBase : public AGameStateBase 18 | { 19 | GENERATED_BODY() 20 | public: 21 | 22 | AExtendableGameStateBase(); 23 | 24 | virtual void PostInitializeComponents() override; 25 | virtual void BeginPlay() override; 26 | virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 27 | static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector); 28 | 29 | /** 30 | * Get a Subsystem of specified type 31 | */ 32 | UGameStateSubsystem* GetSubsystemBase(TSubclassOf SubsystemClass) const 33 | { 34 | return SubsystemCollection.GetSubsystem(SubsystemClass); 35 | } 36 | 37 | /** 38 | * Get a Subsystem of specified type 39 | */ 40 | template 41 | TSubsystemClass* GetSubsystem() const 42 | { 43 | return SubsystemCollection.GetSubsystem(TSubsystemClass::StaticClass()); 44 | } 45 | 46 | /** 47 | * Get a Subsystem of specified type from the provided GameState 48 | * returns nullptr if the Subsystem cannot be found or the GameState is null 49 | */ 50 | template 51 | static FORCEINLINE TSubsystemClass* GetSubsystem(const AExtendableGameStateBase* GameState) 52 | { 53 | if (GameState) 54 | { 55 | return GameState->GetSubsystem(); 56 | } 57 | return nullptr; 58 | } 59 | 60 | /** 61 | * Get all Subsystem of specified type, this is only necessary for interfaces that can have multiple implementations instanced at a time. 62 | * 63 | * Do not hold onto this Array reference unless you are sure the lifetime is less than that of AExtendableGameStateBase 64 | */ 65 | template 66 | const TArray& GetSubsystemArray() const 67 | { 68 | return SubsystemCollection.GetSubsystemArray(TSubsystemClass::StaticClass()); 69 | } 70 | 71 | private: 72 | 73 | FObjectSubsystemCollection SubsystemCollection; 74 | }; -------------------------------------------------------------------------------- /Source/Runtime/Public/GameStateSubsystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Daft Software 2 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 3 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 5 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 6 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 8 | // SOFTWARE. 9 | 10 | #pragma once 11 | 12 | #include "Subsystems/Subsystem.h" 13 | #include "GameStateSubsystem.generated.h" 14 | 15 | class AExtendableGameStateBase; 16 | 17 | /** 18 | * GameStateSubsystems offer an alternative to UGameStateComponent for compositional systems that need 19 | * to be replicated and part of the GameState lifetime. With GameStateComponents typically you would use 20 | * Lyra's Experience system to compose which components are active in certain contexts or you just manually 21 | * add them as subobjects. This subsystem type is by no means a replacement for GameState Components and 22 | * you should be aware there is times where they are more suitable. 23 | * 24 | * The time when GameStateSubsystems are useful is for systems that are for the most apart unconditionally 25 | * available and need to be a modular extension of the GameState itself. Since they are automatically registered 26 | * you only need to create the subsystem and the functionality will run as long as the GameState is alive. 27 | * 28 | * It's also worth nothing that GameStateSubsystems are essentially there to serve a similar purpose to world 29 | * subsystems. However their dependency on the GameState greatly simplifies replication context, since world 30 | * subsystems existing during obscure times in the world lifecycle, and due to their singleton nature, you can't 31 | * have multiple instances of them on the server, which would be required if the player is authoritative over 32 | * their own subsystem (similarly to a player controller). For that functionality I recommend UControllerComponent. 33 | */ 34 | 35 | /** 36 | * UGameStateSubsystem 37 | * Base class for auto instanced and initialized systems that share the lifetime of modular game state 38 | * They share the same replication rules as the GameStateBase. 39 | */ 40 | UCLASS(Abstract, MinimalAPI) 41 | class UGameStateSubsystem : public USubsystem 42 | { 43 | GENERATED_BODY() 44 | public: 45 | 46 | virtual void BeginPlay() {} 47 | 48 | GAMESTATESUBSYSTEM_API AExtendableGameStateBase* GetGameState() const; 49 | 50 | template 51 | T* GetGameState() const 52 | { 53 | return Cast(GetGameState()); 54 | } 55 | 56 | GAMESTATESUBSYSTEM_API FTimerManager& GetWorldTimerManager() { return GetWorld()->GetTimerManager(); } 57 | 58 | GAMESTATESUBSYSTEM_API ENetRole GetLocalRole() const; 59 | GAMESTATESUBSYSTEM_API bool HasAuthority() const; 60 | 61 | GAMESTATESUBSYSTEM_API virtual bool ShouldCreateSubsystem(UObject* Outer) const override; 62 | 63 | /** 64 | * Returns a reference to the UWorld this subsystem is contained within. 65 | * @note This should not be called on default object since the method assumes a valid outer world. 66 | */ 67 | GAMESTATESUBSYSTEM_API UWorld& GetWorldRef() const; 68 | 69 | GAMESTATESUBSYSTEM_API virtual UWorld* GetWorld() const override final; 70 | 71 | //~ Begin UObject 72 | GAMESTATESUBSYSTEM_API virtual bool IsNameStableForNetworking() const override final; 73 | GAMESTATESUBSYSTEM_API virtual bool IsFullNameStableForNetworking() const override final; 74 | GAMESTATESUBSYSTEM_API virtual bool IsSupportedForNetworking() const override final; 75 | GAMESTATESUBSYSTEM_API virtual int32 GetFunctionCallspace(UFunction* Function, FFrame* Stack) override; 76 | GAMESTATESUBSYSTEM_API virtual bool CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack) override; 77 | #if UE_WITH_IRIS 78 | GAMESTATESUBSYSTEM_API virtual void RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags) override; 79 | #endif 80 | //~ End UObject 81 | }; 82 | 83 | /** 84 | * UTickableGameStateSubsystem 85 | * GameStateSubsystem that has tick functionality. 86 | */ 87 | UCLASS(Abstract, MinimalAPI) 88 | class UTickableGameStateSubsystem : public UGameStateSubsystem, public FTickableGameObject 89 | { 90 | GENERATED_BODY() 91 | public: 92 | 93 | //~ Begin FTickableGameObject 94 | virtual UWorld* GetTickableGameObjectWorld() const override { return GetWorld(); } 95 | GAMESTATESUBSYSTEM_API virtual ETickableTickType GetTickableTickType() const override; 96 | GAMESTATESUBSYSTEM_API virtual bool IsAllowedToTick() const override final; 97 | GAMESTATESUBSYSTEM_API virtual void Tick(float DeltaTime) override; 98 | GAMESTATESUBSYSTEM_API virtual TStatId GetStatId() const override PURE_VIRTUAL(UTickableWorldSubsystem::GetStatId, return TStatId();); 99 | //~ End FTickableGameObject 100 | 101 | //~ Begin USubsystem 102 | GAMESTATESUBSYSTEM_API virtual void Initialize(FSubsystemCollectionBase& Collection) override; 103 | GAMESTATESUBSYSTEM_API virtual void Deinitialize() override; 104 | //~End USubsystem 105 | 106 | bool IsInitialized() const { return bInitialized; } 107 | 108 | private: 109 | bool bInitialized = false; 110 | }; 111 | --------------------------------------------------------------------------------