├── .gitignore ├── .gitmodules ├── ArizonaFramework.flaxproj ├── Content ├── Materials │ ├── BlueGrid.flax │ ├── GrayGrid.flax │ ├── GreedGrid.flax │ ├── OrangeGrid.flax │ ├── PrototypeGrid.flax │ ├── RedGrid.flax │ ├── VioletGrid.flax │ └── YellowGrid.flax ├── Models │ ├── Cube Collision.flax │ ├── Cube.flax │ ├── Cylinder Collision.flax │ ├── Cylinder.flax │ ├── CylinderHalf Collision.flax │ ├── CylinderHalf.flax │ ├── CylinderQuarter Collision.flax │ ├── CylinderQuarter.flax │ ├── Ramp Collision.flax │ └── Ramp.flax ├── Textures │ ├── grid_albedo.flax │ ├── grid_mask.flax │ └── grid_orm.flax └── screenshot.png ├── LICENSE ├── README.md └── Source ├── .editorconfig ├── ArizonaFramework ├── ArizonaFramework.Build.cs ├── Core │ ├── GameInstance.Editor.cs │ ├── GameInstance.cpp │ ├── GameInstance.cs │ ├── GameInstance.h │ ├── GameInstanceSettings.h │ ├── GameMode.h │ ├── GameSceneSystem.h │ ├── GameState.h │ ├── GameSystem.h │ ├── PlayerController.h │ ├── PlayerPawn.h │ ├── PlayerState.h │ ├── PlayerUI.h │ └── Types.h ├── Debug │ ├── DebugSettings.h │ ├── DebugSystem.cpp │ ├── DebugSystem.h │ ├── DebugWindow.h │ ├── DebugWindows.cpp │ └── DebugWindows.h ├── Networking │ ├── ReplicationHierarchy.cpp │ ├── ReplicationHierarchy.h │ └── ReplicationSettings.h ├── UI │ ├── UISystem.cpp │ └── UISystem.h └── Utilities │ └── Utilities.h ├── ArizonaFrameworkEditorTarget.Build.cs └── ArizonaFrameworkTarget.Build.cs /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Flax project files 2 | /Binaries/ 3 | /Cache/ 4 | /Logs/ 5 | /Output/ 6 | /Screenshots/ 7 | *.HotReload.* 8 | Source/*.Gen.* 9 | 10 | # Local files 11 | imgui.ini 12 | 13 | # Ignore Visual Studio project files (generated locally) 14 | *.csproj 15 | *.sln 16 | launchSettings.json 17 | 18 | # Ignore Visual Code project files (generated locally) 19 | *.code-workspace 20 | *.vscode/ 21 | launchSettings.json 22 | 23 | # Ignore Rider project files (generated locally) 24 | *.idea/ 25 | 26 | # Ignore thumbnails created by Windows 27 | Thumbs.db 28 | 29 | # Ignore thumbnails created by Mac 30 | .DS_Store 31 | 32 | # Ignore files built by Visual Studio 33 | *.obj 34 | *.exe 35 | *.pdb 36 | *.user 37 | *.aps 38 | *.pch 39 | *.vspscc 40 | *_i.c 41 | *_p.c 42 | *.ncb 43 | *.suo 44 | *.tlb 45 | *.tlh 46 | *.bak 47 | *.cache 48 | *.ilk 49 | *.log 50 | [Bb]in 51 | *.lib 52 | *.sbr 53 | /Source/obj/ 54 | _ReSharper*/ 55 | [Tt]est[Rr]esult* 56 | .vs/ 57 | 58 | # Ignore Nuget packages folder 59 | packages/ 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Plugins/ImGui"] 2 | path = Plugins/ImGui 3 | url = https://github.com/FlaxEngine/ImGui.git 4 | -------------------------------------------------------------------------------- /ArizonaFramework.flaxproj: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "ArizonaFramework", 3 | "Version": "0.1", 4 | "MinEngineVersion": "1.5", 5 | "Company": "Wojciech Figat", 6 | "Copyright": "Copyright (c) Wojciech Figat. All rights reserved.", 7 | "GameTarget": "ArizonaFrameworkTarget", 8 | "EditorTarget": "ArizonaFrameworkEditorTarget", 9 | "References": [ 10 | { 11 | "Name": "$(EnginePath)/Flax.flaxproj" 12 | }, 13 | { 14 | "Name": "$(ProjectPath)/Plugins/ImGui/ImGui.flaxproj" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /Content/Materials/BlueGrid.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Materials/BlueGrid.flax -------------------------------------------------------------------------------- /Content/Materials/GrayGrid.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Materials/GrayGrid.flax -------------------------------------------------------------------------------- /Content/Materials/GreedGrid.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Materials/GreedGrid.flax -------------------------------------------------------------------------------- /Content/Materials/OrangeGrid.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Materials/OrangeGrid.flax -------------------------------------------------------------------------------- /Content/Materials/PrototypeGrid.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Materials/PrototypeGrid.flax -------------------------------------------------------------------------------- /Content/Materials/RedGrid.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Materials/RedGrid.flax -------------------------------------------------------------------------------- /Content/Materials/VioletGrid.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Materials/VioletGrid.flax -------------------------------------------------------------------------------- /Content/Materials/YellowGrid.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Materials/YellowGrid.flax -------------------------------------------------------------------------------- /Content/Models/Cube Collision.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/Cube Collision.flax -------------------------------------------------------------------------------- /Content/Models/Cube.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/Cube.flax -------------------------------------------------------------------------------- /Content/Models/Cylinder Collision.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/Cylinder Collision.flax -------------------------------------------------------------------------------- /Content/Models/Cylinder.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/Cylinder.flax -------------------------------------------------------------------------------- /Content/Models/CylinderHalf Collision.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/CylinderHalf Collision.flax -------------------------------------------------------------------------------- /Content/Models/CylinderHalf.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/CylinderHalf.flax -------------------------------------------------------------------------------- /Content/Models/CylinderQuarter Collision.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/CylinderQuarter Collision.flax -------------------------------------------------------------------------------- /Content/Models/CylinderQuarter.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/CylinderQuarter.flax -------------------------------------------------------------------------------- /Content/Models/Ramp Collision.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/Ramp Collision.flax -------------------------------------------------------------------------------- /Content/Models/Ramp.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Models/Ramp.flax -------------------------------------------------------------------------------- /Content/Textures/grid_albedo.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Textures/grid_albedo.flax -------------------------------------------------------------------------------- /Content/Textures/grid_mask.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Textures/grid_mask.flax -------------------------------------------------------------------------------- /Content/Textures/grid_orm.flax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/Textures/grid_orm.flax -------------------------------------------------------------------------------- /Content/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Content/screenshot.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Flax Engine 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 | # Arizona Framework 2 | 3 | ![Arizona Framework Sample](Content/screenshot.png) 4 | 5 | Ready to use, open-source framework for creating games in Flax. 6 | 7 | ## Features 8 | 9 | * Core game architecture concepts 10 | * Game Mode and Game State 11 | * Player State, Player Pawn and Player UI 12 | * Multiplayer-ready (server-client infrastructure with optional local co op) 13 | * Extendable (eg. via `GameSystem` or `GameSceneSystem`) 14 | * Debug UI ([ImGui](https://github.com/FlaxEngine/ImGui)) 15 | 16 | Minimum supported Flax version: `1.5`. 17 | 18 | ## Sample project 19 | 20 | See the open-source [Arizona Framework Sample](https://github.com/FlaxEngine/ArizonaFrameworkSample) project that showcases Arizona Framework usage in a simple first-person shooter game with multiplayer. 21 | 22 | ## Installation 23 | 24 | 0. Ensure to have proper system setup for C++ Scripting - see [Flax Docs](https://docs.flaxengine.com/manual/scripting/cpp/index.html) 25 | 26 | 1. Clone repo into `\Plugins\ArizonaFramework` (with submodules: `git submodule update --init --recursive`) 27 | 28 | 2. Add reference to Arizona Framework project in your game by modyfying your game `.flaxproj` as follows: 29 | 30 | ``` 31 | ... 32 | "References": [ 33 | { 34 | "Name": "$(EnginePath)/Flax.flaxproj" 35 | }, 36 | { 37 | "Name": "$(ProjectPath)/Plugins/ArizonaFramework/ArizonaFramework.flaxproj" 38 | } 39 | ] 40 | ``` 41 | 42 | 3. Add reference to *ArizonaFramework* module in your game build script (eg. `Game.Build.cs`) as follows: 43 | 44 | ```cs 45 | /// 46 | public override void Setup(BuildOptions options) 47 | { 48 | base.Setup(options); 49 | 50 | BuildNativeCode = false; 51 | options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true; 52 | 53 | // Add reference to ArizonaFramework 54 | options.PrivateDependencies.Add("ArizonaFramework"); 55 | } 56 | ``` 57 | 58 | 4. Create new `Game Instance Settings` (linked to Game Settings by Editor). 59 | 60 | Ensure to have [Network Settings](https://docs.flaxengine.com/manual/networking/high-level.html#network-settings) setup. 61 | 62 | 5. Customize it 63 | 64 | Now you can use *Arizona Framework* in your project. Use created `Game Instance Settings` asset to define your game mode, player pawn and other types to control the game data and logic. 65 | 66 | ## License 67 | 68 | Both this plugin and ImGui are released under **MIT License**. 69 | 70 | ## Core Components 71 | 72 | ### Game Instance 73 | 74 | Main game singleton plugin that manages the game systems and handles Game Mode setup and lifetime for the play. 75 | 76 | ### Game System 77 | 78 | Gameplay system component attached to the Game Instance. Lifetime tied with the game. 79 | 80 | ### Game Scene System 81 | 82 | Scene gameplay component attached to the Game Instance. Lifetime tied with the scene (created for each loaded scene). 83 | 84 | ### Game Mode 85 | 86 | Main, root system of the game that implements the logic and flow of the gameplay. Exists only on server. Handles clients joining and spawning them on the level with local-client authority. Controls the limit of the players on a map, team sizes, allowed weapons and characters. Controls bots and spectators, but also level changes. Persists between scene changes. 87 | 88 | ### Game State 89 | 90 | Global gameplay state container, maintained by server and replicated into all clients. Allows clients to access a list of players or gameplay conditions state (eg. current match score, elapsed game time). 91 | 92 | ### Player State 93 | 94 | Player gameplay state container, maintained by server and replicated into all clients. Allows clients to access each player information (eg. name, score, ping, team, isSpectator, isBot). 95 | 96 | ### Player Pawn 97 | 98 | Player actor on a scene (eg. prefab) that represents it in the game. Replicated to all clients (depending on the relevance) and owned by the server (simulated based on controller inputs). Locally simulated pawn can use autonomous replication mode to allow using locally playing human inputs for smooth gameplay but will still be validated against server state to prevent cheating. 99 | 100 | ### Player Controller 101 | 102 | Player inputs controller that receives input from the player and converts it into movement for pawn. Exists on owning-client and is replicated on server-only. 103 | 104 | ### Player UI 105 | 106 | Player User Interface with HUD. Exists only on local clients and is using Player State with Game State to inform players about the gameplay. 107 | 108 | ## Architecture 109 | 110 | * Game Instance (GamePlugin) 111 | * Game System 112 | * Game Scene System 113 | * Game Mode 114 | * Player Pawn (script attached to spawned actor) 115 | * Player Controller (script attached to spawned actor) 116 | * Player UI (script attached to spawned actor) 117 | * Game State 118 | * Player State 119 | 120 | ## Game Systems 121 | 122 | ``GameSystem`` and ``GameSceneSystem`` are base types for custom gameplay systems that are tied with the game/scene lifetime. This allows quickly extending the gameplay with custom features such as Level Streaming, Weapons Manager, AI Manager, or other game systems/managers. ``GameSceneSystem`` is created once per loaded scene thus allowing to cache of scene-related data (eg. active entities). 123 | 124 | Game Systems are created by Game Instance during game plugin initialization when the game starts. Each system can optionally skip with `CanBeUsed` method. 125 | 126 | Example usage of `GameSystem` that loads game level when game connects to network server or main menu level on disconnect, it has cached list of Spawn Points for Game Mode to spawn players: 127 | 128 | ```cs 129 | /// 130 | /// Game levels manager that handles scene transitions (eg. joining game map after main menu connection). 131 | /// 132 | public class LevelsManager : GameSystem 133 | { 134 | /// 135 | /// Gets the Levels Manager instance. 136 | /// 137 | public static LevelsManager Instance => GameInstance.Instance?.GetGameSystem(); 138 | 139 | /// 140 | /// Active spawn points. 141 | /// 142 | public readonly List SpawnPoints = new List(); 143 | 144 | /// 145 | public override void Initialize() 146 | { 147 | base.Initialize(); 148 | 149 | NetworkManager.StateChanged += OnNetworkStateChanged; 150 | } 151 | 152 | /// 153 | public override void Deinitialize() 154 | { 155 | NetworkManager.StateChanged -= OnNetworkStateChanged; 156 | 157 | base.Deinitialize(); 158 | } 159 | 160 | private void OnNetworkStateChanged() 161 | { 162 | // Select target scene to go to 163 | var mySettings = MySettings.Instance; 164 | var targetScene = mySettings.MainMenuLevel; // Go to menu by default 165 | if (NetworkManager.State == NetworkConnectionState.Connected) 166 | { 167 | targetScene = mySettings.GameLevel; // Go to the game level 168 | } 169 | 170 | // Load that scene (skip if already loaded) 171 | if (Level.FindActor(targetScene.ID) != null) 172 | return; 173 | Level.UnloadAllScenesAsync(); 174 | Level.LoadSceneAsync(targetScene); 175 | } 176 | } 177 | ``` 178 | -------------------------------------------------------------------------------- /Source/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = crlf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.ttinclude] 9 | insert_final_newline = false 10 | 11 | # C++ files 12 | [*.{cpp,c,h,hpp}] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | # C# files 17 | [*.cs] 18 | indent_style = space 19 | indent_size = 4 20 | 21 | # Shader files 22 | [*.{hlsl,shader,glsl}] 23 | indent_style = space 24 | indent_size = 4 25 | 26 | # XAML files 27 | [*.xaml] 28 | indent_style = space 29 | indent_size = 2 30 | 31 | # MSBuild 32 | [*.{csproj,proj,projitems,shproj,fsproj,target,props}] 33 | indent_style = space 34 | indent_size = 2 35 | 36 | # Python files 37 | [*.py] 38 | indent_style = space 39 | indent_size = 4 40 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/ArizonaFramework.Build.cs: -------------------------------------------------------------------------------- 1 | using Flax.Build; 2 | using Flax.Build.NativeCpp; 3 | 4 | public class ArizonaFramework : GameModule 5 | { 6 | /// 7 | public override void Setup(BuildOptions options) 8 | { 9 | base.Setup(options); 10 | 11 | BuildNativeCode = true; 12 | Tags["Network"] = string.Empty; 13 | options.PublicDependencies.Add("Networking"); 14 | options.PrivateDependencies.Add("ImGui"); 15 | options.ScriptingAPI.IgnoreMissingDocumentationWarnings = false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameInstance.Editor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #if FLAX_EDITOR 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using FlaxEngine; 8 | using FlaxEditor; 9 | using FlaxEditor.Content; 10 | using ArizonaFramework.Debug; 11 | 12 | namespace ArizonaFramework.Editor 13 | { 14 | /// 15 | /// Game Instance plugin for Editor. 16 | /// 17 | public sealed class GameInstanceEditor : EditorPlugin 18 | { 19 | private AssetProxy[] _assetProxies; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | public GameInstanceEditor() 25 | { 26 | _description = new PluginDescription 27 | { 28 | Name = "GameInstance", 29 | Category = "Game", 30 | Description = "Main game singleton plugin that manages the game systems and handles Game Mode setup and lifetime for the play.", 31 | Author = "Flax", 32 | Version = new Version(1, 0), 33 | }; 34 | } 35 | 36 | /// 37 | public override Type GamePluginType => typeof(GameInstance); 38 | 39 | /// 40 | public override void InitializeEditor() 41 | { 42 | base.InitializeEditor(); 43 | 44 | _assetProxies = new[] 45 | { 46 | new CustomSettingsProxy(typeof(GameInstanceSettings), "GameInstance"), 47 | new CustomSettingsProxy(typeof(DebugSettings), "Debug"), 48 | }; 49 | foreach (var e in _assetProxies) 50 | Editor.ContentDatabase.Proxy.Add(e); 51 | } 52 | 53 | /// 54 | public override void DeinitializeEditor() 55 | { 56 | foreach (var e in _assetProxies) 57 | Editor.ContentDatabase.Proxy.Remove(e); 58 | _assetProxies = null; 59 | 60 | base.DeinitializeEditor(); 61 | } 62 | } 63 | } 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameInstance.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #include "GameInstance.h" 4 | #include "GameSystem.h" 5 | #include "GameSceneSystem.h" 6 | #include "GameInstanceSettings.h" 7 | #include "GameMode.h" 8 | #include "GameState.h" 9 | #include "PlayerPawn.h" 10 | #include "PlayerController.h" 11 | #include "PlayerState.h" 12 | #include "PlayerUI.h" 13 | #include "ArizonaFramework/Utilities/Utilities.h" 14 | #include "Engine/Content/Content.h" 15 | #include "Engine/Content/JsonAsset.h" 16 | #include "Engine/Core/Log.h" 17 | #include "Engine/Core/Config/GameSettings.h" 18 | #include "Engine/Engine/Engine.h" 19 | #include "Engine/Engine/Time.h" 20 | #include "Engine/Level/Level.h" 21 | #include "Engine/Level/Scene/Scene.h" 22 | #include "Engine/Level/Actors/EmptyActor.h" 23 | #include "Engine/Level/Prefabs/PrefabManager.h" 24 | #include "Engine/Networking/NetworkClient.h" 25 | #include "Engine/Networking/NetworkManager.h" 26 | #include "Engine/Networking/NetworkReplicator.h" 27 | #include "Engine/Networking/NetworkRpc.h" 28 | #include "Engine/Networking/NetworkReplicationHierarchy.h" 29 | #include "Engine/Profiler/ProfilerCPU.h" 30 | #if !BUILD_RELEASE 31 | #include "Engine/Platform/Window.h" 32 | #endif 33 | #include "Engine/Scripting/BinaryModule.h" 34 | #include "Engine/Scripting/Scripting.h" 35 | #include "Engine/Scripting/ManagedCLR/MClass.h" 36 | #include "Engine/Scripting/Plugins/PluginManager.h" 37 | #include "Engine/Threading/Threading.h" 38 | 39 | namespace 40 | { 41 | template 42 | void DeleteScript(T*& obj) 43 | { 44 | if (obj) 45 | { 46 | if (obj->GetActor()) 47 | obj->GetActor()->DeleteObject(); 48 | else 49 | obj->DeleteObject(); 50 | obj = nullptr; 51 | } 52 | } 53 | } 54 | 55 | GameSystem::GameSystem(const SpawnParams& params) 56 | : ScriptingObject(params) 57 | { 58 | } 59 | 60 | GameSceneSystem::GameSceneSystem(const SpawnParams& params) 61 | : GameSystem(params) 62 | { 63 | } 64 | 65 | GameMode::GameMode(const SpawnParams& params) 66 | : ScriptingObject(params) 67 | { 68 | } 69 | 70 | void GameMode::StartGame() 71 | { 72 | } 73 | 74 | void GameMode::StopGame() 75 | { 76 | } 77 | 78 | Actor* GameMode::CreatePlayerPawn(PlayerState* playerState) 79 | { 80 | const auto& settings = *GameInstanceSettings::Get(); 81 | if (Prefab* playerPawnPrefab = settings.PlayerPawnPrefab.Get()) 82 | { 83 | return PrefabManager::SpawnPrefab(playerPawnPrefab, nullptr, nullptr); 84 | } 85 | return nullptr; 86 | } 87 | 88 | Actor* GameMode::CreatePlayerController(PlayerState* playerState) 89 | { 90 | const auto& settings = *GameInstanceSettings::Get(); 91 | if (Prefab* playerControllerPrefab = settings.PlayerControllerPrefab.Get()) 92 | { 93 | return PrefabManager::SpawnPrefab(playerControllerPrefab, nullptr, nullptr); 94 | } 95 | return nullptr; 96 | } 97 | 98 | void GameMode::OnPlayerJoined(PlayerState* playerState) 99 | { 100 | } 101 | 102 | void GameMode::OnPlayerLeft(PlayerState* playerState) 103 | { 104 | } 105 | 106 | void GameMode::OnPlayerSpawned(PlayerState* playerState) 107 | { 108 | } 109 | 110 | GameState::GameState(const SpawnParams& params) 111 | : ScriptingObject(params) 112 | { 113 | } 114 | 115 | PlayerState* GameState::GetPlayerStateByNetworkClientId(uint32 networkClientId) const 116 | { 117 | for (PlayerState* playerState : PlayerStates) 118 | { 119 | if (playerState && playerState->NetworkClientId == networkClientId) 120 | return playerState; 121 | } 122 | return nullptr; 123 | } 124 | 125 | PlayerState* GameState::GetPlayerStateByPlayerId(uint32 playerId) const 126 | { 127 | for (PlayerState* playerState : PlayerStates) 128 | { 129 | if (playerState && playerState->PlayerId == playerId) 130 | return playerState; 131 | } 132 | return nullptr; 133 | } 134 | 135 | PlayerState::PlayerState(const SpawnParams& params) 136 | : ScriptingObject(params) 137 | { 138 | } 139 | 140 | PlayerPawn::PlayerPawn(const SpawnParams& params) 141 | : Script(params) 142 | { 143 | } 144 | 145 | void PlayerPawn::SetPlayerState(PlayerState* value) 146 | { 147 | if (_playerState == value) 148 | return; 149 | _playerState = value; 150 | NetworkReplicator::DirtyObject(this); 151 | } 152 | 153 | void PlayerPawn::SetPlayerId(uint32 value) 154 | { 155 | if (_playerId == value || value == MAX_uint32) 156 | return; 157 | _playerId = value; 158 | 159 | // Register for player spawn event 160 | if (auto* instance = GameInstance::GetInstance()) 161 | instance->_playersToSpawn.AddUnique(value); 162 | } 163 | 164 | void PlayerPawn::OnStart() 165 | { 166 | // Automatic spawn in the network for replication 167 | NetworkReplicator::SpawnObject(this); 168 | } 169 | 170 | void PlayerPawn::OnDestroy() 171 | { 172 | // Invoke player despawn event 173 | if (_spawned) 174 | { 175 | if (auto* instance = GameInstance::GetInstance()) 176 | { 177 | instance->_playersToSpawn.Remove(_playerId); 178 | instance->PlayerDespawned(this); 179 | if (auto* gameState = instance->GetGameState()) 180 | { 181 | if (auto* playerState = gameState->GetPlayerStateByPlayerId(_playerId)) 182 | { 183 | playerState->PlayerPawn = nullptr; 184 | } 185 | } 186 | } 187 | _spawned = false; 188 | } 189 | } 190 | 191 | PlayerController::PlayerController(const SpawnParams& params) 192 | : Script(params) 193 | { 194 | _tickUpdate = true; 195 | } 196 | 197 | PlayerPawn* PlayerController::GetPlayerPawn() const 198 | { 199 | return _playerState ? _playerState->PlayerPawn : nullptr; 200 | } 201 | 202 | uint32 PlayerController::GetPlayerId() const 203 | { 204 | return _playerState ? _playerState->PlayerId : MAX_uint32; 205 | } 206 | 207 | Actor* PlayerController::CreatePlayerUI(PlayerState* playerState) 208 | { 209 | const auto& settings = *GameInstanceSettings::Get(); 210 | if (Prefab* playerUIPrefab = settings.PlayerUIPrefab.Get()) 211 | { 212 | return PrefabManager::SpawnPrefab(playerUIPrefab, nullptr, nullptr); 213 | } 214 | return nullptr; 215 | } 216 | 217 | void PlayerController::MovePawn(const Vector3& translation, const Quaternion& rotation) 218 | { 219 | // Ignore zero deltas 220 | if (translation.LengthSquared() <= 0.0f && rotation.IsIdentity()) 221 | return; 222 | 223 | const PlayerPawn* pawn = GetPlayerPawn(); 224 | Actor* pawnActor = pawn ? pawn->GetActor() : nullptr; 225 | if (!pawnActor) 226 | return; 227 | 228 | // Replicate on server 229 | const NetworkManagerMode networkMode = NetworkManager::Mode; 230 | if (networkMode == NetworkManagerMode::Client) 231 | { 232 | MovePawnServer(translation, rotation); 233 | } 234 | 235 | // Perform local move 236 | OnMovePawn(pawnActor, translation, rotation); 237 | } 238 | 239 | void PlayerController::OnMovePawn(Actor* pawnActor, const Vector3& translation, const Quaternion& rotation) 240 | { 241 | pawnActor->AddMovement(translation, rotation); 242 | } 243 | 244 | void PlayerController::MovePawnServer(const Vector3& translation, const Quaternion& rotation) 245 | { 246 | NETWORK_RPC_IMPL(PlayerController, MovePawnServer, translation, rotation); 247 | 248 | if (OnValidateMove(translation, rotation)) 249 | { 250 | MovePawn(translation, rotation); 251 | } 252 | } 253 | 254 | void PlayerController::OnUpdate() 255 | { 256 | } 257 | 258 | void PlayerController::OnDestroy() 259 | { 260 | if (_spawned) 261 | { 262 | if (_playerState) 263 | { 264 | _playerState->PlayerController = nullptr; 265 | _playerState = nullptr; 266 | } 267 | _spawned = false; 268 | } 269 | } 270 | 271 | PlayerUI::PlayerUI(const SpawnParams& params) 272 | : Script(params) 273 | { 274 | } 275 | 276 | void PlayerUI::SetPlayerState(PlayerState* value) 277 | { 278 | _playerState = value; 279 | } 280 | 281 | void PlayerUI::OnDestroy() 282 | { 283 | // Unlink from player state 284 | if (_playerState) 285 | { 286 | _playerState->PlayerUI = nullptr; 287 | _playerState = nullptr; 288 | } 289 | 290 | Script::OnDestroy(); 291 | } 292 | 293 | IMPLEMENT_GAME_SETTINGS_GETTER(GameInstanceSettings, "GameInstance"); 294 | 295 | GameInstance::GameInstance(const SpawnParams& params) 296 | : GamePlugin(SpawnParams(Guid(0x12345678, 0x99634f61, 0x84723632, 0x54c776af), params.Type)) // Override ID to be the same on all clients (a cross-device singleton) to keep network id stable 297 | { 298 | _description.Category = TEXT("Game"); 299 | #if USE_EDITOR 300 | _description.Description = TEXT("Main game singleton plugin that manages the game systems and handles Game Mode setup and lifetime for the play."); 301 | #endif 302 | } 303 | 304 | GameInstance* GameInstance::GetInstance() 305 | { 306 | return PluginManager::GetPlugin(); 307 | } 308 | 309 | GameSystem* GameInstance::GetGameSystem(const MClass* type) 310 | { 311 | for (auto* e : _systems) 312 | { 313 | if (e->Is(type)) 314 | return e; 315 | } 316 | return nullptr; 317 | } 318 | 319 | GameSystem* GameInstance::GetGameSystem(const ScriptingTypeHandle& type) 320 | { 321 | for (auto* e : _systems) 322 | { 323 | if (e->Is(type)) 324 | return e; 325 | } 326 | return nullptr; 327 | } 328 | 329 | void GameInstance::Initialize() 330 | { 331 | GamePlugin::Initialize(); 332 | 333 | // Find all game system types from all loaded binary modules 334 | _sceneSystemTypes.Clear(); 335 | for (BinaryModule* e : BinaryModule::GetModules()) 336 | { 337 | for (int32 i = 0; i < e->Types.Count(); i++) 338 | { 339 | const ScriptingType& type = e->Types[i]; 340 | const ScriptingTypeHandle typeHandle(e, i); 341 | if (type.Type == ScriptingTypes::Script && typeHandle.IsSubclassOf(GameSystem::TypeInitializer)) 342 | { 343 | // Skip abstract types 344 | if (type.ManagedClass && type.ManagedClass->IsAbstract()) 345 | continue; 346 | 347 | if (GameSceneSystem::TypeInitializer.IsAssignableFrom(typeHandle)) 348 | { 349 | // Cache scene types 350 | _sceneSystemTypes.Add(typeHandle); 351 | } 352 | else 353 | { 354 | // Spawn game system 355 | const ScriptingObjectSpawnParams spawnParams(Guid::New(), typeHandle); 356 | auto* system = (GameSystem*)type.Script.Spawn(spawnParams); 357 | if (!system) 358 | continue; 359 | system->_instance = this; 360 | if (system->CanBeUsed()) 361 | { 362 | system->Initialize(); 363 | _systems.Add(system); 364 | } 365 | else 366 | { 367 | Delete(system); 368 | } 369 | } 370 | } 371 | } 372 | } 373 | 374 | // Register for network events 375 | Engine::Update.Bind(this); 376 | NetworkManager::StateChanged.Bind(this); 377 | NetworkManager::ClientConnected.Bind(this); 378 | NetworkManager::ClientDisconnected.Bind(this); 379 | 380 | // Initialize scene systems 381 | Level::ScenesLock.Lock(); 382 | for (Scene* scene : Level::Scenes) 383 | OnSceneLoading(scene, scene->GetID()); 384 | Level::SceneLoading.Bind(this); 385 | Level::SceneLoaded.Bind(this); 386 | Level::SceneUnloading.Bind(this); 387 | Level::SceneUnloaded.Bind(this); 388 | Level::ScenesLock.Unlock(); 389 | } 390 | 391 | void GameInstance::Deinitialize() 392 | { 393 | // Ensure to stop any game 394 | EndGame(); 395 | 396 | // Unregister from events 397 | NetworkManager::StateChanged.Unbind(this); 398 | NetworkManager::ClientConnected.Unbind(this); 399 | NetworkManager::ClientDisconnected.Unbind(this); 400 | Level::SceneLoading.Unbind(this); 401 | Level::SceneLoaded.Unbind(this); 402 | Level::SceneUnloading.Unbind(this); 403 | Level::SceneUnloaded.Unbind(this); 404 | Engine::Update.Unbind(this); 405 | 406 | // Shutdown game systems (reversed order) 407 | for (int32 i = _systems.Count() - 1; i >= 0; i--) 408 | { 409 | GameSystem* system = _systems[i]; 410 | _systems.RemoveAt(i); 411 | system->Deinitialize(); 412 | Delete(system); 413 | } 414 | _sceneSystemTypes.Clear(); 415 | 416 | GamePlugin::Deinitialize(); 417 | } 418 | 419 | void GameInstance::OnUpdate() 420 | { 421 | if (!_gameStarted || Time::GetGamePaused()) 422 | return; 423 | PROFILE_CPU(); 424 | 425 | // Process players spawn events (ensure that both pawn and controller are ready on server and client) 426 | for (int32 i = 0; i < _playersToSpawn.Count() && _playersToSpawn.Count() != 0; i++) 427 | { 428 | const uint32 playerId = _playersToSpawn[i]; 429 | if (PlayerState* playerState = _gameState->GetPlayerStateByPlayerId(playerId)) 430 | { 431 | const bool needController = !NetworkManager::IsClient() || playerState->NetworkClientId == NetworkManager::LocalClientId; 432 | if (playerState->PlayerPawn == nullptr || playerState->PlayerPawn->GetPlayerId() != playerId) 433 | continue; 434 | if (playerState->PlayerPawn->_spawned) 435 | { 436 | // Already spawned 437 | _playersToSpawn.RemoveAtKeepOrder(i--); 438 | continue; 439 | } 440 | if (needController && playerState->PlayerController == nullptr) 441 | continue; 442 | if (Level::Scenes.IsEmpty()) 443 | break; 444 | 445 | Actor* pawnActor = playerState->PlayerPawn->GetParent(); 446 | Actor* controllerActor = playerState->PlayerController ? playerState->PlayerController->GetParent() : nullptr; 447 | 448 | #if !BUILD_RELEASE 449 | // Set proper name for the player actors to improve dev usage 450 | ASSERT(pawnActor); 451 | if (pawnActor) 452 | pawnActor->SetName(String::Format(TEXT("Player Pawn PlayerId={}"), playerId)); 453 | if (controllerActor) 454 | controllerActor->SetName(String::Format(TEXT("Player Controller PlayerId={}"), playerId)); 455 | #endif 456 | 457 | // Ensure that player exists on a level (could be unlinked due to level transition when starting game) 458 | if (!pawnActor->GetParent()) 459 | { 460 | Level::SpawnActor(pawnActor); 461 | _sceneTransitionActors.Remove(pawnActor); 462 | } 463 | if (controllerActor && !controllerActor->GetParent()) 464 | { 465 | Level::SpawnActor(controllerActor); 466 | _sceneTransitionActors.Remove(controllerActor); 467 | } 468 | 469 | // Spawn player 470 | playerState->PlayerPawn->_spawned = true; 471 | playerState->PlayerPawn->SetPlayerState(playerState); 472 | if (playerState->PlayerController) 473 | { 474 | playerState->PlayerController->_spawned = true; 475 | playerState->PlayerController->_playerState = playerState; 476 | } 477 | playerState->PlayerPawn->OnPlayerSpawned(); 478 | if (playerState->PlayerController) 479 | playerState->PlayerController->OnPlayerSpawned(); 480 | _playersToSpawn.RemoveAtKeepOrder(i--); 481 | 482 | // Create UI for local player 483 | if (playerState->NetworkClientId == NetworkManager::LocalClientId) 484 | { 485 | Actor* uiActor = playerState->PlayerController->CreatePlayerUI(playerState); 486 | PlayerUI* uiScript = Utilities::GetActiveScript(uiActor); 487 | if (uiActor && !uiScript) 488 | { 489 | LOG(Error, "Invalid player UI actor spawned without PlayerUI script attached (to the root actor)."); 490 | Delete(uiActor); 491 | uiActor = nullptr; 492 | } 493 | if (!uiActor) 494 | { 495 | // Fallback to default UI 496 | uiActor = New(); 497 | uiScript = uiActor->AddScript(); 498 | } 499 | uiScript->SetPlayerState(playerState); 500 | playerState->PlayerUI = uiScript; 501 | #if !BUILD_RELEASE 502 | uiActor->SetName(String::Format(TEXT("Player UI PlayerId={}"), playerId)); 503 | #endif 504 | Level::SpawnActor(uiActor); 505 | uiScript->OnPlayerSpawned(); 506 | } 507 | 508 | // Custom logic after spawning player 509 | PlayerSpawned(playerState->PlayerPawn); 510 | } 511 | } 512 | 513 | // Update inputs (before scripting update) 514 | const PlayerState* localPlayerState = GetLocalPlayerState(); 515 | if (localPlayerState && localPlayerState->PlayerController && localPlayerState->PlayerController->_spawned) 516 | { 517 | localPlayerState->PlayerController->OnUpdateInput(); 518 | } 519 | } 520 | 521 | PlayerState* GameInstance::GetLocalPlayerState() const 522 | { 523 | return _gameState ? _gameState->GetPlayerStateByNetworkClientId(NetworkManager::LocalClientId) : nullptr; 524 | } 525 | 526 | Array> GameInstance::GetLocalPlayerStates() const 527 | { 528 | Array> result; 529 | if (_gameState) 530 | { 531 | for (PlayerState* playerState : _gameState->PlayerStates) 532 | { 533 | if (playerState && playerState->NetworkClientId == NetworkManager::LocalClientId) 534 | result.Add(playerState); 535 | } 536 | } 537 | return result; 538 | } 539 | 540 | void GameInstance::StartGame() 541 | { 542 | ASSERT(IsInMainThread()); 543 | if (_gameStarted) 544 | return; 545 | GameStarting(); 546 | const NetworkManagerMode networkMode = NetworkManager::Mode; 547 | _isHosting = networkMode != NetworkManagerMode::Client; 548 | const auto& settings = *GameInstanceSettings::Get(); 549 | 550 | // Register Game Instance as a root for networking objects 551 | NetworkReplicator::AddObject(this); 552 | 553 | // Setup replication hierarchy 554 | if (NetworkReplicator::GetHierarchy() == nullptr && settings.ReplicationHierarchy) 555 | { 556 | NetworkReplicator::SetHierarchy(settings.ReplicationHierarchy.NewObject()); 557 | } 558 | 559 | // Create game mode and state 560 | if (_isHosting) 561 | { 562 | _gameMode = settings.GameModeType.NewObject(); 563 | } 564 | _gameState = settings.GameStateType.NewObject(); 565 | NetworkReplicator::AddObject(_gameState, this); 566 | 567 | if (_isHosting) 568 | { 569 | _gameMode->StartGame(); 570 | } 571 | 572 | _gameStarted = true; 573 | GameStarted(); 574 | 575 | // Spawn local player 576 | if (networkMode == NetworkManagerMode::Host && NetworkManager::LocalClient && NetworkManager::LocalClient->State == NetworkConnectionState::Connected) 577 | OnNetworkClientConnected(NetworkManager::LocalClient); 578 | } 579 | 580 | void GameInstance::EndGame() 581 | { 582 | if (!_gameStarted) 583 | return; 584 | if (NetworkManager::State == NetworkConnectionState::Connected) 585 | { 586 | // Disconnect when ending multi game via local end 587 | NetworkManager::Stop(); 588 | return; 589 | } 590 | GameEnding(); 591 | 592 | if (_gameMode) 593 | { 594 | ASSERT(_gameMode); 595 | _gameMode->StopGame(); 596 | } 597 | 598 | // Delete game objects 599 | if (_gameState) 600 | { 601 | for (ScriptingObjectReference& playerState : _gameState->PlayerStates) 602 | { 603 | if (playerState) 604 | { 605 | DeleteScript(playerState->PlayerUI); 606 | DeleteScript(playerState->PlayerController); 607 | DeleteScript(playerState->PlayerPawn); 608 | playerState->DeleteObject(); 609 | } 610 | } 611 | _gameState->DeleteObject(); 612 | _gameState = nullptr; 613 | } 614 | _sceneTransitionActors.Clear(); 615 | _sceneTransitionPlayers.Clear(); 616 | if (_isHosting) 617 | { 618 | _gameMode->DeleteObject(); 619 | _gameMode = nullptr; 620 | } 621 | NetworkReplicator::SetHierarchy(nullptr); 622 | 623 | _gameStarted = false; 624 | GameEnded(); 625 | } 626 | 627 | PlayerState* GameInstance::SpawnLocalPlayer() 628 | { 629 | return CreatePlayer(NetworkManager::LocalClient); 630 | } 631 | 632 | void GameInstance::OnNetworkStateChanged() 633 | { 634 | switch (NetworkManager::State) 635 | { 636 | case NetworkConnectionState::Connected: 637 | StartGame(); 638 | #if !BUILD_RELEASE 639 | if (Engine::MainWindow) 640 | { 641 | // Rename window to make it easier to debug multiple sessions locally 642 | _windowTitle = Engine::MainWindow->GetTitle(); 643 | Engine::MainWindow->SetTitle(String::Format(TEXT("{} - {} Id {}"), _windowTitle, NetworkManager::IsClient() ? TEXT("Client") : (NetworkManager::IsHost() ? TEXT("Host") : TEXT("Server")), NetworkManager::LocalClientId)); 644 | } 645 | #endif 646 | break; 647 | case NetworkConnectionState::Offline: 648 | case NetworkConnectionState::Disconnected: 649 | #if !BUILD_RELEASE 650 | if (Engine::MainWindow) 651 | Engine::MainWindow->SetTitle(_windowTitle); 652 | #endif 653 | EndGame(); 654 | break; 655 | } 656 | } 657 | 658 | void GameInstance::OnNetworkClientConnected(NetworkClient* client) 659 | { 660 | if (NetworkManager::IsClient() || !_gameStarted) 661 | return; 662 | CreatePlayer(client); 663 | } 664 | 665 | void GameInstance::OnNetworkClientDisconnected(NetworkClient* client) 666 | { 667 | if (NetworkManager::IsClient() || !_gameStarted) 668 | return; 669 | 670 | // Remove player(s) from that client 671 | for (int32 i = 0; i < _gameState->PlayerStates.Count(); i++) 672 | { 673 | auto playerState = _gameState->PlayerStates[i]; 674 | if (playerState && playerState->NetworkClientId == client->ClientId) 675 | { 676 | _gameMode->OnPlayerLeft(playerState); 677 | _gameState->PlayerStates.RemoveAtKeepOrder(i--); 678 | DeleteScript(playerState->PlayerUI); 679 | NetworkReplicator::DespawnObject(playerState->PlayerController); 680 | NetworkReplicator::DespawnObject(playerState->PlayerPawn); 681 | NetworkReplicator::DespawnObject(playerState); 682 | } 683 | } 684 | } 685 | 686 | void GameInstance::OnSceneLoading(Scene* scene, const Guid& sceneId) 687 | { 688 | for (const ScriptingTypeHandle& typeHandle : _sceneSystemTypes) 689 | { 690 | const ScriptingObjectSpawnParams spawnParams(Guid::New(), typeHandle); 691 | auto* system = (GameSceneSystem*)typeHandle.GetType().Script.Spawn(spawnParams); 692 | if (!system) 693 | continue; 694 | system->_instance = this; 695 | system->_scene = scene; 696 | if (system->CanBeUsed()) 697 | { 698 | system->Initialize(); 699 | _systems.Add(system); 700 | } 701 | else 702 | { 703 | Delete(system); 704 | } 705 | } 706 | } 707 | 708 | void GameInstance::OnSceneLoaded(Scene* scene, const Guid& sceneId) 709 | { 710 | // If game performed scene transition, then respawn any cached scene objects 711 | if (_gameStarted) 712 | { 713 | for (Actor* a : _sceneTransitionActors) 714 | a->SetParent(scene); 715 | _sceneTransitionActors.Clear(); 716 | if (_gameMode) 717 | { 718 | for (PlayerState* player : _sceneTransitionPlayers) 719 | _gameMode->OnPlayerSpawned(player); 720 | } 721 | _sceneTransitionPlayers.Clear(); 722 | } 723 | } 724 | 725 | void GameInstance::OnSceneUnloading(Scene* scene, const Guid& sceneId) 726 | { 727 | // If game performs scene transition, then unlink any scene objects (player pawn/controller/ui actors) to be respawned after new map gets loaded 728 | if (_gameStarted) 729 | { 730 | for (int32 i = 0; i < _gameState->PlayerStates.Count(); i++) 731 | { 732 | auto playerState = _gameState->PlayerStates[i]; 733 | if (playerState) 734 | { 735 | Actor* a; 736 | #define TRANSITION_SCRIPT(s) \ 737 | a = playerState->s ? playerState->s->GetActor() : nullptr; \ 738 | if (a && a->GetScene() == scene) \ 739 | { \ 740 | _sceneTransitionActors.Add(a); \ 741 | a->SetParent(nullptr); \ 742 | } 743 | TRANSITION_SCRIPT(PlayerUI); 744 | TRANSITION_SCRIPT(PlayerController); 745 | TRANSITION_SCRIPT(PlayerPawn); 746 | #undef TRANSITION_SCRIPT 747 | _sceneTransitionPlayers.Add(playerState); 748 | } 749 | } 750 | } 751 | } 752 | 753 | void GameInstance::OnSceneUnloaded(Scene* scene, const Guid& sceneId) 754 | { 755 | for (int32 i = _systems.Count() - 1; i >= 0; i--) 756 | { 757 | auto* system = Cast(_systems[i]); 758 | if (!system || system->_scene != scene) 759 | continue; 760 | _systems.RemoveAt(i); 761 | system->Deinitialize(); 762 | Delete(system); 763 | } 764 | } 765 | 766 | PlayerState* GameInstance::CreatePlayer(NetworkClient* client) 767 | { 768 | // Add player 769 | const auto& settings = *GameInstanceSettings::Get(); 770 | auto playerState = settings.PlayerStateType.NewObject(); 771 | if (client) 772 | playerState->NetworkClientId = client->ClientId; 773 | else 774 | playerState->NetworkClientId = NetworkManager::LocalClientId; 775 | playerState->PlayerId = _gameState->NextPlayerId++; // TODO: for local coop use RPC to synchronize remote session with server 776 | _gameState->PlayerStates.Add(playerState); 777 | NetworkReplicator::AddObject(playerState, this); 778 | NetworkReplicator::SpawnObject(playerState); 779 | 780 | // Create player pawn 781 | Actor* pawnActor = _gameMode->CreatePlayerPawn(playerState); 782 | PlayerPawn* pawnScript = Utilities::GetActiveScript(pawnActor); 783 | if (pawnActor && !pawnScript) 784 | { 785 | LOG(Error, "Invalid player pawn actor spawned without PlayerPawn script attached (to the root actor)."); 786 | Delete(pawnActor); 787 | pawnActor = nullptr; 788 | } 789 | if (!pawnActor) 790 | { 791 | // Fallback to default pawn 792 | pawnActor = New(); 793 | pawnScript = pawnActor->AddScript(); 794 | } 795 | pawnScript->SetPlayerState(playerState); 796 | pawnScript->SetPlayerId(playerState->PlayerId); 797 | playerState->PlayerPawn = pawnScript; 798 | 799 | // Spawn player pawn on all connected clients and locally 800 | const bool canSpawn = Level::Scenes.HasItems(); 801 | NetworkReplicator::SpawnObject(pawnActor); 802 | if (canSpawn) 803 | Level::SpawnActor(pawnActor); 804 | else 805 | _sceneTransitionActors.Add(pawnActor); 806 | 807 | // Create player controller 808 | Actor* controllerActor = _gameMode->CreatePlayerController(playerState); 809 | PlayerController* controllerScript = Utilities::GetActiveScript(controllerActor); 810 | if (controllerActor && !controllerScript) 811 | { 812 | LOG(Error, "Invalid player controller actor spawned without PlayerController script attached (to the root actor)."); 813 | Delete(controllerActor); 814 | controllerActor = nullptr; 815 | } 816 | if (!controllerActor) 817 | { 818 | // Fallback to default controller 819 | controllerActor = New(); 820 | controllerScript = controllerActor->AddScript(); 821 | } 822 | controllerScript->_playerState = playerState; 823 | playerState->PlayerController = controllerScript; 824 | 825 | if (NetworkManager::IsConnected()) 826 | { 827 | // Spawn player controller on connected client and locally (client ownership over controller) 828 | if (NetworkManager::LocalClientId != playerState->NetworkClientId) 829 | { 830 | const uint32 controllerTargetsData[2] = { NetworkManager::LocalClientId, playerState->NetworkClientId }; 831 | const DataContainer controllerTargets(controllerTargetsData, ARRAY_COUNT(controllerTargetsData)); 832 | // TODO: support inheritance of targetClientIds for spawned objects so if we set it for controller actor, all attached scripts to it will inherit that too 833 | NetworkReplicator::SpawnObject(controllerActor, controllerTargets); 834 | NetworkReplicator::SpawnObject(controllerScript, controllerTargets); 835 | NetworkReplicator::SetObjectOwnership(controllerActor, client->ClientId, NetworkObjectRole::ReplicatedSimulated, true); 836 | } 837 | else 838 | { 839 | const uint32 controllerTargetsData[1] = { NetworkManager::LocalClientId }; 840 | const DataContainer controllerTargets(controllerTargetsData, ARRAY_COUNT(controllerTargetsData)); 841 | // TODO: support inheritance of targetClientIds for spawned objects so if we set it for controller actor, all attached scripts to it will inherit that too 842 | NetworkReplicator::SpawnObject(controllerActor, controllerTargets); 843 | NetworkReplicator::SpawnObject(controllerScript, controllerTargets); 844 | NetworkReplicator::SetObjectOwnership(controllerActor, client->ClientId, NetworkObjectRole::OwnedAuthoritative, true); 845 | } 846 | } 847 | else 848 | { 849 | // Offline mode requires some manual setup (no events from replication system) 850 | _playersToSpawn.AddUnique(playerState->PlayerId); 851 | } 852 | if (canSpawn) 853 | Level::SpawnActor(controllerActor); 854 | else 855 | _sceneTransitionActors.Add(controllerActor); 856 | 857 | _gameMode->OnPlayerJoined(playerState); 858 | if (canSpawn) 859 | _gameMode->OnPlayerSpawned(playerState); 860 | else 861 | _sceneTransitionPlayers.Add(playerState); 862 | 863 | return playerState; 864 | } 865 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameInstance.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | namespace ArizonaFramework 4 | { 5 | partial class GameInstance 6 | { 7 | /// 8 | /// Gets the game system of the given type. 9 | /// 10 | /// Type of the system to search for. Includes any scripts derived from the type. 11 | /// Found system or null. 12 | public T GetGameSystem() where T : GameSystem 13 | { 14 | return GetGameSystem(typeof(T)) as T; 15 | } 16 | } 17 | 18 | partial class GameInstanceSettings 19 | { 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public GameInstanceSettings() 24 | { 25 | // Init with defaults (C# lacks of proper empty ctor for structures) 26 | DefaultReplicationSettings = ReplicationSettings.Default; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameInstance.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Scripting/Plugins/GamePlugin.h" 6 | #include "Types.h" 7 | 8 | class Scene; 9 | class Actor; 10 | class NetworkClient; 11 | 12 | /// 13 | /// Main game singleton plugin that manages the game systems and handles Game Mode setup and lifetime for the play. 14 | /// 15 | API_CLASS() class ARIZONAFRAMEWORK_API GameInstance : public GamePlugin 16 | { 17 | friend PlayerPawn; 18 | friend PlayerController; 19 | DECLARE_SCRIPTING_TYPE(GameInstance); 20 | 21 | private: 22 | Array _systems; 23 | Array _sceneSystemTypes; 24 | bool _gameStarted = false; 25 | bool _isHosting = false; 26 | GameMode* _gameMode = nullptr; 27 | GameState* _gameState = nullptr; 28 | Array> _playersToSpawn; 29 | #if !BUILD_RELEASE 30 | String _windowTitle; 31 | #endif 32 | Array _sceneTransitionActors; 33 | Array _sceneTransitionPlayers; 34 | 35 | public: 36 | /// 37 | /// Gets the singleton instance of the game instance. 38 | /// 39 | API_PROPERTY() static GameInstance* GetInstance(); 40 | 41 | /// 42 | /// Gets the list of active game systems (including scene systems). 43 | /// 44 | API_PROPERTY() FORCE_INLINE const Array& GetSystems() const 45 | { 46 | return _systems; 47 | } 48 | 49 | /// 50 | /// Gets the game system of the given type. 51 | /// 52 | /// Type of the system to search for. Includes any actors derived from the type. 53 | /// Found system or null. 54 | API_FUNCTION() GameSystem* GetGameSystem(API_PARAM(Attributes="TypeReference(typeof(GameSystem))") const MClass* type); 55 | 56 | /// 57 | /// Gets the game system of the given type. 58 | /// 59 | /// Type of the system to search for. Includes any actors derived from the type. 60 | /// Found system or null. 61 | GameSystem* GetGameSystem(const ScriptingTypeHandle& type); 62 | 63 | /// 64 | /// Gets the game system of the given type. 65 | /// 66 | template 67 | FORCE_INLINE T* GetGameSystem() 68 | { 69 | return (T*)GetGameSystem(T::TypeInitializer); 70 | } 71 | 72 | public: 73 | /// 74 | /// Event called when game starts. 75 | /// 76 | API_EVENT() Action GameStarting; 77 | 78 | /// 79 | /// Event called when game started. 80 | /// 81 | API_EVENT() Action GameStarted; 82 | 83 | /// 84 | /// Event called when game ends. 85 | /// 86 | API_EVENT() Action GameEnding; 87 | 88 | /// 89 | /// Event called when game ended. 90 | /// 91 | API_EVENT() Action GameEnded; 92 | 93 | /// 94 | /// Event called when player is spawned on a level. 95 | /// 96 | API_EVENT() Delegate PlayerSpawned; 97 | 98 | /// 99 | /// Event called when player is despawned from a level. 100 | /// 101 | API_EVENT() Delegate PlayerDespawned; 102 | 103 | public: 104 | /// 105 | /// Gets the current game mode. Exists only on server or host, null on clients. 106 | /// 107 | API_PROPERTY() FORCE_INLINE GameMode* GetGameMode() const 108 | { 109 | return _gameMode; 110 | } 111 | 112 | /// 113 | /// Gets the current game state (always valid during game). 114 | /// 115 | API_PROPERTY() FORCE_INLINE GameState* GetGameState() const 116 | { 117 | return _gameState; 118 | } 119 | 120 | /// 121 | /// Gets the local player state (null on server). Returns the first local player in case of local coop. 122 | /// 123 | API_PROPERTY() PlayerState* GetLocalPlayerState() const; 124 | 125 | /// 126 | /// Gets all the local player states. 127 | /// 128 | API_PROPERTY() Array> GetLocalPlayerStates() const; 129 | 130 | public: 131 | /// 132 | /// Starts the game. Use it to control local game flow. Called automatically on NetworkManager events for multiplayer games. 133 | /// 134 | API_FUNCTION() void StartGame(); 135 | 136 | /// 137 | /// Starts the game. Use it to end the local game. 138 | /// 139 | API_FUNCTION() void EndGame(); 140 | 141 | /// 142 | /// Spans a local player. Use it when playing local game or coop. 143 | /// 144 | /// The newly added player. 145 | API_FUNCTION() PlayerState* SpawnLocalPlayer(); 146 | 147 | private: 148 | // [GamePlugin] 149 | void Initialize() override; 150 | void Deinitialize() override; 151 | 152 | void OnUpdate(); 153 | void OnNetworkStateChanged(); 154 | void OnNetworkClientConnected(NetworkClient* client); 155 | void OnNetworkClientDisconnected(NetworkClient* client); 156 | void OnSceneLoading(Scene* scene, const Guid& sceneId); 157 | void OnSceneLoaded(Scene* scene, const Guid& sceneId); 158 | void OnSceneUnloading(Scene* scene, const Guid& sceneId); 159 | void OnSceneUnloaded(Scene* scene, const Guid& sceneId); 160 | PlayerState* CreatePlayer(NetworkClient* client); 161 | }; 162 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameInstanceSettings.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Core/Config/Settings.h" 6 | #include "Engine/Content/SoftAssetReference.h" 7 | #include "Engine/Scripting/SoftTypeReference.h" 8 | #include "Engine/Level/Prefabs/Prefab.h" 9 | #include "../Networking/ReplicationSettings.h" 10 | 11 | class NetworkReplicationHierarchy; 12 | 13 | /// 14 | /// The settings for Game Instance. 15 | /// 16 | API_CLASS(NoConstructor) class ARIZONAFRAMEWORK_API GameInstanceSettings : public SettingsBase 17 | { 18 | API_AUTO_SERIALIZATION(); 19 | DECLARE_SCRIPTING_TYPE_MINIMAL(GameInstanceSettings); 20 | DECLARE_SETTINGS_GETTER(GameInstanceSettings); 21 | 22 | public: 23 | /// 24 | /// The type of the Game Mode that will be spawned on game host/server. Used to control the game logic and match flow. 25 | /// 26 | API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Types\"), TypeReference(typeof(GameMode))") 27 | SoftTypeReference GameModeType = "ArizonaFramework.GameMode"; 28 | 29 | /// 30 | /// The type of the Game State that will be spawned on host and clients (server-authoritative). Holds the global game state (eg. teams score). 31 | /// 32 | API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Types\"), TypeReference(typeof(GameState))") 33 | SoftTypeReference GameStateType = "ArizonaFramework.GameState"; 34 | 35 | /// 36 | /// The type of the Player State that will be spawned on host and clients (server-authoritative). Holds the per-player state (eg. health and inventory). 37 | /// 38 | API_FIELD(Attributes="EditorOrder(120), EditorDisplay(\"Types\"), TypeReference(typeof(PlayerState))") 39 | SoftTypeReference PlayerStateType = "ArizonaFramework.PlayerState"; 40 | 41 | /// 42 | /// The Player Pawn prefab asset to spawn by Game Mode for each joining player. 43 | /// 44 | API_FIELD(Attributes="EditorOrder(150), EditorDisplay(\"Types\")") 45 | SoftAssetReference PlayerPawnPrefab; 46 | 47 | /// 48 | /// The Player Controller prefab asset to spawn by Game Mode for each joining player (on server and player's client only). 49 | /// 50 | API_FIELD(Attributes="EditorOrder(160), EditorDisplay(\"Types\")") 51 | SoftAssetReference PlayerControllerPrefab; 52 | 53 | /// 54 | /// The Player UI prefab asset to spawn for local players. 55 | /// 56 | API_FIELD(Attributes="EditorOrder(160), EditorDisplay(\"Types\")") 57 | SoftAssetReference PlayerUIPrefab; 58 | 59 | public: 60 | /// 61 | /// Type of the network replication hierarchy system to use. 62 | /// 63 | API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Replication\"), TypeReference(typeof(FlaxEngine.Networking.NetworkReplicationHierarchy))") 64 | SoftTypeReference ReplicationHierarchy = "ArizonaFramework.ReplicationHierarchy"; 65 | 66 | /// 67 | /// Default replication settings. Used when not overriden by specific object type. 68 | /// 69 | API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Replication\")") 70 | ReplicationSettings DefaultReplicationSettings; 71 | 72 | /// 73 | /// Per-type replication settings. Runtime lookup includes base classes (but not interfaces). 74 | /// 75 | API_FIELD(Attributes="EditorOrder(1050), EditorDisplay(\"Replication\")") 76 | Dictionary, ReplicationSettings> ReplicationSettingsPerType; 77 | }; 78 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameMode.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Scripting/ScriptingObject.h" 6 | #include "Types.h" 7 | 8 | class Actor; 9 | class NetworkClient; 10 | 11 | /// 12 | /// Main, root system of the game that implements the logic and flow of the gameplay. 13 | /// 14 | API_CLASS() class ARIZONAFRAMEWORK_API GameMode : public ScriptingObject 15 | { 16 | DECLARE_SCRIPTING_TYPE(GameMode); 17 | 18 | public: 19 | /// 20 | /// Starts the game (after creating Game State but before any players joining). 21 | /// 22 | API_FUNCTION() virtual void StartGame(); 23 | 24 | /// 25 | /// Stops the game. 26 | /// 27 | API_FUNCTION() virtual void StopGame(); 28 | 29 | /// 30 | /// Creates the Player Pawn actor for a given player which will be spawned over network on all connected clients. 31 | /// 32 | /// The player state. 33 | /// The created player pawn actor (eg. from prefab). 34 | API_FUNCTION() virtual Actor* CreatePlayerPawn(PlayerState* playerState); 35 | 36 | /// 37 | /// Creates the Player Controller actor for a given player which will be spawned over network on connected client. 38 | /// 39 | /// The player state. 40 | /// The created player controller actor (eg. from prefab). 41 | API_FUNCTION() virtual Actor* CreatePlayerController(PlayerState* playerState); 42 | 43 | /// 44 | /// Called when player joins the game. 45 | /// 46 | /// The player state. 47 | API_FUNCTION() virtual void OnPlayerJoined(PlayerState* playerState); 48 | 49 | /// 50 | /// Called when player leaves the game (eg. disconnected). Despawns player pawn and controller. 51 | /// 52 | /// The player state. 53 | API_FUNCTION() virtual void OnPlayerLeft(PlayerState* playerState); 54 | 55 | /// 56 | /// Called when player gets spawned on a level. Called again during respawn (eg. when scene gets changed). 57 | /// 58 | /// The player state. 59 | API_FUNCTION() virtual void OnPlayerSpawned(PlayerState* playerState); 60 | }; 61 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameSceneSystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "GameSystem.h" 6 | 7 | class Scene; 8 | 9 | /// 10 | /// Scene gameplay component attached to the Game Instance. Lifetime tied with the scene (multiple systems can exists, one for each loaded scene). 11 | /// 12 | API_CLASS(Abstract) class ARIZONAFRAMEWORK_API GameSceneSystem : public GameSystem 13 | { 14 | DECLARE_SCRIPTING_TYPE(GameSceneSystem); 15 | friend GameInstance; 16 | 17 | private: 18 | Scene* _scene = nullptr; 19 | 20 | public: 21 | /// 22 | /// Gets the scene that is connected to this system. 23 | /// 24 | API_PROPERTY() FORCE_INLINE Scene* GetScene() const 25 | { 26 | return _scene; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameState.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Core/Collections/Array.h" 6 | #include "Engine/Scripting/ScriptingObject.h" 7 | #include "Engine/Scripting/ScriptingObjectReference.h" 8 | #include "Types.h" 9 | 10 | /// 11 | /// Global gameplay state container. 12 | /// 13 | API_CLASS() class ARIZONAFRAMEWORK_API GameState : public ScriptingObject 14 | { 15 | DECLARE_SCRIPTING_TYPE(GameState); 16 | 17 | public: 18 | /// 19 | /// Global counter for player identifiers. Managed by game host when spawning players (including local coop). 20 | /// 21 | API_FIELD(NetworkReplicated, ReadOnly) uint32 NextPlayerId = 0; 22 | 23 | /// 24 | /// List with all connected players state. 25 | /// 26 | API_FIELD(NetworkReplicated, ReadOnly) Array> PlayerStates; 27 | 28 | public: 29 | /// 30 | /// Gets the player state for a given unique NetworkClientId. In case of local coop, the first player is returned. 31 | /// 32 | API_FUNCTION() PlayerState* GetPlayerStateByNetworkClientId(uint32 networkClientId) const; 33 | 34 | /// 35 | /// Gets the player state for a given unique PlayerId. 36 | /// 37 | API_FUNCTION() PlayerState* GetPlayerStateByPlayerId(uint32 playerId) const; 38 | }; 39 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/GameSystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Scripting/ScriptingObject.h" 6 | #include "Types.h" 7 | 8 | /// 9 | /// Gameplay system component attached to the Game Instance. Lifetime tied with the game. 10 | /// 11 | API_CLASS(Abstract) class ARIZONAFRAMEWORK_API GameSystem : public ScriptingObject 12 | { 13 | DECLARE_SCRIPTING_TYPE(GameSystem); 14 | friend GameInstance; 15 | 16 | private: 17 | GameInstance* _instance = nullptr; 18 | 19 | public: 20 | /// 21 | /// Gets the game instance that owns this system. 22 | /// 23 | API_PROPERTY() FORCE_INLINE GameInstance* GetGameInstance() const 24 | { 25 | return _instance; 26 | } 27 | 28 | /// 29 | /// Checks if the system can be used. Called before Initialize/Deinitialize but with game instance set. 30 | /// 31 | API_FUNCTION() virtual bool CanBeUsed() 32 | { 33 | return true; 34 | } 35 | 36 | /// 37 | /// Initialization method for the system. Can be used to allocate resource and setup event handlers. 38 | /// 39 | API_FUNCTION() virtual void Initialize() 40 | { 41 | } 42 | 43 | /// 44 | /// Cleanup method for the system. Used to release any used resources. 45 | /// 46 | API_FUNCTION() virtual void Deinitialize() 47 | { 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/PlayerController.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Scripting/Script.h" 6 | #include "Types.h" 7 | 8 | /// 9 | /// Player inputs controller that receives input from the player and converts it into movement for pawn. 10 | /// 11 | API_CLASS() class ARIZONAFRAMEWORK_API PlayerController : public Script 12 | { 13 | friend GameInstance; 14 | friend PlayerPawn; 15 | API_AUTO_SERIALIZATION(); 16 | DECLARE_SCRIPTING_TYPE(PlayerController); 17 | 18 | protected: 19 | PlayerState* _playerState = nullptr; 20 | bool _spawned = false; 21 | 22 | public: 23 | /// 24 | /// Gets the player state for this controller. 25 | /// 26 | API_PROPERTY() FORCE_INLINE PlayerState* GetPlayerState() const 27 | { 28 | return _playerState; 29 | } 30 | 31 | /// 32 | /// Gets the player pawn for this controller. 33 | /// 34 | API_PROPERTY() PlayerPawn* GetPlayerPawn() const; 35 | 36 | /// 37 | /// Gets the unique PlayerId for this controller. 38 | /// 39 | API_PROPERTY() uint32 GetPlayerId() const; 40 | 41 | public: 42 | /// 43 | /// Event called every update for local controller to fetch new inputs before any gameplay logic updates. Never called on server. 44 | /// 45 | API_FUNCTION() virtual void OnUpdateInput() 46 | { 47 | } 48 | 49 | /// 50 | /// Event called after controller is spawned on a level (locally or after replicated). 51 | /// 52 | API_FUNCTION() virtual void OnPlayerSpawned() 53 | { 54 | } 55 | 56 | /// 57 | /// Event called after receiving pawn movement from the client. Can be used to reject too big deltas that prevent players from cheating. Called on server-only. 58 | /// 59 | /// The translation vector. 60 | /// The rotation quaternion. 61 | API_FUNCTION() virtual bool OnValidateMove(const Vector3& translation, const Quaternion& rotation) 62 | { 63 | return true; 64 | } 65 | 66 | public: 67 | /// 68 | /// Creates the Player UI actor for a player which will be used by the local player. 69 | /// 70 | /// The player state. 71 | /// The created player UI actor (eg. from prefab). 72 | API_FUNCTION() virtual Actor* CreatePlayerUI(PlayerState* playerState); 73 | 74 | /// 75 | /// Moves pawn (on both local client and server). 76 | /// 77 | /// The translation vector. 78 | /// The rotation quaternion. 79 | API_FUNCTION() void MovePawn(const Vector3& translation, const Quaternion& rotation); 80 | 81 | protected: 82 | // Performs local movement of the pawn actor. 83 | virtual void OnMovePawn(Actor* pawnActor, const Vector3& translation, const Quaternion& rotation); 84 | 85 | private: 86 | API_FUNCTION(NetworkRpc=Server) void MovePawnServer(const Vector3& translation, const Quaternion& rotation); 87 | 88 | public: 89 | // [Script] 90 | void OnUpdate() override; 91 | void OnDestroy() override; 92 | }; 93 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/PlayerPawn.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Scripting/Script.h" 6 | #include "Types.h" 7 | 8 | /// 9 | /// Player script on a scene (attached to player prefab root actor) that represents it in the game (on level). 10 | /// 11 | API_CLASS() class ARIZONAFRAMEWORK_API PlayerPawn : public Script 12 | { 13 | friend GameInstance; 14 | API_AUTO_SERIALIZATION(); 15 | DECLARE_SCRIPTING_TYPE(PlayerPawn); 16 | 17 | protected: 18 | PlayerState* _playerState = nullptr; 19 | uint32 _playerId = MAX_uint32; 20 | bool _spawned = false; 21 | 22 | public: 23 | /// 24 | /// Gets the player state for this pawn. 25 | /// 26 | API_PROPERTY(NetworkReplicated) FORCE_INLINE PlayerState* GetPlayerState() const 27 | { 28 | return _playerState; 29 | } 30 | 31 | /// 32 | /// Gets the unique player identifier for this pawn. 33 | /// 34 | API_PROPERTY(NetworkReplicated) FORCE_INLINE uint32 GetPlayerId() const 35 | { 36 | return _playerId; 37 | } 38 | 39 | public: 40 | /// 41 | /// Event called after player is spawned on a level (locally or after replicated). 42 | /// 43 | API_FUNCTION() virtual void OnPlayerSpawned() 44 | { 45 | } 46 | 47 | private: 48 | API_PROPERTY(NetworkReplicated) void SetPlayerState(PlayerState* value); 49 | API_PROPERTY(NetworkReplicated) void SetPlayerId(uint32 value); 50 | 51 | public: 52 | // [Script] 53 | void OnStart() override; 54 | void OnDestroy() override; 55 | }; 56 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/PlayerState.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Scripting/ScriptingObject.h" 6 | #include "Types.h" 7 | 8 | /// 9 | /// Player gameplay state container. 10 | /// 11 | API_CLASS() class ARIZONAFRAMEWORK_API PlayerState : public ScriptingObject 12 | { 13 | DECLARE_SCRIPTING_TYPE(PlayerState); 14 | 15 | public: 16 | /// 17 | /// Unique network client identifier. 18 | /// 19 | API_FIELD(NetworkReplicated, ReadOnly) uint32 NetworkClientId = MAX_uint32; 20 | 21 | /// 22 | /// Unique player identifier. 23 | /// 24 | API_FIELD(NetworkReplicated, ReadOnly) uint32 PlayerId = MAX_uint32; 25 | 26 | /// 27 | /// Player pawn script (attached to the player pawn actor). 28 | /// 29 | API_FIELD(NetworkReplicated, ReadOnly) PlayerPawn* PlayerPawn = nullptr; 30 | 31 | /// 32 | /// Player controller script (attached to the player controller actor). Created only on server and local player client. 33 | /// 34 | API_FIELD(NetworkReplicated, ReadOnly) PlayerController* PlayerController = nullptr; 35 | 36 | /// 37 | /// Player UI script (attached to the player UI actor). Created only on local player client. Not replicated. 38 | /// 39 | API_FIELD(ReadOnly) PlayerUI* PlayerUI = nullptr; 40 | }; 41 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/PlayerUI.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Scripting/Script.h" 6 | #include "Types.h" 7 | 8 | /// 9 | /// Player User Interface with HUD. 10 | /// 11 | API_CLASS() class ARIZONAFRAMEWORK_API PlayerUI : public Script 12 | { 13 | friend GameInstance; 14 | API_AUTO_SERIALIZATION(); 15 | DECLARE_SCRIPTING_TYPE(PlayerUI); 16 | 17 | protected: 18 | PlayerState* _playerState = nullptr; 19 | 20 | public: 21 | /// 22 | /// Gets the player state for this UI. 23 | /// 24 | API_PROPERTY(NetworkReplicated) FORCE_INLINE PlayerState* GetPlayerState() const 25 | { 26 | return _playerState; 27 | } 28 | 29 | public: 30 | /// 31 | /// Event called after player is spawned on a level (locally). 32 | /// 33 | API_FUNCTION() virtual void OnPlayerSpawned() 34 | { 35 | } 36 | 37 | private: 38 | API_PROPERTY(NetworkReplicated) void SetPlayerState(PlayerState* value); 39 | 40 | public: 41 | // [Script] 42 | void OnDestroy() override; 43 | }; 44 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Core/Types.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | class GameInstance; 6 | class GameSystem; 7 | class GameSceneSystem; 8 | class GameMode; 9 | class GameState; 10 | class PlayerState; 11 | class PlayerPawn; 12 | class PlayerController; 13 | class PlayerUI; 14 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Debug/DebugSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Engine/Core/Config/Settings.h" 4 | #include "Engine/Core/Collections/Array.h" 5 | #include "Engine/Scripting/SoftTypeReference.h" 6 | 7 | class DebugWindow; 8 | 9 | /// 10 | /// The settings for debug tools in game. 11 | /// 12 | API_CLASS(NoConstructor, Namespace="ArizonaFramework.Debug") class ARIZONAFRAMEWORK_API DebugSettings : public SettingsBase 13 | { 14 | API_AUTO_SERIALIZATION(); 15 | DECLARE_SCRIPTING_TYPE_MINIMAL(DebugSettings); 16 | DECLARE_SETTINGS_GETTER(DebugSettings); 17 | public: 18 | /// 19 | /// Input action that opens debug window (defined in Input Settings). 20 | /// 21 | API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"ImGui\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.InputEventEditor\")") 22 | String DebugMenuOpen; 23 | 24 | /// 25 | /// Input action that opens and focuses or hides console window (defined in Input Settings). 26 | /// 27 | API_FIELD(Attributes="EditorOrder(51), EditorDisplay(\"ImGui\"), CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.InputEventEditor\")") 28 | String DebugConsoleOpen; 29 | 30 | /// 31 | /// List of debug windows to display in debug menu. 32 | /// 33 | API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"ImGui\"), TypeReference(typeof(ArizonaFramework.Debug.DebugWindow))") 34 | Array> DebugWindows; 35 | }; 36 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Debug/DebugSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "DebugSystem.h" 2 | #include "DebugWindow.h" 3 | #include "DebugSettings.h" 4 | #include "DebugWindows.h" 5 | #include "ArizonaFramework/UI/UISystem.h" 6 | #include "Engine/Core/Config/GameSettings.h" 7 | #include "Engine/Core/Collections/Sorting.h" 8 | #include "Engine/Engine/Engine.h" 9 | #include "Engine/Input/Input.h" 10 | #include "Engine/Content/Content.h" 11 | #include "Engine/Content/JsonAsset.h" 12 | #include "Engine/Core/Collections/ArrayExtensions.h" 13 | #include "Engine/Profiler/ProfilerCPU.h" 14 | #include "ImGui/imgui.h" 15 | 16 | IMPLEMENT_GAME_SETTINGS_GETTER(DebugSettings, "Debug"); 17 | 18 | bool SortDebugWindows(DebugWindow* const& a, DebugWindow* const& b) 19 | { 20 | return a->MenuName < b->MenuName; 21 | } 22 | 23 | DebugWindow::DebugWindow(const SpawnParams& params) 24 | : ScriptingObject(params) 25 | { 26 | MenuName = params.Type.GetType().GetName(); 27 | } 28 | 29 | DebugSystem::DebugSystem(const SpawnParams& params) 30 | : GameSystem(params) 31 | { 32 | } 33 | 34 | void DebugSystem::SetActive(bool active) 35 | { 36 | if (_menuActive == active) 37 | return; 38 | _menuActive = active; 39 | if (auto* ui = UISystem::GetInstance()) 40 | { 41 | if (_menuActive) 42 | ui->PushInputContext(InputContextType::DebugImGui); 43 | else 44 | ui->PopInputContext(); 45 | } 46 | } 47 | 48 | void DebugSystem::Initialize() 49 | { 50 | Scripting::Update.Bind(this); 51 | _menuActive = false; 52 | } 53 | 54 | void DebugSystem::Deinitialize() 55 | { 56 | Scripting::Update.Unbind(this); 57 | _windows.ClearDelete(); 58 | } 59 | 60 | void DebugSystem::OnUpdate() 61 | { 62 | PROFILE_CPU(); 63 | const auto& debugSettings = *DebugSettings::Get(); 64 | 65 | // Toggle menu visibility via input action 66 | if (Input::GetAction(debugSettings.DebugMenuOpen)) 67 | { 68 | SetActive(!_menuActive); 69 | } 70 | 71 | // Toggle console for quick debug commands access 72 | bool openConsole = false, closeConsole = false; 73 | if (Input::GetAction(debugSettings.DebugConsoleOpen)) 74 | { 75 | if (!_menuActive) 76 | { 77 | SetActive(true); 78 | openConsole = true; 79 | } 80 | else 81 | { 82 | closeConsole = true; 83 | } 84 | } 85 | 86 | if (_menuActive) 87 | { 88 | // Init windows 89 | if (_windows.Count() != debugSettings.DebugWindows.Count()) 90 | { 91 | _windows.ClearDelete(); 92 | for (const auto& e : debugSettings.DebugWindows) 93 | { 94 | if (auto* window = e.NewObject()) 95 | { 96 | _windows.Add(window); 97 | } 98 | } 99 | Sorting::QuickSort(_windows.Get(), _windows.Count(), &SortDebugWindows); 100 | } 101 | 102 | // Console toggle 103 | if (openConsole || closeConsole) 104 | { 105 | const Function findConsole = [](const DebugWindow* window) -> bool { return window->GetTypeHandle() == DebugGeneralConsoleWindow::TypeInitializer; }; 106 | if (auto* console = ArrayExtensions::First(_windows, findConsole)) 107 | { 108 | if (openConsole) 109 | { 110 | if (!console->_active) 111 | { 112 | // Open console 113 | console->_active = true; 114 | console->OnActivated(); 115 | 116 | // TODO: dock console in bottom of the game viewport 117 | 118 | // Skip input processing this frame as user pressed/released DebugConsoleOpen action 119 | return; 120 | } 121 | } 122 | else 123 | { 124 | if (console->_active) 125 | { 126 | // Close console 127 | console->_active = false; 128 | console->OnDeactivated(); 129 | const Function allInactive = [](const DebugWindow* window) -> bool { return !window->_active; }; 130 | if (ArrayExtensions::All(_windows, allInactive)) 131 | { 132 | // Hide menu if none other window is in use (eg. user used console button again) 133 | SetActive(false); 134 | 135 | // Skip further processing once hidden 136 | return; 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | // Draw debug menu 144 | if (ImGui::BeginMainMenuBar()) 145 | { 146 | StringAnsi currentMenu; 147 | bool currentMenuOpen = false; 148 | for (auto window : _windows) 149 | { 150 | StringAnsi menu; 151 | int32 menuSize = window->MenuName.Find('/'); 152 | if (menuSize != -1) 153 | menu = window->MenuName.Left(menuSize); 154 | if (menu != currentMenu) 155 | { 156 | if (currentMenuOpen && currentMenu.HasChars()) 157 | ImGui::EndMenu(); 158 | currentMenuOpen = ImGui::BeginMenu(*menu); 159 | currentMenu = menu; 160 | } 161 | if (currentMenuOpen) 162 | { 163 | StringAnsi item = window->MenuName.Substring(menuSize + 1); 164 | const bool wasActive = window->_active; 165 | if (ImGui::MenuItem(*item, nullptr, &window->_active)) 166 | { 167 | } 168 | if (wasActive && !window->_active) 169 | window->OnDeactivated(); 170 | else if (window->_active && !wasActive) 171 | window->OnActivated(); 172 | } 173 | } 174 | if (currentMenuOpen && currentMenu.HasChars()) 175 | ImGui::EndMenu(); 176 | ImGui::EndMainMenuBar(); 177 | } 178 | 179 | // Draw active windows 180 | for (auto window : _windows) 181 | { 182 | if (window->_active) 183 | { 184 | window->OnDraw(); 185 | if (!window->_active) 186 | window->OnDeactivated(); 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Debug/DebugSystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ArizonaFramework/Core/GameSystem.h" 4 | #include "Engine/Core/Collections/Array.h" 5 | 6 | /// 7 | /// Gameplay debugging system. 8 | /// 9 | API_CLASS(Namespace="ArizonaFramework.Debug") class ARIZONAFRAMEWORK_API DebugSystem : public GameSystem 10 | { 11 | DECLARE_SCRIPTING_TYPE(DebugSystem); 12 | 13 | private: 14 | bool _menuActive = false; 15 | Array _windows; 16 | 17 | public: 18 | void SetActive(bool active); 19 | 20 | public: 21 | // [GameSystem] 22 | void Initialize() override; 23 | void Deinitialize() override; 24 | 25 | private: 26 | void OnUpdate(); 27 | }; 28 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Debug/DebugWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Engine/Scripting/ScriptingObject.h" 4 | 5 | /// 6 | /// Base class for debug windows used to inspect gameplay and provide additional development utilities inside the game. 7 | /// 8 | API_CLASS(Abstract, Namespace="ArizonaFramework.Debug") class ARIZONAFRAMEWORK_API DebugWindow : public ScriptingObject 9 | { 10 | DECLARE_SCRIPTING_TYPE(DebugWindow); 11 | friend class DebugSystem; 12 | 13 | protected: 14 | // True if window is active. 15 | bool _active = false; 16 | 17 | public: 18 | // The name of the window in the menu bar. Can contain slashes for groupping. Eg. 'Tools/Profiler'. 19 | API_FIELD() StringAnsi MenuName; 20 | 21 | public: 22 | // Called when window gets shown. 23 | API_FUNCTION() virtual void OnActivated() {} 24 | 25 | // Called when window gets hidden. 26 | API_FUNCTION() virtual void OnDeactivated() {} 27 | 28 | // Called when window is active and can draw it's contents. 29 | API_FUNCTION() virtual void OnDraw() {} 30 | }; 31 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Debug/DebugWindows.cpp: -------------------------------------------------------------------------------- 1 | #include "DebugWindows.h" 2 | #include "Engine/Core/Log.h" 3 | #include "Engine/Core/Collections/ChunkedArray.h" 4 | #include "Engine/Platform/Platform.h" 5 | #include "Engine/Platform/CreateProcessSettings.h" 6 | #include "Engine/Level/Level.h" 7 | #include "Engine/Level/Scene/Scene.h" 8 | #include "Engine/Utilities/StringConverter.h" 9 | #include 10 | 11 | DebugGeneralToolsWindow::DebugGeneralToolsWindow(const SpawnParams& params) 12 | : DebugWindow(params) 13 | { 14 | MenuName = "General/Tools"; 15 | } 16 | 17 | void DebugGeneralToolsWindow::OnDraw() 18 | { 19 | if (!ImGui::Begin("Tools", &_active)) 20 | return; 21 | if (ImGui::Button("Open log")) 22 | { 23 | CreateProcessSettings procSettings; 24 | procSettings.FileName = Log::Logger::LogFilePath; 25 | procSettings.ShellExecute = true; 26 | Platform::CreateProcess(procSettings); 27 | } 28 | ImGui::End(); 29 | } 30 | 31 | #if FLAX_1_10_OR_NEWER 32 | 33 | #include "Engine/Debug/DebugCommands.h" 34 | 35 | DebugGeneralConsoleWindow::DebugGeneralConsoleWindow(const SpawnParams& params) 36 | : DebugWindow(params) 37 | { 38 | MenuName = "General/Console"; 39 | strcpy(_inputBuffer, ""); 40 | Log::Logger::OnMessage.Bind(this); 41 | } 42 | 43 | DebugGeneralConsoleWindow::~DebugGeneralConsoleWindow() 44 | { 45 | Log::Logger::OnMessage.Unbind(this); 46 | } 47 | 48 | void DebugGeneralConsoleWindow::OnDraw() 49 | { 50 | if (!ImGui::Begin("Console", &_active)) 51 | return; 52 | ScopeLock lock(_locker); 53 | 54 | // Kick off early-init for commands 55 | DebugCommands::InitAsync(); 56 | 57 | // Context menu 58 | if (ImGui::BeginPopupContextItem()) 59 | { 60 | if (ImGui::MenuItem("Close")) 61 | _active = false; 62 | ImGui::EndPopup(); 63 | } 64 | 65 | // Options 66 | if (ImGui::SmallButton("Clear")) 67 | _entries.Clear(); 68 | ImGui::SameLine(); 69 | if (ImGui::SmallButton("Scroll")) 70 | _scrollToBottom = true; 71 | 72 | ImGui::Separator(); 73 | const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); 74 | if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeight), false, ImGuiWindowFlags_HorizontalScrollbar)) 75 | { 76 | // Context menu 77 | if (ImGui::BeginPopupContextWindow()) 78 | { 79 | if (ImGui::Selectable("Clear")) 80 | _entries.Clear(); 81 | ImGui::Checkbox("Auto-scroll", &_autoScroll); 82 | ImGui::EndPopup(); 83 | } 84 | 85 | // Tighten spacing 86 | ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); 87 | 88 | // Display all log items 89 | for (const auto& e : _entries) 90 | { 91 | ImVec4 color; 92 | bool hasColor = false; 93 | if (e.Type == LogType::Error) 94 | { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); hasColor = true; } 95 | else if (e.Type == LogType::Warning) 96 | { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); hasColor = true; } 97 | else if (e.Message.StartsWith("> ")) 98 | { color = ImVec4(0.8f, 0.8f, 0.8f, 1.0f); hasColor = true; } 99 | if (hasColor) 100 | ImGui::PushStyleColor(ImGuiCol_Text, color); 101 | 102 | ImGui::TextUnformatted(e.Message.Get()); 103 | 104 | if (hasColor) 105 | ImGui::PopStyleColor(); 106 | } 107 | 108 | // Auto-scroll 109 | if (_scrollToBottom || (_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) 110 | ImGui::SetScrollHereY(1.0f); 111 | _scrollToBottom = false; 112 | 113 | ImGui::PopStyleVar(); 114 | } 115 | ImGui::EndChild(); 116 | ImGui::Separator(); 117 | 118 | // Command-line 119 | bool getFocus = false; 120 | const ImGuiInputTextFlags commandFlags = 121 | ImGuiInputTextFlags_EnterReturnsTrue | 122 | ImGuiInputTextFlags_EscapeClearsAll | 123 | ImGuiInputTextFlags_CallbackCompletion | 124 | ImGuiInputTextFlags_CallbackHistory; 125 | if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0)) 126 | ImGui::SetKeyboardFocusHere(0); // Auto-focus input field 127 | if (ImGui::InputText("Command", _inputBuffer, ARRAY_COUNT(_inputBuffer), commandFlags, &OnTextEditCallbackStub, (void*)this)) 128 | { 129 | OnCommand(_inputBuffer); 130 | strcpy(_inputBuffer, ""); 131 | getFocus = true; 132 | } 133 | 134 | // Auto-focus on window apparition 135 | ImGui::SetItemDefaultFocus(); 136 | if (getFocus) 137 | ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget 138 | 139 | ImGui::End(); 140 | } 141 | 142 | void DebugGeneralConsoleWindow::OnActivated() 143 | { 144 | // Clear input buffer 145 | strcpy(_inputBuffer, ""); 146 | } 147 | 148 | void DebugGeneralConsoleWindow::OnCommand(const char* command) 149 | { 150 | if (!command || !*command) 151 | return; 152 | _scrollToBottom = true; 153 | 154 | // Add into history (remove if already added previously) 155 | _historyPos = -1; 156 | for (int32 i = _history.Count() - 1; i >= 0; i--) 157 | { 158 | if (StringUtils::CompareIgnoreCase(*_history[i], command) == 0) 159 | { 160 | _history.RemoveAtKeepOrder(i); 161 | break; 162 | } 163 | } 164 | _history.Add(command); 165 | 166 | // Process command 167 | if (StringUtils::CompareIgnoreCase(command, "clear") == 0) 168 | { 169 | _entries.Clear(); 170 | return; 171 | } 172 | String commandStr(command); 173 | DebugCommands::Execute(commandStr); 174 | } 175 | 176 | void DebugGeneralConsoleWindow::AddLog(StringAnsi&& msg) 177 | { 178 | ScopeLock lock(_locker); 179 | _entries.Add({ LogType::Info, MoveTemp(msg) }); 180 | } 181 | 182 | void DebugGeneralConsoleWindow::OnMessage(LogType type, const StringView& msg) 183 | { 184 | ScopeLock lock(_locker); 185 | _entries.Add({ type, StringAnsi(msg) }); 186 | } 187 | 188 | int DebugGeneralConsoleWindow::OnTextEditCallbackStub(ImGuiInputTextCallbackData* data) 189 | { 190 | return ((DebugGeneralConsoleWindow*)data->UserData)->OnTextEditCallback(data); 191 | } 192 | 193 | int DebugGeneralConsoleWindow::OnTextEditCallback(ImGuiInputTextCallbackData* data) 194 | { 195 | switch (data->EventFlag) 196 | { 197 | case ImGuiInputTextFlags_CallbackCompletion: 198 | { 199 | const char* wordEnd = data->Buf + data->CursorPos; 200 | const char* wordStart = wordEnd; 201 | while (wordStart > data->Buf) 202 | { 203 | const char c = wordStart[-1]; 204 | if (c == ' ' || c == '\t' || c == ',' || c == ';') 205 | break; 206 | wordStart--; 207 | } 208 | ImVector candidates; 209 | const StringAnsiView word(wordStart, (int)(wordEnd - wordStart)); 210 | if (StringAnsiView("clear").StartsWith(word, StringSearchCase::IgnoreCase)) 211 | candidates.push_back("clear"); 212 | int32 cmdIndex = 0; 213 | StringAsUTF16<> wordUTF16(word.Get(), word.Length()); 214 | StringView wordUTF16View(wordUTF16.Get(), wordUTF16.Length()); 215 | ChunkedArray candidatesCache; 216 | while (DebugCommands::Iterate(wordUTF16View, cmdIndex)) 217 | { 218 | StringAnsi& candidate = candidatesCache.AddOne(); 219 | candidate = StringAnsi(DebugCommands::GetCommandName(cmdIndex)); 220 | candidates.push_back(candidate.Get()); 221 | cmdIndex++; 222 | } 223 | if (word.IsEmpty()) 224 | { 225 | // Ignore 226 | } 227 | else if (candidates.Size == 0) 228 | { 229 | //AddLog(StringAnsi::Format("No match for \"{}\"", word)); 230 | } 231 | else if (candidates.Size == 1) 232 | { 233 | data->DeleteChars((int)(wordStart - data->Buf), (int)(wordEnd - wordStart)); 234 | data->InsertChars(data->CursorPos, candidates[0]); 235 | } 236 | else 237 | { 238 | int matchLen = (int)(wordEnd - wordStart); 239 | for (;;) 240 | { 241 | int c = 0; 242 | bool allCandidatesMatches = true; 243 | for (int i = 0; i < candidates.Size && allCandidatesMatches; i++) 244 | { 245 | if (i == 0) 246 | c = StringUtils::ToUpper(candidates[i][matchLen]); 247 | else if (c == 0 || c != StringUtils::ToUpper(candidates[i][matchLen])) 248 | allCandidatesMatches = false; 249 | } 250 | if (!allCandidatesMatches) 251 | break; 252 | matchLen++; 253 | } 254 | if (matchLen > 0) 255 | { 256 | data->DeleteChars((int)(wordStart - data->Buf), (int)(wordEnd - wordStart)); 257 | data->InsertChars(data->CursorPos, candidates[0], candidates[0] + matchLen); 258 | } 259 | AddLog("Possible matches:"); 260 | for (int i = 0; i < candidates.Size; i++) 261 | AddLog(StringAnsi::Format("- {}", candidates[i])); 262 | } 263 | break; 264 | } 265 | case ImGuiInputTextFlags_CallbackHistory: 266 | { 267 | // Filter commands history 268 | const int prevHistoryPos = _historyPos; 269 | if (data->EventKey == ImGuiKey_UpArrow) 270 | { 271 | if (_historyPos == -1) 272 | _historyPos = _history.Count() - 1; 273 | else if (_historyPos > 0) 274 | _historyPos--; 275 | } 276 | else if (data->EventKey == ImGuiKey_DownArrow) 277 | { 278 | if (_historyPos != -1 && ++_historyPos >= _history.Count()) 279 | _historyPos = -1; 280 | } 281 | if (prevHistoryPos != _historyPos) 282 | { 283 | const char* historyStr = _historyPos >= 0 ? _history[_historyPos].Get() : ""; 284 | data->DeleteChars(0, data->BufTextLen); 285 | data->InsertChars(0, historyStr); 286 | } 287 | break; 288 | } 289 | } 290 | return 0; 291 | } 292 | 293 | #endif 294 | 295 | DebugSceneTreeWindow::DebugSceneTreeWindow(const SpawnParams& params) 296 | : DebugWindow(params) 297 | { 298 | MenuName = "Scene/Tree"; 299 | } 300 | 301 | void DrawActor(Actor* a, ImGuiTreeNodeFlags flags = 0) 302 | { 303 | const StringAsANSI<> name(a->GetName().Get(), a->GetName().Length()); 304 | if (a->Children.HasItems()) 305 | { 306 | if (ImGui::TreeNodeEx(name.Get(), flags)) 307 | { 308 | for (auto child : a->Children) 309 | DrawActor(child); 310 | ImGui::TreePop(); 311 | } 312 | } 313 | else 314 | { 315 | ImGui::Indent(); 316 | ImGui::Text("%s", name.Get() && a->GetName().HasChars() ? name.Get() : ""); 317 | ImGui::Unindent(); 318 | } 319 | } 320 | 321 | void DebugSceneTreeWindow::OnDraw() 322 | { 323 | if (!ImGui::Begin("Scene Tree", &_active)) 324 | return; 325 | for (auto a : Level::Scenes) 326 | { 327 | DrawActor(a, ImGuiTreeNodeFlags_DefaultOpen); 328 | } 329 | ImGui::End(); 330 | } 331 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Debug/DebugWindows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DebugWindow.h" 4 | #include "Engine/Core/Log.h" 5 | #include "Engine/Core/Collections/Array.h" 6 | 7 | // General utilities window (log file opening, etc.). 8 | API_CLASS(Namespace="ArizonaFramework.Debug") class ARIZONAFRAMEWORK_API DebugGeneralToolsWindow : public DebugWindow 9 | { 10 | DECLARE_SCRIPTING_TYPE(DebugGeneralToolsWindow); 11 | void OnDraw() override; 12 | }; 13 | 14 | #if FLAX_1_10_OR_NEWER 15 | 16 | // Console output and command line access. 17 | API_CLASS(Namespace="ArizonaFramework.Debug") class ARIZONAFRAMEWORK_API DebugGeneralConsoleWindow : public DebugWindow 18 | { 19 | DECLARE_SCRIPTING_TYPE(DebugGeneralConsoleWindow); 20 | ~DebugGeneralConsoleWindow(); 21 | void OnDraw() override; 22 | void OnActivated() override; 23 | private: 24 | struct Entry 25 | { 26 | LogType Type; 27 | StringAnsi Message; 28 | }; 29 | CriticalSection _locker; 30 | Array _entries; 31 | bool _autoScroll = true; 32 | bool _scrollToBottom = false; 33 | int32 _historyPos = -1; 34 | Array _history; 35 | char _inputBuffer[512]; 36 | 37 | void AddLog(StringAnsi&& msg); 38 | void OnCommand(const char* command); 39 | void OnMessage(LogType type, const StringView& msg); 40 | static int OnTextEditCallbackStub(struct ImGuiInputTextCallbackData* data); 41 | int OnTextEditCallback(ImGuiInputTextCallbackData* data); 42 | }; 43 | 44 | #endif 45 | 46 | // Scene hierarchy debugging window. 47 | API_CLASS(Namespace="ArizonaFramework.Debug") class ARIZONAFRAMEWORK_API DebugSceneTreeWindow : public DebugWindow 48 | { 49 | DECLARE_SCRIPTING_TYPE(DebugSceneTreeWindow); 50 | void OnDraw() override; 51 | }; 52 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Networking/ReplicationHierarchy.cpp: -------------------------------------------------------------------------------- 1 | #include "ReplicationHierarchy.h" 2 | #include "ArizonaFramework/Core/GameInstance.h" 3 | #include "ArizonaFramework/Core/GameInstanceSettings.h" 4 | #include "ArizonaFramework/Core/GameState.h" 5 | #include "ArizonaFramework/Core/PlayerPawn.h" 6 | #include "ArizonaFramework/Core/PlayerState.h" 7 | #include "Engine/Level/Actor.h" 8 | #include "Engine/Networking/NetworkClient.h" 9 | #include "Engine/Networking/NetworkManager.h" 10 | 11 | Dictionary GlobalReplicationSettings; 12 | float ReplicationHierarchy::ReplicationScale = 1.0f; 13 | 14 | ReplicationHierarchy::~ReplicationHierarchy() 15 | { 16 | SAFE_DELETE(_grid); 17 | } 18 | 19 | void ReplicationHierarchy::SetSettings(ScriptingTypeHandle type, const ReplicationSettings& settings) 20 | { 21 | GlobalReplicationSettings[type] = settings; 22 | #if USE_EDITOR 23 | // TODO: register event for type.Module unloading to safely remove type ref 24 | #endif 25 | } 26 | 27 | void ReplicationHierarchy::AddObject(NetworkReplicationHierarchyObject obj) 28 | { 29 | // Get object settings 30 | ScriptingTypeHandle typeHandle = obj.Object->GetTypeHandle(); 31 | ReplicationSettings settings; 32 | if (!_settingsCache.TryGet(typeHandle, settings)) 33 | { 34 | // Resolve settings 35 | const auto& gameSettings = GameInstanceSettings::Get(); 36 | settings = gameSettings->DefaultReplicationSettings; 37 | while (typeHandle) 38 | { 39 | // Overriden by code 40 | if (GlobalReplicationSettings.TryGet(typeHandle, settings)) 41 | break; 42 | 43 | // Overriden by game settings 44 | const ScriptingType& type = typeHandle.GetType(); 45 | if (gameSettings->ReplicationSettingsPerType.TryGet(type.Fullname, settings)) 46 | break; 47 | 48 | typeHandle = type.GetBaseType(); 49 | } 50 | 51 | // Cache result 52 | typeHandle = obj.Object->GetTypeHandle(); 53 | _settingsCache.Add(typeHandle, settings); 54 | } 55 | obj.ReplicationFPS = settings.ReplicationFPS; 56 | obj.CullDistance = settings.CullDistance; 57 | 58 | const Actor* actor = obj.GetActor(); 59 | if (actor && actor->HasStaticFlag(StaticFlags::Transform)) 60 | { 61 | // Insert static objects into a grid for faster replication 62 | if (!_grid) 63 | _grid = New(); 64 | _grid->AddObject(obj); 65 | return; 66 | } 67 | 68 | NetworkReplicationHierarchy::AddObject(obj); 69 | } 70 | 71 | bool ReplicationHierarchy::RemoveObject(ScriptingObject* obj) 72 | { 73 | if (_grid && _grid->RemoveObject(obj)) 74 | return true; 75 | return NetworkReplicationHierarchy::RemoveObject(obj); 76 | } 77 | 78 | bool ReplicationHierarchy::DirtyObject(ScriptingObject* obj) 79 | { 80 | if (_grid && _grid->DirtyObject(obj)) 81 | return true; 82 | return NetworkReplicationHierarchy::DirtyObject(obj); 83 | } 84 | 85 | void ReplicationHierarchy::Update(NetworkReplicationHierarchyUpdateResult* result) 86 | { 87 | if (const auto* instance = GameInstance::GetInstance()) 88 | { 89 | // Setup players locations for distance culling 90 | const auto& clients = NetworkManager::Clients; 91 | for (int32 i = 0; i < clients.Count(); i++) 92 | { 93 | if (const auto* playerState = instance->GetGameState()->GetPlayerStateByNetworkClientId(clients[i]->ClientId)) 94 | { 95 | if (playerState->PlayerPawn && playerState->PlayerPawn->GetActor()) 96 | { 97 | const Vector3 playerPosition = playerState->PlayerPawn->GetActor()->GetPosition(); 98 | result->SetClientLocation(i, playerPosition); 99 | } 100 | } 101 | } 102 | } 103 | 104 | // Apply settings 105 | result->ReplicationScale *= ReplicationScale; 106 | 107 | // Update hierarchy 108 | if (_grid) 109 | _grid->Update(result); 110 | NetworkReplicationHierarchy::Update(result); 111 | } 112 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Networking/ReplicationHierarchy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Engine/Networking/NetworkReplicationHierarchy.h" 4 | #include "ReplicationSettings.h" 5 | 6 | /// 7 | /// Basic implementation of NetworkReplicationHierarchy that uses spatial grid for static actor objects and allows to configure replication settings per-type. 8 | /// 9 | API_CLASS() class ARIZONAFRAMEWORK_API ReplicationHierarchy : public NetworkReplicationHierarchy 10 | { 11 | DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ReplicationHierarchy, NetworkReplicationHierarchy); 12 | ~ReplicationHierarchy(); 13 | 14 | private: 15 | NetworkReplicationGridNode* _grid = nullptr; 16 | Dictionary _settingsCache; 17 | 18 | public: 19 | // Scales globally replication rate for all objects in hierarchy (normalized scale - eg. 0.7 slows down rep rate by 30%). 20 | API_FIELD() static float ReplicationScale; 21 | 22 | /// 23 | /// Sets the replication settings for a given type (globally). 24 | /// 25 | /// The object type. 26 | /// The replication settings. 27 | API_FUNCTION() static void SetSettings(ScriptingTypeHandle type, const ReplicationSettings& settings); 28 | 29 | // [NetworkReplicationHierarchy] 30 | void AddObject(NetworkReplicationHierarchyObject obj) override; 31 | bool RemoveObject(ScriptingObject* obj) override; 32 | bool DirtyObject(ScriptingObject* obj) override; 33 | void Update(NetworkReplicationHierarchyUpdateResult* result) override; 34 | }; 35 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Networking/ReplicationSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Engine/Core/ISerializable.h" 4 | 5 | /// 6 | /// Network object replication settings container. 7 | /// 8 | API_STRUCT() struct ARIZONAFRAMEWORK_API ReplicationSettings : ISerializable 9 | { 10 | API_AUTO_SERIALIZATION(); 11 | DECLARE_SCRIPTING_TYPE_MINIMAL(ReplicationSettings); 12 | 13 | // The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkFPS specified in NetworkSettings. Use 0 for 'always relevant' object. 14 | API_FIELD() float ReplicationFPS = 60; 15 | // The minimum distance from the player to the object at which it can process replication. For example, players further away won't receive object data. Use 0 if unused. 16 | API_FIELD() float CullDistance = 15000; 17 | }; 18 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/UI/UISystem.cpp: -------------------------------------------------------------------------------- 1 | #include "UISystem.h" 2 | #include "ArizonaFramework/Core/GameInstance.h" 3 | #include "ImGui/ImGuiPlugin.h" 4 | #include "Engine/Engine/Engine.h" 5 | #include "Engine/Engine/Screen.h" 6 | #define UI_DEBUG 0 7 | #if UI_DEBUG 8 | #include "Engine/Core/Log.h" 9 | #include "Engine/Scripting/Enums.h" 10 | #endif 11 | 12 | UISystem::UISystem(const SpawnParams& params) 13 | : GameSystem(params) 14 | { 15 | } 16 | 17 | InputContextType UISystem::GetInputContext() const 18 | { 19 | return _inputContextStack.Peek(); 20 | } 21 | 22 | void UISystem::PushInputContext(InputContextType type) 23 | { 24 | auto prev = GetInputContext(); 25 | _inputContextStack.Push(type); 26 | OnInputContextChanged(prev); 27 | } 28 | 29 | void UISystem::PopInputContext() 30 | { 31 | auto prev = GetInputContext(); 32 | _inputContextStack.Pop(); 33 | OnInputContextChanged(prev); 34 | } 35 | 36 | void UISystem::OnInputContextChanged(InputContextType prev) 37 | { 38 | auto curr = GetInputContext(); 39 | 40 | switch (curr) 41 | { 42 | case InputContextType::Menu: 43 | case InputContextType::DebugImGui: 44 | // Ensure mouse is visible in menus 45 | Screen::SetCursorLock(CursorLockMode::None); 46 | Screen::SetCursorVisible(true); 47 | break; 48 | } 49 | 50 | // Update ImGui inputs reading 51 | if (auto* imGui = ImGuiPlugin::GetInstance()) 52 | { 53 | if (prev == InputContextType::DebugImGui) 54 | imGui->EnableInput = false; 55 | else if (curr == InputContextType::DebugImGui) 56 | imGui->EnableInput = true; 57 | } 58 | 59 | // Auto-focus viewport when using menus (esp. for gamepad navigation to work) 60 | Engine::FocusGameViewport(); 61 | 62 | #if UI_DEBUG 63 | LOG(Info, "Push input context {} -> {}", ScriptingEnum::ToString(prev), ScriptingEnum::ToString(curr)); 64 | #endif 65 | } 66 | 67 | UISystem* UISystem::GetInstance() 68 | { 69 | if (auto* instance = GameInstance::GetInstance()) 70 | return instance->GetGameSystem(); 71 | return nullptr; 72 | } 73 | 74 | void UISystem::Initialize() 75 | { 76 | // Start with normal game as default 77 | _inputContextStack.Clear(); 78 | _inputContextStack.Add(InputContextType::Gameplay); 79 | if (auto* imGui = ImGuiPlugin::GetInstance()) 80 | imGui->EnableInput = false; 81 | } 82 | 83 | void UISystem::Deinitialize() 84 | { 85 | } 86 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/UI/UISystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ArizonaFramework/Core/GameSystem.h" 4 | #include "Engine/Core/Collections/Array.h" 5 | 6 | /// 7 | /// Different types of input contexts that can exist in game and affect user interactions. 8 | /// 9 | API_ENUM(Namespace="ArizonaFramework.UI") enum class InputContextType 10 | { 11 | /// 12 | /// Player Controller uses inputs to control player or other gameplay components. 13 | /// 14 | Gameplay, 15 | 16 | /// 17 | /// Pause menu, main menu or other menu in use. 18 | /// 19 | Menu, 20 | 21 | /// 22 | /// Debug ImGui tool in use. 23 | /// 24 | DebugImGui, 25 | }; 26 | 27 | /// 28 | /// User Interface manager (local UI). 29 | /// 30 | API_CLASS(Namespace="ArizonaFramework.UI") class ARIZONAFRAMEWORK_API UISystem : public GameSystem 31 | { 32 | DECLARE_SCRIPTING_TYPE(UISystem); 33 | 34 | private: 35 | Array _inputContextStack; 36 | 37 | public: 38 | // Gets the current input context from the stack for proper input handling. 39 | API_PROPERTY() InputContextType GetInputContext() const; 40 | 41 | // Pushes the new UI input context onto the stack. 42 | API_FUNCTION() void PushInputContext(InputContextType type); 43 | 44 | // Pops the latest input context type from the input stack. 45 | API_FUNCTION() void PopInputContext(); 46 | 47 | public: 48 | /// 49 | /// Gets the User Interface system instance. 50 | /// 51 | API_PROPERTY() static UISystem* GetInstance(); 52 | 53 | // [GameSystem] 54 | void Initialize() override; 55 | void Deinitialize() override; 56 | 57 | private: 58 | void OnInputContextChanged(InputContextType prev); 59 | }; 60 | -------------------------------------------------------------------------------- /Source/ArizonaFramework/Utilities/Utilities.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Wojciech Figat. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/Scripting/ScriptingType.h" 6 | #include "Engine/Scripting/Script.h" 7 | #include "Engine/Level/Actor.h" 8 | 9 | /// 10 | /// Game utilities function library. 11 | /// 12 | API_CLASS(Static) class ARIZONAFRAMEWORK_API Utilities 13 | { 14 | DECLARE_SCRIPTING_TYPE_MINIMAL(Utilities); 15 | 16 | // Gets the first typed script from the actor that is active. Input actor can be null to return null. 17 | template 18 | static T* GetActiveScript(Actor* a) 19 | { 20 | if (!a) 21 | return nullptr; 22 | for (auto* script : a->Scripts) 23 | { 24 | if (script->Is() && script->IsEnabledInHierarchy()) 25 | return (T*)script; 26 | } 27 | return nullptr; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /Source/ArizonaFrameworkEditorTarget.Build.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Source/ArizonaFrameworkEditorTarget.Build.cs -------------------------------------------------------------------------------- /Source/ArizonaFrameworkTarget.Build.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlaxEngine/ArizonaFramework/1ab9eb3de1ca0b07331736bd4f8ce5d2a710eac2/Source/ArizonaFrameworkTarget.Build.cs --------------------------------------------------------------------------------