├── Properties
└── .gdignore
├── Framework
├── Game.cs.uid
├── InputActions.cs.uid
├── Autoloads
│ ├── BBColor.cs.uid
│ ├── Logger.cs.uid
│ ├── AudioManager.cs.uid
│ ├── Autoloads.cs.uid
│ ├── ModLoaderUI.cs.uid
│ ├── SceneManager.cs.uid
│ ├── Services.cs.uid
│ ├── BBColor.cs
│ ├── Autoloads.tscn
│ ├── Services.cs
│ ├── ModLoaderUI.cs
│ ├── Autoloads.cs
│ ├── SceneManager.cs
│ ├── AudioManager.cs
│ └── Logger.cs
├── Debugging
│ ├── Commands.cs.uid
│ ├── Profiler.cs.uid
│ ├── MetricsOverlay.cs.uid
│ ├── ProfilerEntry.cs.uid
│ ├── ProfilerEntry.cs
│ ├── Commands.cs
│ ├── Profiler.cs
│ └── MetricsOverlay.cs
├── Scenes
│ ├── MenuScenes.cs.uid
│ ├── Scenes.cs.uid
│ ├── MainMenuNav.cs.uid
│ ├── Options
│ │ ├── Options.cs.uid
│ │ ├── Scripts
│ │ │ ├── OptionsNav.cs.uid
│ │ │ ├── OptionsAudio.cs.uid
│ │ │ ├── OptionsDisplay.cs.uid
│ │ │ ├── OptionsGeneral.cs.uid
│ │ │ ├── OptionsInput.cs.uid
│ │ │ ├── OptionsManager.cs.uid
│ │ │ ├── OptionsGameplay.cs.uid
│ │ │ ├── OptionsGraphics.cs.uid
│ │ │ ├── ResourceHotkeys.cs.uid
│ │ │ ├── ResourceOptions.cs.uid
│ │ │ ├── ResourceHotkeys.cs
│ │ │ ├── OptionsAudio.cs
│ │ │ ├── OptionsGeneral.cs
│ │ │ ├── OptionsGameplay.cs
│ │ │ ├── OptionsGraphics.cs
│ │ │ ├── ResourceOptions.cs
│ │ │ ├── OptionsNav.cs
│ │ │ ├── OptionsDisplay.cs
│ │ │ └── OptionsManager.cs
│ │ └── Options.cs
│ ├── PopupMenu
│ │ ├── PopupMenu.cs.uid
│ │ ├── PopupMenu.cs
│ │ └── PopupMenu.tscn
│ ├── MenuUI
│ │ ├── Credits
│ │ │ ├── Credits.cs.uid
│ │ │ ├── Credits.tscn
│ │ │ └── Credits.cs
│ │ ├── Console
│ │ │ └── Gear.png
│ │ └── MainMenu
│ │ │ └── MainMenu.tscn
│ ├── Scenes.cs
│ ├── MenuScenes.cs
│ ├── MenuScenes.tres
│ └── MainMenuNav.cs
├── Components
│ ├── Component.cs.uid
│ ├── ComponentManager.cs.uid
│ ├── RotationComponent.cs.uid
│ ├── RotationComponent.cs
│ ├── Component.cs
│ └── ComponentManager.cs
├── Console
│ ├── ConsoleHistory.cs.uid
│ ├── GameConsole.cs.uid
│ ├── CommandLineArgs.cs.uid
│ ├── ConsoleCommandInfo.cs.uid
│ ├── ConsoleCommandInfo.cs
│ ├── ConsoleHistory.cs
│ ├── GameConsole.tscn
│ ├── CommandLineArgs.cs
│ └── GameConsole.cs
├── ModLoader
│ ├── ModLoader.cs.uid
│ ├── ModLoader.cs
│ └── ModLoader.tscn
├── Theme
│ ├── FocusOutlineManager.cs.uid
│ ├── CornerDashOutline.png
│ ├── PressedButton.tres
│ ├── CornerDashOutline.tres
│ ├── NormalButton.tres
│ ├── MainTheme.tres
│ └── FocusOutlineManager.cs
├── Icon.svg
├── Localisation
│ └── text.csv
├── Game.cs
└── InputActions.cs
├── Setup
├── SetupEditor.cs.uid
├── SetupUI.cs.uid
├── SetupUtils.cs.uid
├── CheckDotNetVersion.cs.uid
├── VSCode Templates
│ ├── launch.json
│ └── tasks.json
├── SetupEditor.cs
├── CheckDotNetVersion.cs
├── SetupUI.cs
├── SetupUtils.cs
└── SetupUI.tscn
├── .gitattributes
├── addons
├── GodotUtils.dll
└── Visualize.dll
├── Mods
└── Example Mod
│ ├── Mod.dll
│ ├── mod.pck
│ └── mod.json
├── .gitmodules
├── Template.csproj.user
├── Level.tscn
├── Credits.txt
├── .gitignore
├── LICENSE
├── Template.sln
├── .editorconfig
├── Template.csproj
├── README.md
└── project.godot
/Properties/.gdignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Framework/Game.cs.uid:
--------------------------------------------------------------------------------
1 | uid://ct7wor4v6sbsp
2 |
--------------------------------------------------------------------------------
/Setup/SetupEditor.cs.uid:
--------------------------------------------------------------------------------
1 | uid://do87uqxx44ab8
2 |
--------------------------------------------------------------------------------
/Setup/SetupUI.cs.uid:
--------------------------------------------------------------------------------
1 | uid://dosqetacy3knn
2 |
--------------------------------------------------------------------------------
/Setup/SetupUtils.cs.uid:
--------------------------------------------------------------------------------
1 | uid://dtfwm0v2yt6pl
2 |
--------------------------------------------------------------------------------
/Framework/InputActions.cs.uid:
--------------------------------------------------------------------------------
1 | uid://7tilyyyanloa
2 |
--------------------------------------------------------------------------------
/Framework/Autoloads/BBColor.cs.uid:
--------------------------------------------------------------------------------
1 | uid://n1sa3nkk7iho
2 |
--------------------------------------------------------------------------------
/Framework/Autoloads/Logger.cs.uid:
--------------------------------------------------------------------------------
1 | uid://br42xukyu1bkq
2 |
--------------------------------------------------------------------------------
/Framework/Debugging/Commands.cs.uid:
--------------------------------------------------------------------------------
1 | uid://2ai8vctcbook
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/MenuScenes.cs.uid:
--------------------------------------------------------------------------------
1 | uid://17yxgcswri77
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Scenes.cs.uid:
--------------------------------------------------------------------------------
1 | uid://dhjdrl43yn46w
2 |
--------------------------------------------------------------------------------
/Setup/CheckDotNetVersion.cs.uid:
--------------------------------------------------------------------------------
1 | uid://blxj0cebqkjo2
2 |
--------------------------------------------------------------------------------
/Framework/Autoloads/AudioManager.cs.uid:
--------------------------------------------------------------------------------
1 | uid://d102rl17730xk
2 |
--------------------------------------------------------------------------------
/Framework/Autoloads/Autoloads.cs.uid:
--------------------------------------------------------------------------------
1 | uid://cdt2k6s3xm13g
2 |
--------------------------------------------------------------------------------
/Framework/Autoloads/ModLoaderUI.cs.uid:
--------------------------------------------------------------------------------
1 | uid://duyyelfprqewg
2 |
--------------------------------------------------------------------------------
/Framework/Autoloads/SceneManager.cs.uid:
--------------------------------------------------------------------------------
1 | uid://di53rvtypdwb0
2 |
--------------------------------------------------------------------------------
/Framework/Autoloads/Services.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bhx5ou1uiwfkj
2 |
--------------------------------------------------------------------------------
/Framework/Components/Component.cs.uid:
--------------------------------------------------------------------------------
1 | uid://d26gq73rajotn
2 |
--------------------------------------------------------------------------------
/Framework/Console/ConsoleHistory.cs.uid:
--------------------------------------------------------------------------------
1 | uid://rwa25d0443r2
2 |
--------------------------------------------------------------------------------
/Framework/Console/GameConsole.cs.uid:
--------------------------------------------------------------------------------
1 | uid://blp5ybyxnppar
2 |
--------------------------------------------------------------------------------
/Framework/Debugging/Profiler.cs.uid:
--------------------------------------------------------------------------------
1 | uid://brlcnx2mkimq1
2 |
--------------------------------------------------------------------------------
/Framework/ModLoader/ModLoader.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bsp7uk58xytxa
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/MainMenuNav.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bi551lrt82awu
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Options.cs.uid:
--------------------------------------------------------------------------------
1 | uid://b88ck7eam4e70
2 |
--------------------------------------------------------------------------------
/Framework/Components/ComponentManager.cs.uid:
--------------------------------------------------------------------------------
1 | uid://ct4og8hi8urvd
2 |
--------------------------------------------------------------------------------
/Framework/Components/RotationComponent.cs.uid:
--------------------------------------------------------------------------------
1 | uid://0pg7exsmsjkh
2 |
--------------------------------------------------------------------------------
/Framework/Console/CommandLineArgs.cs.uid:
--------------------------------------------------------------------------------
1 | uid://cwtgwqgmuuihe
2 |
--------------------------------------------------------------------------------
/Framework/Console/ConsoleCommandInfo.cs.uid:
--------------------------------------------------------------------------------
1 | uid://ni0io7iigcb6
2 |
--------------------------------------------------------------------------------
/Framework/Debugging/MetricsOverlay.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bamosxalm8lxs
2 |
--------------------------------------------------------------------------------
/Framework/Debugging/ProfilerEntry.cs.uid:
--------------------------------------------------------------------------------
1 | uid://dg7hgnei4yux6
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/PopupMenu/PopupMenu.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bj7mf85f3xll2
2 |
--------------------------------------------------------------------------------
/Framework/Theme/FocusOutlineManager.cs.uid:
--------------------------------------------------------------------------------
1 | uid://crt03orciniqn
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/MenuUI/Credits/Credits.cs.uid:
--------------------------------------------------------------------------------
1 | uid://cg3so70gfansf
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsNav.cs.uid:
--------------------------------------------------------------------------------
1 | uid://pdomcsu3uajx
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsAudio.cs.uid:
--------------------------------------------------------------------------------
1 | uid://nots3syd4eur
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsDisplay.cs.uid:
--------------------------------------------------------------------------------
1 | uid://duywpdpydv488
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsGeneral.cs.uid:
--------------------------------------------------------------------------------
1 | uid://byg8c8jdptylv
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsInput.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bpcd5ahs73ga4
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsManager.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bji01posut5cy
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsGameplay.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bnb0qpdygtgve
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsGraphics.cs.uid:
--------------------------------------------------------------------------------
1 | uid://bkctgqs1uogu8
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/ResourceHotkeys.cs.uid:
--------------------------------------------------------------------------------
1 | uid://b671novbig7ts
2 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/ResourceOptions.cs.uid:
--------------------------------------------------------------------------------
1 | uid://csiwm4478j43k
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Normalize EOL for all files that Git considers text files.
2 | * text=auto eol=lf
3 |
--------------------------------------------------------------------------------
/addons/GodotUtils.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CSharpGodotTools/Template/HEAD/addons/GodotUtils.dll
--------------------------------------------------------------------------------
/addons/Visualize.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CSharpGodotTools/Template/HEAD/addons/Visualize.dll
--------------------------------------------------------------------------------
/Mods/Example Mod/Mod.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CSharpGodotTools/Template/HEAD/Mods/Example Mod/Mod.dll
--------------------------------------------------------------------------------
/Mods/Example Mod/mod.pck:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CSharpGodotTools/Template/HEAD/Mods/Example Mod/mod.pck
--------------------------------------------------------------------------------
/Framework/Theme/CornerDashOutline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CSharpGodotTools/Template/HEAD/Framework/Theme/CornerDashOutline.png
--------------------------------------------------------------------------------
/Framework/Scenes/MenuUI/Console/Gear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CSharpGodotTools/Template/HEAD/Framework/Scenes/MenuUI/Console/Gear.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "addons/imgui-godot"]
2 | path = addons/imgui-godot
3 | url = https://github.com/CSharpGodotTools/imgui-godot-csharp
4 |
--------------------------------------------------------------------------------
/Template.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Framework/Scenes/Scenes.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 |
3 | namespace __TEMPLATE__.UI;
4 |
5 | [GlobalClass]
6 | public partial class Scenes : Node
7 | {
8 | [Export] public PackedScene Game { get; private set; }
9 | }
10 |
--------------------------------------------------------------------------------
/Mods/Example Mod/mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Example Mod",
3 | "id": "example_mod",
4 | "modVersion": "1.0",
5 | "gameVersion": "*",
6 | "description": "Example description.",
7 | "author": "valkyrienyanko",
8 | "dependencies": {},
9 | "incompatibilities": {}
10 | }
11 |
--------------------------------------------------------------------------------
/Framework/Console/ConsoleCommandInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace __TEMPLATE__.UI.Console;
4 |
5 | public class ConsoleCommandInfo
6 | {
7 | public required string Name { get; set; }
8 | public required Action Code { get; set; } // string[] is the the functions params
9 | public string[] Aliases { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/Framework/Autoloads/BBColor.cs:
--------------------------------------------------------------------------------
1 | namespace __TEMPLATE__;
2 |
3 | // Full list of BBCode color tags: https://absitomen.com/index.php?topic=331.0
4 | public enum BBColor
5 | {
6 | Gray,
7 | DarkGray,
8 | Green,
9 | DarkGreen,
10 | LightGreen,
11 | Aqua,
12 | DarkAqua,
13 | Deepskyblue,
14 | Magenta,
15 | Red,
16 | White,
17 | Yellow,
18 | Orange
19 | }
20 |
--------------------------------------------------------------------------------
/Framework/Theme/PressedButton.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StyleBoxFlat" format=3 uid="uid://b8uqbeckjjkp0"]
2 |
3 | [resource]
4 | bg_color = Color(0.24524486, 0.2554823, 0.39747292, 1)
5 | border_width_top = 5
6 | border_color = Color(1, 1, 1, 0)
7 | corner_radius_top_left = 10
8 | corner_radius_top_right = 10
9 | corner_radius_bottom_right = 10
10 | corner_radius_bottom_left = 10
11 | anti_aliasing = false
12 |
--------------------------------------------------------------------------------
/Level.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://djhd6uw7l1ok"]
2 |
3 | [ext_resource type="PackedScene" uid="uid://c6pism2kgjr5c" path="res://Framework/Scenes/PopupMenu/PopupMenu.tscn" id="1_bexhn"]
4 |
5 | [node name="Main" type="Node2D"]
6 |
7 | [node name="CanvasLayer" type="CanvasLayer" parent="."]
8 |
9 | [node name="PopupMenu" parent="CanvasLayer" instance=ExtResource("1_bexhn")]
10 | visible = false
11 |
--------------------------------------------------------------------------------
/Framework/Scenes/MenuScenes.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 |
3 | namespace __TEMPLATE__.UI;
4 |
5 | [GlobalClass]
6 | public partial class MenuScenes : Resource
7 | {
8 | [Export] public PackedScene MainMenu { get; private set; }
9 | [Export] public PackedScene ModLoader { get; private set; }
10 | [Export] public PackedScene Options { get; private set; }
11 | [Export] public PackedScene Credits { get; private set; }
12 | }
13 |
--------------------------------------------------------------------------------
/Framework/Theme/CornerDashOutline.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StyleBoxTexture" load_steps=2 format=3 uid="uid://bb5d4xfkdayi8"]
2 |
3 | [ext_resource type="Texture2D" uid="uid://djggjhepn0bsf" path="res://Framework/Theme/CornerDashOutline.png" id="1_h1p6h"]
4 |
5 | [resource]
6 | texture = ExtResource("1_h1p6h")
7 | texture_margin_left = 8.0
8 | texture_margin_top = 8.0
9 | texture_margin_right = 8.0
10 | texture_margin_bottom = 8.0
11 | draw_center = false
12 |
--------------------------------------------------------------------------------
/Framework/Theme/NormalButton.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StyleBoxFlat" format=3 uid="uid://bu2ywx2nob8v3"]
2 |
3 | [resource]
4 | content_margin_left = 10.0
5 | content_margin_right = 10.0
6 | bg_color = Color(0.112, 0.112, 0.14, 1)
7 | border_width_bottom = 5
8 | border_color = Color(0.0923971, 0.0923971, 0.0923971, 1)
9 | corner_radius_top_left = 10
10 | corner_radius_top_right = 10
11 | corner_radius_bottom_right = 10
12 | corner_radius_bottom_left = 10
13 |
--------------------------------------------------------------------------------
/Credits.txt:
--------------------------------------------------------------------------------
1 | [h1]Big Header
2 |
3 | [h2]Smaller Header
4 |
5 | Anything here will show up in the credits in-game. Update this file with the appropriate credits!!!
6 |
7 | You do NOT have to mention the link to Template although it is appreciated.
8 |
9 | This game used https://github.com/CSharpGodotTools/Template by Valkyrienyanko and Contributors
10 |
11 | [h2]Assets Used
12 |
13 | "Example Asset" by FirstName LastName LICENSED CC0: https://github.com/
14 |
--------------------------------------------------------------------------------
/Setup/VSCode Templates/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Debug Game",
6 | "type": "coreclr",
7 | "request": "launch",
8 | "preLaunchTask": "build",
9 | "program": "ENGINE_EXE",
10 | "args": [],
11 | "cwd": "${workspaceFolder}",
12 | "stopAtEntry": false,
13 | "console": "integratedTerminal"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/Framework/Components/RotationComponent.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 |
3 | namespace __TEMPLATE__;
4 |
5 | // Useful to quickly rotate a Sprite2D node to see if the game is truly paused or not
6 | [GlobalClass]
7 | public partial class RotationComponent : Node
8 | {
9 | [Export] private float _speed = 1.5f;
10 |
11 | private Node2D _parent;
12 |
13 | public override void _Ready()
14 | {
15 | _parent = GetParent();
16 | }
17 |
18 | public override void _Process(double delta)
19 | {
20 | _parent.Rotation += _speed * (float)delta;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot
2 | .godot/*
3 | Exported/*
4 | .import/
5 | **/*.import
6 | export.cfg
7 | export_presets.cfg
8 | *.translation
9 |
10 | # Addons
11 | addons/**/*.template_debug.x86_64.dll
12 |
13 | # GdUnit4
14 | TestResults/
15 |
16 | # Mono-specific
17 | .mono/
18 | data_*/
19 | mono_crash.*.json
20 | *.DotSettings.user
21 |
22 | # Visual Studio
23 | .vs/*
24 | Properties/*
25 |
26 | # Visual Studio Code
27 | .vscode/*
28 |
29 | # Rider
30 | .idea/*
31 |
32 | # enet custom
33 | ENet-CSharp.dll
34 |
35 | libenet.so
36 | enet.so
37 |
38 | libenet.dylib
39 | enet.dylib
40 |
41 | # os files
42 | .DS_Store
43 |
44 | # Include these files
45 | !**/*.gdignore
46 | !.vscode/settings.json
47 | !.vscode/extensions.json
48 |
49 | # Make sure this file is ignored
50 | .godot/.gdignore
51 |
--------------------------------------------------------------------------------
/Setup/SetupEditor.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System;
3 | using System.Diagnostics;
4 |
5 | namespace __TEMPLATE__.Setup;
6 |
7 | public class SetupEditor
8 | {
9 | public static void Start()
10 | {
11 | OS.Execute(OS.GetExecutablePath(), ["--editor"]);
12 | }
13 |
14 | public static void Restart()
15 | {
16 | Quit();
17 | Start();
18 | }
19 |
20 | public static void Quit()
21 | {
22 | string[] names = ["redot", "godot"];
23 |
24 | foreach (Process process in Process.GetProcesses())
25 | {
26 | foreach (string name in names)
27 | {
28 | string winTitle = process.MainWindowTitle.ToLower();
29 |
30 | if (winTitle.Contains(name) && !winTitle.Contains("console"))
31 | {
32 | process.Kill();
33 | return;
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Framework/Icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Framework/Scenes/MenuScenes.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="Resource" script_class="MenuScenes" load_steps=6 format=3 uid="uid://xig0kceiok04"]
2 |
3 | [ext_resource type="PackedScene" uid="uid://rbcqvr4snrvn" path="res://Framework/Scenes/MenuUI/Credits/Credits.tscn" id="1_sc3de"]
4 | [ext_resource type="PackedScene" uid="uid://d4a5xfmaulku1" path="res://Framework/Scenes/MenuUI/MainMenu/MainMenu.tscn" id="2_65alf"]
5 | [ext_resource type="PackedScene" uid="uid://d1jo48n2hdkih" path="res://Framework/ModLoader/ModLoader.tscn" id="3_jpuli"]
6 | [ext_resource type="PackedScene" uid="uid://7tfets4irkba" path="res://Framework/Scenes/Options/Options.tscn" id="4_u3dbg"]
7 | [ext_resource type="Script" uid="uid://17yxgcswri77" path="res://Framework/Scenes/MenuScenes.cs" id="5_o2h5i"]
8 |
9 |
10 | [resource]
11 | script = ExtResource("5_o2h5i")
12 | MainMenu = ExtResource("2_65alf")
13 | ModLoader = ExtResource("3_jpuli")
14 | Options = ExtResource("4_u3dbg")
15 | Credits = ExtResource("1_sc3de")
16 |
--------------------------------------------------------------------------------
/Framework/Debugging/ProfilerEntry.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 |
3 | namespace __TEMPLATE__.Debugging;
4 |
5 | public class ProfilerEntry
6 | {
7 | public ulong StartTimeUsec { get; private set; }
8 | public ulong AccumulatedTimeUsec { get; private set; }
9 | public int FrameCount { get; private set; }
10 |
11 | public void Start()
12 | {
13 | StartTimeUsec = Time.GetTicksUsec();
14 | }
15 |
16 | public void Stop()
17 | {
18 | AccumulatedTimeUsec += Time.GetTicksUsec() - StartTimeUsec;
19 | FrameCount++;
20 | }
21 |
22 | public void Reset()
23 | {
24 | AccumulatedTimeUsec = 0UL;
25 | FrameCount = 0;
26 | }
27 |
28 | ///
29 | /// Returns the average frame time in milliseconds with specified accuracy.
30 | ///
31 | public string GetAverageMs(int accuracy)
32 | {
33 | if (FrameCount == 0)
34 | return "0.0";
35 |
36 | double avgMs = (double)AccumulatedTimeUsec / FrameCount / 1000.0;
37 | return avgMs.ToString($"F{accuracy}");
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 ValksGodotTools
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 |
--------------------------------------------------------------------------------
/Framework/Debugging/Commands.cs:
--------------------------------------------------------------------------------
1 | using __TEMPLATE__.UI.Console;
2 | using GodotUtils;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace __TEMPLATE__.UI;
7 |
8 | public class Commands
9 | {
10 | public static void RegisterAll()
11 | {
12 | GameConsole console = Game.Console;
13 | console.RegisterCommand("help", CommandHelp);
14 | console.RegisterCommand("quit", CommandQuit).WithAliases("exit");
15 | console.RegisterCommand("debug", CommandDebug);
16 | }
17 |
18 | private static void CommandHelp(string[] args)
19 | {
20 | IEnumerable cmds = Game.Console.GetCommands().Select(x => x.Name);
21 | Game.Logger.Log(cmds.ToFormattedString());
22 | }
23 |
24 | private async static void CommandQuit(string[] args)
25 | {
26 | await Autoloads.Instance.ExitGame();
27 | }
28 |
29 | private static void CommandDebug(string[] args)
30 | {
31 | if (args.Length <= 0)
32 | {
33 | Game.Logger.Log("Specify at least one argument");
34 | return;
35 | }
36 |
37 | Game.Logger.Log(args[0]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Template.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.5.2.0
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Template", "Template.csproj", "{87041398-62C1-8D78-B512-37DE781C5205}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {87041398-62C1-8D78-B512-37DE781C5205}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {87041398-62C1-8D78-B512-37DE781C5205}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {87041398-62C1-8D78-B512-37DE781C5205}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {87041398-62C1-8D78-B512-37DE781C5205}.Release|Any CPU.Build.0 = Release|Any CPU
17 | EndGlobalSection
18 | GlobalSection(SolutionProperties) = preSolution
19 | HideSolutionNode = FALSE
20 | EndGlobalSection
21 | GlobalSection(ExtensibilityGlobals) = postSolution
22 | SolutionGuid = {E6313EBD-3921-4BBD-82BF-B3AF80D6765E}
23 | EndGlobalSection
24 | EndGlobal
25 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/ResourceHotkeys.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using Godot.Collections;
3 |
4 | // This was intentionally set to GodotUtils instead of __TEMPLATE__ as GodotUtils relies on MainMenuBtnPressed
5 | // and GodotUtils should NOT have any trace of using __TEMPLATE__.
6 | namespace __TEMPLATE__.UI;
7 |
8 | /*
9 | * If the ResourceHotkeys.cs script is moved then the file path will not updated
10 | * in the .tres file. In order to fix this go to AppData\Roaming\Godot\app_userdata\Template
11 | * and delete the .tres file so Godot will be forced to generate it from
12 | * scratch. This is not a Godot bug it is just something to look out for.
13 | *
14 | * Resource props must have [Export] attribute otherwise they will not save
15 | * properly.
16 | *
17 | * The 'recommended' way of storing config files can be found here
18 | * https://docs.godotengine.org/en/stable/classes/class_configfile.html
19 | * However this is undesired because values are saved through string keys
20 | * instead of props.
21 | */
22 | public partial class ResourceHotkeys : Resource
23 | {
24 | [Export] public Dictionary> Actions { get; set; } = [];
25 | }
26 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Options.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 |
3 | namespace __TEMPLATE__.UI;
4 |
5 | public partial class Options : PanelContainer
6 | {
7 | private OptionsNav _optionsNav;
8 | private OptionsGeneral _optionsGeneral;
9 | private OptionsGameplay _optionsGameplay;
10 | private OptionsDisplay _optionsDisplay;
11 | private OptionsGraphics _optionsGraphics;
12 | private OptionsAudio _optionsAudio;
13 | private OptionsInput _optionsInput;
14 |
15 | public override void _Ready()
16 | {
17 | _optionsNav = new OptionsNav(this, GetNode
27 |
28 | First make sure you have the following.
29 | - [.NET SDK](https://dotnet.microsoft.com/download) is at least `8.0.400`. Check with `dotnet --version`.
30 | - [Latest Godot C# Release](https://godotengine.org/)
31 | - [Custom ENet build](https://github.com/CSharpGodotTools/Template/wiki/Custom-ENet-Builds) may be required if using Mac or Linux
32 |
33 | Download the [latest stable release](https://github.com/CSharpGodotTools/Template/releases/latest).
34 |
35 | Press play (`F5`) to run the setup and click apply changes. Godot will restart with your template ready to go.
36 |
37 |
38 |
39 |
40 |
Thank You
41 |
42 |
43 | [Brian Shao](https://github.com/cydq) for helping with cross-platform compatibility.
44 | [Piep Matz](https://github.com/riffy) for helping with the drag drop system.
45 | [Vaggelismsxp](https://github.com/vaggelismsxp) for helping improve the options theme.
46 |
47 | New contributors please read [this](https://github.com/CSharpGodotTools/Template/wiki/Contributing).
48 |
--------------------------------------------------------------------------------
/Setup/SetupUI.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using GodotUtils;
3 | using System.IO;
4 |
5 | namespace __TEMPLATE__.Setup;
6 |
7 | public partial class SetupUI : Node
8 | {
9 | private string _prevGameName = string.Empty;
10 | private LineEdit _gameNameLineEdit;
11 | private RichTextLabel _namePreviewLabel;
12 | private PopupPanel _popupPanel;
13 |
14 | public override void _Ready()
15 | {
16 | _gameNameLineEdit = GetNode("%GameName");
17 | _namePreviewLabel = GetNode("%NamePreview");
18 | _popupPanel = GetNode("%NodePopupPanel");
19 |
20 | SetupUtils.DisplayGameNamePreview("Undefined", _namePreviewLabel);
21 | }
22 |
23 | private void _OnYesPressed()
24 | {
25 | string gameName = SetupUtils.FormatGameName(_gameNameLineEdit.Text);
26 | string path = ProjectSettings.GlobalizePath("res://");
27 |
28 | // Prevent namespace being the same name as a class name in the project
29 | bool namespaceSameAsClassName = false;
30 |
31 | DirectoryUtils.Traverse("res://", fullFilePath =>
32 | {
33 | if (Path.GetFileName(fullFilePath).Equals(_gameNameLineEdit.Text + ".cs"))
34 | {
35 | namespaceSameAsClassName = true;
36 | return true;
37 | }
38 |
39 | return false;
40 | });
41 |
42 | if (namespaceSameAsClassName)
43 | {
44 | GD.PrintErr($"Namespace {_gameNameLineEdit.Text} is the same name as {_gameNameLineEdit.Text}.cs");
45 | return;
46 | }
47 |
48 | // The IO functions ran below will break if empty folders exist
49 | DirectoryUtils.DeleteEmptyDirectories(path);
50 |
51 | SetupUtils.SetMainScene(path, "Level");
52 | SetupUtils.RenameProjectFiles(path, gameName);
53 | SetupUtils.RenameAllNamespaces(path, gameName);
54 |
55 | // Delete the "res://Setup" directory
56 | Directory.Delete(Path.Combine(path, "Setup"), recursive: true);
57 |
58 | // Ensure all empty folders are deleted when finished
59 | DirectoryUtils.DeleteEmptyDirectories(path);
60 |
61 | SetupEditor.Restart();
62 | GetTree().Quit();
63 | }
64 |
65 | private void _OnGameNameTextChanged(string newText)
66 | {
67 | if (string.IsNullOrWhiteSpace(newText))
68 | return;
69 |
70 | // Since this name is being used for the namespace its first character must not be
71 | // a number and every other character must be alphanumeric
72 | if (!SetupUtils.IsAlphaNumericAndAllowSpaces(newText) || char.IsNumber(newText.Trim()[0]))
73 | {
74 | SetupUtils.DisplayGameNamePreview(_prevGameName, _namePreviewLabel);
75 | _gameNameLineEdit.Text = _prevGameName;
76 | _gameNameLineEdit.CaretColumn = _prevGameName.Length;
77 | return;
78 | }
79 |
80 | SetupUtils.DisplayGameNamePreview(newText, _namePreviewLabel);
81 | _prevGameName = newText;
82 | }
83 |
84 | private void _OnNoPressed()
85 | {
86 | _popupPanel.Hide();
87 | }
88 |
89 | private void _OnApplyChangesPressed()
90 | {
91 | string gameName = SetupUtils.FormatGameName(_gameNameLineEdit.Text);
92 |
93 | if (string.IsNullOrWhiteSpace(gameName))
94 | {
95 | GD.Print("Please type a game name first!");
96 | return;
97 | }
98 |
99 | _popupPanel.PopupCentered();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Framework/Autoloads/Services.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using GodotUtils;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace __TEMPLATE__;
7 |
8 | ///
9 | /// Services have a scene lifetime meaning they will be destroyed when the scene changes. Services
10 | /// aid as an alternative to using the static keyword everywhere.
11 | ///
12 | public class Services(Autoloads autoloads)
13 | {
14 | ///
15 | /// Dictionary to store registered services, keyed by their type.
16 | ///
17 | private Dictionary _services = [];
18 | private SceneManager _sceneManager = autoloads.SceneManager;
19 |
20 | ///
21 | /// Retrieves a service of the specified type.
22 | ///
23 | /// The type of the service to retrieve.
24 | /// The instance of the service.
25 | public T Get()
26 | {
27 | if (!_services.ContainsKey(typeof(T)))
28 | {
29 | throw new Exception($"Unable to obtain service '{typeof(T)}'");
30 | }
31 |
32 | return (T)_services[typeof(T)].Instance;
33 | }
34 |
35 | ///
36 | /// Registers the given as a singleton-style service for the current scene.
37 | /// Only one service of a particular type may be registered at a time, and it will be
38 | /// automatically unregistered when the scene changes.
39 | ///
40 | /// The node to register as a service.
41 | ///
42 | /// Thrown if a service of the same has already been registered.
43 | ///
44 | public void Register(Node node)
45 | {
46 | if (_services.ContainsKey(node.GetType()))
47 | {
48 | throw new Exception($"There can only be one service of type '{node.GetType().Name}'");
49 | }
50 |
51 | //GD.Print($"Registering service: {node.GetType().Name}");
52 | AddService(node);
53 | }
54 |
55 | ///
56 | /// Adds a service to the service provider.
57 | ///
58 | private void AddService(Node node)
59 | {
60 | Service service = new()
61 | {
62 | Instance = node
63 | };
64 |
65 | _services.Add(node.GetType(), service);
66 |
67 | RemoveServiceOnSceneChanged(service);
68 | }
69 |
70 | ///
71 | /// Removes a service when the scene changes.
72 | ///
73 | private void RemoveServiceOnSceneChanged(Service service)
74 | {
75 | // The scene has changed, remove all services
76 | _sceneManager.PreSceneChanged += Cleanup;
77 |
78 | void Cleanup(string scene)
79 | {
80 | // Stop listening to PreSceneChanged
81 | _sceneManager.PreSceneChanged -= Cleanup;
82 |
83 | // Remove the service
84 | bool success = _services.Remove(service.Instance.GetType());
85 |
86 | if (!success)
87 | {
88 | throw new Exception($"Failed to remove the service '{service.Instance.GetType().Name}'");
89 | }
90 | }
91 | }
92 |
93 | ///
94 | /// A formatted string of the all the services.
95 | ///
96 | public override string ToString()
97 | {
98 | return _services.ToFormattedString();
99 | }
100 |
101 | ///
102 | /// A class representing a service instance
103 | ///
104 | private class Service
105 | {
106 | ///
107 | /// The instance of the service.
108 | ///
109 | public object Instance { get; set; }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Framework/Theme/FocusOutlineManager.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 |
3 | namespace __TEMPLATE__.UI;
4 |
5 | public partial class FocusOutlineManager(Node owner) : Component(owner)
6 | {
7 | // Config
8 | private float _flashSpeed = 4f;
9 | private float _minAlpha = 0.35f;
10 | private float _maxAlpha = 0.7f;
11 | //private float _fadeDelay = 2f;
12 | //private float _fadeSpeed = 2f;
13 |
14 | private NavigationMethod _lastNavigation = NavigationMethod.Mouse;
15 | private Control _currentFocus;
16 | private Control _outline;
17 | private float _time;
18 | private bool _ignoreNextFocus;
19 | private Node _owner = owner;
20 | //private float _lastInputTime;
21 |
22 | public override void Ready()
23 | {
24 | _outline = _owner.GetNode("CornerDashOutline");
25 | _outline.Visible = false;
26 |
27 | _owner.GetViewport().GuiFocusChanged += OnGuiFocusChanged;
28 | Game.Scene.PreSceneChanged += OnPreSceneChanged;
29 |
30 | SetProcess(false);
31 | SetInput(true);
32 | }
33 |
34 | public override void ProcessInput(InputEvent @event)
35 | {
36 | if (@event is InputEventMouse)
37 | {
38 | _lastNavigation = NavigationMethod.Mouse;
39 | }
40 | else if (@event is InputEventKey || @event is InputEventJoypadButton)
41 | {
42 | _lastNavigation = NavigationMethod.KeyboardOrGamepad;
43 | //_lastInputTime = (float)(Time.GetTicksUsec() / 1_000_000.0);
44 | }
45 | }
46 |
47 | private void OnPreSceneChanged(string scene)
48 | {
49 | _outline.Visible = false;
50 | _currentFocus = null;
51 | SetProcess(false);
52 | }
53 |
54 | public override void Process(double delta)
55 | {
56 | if (_currentFocus == null)
57 | return;
58 |
59 | _time += (float)delta;
60 |
61 | // Alpha pulse
62 | float t = Mathf.Sin(_time * _flashSpeed) * 0.5f + 0.5f;
63 | float alpha = Mathf.Lerp(_minAlpha, _maxAlpha, t);
64 |
65 | // Fade out if inactive
66 | /*double currentTime = Time.GetTicksUsec() / 1_000_000.0; // seconds
67 | float inactiveTime = (float)(currentTime - _lastInputTime);
68 |
69 | if (inactiveTime > _fadeDelay)
70 | alpha = Mathf.Lerp(alpha, 0f, (inactiveTime - _fadeDelay) * _fadeSpeed);*/
71 |
72 | // Modulate
73 | Color c = _outline.Modulate;
74 | c.A = alpha;
75 | _outline.Modulate = c;
76 |
77 | // Position and size match the focused control, with padding
78 | Vector2 padding = new(1, 1);
79 | _outline.GlobalPosition = _currentFocus.GlobalPosition - padding;
80 | _outline.Size = _currentFocus.Size + padding * 2;
81 | }
82 |
83 | ///
84 | /// Prevents the focus outline from appearing on next received input.
85 | /// Use this when setting a control as focused with GrabFocus() but don't
86 | /// want the focus outline to show.
87 | ///
88 | public void IgnoreNextFocus()
89 | {
90 | _ignoreNextFocus = true;
91 | }
92 |
93 | private void OnGuiFocusChanged(Control newFocus)
94 | {
95 | if (_ignoreNextFocus)
96 | {
97 | _ignoreNextFocus = false;
98 | return;
99 | }
100 |
101 | _currentFocus = newFocus;
102 |
103 | if (_currentFocus != null && _lastNavigation == NavigationMethod.KeyboardOrGamepad)
104 | {
105 | _outline.Visible = true;
106 | SetProcess(true);
107 | }
108 | else
109 | {
110 | _outline.Visible = false;
111 | SetProcess(false);
112 | }
113 | }
114 |
115 | private enum NavigationMethod
116 | {
117 | Mouse,
118 | KeyboardOrGamepad
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Framework/Scenes/PopupMenu/PopupMenu.cs:
--------------------------------------------------------------------------------
1 | using __TEMPLATE__.UI.Console;
2 | using Godot;
3 | using GodotUtils;
4 | using System;
5 |
6 | // This was intentionally set to GodotUtils instead of __TEMPLATE__ as GodotUtils relies on MainMenuBtnPressed
7 | // and GodotUtils should NOT have any trace of using __TEMPLATE__.
8 | namespace __TEMPLATE__.UI;
9 |
10 | public partial class PopupMenu : Control
11 | {
12 | [Export] private PackedScene _optionsPrefab;
13 | [Export] private Button _resumeBtn;
14 | [Export] private Button _restartBtn;
15 | [Export] private Button _optionsBtn;
16 | [Export] private Button _mainMenuBtn;
17 | [Export] private Button _quitBtn;
18 |
19 | public event Action Opened;
20 | public event Action Closed;
21 | public event Action MainMenuBtnPressed;
22 |
23 | private GameConsole _console;
24 | private PanelContainer _menu;
25 | private VBoxContainer _nav;
26 | private Options _options;
27 |
28 | public override void _Ready()
29 | {
30 | _console = Game.Console;
31 | _menu = GetNode("%Menu");
32 | _nav = GetNode("%Navigation");
33 |
34 | _resumeBtn.Pressed += OnResumePressed;
35 | _restartBtn.Pressed += OnRestartPressed;
36 | _optionsBtn.Pressed += OnOptionsPressed;
37 | _mainMenuBtn.Pressed += OnMainMenuPressed;
38 | _quitBtn.Pressed += OnQuitPressed;
39 |
40 | Game.Services.Register(this);
41 | CreateOptions();
42 | HideOptions();
43 | Hide();
44 | }
45 |
46 | public override void _PhysicsProcess(double delta)
47 | {
48 | if (!Input.IsActionJustPressed(InputActions.UICancel))
49 | return;
50 |
51 | if (_console.Visible)
52 | {
53 | _console.ToggleVisibility();
54 | return;
55 | }
56 |
57 | if (_options.Visible)
58 | {
59 | HideOptions();
60 | ShowPopupMenu();
61 | return;
62 | }
63 |
64 | ToggleGamePause();
65 | }
66 |
67 | private void OnResumePressed()
68 | {
69 | Hide();
70 | GetTree().Paused = false;
71 | }
72 |
73 | private void OnRestartPressed()
74 | {
75 | GetTree().Paused = false;
76 | Game.Scene.ResetCurrentScene();
77 | }
78 |
79 | private void OnOptionsPressed()
80 | {
81 | ShowOptions();
82 | HidePopupMenu();
83 | }
84 |
85 | private void OnMainMenuPressed()
86 | {
87 | MainMenuBtnPressed?.Invoke();
88 | GetTree().Paused = false;
89 | Game.Scene.SwitchToMainMenu();
90 | }
91 |
92 | private void OnQuitPressed()
93 | {
94 | Autoloads.Instance.ExitGame().FireAndForget();
95 | }
96 |
97 | private void CreateOptions()
98 | {
99 | _options = _optionsPrefab.Instantiate();
100 | AddChild(_options);
101 | }
102 |
103 | private void ShowOptions()
104 | {
105 | _options.ProcessMode = ProcessModeEnum.Always;
106 | _options.Show();
107 | }
108 |
109 | private void HideOptions()
110 | {
111 | _options.ProcessMode = ProcessModeEnum.Disabled;
112 | _options.Hide();
113 | }
114 |
115 | private void ShowPopupMenu() => _menu.Show();
116 | private void HidePopupMenu() => _menu.Hide();
117 |
118 | private void ToggleGamePause()
119 | {
120 | if (Visible)
121 | ResumeGame();
122 | else
123 | PauseGame();
124 | }
125 |
126 | private void PauseGame()
127 | {
128 | Visible = true;
129 | GetTree().Paused = true;
130 | Opened?.Invoke();
131 | }
132 |
133 | private void ResumeGame()
134 | {
135 | Visible = false;
136 | GetTree().Paused = false;
137 | Closed?.Invoke();
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/Framework/Scenes/PopupMenu/PopupMenu.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=10 format=3 uid="uid://c6pism2kgjr5c"]
2 |
3 | [ext_resource type="Script" uid="uid://bj7mf85f3xll2" path="res://Framework/Scenes/PopupMenu/PopupMenu.cs" id="2_g08n2"]
4 | [ext_resource type="PackedScene" uid="uid://7tfets4irkba" path="res://Framework/Scenes/Options/Options.tscn" id="2_p67ws"]
5 |
6 |
7 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_j3vts"]
8 |
9 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xx28n"]
10 | bg_color = Color(0, 0, 0, 0.345098)
11 | border_width_left = 100
12 | border_width_top = 100
13 | border_width_right = 100
14 | border_width_bottom = 100
15 | border_color = Color(0, 0, 0, 0)
16 | border_blend = true
17 | corner_radius_top_left = 50
18 | corner_radius_top_right = 50
19 | corner_radius_bottom_right = 50
20 | corner_radius_bottom_left = 50
21 |
22 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t3nk4"]
23 | bg_color = Color(0, 0, 0, 0.470588)
24 |
25 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c4p5d"]
26 | bg_color = Color(0, 0, 0, 0.219608)
27 |
28 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3lyh8"]
29 | content_margin_left = 10.0
30 | content_margin_top = 10.0
31 | content_margin_right = 10.0
32 | content_margin_bottom = 10.0
33 | bg_color = Color(0, 0, 0, 0.219608)
34 |
35 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ymm5v"]
36 |
37 | [sub_resource type="Theme" id="Theme_gqro3"]
38 | Button/styles/focus = SubResource("StyleBoxFlat_t3nk4")
39 | Button/styles/hover = SubResource("StyleBoxFlat_c4p5d")
40 | Button/styles/normal = SubResource("StyleBoxFlat_3lyh8")
41 | Button/styles/pressed = SubResource("StyleBoxEmpty_ymm5v")
42 |
43 | [node name="PopupMenu" type="PanelContainer" node_paths=PackedStringArray("_resumeBtn", "_restartBtn", "_optionsBtn", "_mainMenuBtn", "_quitBtn")]
44 | process_mode = 3
45 | anchors_preset = 15
46 | anchor_right = 1.0
47 | anchor_bottom = 1.0
48 | grow_horizontal = 2
49 | grow_vertical = 2
50 | mouse_filter = 2
51 | theme_override_styles/panel = SubResource("StyleBoxEmpty_j3vts")
52 | script = ExtResource("2_g08n2")
53 | _optionsPrefab = ExtResource("2_p67ws")
54 | _resumeBtn = NodePath("Center/Menu/Margin/Navigation/Resume")
55 | _restartBtn = NodePath("Center/Menu/Margin/Navigation/Restart")
56 | _optionsBtn = NodePath("Center/Menu/Margin/Navigation/Options")
57 | _mainMenuBtn = NodePath("Center/Menu/Margin/Navigation/Main Menu")
58 | _quitBtn = NodePath("Center/Menu/Margin/Navigation/Quit")
59 | metadata/_edit_lock_ = true
60 |
61 | [node name="Center" type="CenterContainer" parent="."]
62 | layout_mode = 2
63 |
64 | [node name="Menu" type="PanelContainer" parent="Center"]
65 | unique_name_in_owner = true
66 | layout_mode = 2
67 | theme_override_styles/panel = SubResource("StyleBoxFlat_xx28n")
68 |
69 | [node name="Margin" type="MarginContainer" parent="Center/Menu"]
70 | layout_mode = 2
71 | theme_override_constants/margin_left = 5
72 | theme_override_constants/margin_top = 5
73 | theme_override_constants/margin_right = 5
74 | theme_override_constants/margin_bottom = 5
75 |
76 | [node name="Navigation" type="VBoxContainer" parent="Center/Menu/Margin"]
77 | unique_name_in_owner = true
78 | layout_mode = 2
79 | theme = SubResource("Theme_gqro3")
80 |
81 | [node name="Resume" type="Button" parent="Center/Menu/Margin/Navigation"]
82 | custom_minimum_size = Vector2(150, 0)
83 | layout_mode = 2
84 | text = "RESUME"
85 |
86 | [node name="Restart" type="Button" parent="Center/Menu/Margin/Navigation"]
87 | layout_mode = 2
88 | text = "RESTART"
89 |
90 | [node name="Options" type="Button" parent="Center/Menu/Margin/Navigation"]
91 | custom_minimum_size = Vector2(150, 0)
92 | layout_mode = 2
93 | text = "OPTIONS"
94 |
95 | [node name="Main Menu" type="Button" parent="Center/Menu/Margin/Navigation"]
96 | custom_minimum_size = Vector2(150, 0)
97 | layout_mode = 2
98 | text = "MAIN MENU"
99 |
100 | [node name="Quit" type="Button" parent="Center/Menu/Margin/Navigation"]
101 | custom_minimum_size = Vector2(150, 0)
102 | layout_mode = 2
103 | text = "QUIT"
104 |
--------------------------------------------------------------------------------
/Framework/Console/CommandLineArgs.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System.Collections.Generic;
3 |
4 | namespace __TEMPLATE__.UI.Console;
5 |
6 | ///
7 | /// Handles custom command line arguments set for each instance.
8 | ///
9 | public class CommandLineArgs
10 | {
11 | public static void Initialize()
12 | {
13 | // Get command-line arguments
14 | string[] args = OS.GetCmdlineArgs();
15 |
16 | Dictionary windowSettings = GetWindowSettingsMap();
17 |
18 | // Loop through arguments to find the position argument
19 | foreach (string arg in args)
20 | {
21 | if (!windowSettings.TryGetValue(arg, out WindowSettings settings))
22 | continue;
23 |
24 | // Set the window size
25 | DisplayServer.WindowSetSize(settings.Size);
26 |
27 | // Set the window position
28 | DisplayServer.WindowSetPosition(settings.Position);
29 | break;
30 | }
31 | }
32 |
33 | private static Dictionary GetWindowSettingsMap()
34 | {
35 | // Define the vertical space for the window bar
36 | int windowBarHeight = 30; // Adjust this value based on your actual window bar height
37 |
38 | // Get screen size
39 | Vector2 screenSize = DisplayServer.ScreenGetSize();
40 |
41 | return new Dictionary()
42 | {
43 | {
44 | "top_left",
45 | new WindowSettings(
46 | new Vector2I(0, windowBarHeight),
47 | new Vector2I((int)(screenSize.X / 2), (int)((screenSize.Y - windowBarHeight) / 2)))
48 | },
49 | {
50 | "top_right",
51 | new WindowSettings(
52 | new Vector2I((int)(screenSize.X / 2), windowBarHeight),
53 | new Vector2I((int)(screenSize.X / 2), (int)((screenSize.Y - windowBarHeight) / 2)))
54 | },
55 | {
56 | "bottom_left",
57 | new WindowSettings(
58 | new Vector2I(0, (int)(screenSize.Y / 2) + windowBarHeight),
59 | new Vector2I((int)(screenSize.X / 2), (int)((screenSize.Y - windowBarHeight) / 2)))
60 | },
61 | {
62 | "bottom_right",
63 | new WindowSettings(
64 | new Vector2I((int)(screenSize.X / 2), (int)(screenSize.Y / 2) + windowBarHeight),
65 | new Vector2I((int)(screenSize.X / 2), (int)((screenSize.Y - windowBarHeight) / 2)))
66 | },
67 | {
68 | "middle_left",
69 | new WindowSettings(
70 | new Vector2I(0, windowBarHeight),
71 | new Vector2I((int)(screenSize.X / 2), (int)(screenSize.Y - windowBarHeight)))
72 | },
73 | {
74 | "middle_right",
75 | new WindowSettings(
76 | new Vector2I((int)(screenSize.X / 2), windowBarHeight),
77 | new Vector2I((int)(screenSize.X / 2), (int)(screenSize.Y - windowBarHeight)))
78 | },
79 | {
80 | "middle_top",
81 | new WindowSettings(
82 | new Vector2I(0, windowBarHeight),
83 | new Vector2I((int)screenSize.X, (int)((screenSize.Y - windowBarHeight) / 2)))
84 | },
85 | {
86 | "middle_bottom",
87 | new WindowSettings(
88 | new Vector2I(0, (int)(screenSize.Y / 2) + windowBarHeight),
89 | new Vector2I((int)screenSize.X, (int)((screenSize.Y - windowBarHeight) / 2)))
90 | }
91 | };
92 | }
93 |
94 | private class WindowSettings(Vector2I position, Vector2I size)
95 | {
96 | public Vector2I Position { get; } = position;
97 | public Vector2I Size { get; } = size;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Framework/Autoloads/ModLoaderUI.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Runtime.Loader;
6 | using System.Text.Json;
7 |
8 | namespace __TEMPLATE__.UI;
9 |
10 | public class ModLoaderUI
11 | {
12 | private Dictionary _mods = [];
13 |
14 | public Dictionary GetMods()
15 | {
16 | return _mods;
17 | }
18 |
19 | public void LoadMods(Node node)
20 | {
21 | string modsPath = ProjectSettings.GlobalizePath("res://Mods");
22 |
23 | // Ensure "Mods" directory always exists
24 | Directory.CreateDirectory(modsPath);
25 |
26 | DirAccess dir = DirAccess.Open(modsPath);
27 |
28 | if (dir == null)
29 | {
30 | Game.Logger.LogWarning("Failed to open Mods directory has it does not exist");
31 | return;
32 | }
33 |
34 | dir.ListDirBegin();
35 |
36 | string filename = dir.GetNext();
37 |
38 | JsonSerializerOptions options = new()
39 | {
40 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase
41 | };
42 |
43 | while (filename != "")
44 | {
45 | if (!dir.CurrentIsDir())
46 | {
47 | goto Next;
48 | }
49 |
50 | string modRoot = $@"{modsPath}/{filename}";
51 | string modJson = $@"{modRoot}/mod.json";
52 |
53 | if (!File.Exists(modJson))
54 | {
55 | Game.Logger.LogWarning($"The mod folder '{filename}' does not have a mod.json so it will not be loaded");
56 | goto Next;
57 | }
58 |
59 | string jsonFileContents = File.ReadAllText(modJson);
60 |
61 | jsonFileContents = jsonFileContents.Replace("*", "Any");
62 |
63 | ModInfo modInfo = JsonSerializer.Deserialize(jsonFileContents, options);
64 |
65 | if (_mods.ContainsKey(modInfo.Id))
66 | {
67 | Game.Logger.LogWarning($"Duplicate mod id '{modInfo.Id}' was skipped");
68 | goto Next;
69 | }
70 |
71 | _mods.Add(modInfo.Id, modInfo);
72 |
73 | // Load dll
74 | string dllPath = $@"{modRoot}/Mod.dll";
75 |
76 | if (File.Exists(dllPath))
77 | {
78 | AssemblyLoadContext context = AssemblyLoadContext.GetLoadContext(typeof(Godot.Bridge.ScriptManagerBridge).Assembly);
79 | Assembly assembly = context.LoadFromAssemblyPath(dllPath);
80 | Godot.Bridge.ScriptManagerBridge.LookupScriptsInAssembly(assembly);
81 | }
82 |
83 | // Load pck
84 | string pckPath = $@"{modRoot}/mod.pck";
85 |
86 | if (File.Exists(pckPath))
87 | {
88 | bool success = ProjectSettings.LoadResourcePack(pckPath, replaceFiles: true);
89 |
90 | if (!success)
91 | {
92 | Game.Logger.LogWarning($"Failed to load pck file for mod '{modInfo.Name}'");
93 | goto Next;
94 | }
95 |
96 | string modScenePath = $"res://{modInfo.Author}/{modInfo.Id}/mod.tscn";
97 |
98 | PackedScene importedScene = (PackedScene)ResourceLoader.Load(modScenePath);
99 |
100 | if (importedScene == null)
101 | {
102 | Game.Logger.LogWarning($"Failed to load mod.tscn for mod '{modInfo.Name}'");
103 | goto Next;
104 | }
105 |
106 | Node mod = importedScene.Instantiate();
107 | node.GetTree().Root.CallDeferred(Node.MethodName.AddChild, mod);
108 | }
109 |
110 | Next:
111 | filename = dir.GetNext();
112 | }
113 |
114 | dir.ListDirEnd();
115 | dir.Dispose();
116 | }
117 | }
118 |
119 | public class ModInfo
120 | {
121 | public string Name { get; set; }
122 | public string Id { get; set; }
123 | public string ModVersion { get; set; }
124 | public string GameVersion { get; set; }
125 | public string Description { get; set; }
126 | public string Author { get; set; }
127 |
128 | public Dictionary Dependencies { get; set; }
129 | public Dictionary Incompatibilities { get; set; }
130 | }
131 |
--------------------------------------------------------------------------------
/Framework/Components/ComponentManager.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using System.Collections.Generic;
3 |
4 | namespace __TEMPLATE__;
5 |
6 | ///
7 | /// Using any kind of Godot functions from C# is expensive, so we try to minimize this with centralized logic.
8 | /// See stress test results.
9 | ///
10 | public partial class ComponentManager : Node
11 | {
12 | private List _process = [];
13 | private List _physicsProcess = [];
14 | private List _unhandledInput = [];
15 | private List _input = [];
16 |
17 | // Disable overrides on startup for performance
18 | public override void _EnterTree()
19 | {
20 | //SetProcess(false); // Assume there will always be at least one process
21 | //SetPhysicsProcess(false); // Assume there will always be at least one physics process
22 | SetProcessInput(false);
23 | SetProcessUnhandledInput(false);
24 | }
25 |
26 | // Handle Godot overrides
27 | public override void _Process(double delta)
28 | {
29 | foreach (Component component in _process)
30 | {
31 | component.Process(delta);
32 | }
33 | }
34 |
35 | public override void _PhysicsProcess(double delta)
36 | {
37 | foreach (Component component in _physicsProcess)
38 | {
39 | component.PhysicsProcess(delta);
40 | }
41 | }
42 |
43 | public override void _Input(InputEvent @event)
44 | {
45 | foreach (Component component in _input)
46 | {
47 | component.ProcessInput(@event);
48 | }
49 | }
50 |
51 | public override void _UnhandledInput(InputEvent @event)
52 | {
53 | foreach (Component component in _unhandledInput)
54 | {
55 | component.UnhandledInput(@event);
56 | }
57 | }
58 |
59 | // Exposed register functions
60 | public void RegisterProcess(Component component)
61 | {
62 | if (_process.Contains(component))
63 | return;
64 |
65 | _process.Add(component);
66 |
67 | // Assume there will always be at least one process
68 | //if (_process.Count == 1)
69 | // SetProcess(true);
70 | }
71 |
72 | public void UnregisterProcess(Component component)
73 | {
74 | _process.Remove(component);
75 |
76 | // Assume there will always be at least one process
77 | //if (_process.Count == 0)
78 | // SetProcess(false);
79 | }
80 |
81 | public void RegisterPhysicsProcess(Component component)
82 | {
83 | if (_physicsProcess.Contains(component))
84 | return;
85 |
86 | _physicsProcess.Add(component);
87 |
88 | // Assume there will always be at least one physics process
89 | //if (_physicsProcess.Count == 1)
90 | // SetPhysicsProcess(true);
91 | }
92 |
93 | public void UnregisterPhysicsProcess(Component component)
94 | {
95 | _physicsProcess.Remove(component);
96 |
97 | // Assume there will always be at least one physics process
98 | //if (_physicsProcess.Count == 0)
99 | // SetPhysicsProcess(false);
100 | }
101 |
102 | public void RegisterInput(Component component)
103 | {
104 | if (_input.Contains(component))
105 | return;
106 |
107 | _input.Add(component);
108 |
109 | if (_input.Count == 1)
110 | SetProcessInput(true);
111 | }
112 |
113 | public void UnregisterInput(Component component)
114 | {
115 | _input.Remove(component);
116 |
117 | if (_input.Count == 0)
118 | SetProcessInput(false);
119 | }
120 |
121 | public void RegisterUnhandledInput(Component component)
122 | {
123 | if (_unhandledInput.Contains(component))
124 | return;
125 |
126 | _unhandledInput.Add(component);
127 |
128 | if (_unhandledInput.Count == 1)
129 | SetProcessUnhandledInput(true);
130 | }
131 |
132 | public void UnregisterUnhandledInput(Component component)
133 | {
134 | _unhandledInput.Remove(component);
135 |
136 | if (_unhandledInput.Count == 0)
137 | SetProcessUnhandledInput(false);
138 | }
139 |
140 | public void UnregisterAll(Component component)
141 | {
142 | UnregisterProcess(component);
143 | UnregisterPhysicsProcess(component);
144 | UnregisterInput(component);
145 | UnregisterUnhandledInput(component);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/Framework/Autoloads/Autoloads.cs:
--------------------------------------------------------------------------------
1 | using __TEMPLATE__.Debugging;
2 | using __TEMPLATE__.UI;
3 | using __TEMPLATE__.UI.Console;
4 | using Godot;
5 | using GodotUtils;
6 | using System;
7 | using System.Threading.Tasks;
8 |
9 | #if DEBUG
10 | using GodotUtils.Debugging;
11 | #endif
12 |
13 | namespace __TEMPLATE__;
14 |
15 | // Autoload
16 | // Access this with GetNode("/root/Autoloads")
17 | public partial class Autoloads : Node
18 | {
19 | [Export] private MenuScenes _scenes;
20 |
21 | public event Func PreQuit;
22 |
23 | public static Autoloads Instance { get; private set; }
24 |
25 | public ComponentManager ComponentManager { get; private set; } // Cannot use [Export] here because Godot will bug out and unlink export path in editor after setup completes and restarts the editor
26 | public GameConsole GameConsole { get; private set; } // Cannot use [Export] here because Godot will bug out and unlink export path in editor after setup completes and restarts the editor
27 | public AudioManager AudioManager { get; private set; }
28 | public OptionsManager OptionsManager { get; private set; }
29 | public Services Services { get; private set; }
30 | public MetricsOverlay MetricsOverlay { get; private set; }
31 | public SceneManager SceneManager { get; private set; }
32 | public Profiler Profiler { get; private set; }
33 | public FocusOutlineManager FocusOutline { get; private set; }
34 |
35 | #if NETCODE_ENABLED
36 | public Logger Logger { get; private set; }
37 | #endif
38 |
39 | #if DEBUG
40 | private VisualizeAutoload _visualizeAutoload;
41 | #endif
42 |
43 | public override void _EnterTree()
44 | {
45 | if (Instance != null)
46 | throw new InvalidOperationException($"{nameof(Autoloads)} has been initialized already");
47 |
48 | Instance = this;
49 | ComponentManager = GetNode("ComponentManager");
50 | SceneManager = new SceneManager(this, _scenes);
51 | Services = new Services(this);
52 | MetricsOverlay = new MetricsOverlay();
53 | Profiler = new Profiler();
54 | GameConsole = GetNode("%Console");
55 | FocusOutline = new FocusOutlineManager(this);
56 |
57 | #if NETCODE_ENABLED
58 | Logger = new Logger(GameConsole);
59 | #endif
60 | }
61 |
62 | public override void _Ready()
63 | {
64 | CommandLineArgs.Initialize();
65 | Commands.RegisterAll();
66 |
67 | OptionsManager = new OptionsManager(this);
68 | AudioManager = new AudioManager(this);
69 |
70 | #if DEBUG
71 | _visualizeAutoload = new VisualizeAutoload();
72 | #endif
73 | }
74 |
75 | public override void _Process(double delta)
76 | {
77 | OptionsManager.Update();
78 | MetricsOverlay.Update();
79 |
80 | #if DEBUG
81 | _visualizeAutoload.Update();
82 | #endif
83 |
84 | #if NETCODE_ENABLED
85 | Logger.Update();
86 | #endif
87 | }
88 |
89 | public override void _PhysicsProcess(double delta)
90 | {
91 | MetricsOverlay.UpdatePhysics();
92 | }
93 |
94 | public override void _Notification(int what)
95 | {
96 | if (what == NotificationWMCloseRequest)
97 | {
98 | ExitGame().FireAndForget();
99 | }
100 | }
101 |
102 | public override void _ExitTree()
103 | {
104 | AudioManager.Dispose();
105 | OptionsManager.Dispose();
106 | SceneManager.Dispose();
107 |
108 | #if DEBUG
109 | _visualizeAutoload.Dispose();
110 | #endif
111 |
112 | #if NETCODE_ENABLED
113 | Logger.Dispose();
114 | #endif
115 |
116 | Profiler.Dispose();
117 |
118 | Instance = null;
119 | }
120 |
121 | // I'm pretty sure Deferred must be called from a script that extends from Node
122 | public void DeferredSwitchSceneProxy(string rawName, Variant transTypeVariant)
123 | {
124 | SceneManager.DeferredSwitchScene(rawName, transTypeVariant);
125 | }
126 |
127 | public async Task ExitGame()
128 | {
129 | GetTree().AutoAcceptQuit = false;
130 |
131 | // Wait for cleanup
132 | if (PreQuit != null)
133 | {
134 | // Since the PreQuit event contains a Task only the first subscriber will be invoked
135 | // with await PreQuit?.Invoke(); so need to ensure all subs are invoked.
136 | foreach (Func subscriber in PreQuit.GetInvocationList())
137 | {
138 | try
139 | {
140 | await subscriber();
141 | }
142 | catch (Exception ex)
143 | {
144 | GD.PrintErr($"PreQuit subscriber failed: {ex}");
145 | }
146 | }
147 | }
148 |
149 | GetTree().Quit();
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/Setup/SetupUtils.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using GodotUtils;
3 | using GodotUtils.RegEx;
4 | using System.IO;
5 |
6 | namespace __TEMPLATE__.Setup;
7 |
8 | public static partial class SetupUtils
9 | {
10 | public static void SetMainScene(string path, string sceneName)
11 | {
12 | string text = File.ReadAllText(Path.Combine(path, "project.godot"));
13 |
14 | text = text.Replace(
15 | "run/main_scene=\"uid://dnmu3cujgayk2\"",
16 | $"run/main_scene=\"{SetupUtils.GetUIdFromSceneFile(Path.Combine(path, $"{sceneName}.tscn"))}\"");
17 |
18 | File.WriteAllText(Path.Combine(path, "project.godot"), text);
19 | }
20 |
21 | ///
22 | /// Replaces all instances of the keyword "Template" with the new
23 | /// specified game name in several project files.
24 | ///
25 | public static void RenameProjectFiles(string path, string name)
26 | {
27 | RenameCSProjFile(path, name);
28 | RenameSolutionFile(path, name);
29 | RenameProjectGodotFile(path, name);
30 | }
31 |
32 | private static void RenameProjectGodotFile(string path, string name)
33 | {
34 | string fullPath = Path.Combine(path, "project.godot");
35 | string text = File.ReadAllText(fullPath);
36 |
37 | text = text.Replace(
38 | "project/assembly_name=\"Template\"",
39 | $"project/assembly_name=\"{name}\"");
40 |
41 | text = text.Replace(
42 | "config/name=\"Template\"",
43 | $"config/name=\"{name}\""
44 | );
45 |
46 | File.WriteAllText(fullPath, text);
47 | }
48 |
49 | private static void RenameSolutionFile(string path, string name)
50 | {
51 | string fullPath = Path.Combine(path, "Template.sln");
52 | string text = File.ReadAllText(fullPath);
53 | text = text.Replace("Template", name);
54 | File.Delete(fullPath);
55 | File.WriteAllText(Path.Combine(path, name + ".sln"), text);
56 | }
57 |
58 | private static void RenameCSProjFile(string path, string name)
59 | {
60 | string fullPath = Path.Combine(path, "Template.csproj");
61 | string text = File.ReadAllText(fullPath);
62 | text = text.Replace("Template", $"{name}");
63 | File.Delete(fullPath);
64 | File.WriteAllText(Path.Combine(path, name + ".csproj"), text);
65 | }
66 |
67 | ///
68 | /// Renames the default "__TEMPLATE__" namespace to the new specified game name in all scripts.
69 | /// Note that this assumes no one will use the namespace name "__TEMPLATE__".
70 | ///
71 | public static void RenameAllNamespaces(string path, string newNamespaceName)
72 | {
73 | DirectoryUtils.Traverse(path, RenameNamespaces);
74 |
75 | bool RenameNamespaces(string fullFilePath)
76 | {
77 | // Ignore these directories
78 | switch (Path.GetDirectoryName(fullFilePath))
79 | {
80 | case ".godot":
81 | case "GodotUtils":
82 | case "addons":
83 | return false;
84 | }
85 |
86 | // Modify all scripts
87 | if (fullFilePath.EndsWith(".cs"))
88 | {
89 | // Do not modify this script
90 | if (!fullFilePath.EndsWith("Setup.cs"))
91 | {
92 | const string oldNamespaceName = "__TEMPLATE__";
93 |
94 | string text = File.ReadAllText(fullFilePath);
95 |
96 | text = text.Replace($"namespace {oldNamespaceName}", $"namespace {newNamespaceName}");
97 | text = text.Replace($"using {oldNamespaceName}", $"using {newNamespaceName}");
98 | text = text.Replace($"{oldNamespaceName}.", $"{newNamespaceName}.");
99 |
100 | File.WriteAllText(fullFilePath, text);
101 | }
102 | }
103 |
104 | return true;
105 | }
106 | }
107 |
108 | public static string GetUIdFromSceneFile(string path)
109 | {
110 | string uid;
111 |
112 | using StreamReader reader = new(path);
113 |
114 | // Assuming the scene uid is on the first line in the file
115 | string line = reader.ReadLine();
116 |
117 | // [gd_scene load_steps=35 format=4 uid="uid://btkfgi3rc5wm1"]
118 | if (line.Contains("gd_scene"))
119 | {
120 | uid = line.Split("uid=")[1].Split('"')[1];
121 | return uid;
122 | }
123 |
124 | return null;
125 | }
126 |
127 | public static string Highlight(string text)
128 | {
129 | return $"[wave amp=20.0 freq=2.0 connected=1][color=white]{text}[/color][/wave]";
130 | }
131 |
132 | public static string FormatGameName(string name)
133 | {
134 | return name.Trim().FirstCharToUpper().Replace(" ", "");
135 | }
136 |
137 | public static bool IsAlphaNumericAndAllowSpaces(string str)
138 | {
139 | return RegexUtils.AlphaNumericAndSpaces().IsMatch(str);
140 | }
141 |
142 | public static void DisplayGameNamePreview(string inputName, RichTextLabel gameNamePreview)
143 | {
144 | string name = FormatGameName(inputName);
145 |
146 | string text = $"[color=gray]The name of the project will be {Highlight(name)}. " +
147 | $"The root namespace for all scripts will be {Highlight(name)}. " +
148 | $"Please ensure the name is in PascalFormat.[/color]";
149 |
150 | gameNamePreview.Text = text;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/Framework/Autoloads/SceneManager.cs:
--------------------------------------------------------------------------------
1 | using __TEMPLATE__.UI;
2 | using Godot;
3 | using GodotUtils;
4 | using System;
5 |
6 | namespace __TEMPLATE__;
7 |
8 | // About Scene Switching: https://docs.godotengine.org/en/latest/tutorials/scripting/singletons_autoload.html
9 | public class SceneManager
10 | {
11 | public event Action PreSceneChanged;
12 | public event Action PostSceneChanged;
13 |
14 | public const int DefaultSceneFadeDuration = 2;
15 |
16 | public Node CurrentScene => _currentScene;
17 |
18 | private MenuScenes _menuScenes;
19 | private SceneTree _tree;
20 | private Autoloads _autoloads;
21 | private Node _currentScene;
22 |
23 | public SceneManager(Autoloads autoloads, MenuScenes scenes)
24 | {
25 | SetupFields(autoloads, scenes);
26 |
27 | // Gradually fade out all SFX whenever the scene is changed
28 | PreSceneChanged += OnPreSceneChanged;
29 | }
30 |
31 | public void Dispose()
32 | {
33 | PreSceneChanged -= OnPreSceneChanged;
34 | }
35 |
36 | public void SwitchToOptions(TransType transType = TransType.None) => SwitchTo(_menuScenes.Options, transType);
37 | public void SwitchToMainMenu(TransType transType = TransType.None) => SwitchTo(_menuScenes.MainMenu, transType);
38 | public void SwitchToModLoader(TransType transType = TransType.None) => SwitchTo(_menuScenes.ModLoader, transType);
39 | public void SwitchToCredits(TransType transType = TransType.None) => SwitchTo(_menuScenes.Credits, transType);
40 |
41 | public void SwitchTo(PackedScene scene, TransType transType = TransType.None)
42 | {
43 | ArgumentNullException.ThrowIfNull(scene);
44 | string path = scene.ResourcePath;
45 | PreSceneChanged?.Invoke(path);
46 |
47 | switch (transType)
48 | {
49 | case TransType.None:
50 | ChangeScene(path, transType);
51 | break;
52 | case TransType.Fade:
53 | FadeTo(TransColor.Black, DefaultSceneFadeDuration, () => ChangeScene(path, transType));
54 | break;
55 | }
56 |
57 | PostSceneChanged?.Invoke(path);
58 | }
59 |
60 | ///
61 | /// Resets the currently active scene.
62 | ///
63 | public void ResetCurrentScene()
64 | {
65 | string sceneFilePath = _tree.CurrentScene.SceneFilePath;
66 |
67 | string[] words = sceneFilePath.Split("/");
68 | string sceneName = words[words.Length - 1].Replace(".tscn", "");
69 |
70 | PreSceneChanged?.Invoke(sceneName);
71 |
72 | // Wait for engine to be ready before switching scenes
73 | _autoloads.CallDeferred(nameof(Autoloads.DeferredSwitchSceneProxy), sceneFilePath, Variant.From(TransType.None));
74 |
75 | PostSceneChanged?.Invoke(sceneName);
76 | }
77 |
78 | public void DeferredSwitchScene(string rawName, Variant transTypeVariant)
79 | {
80 | // Safe to remove scene now
81 | _currentScene.Free();
82 |
83 | // Load a new scene.
84 | PackedScene nextScene = (PackedScene)GD.Load(rawName);
85 |
86 | // Internal the new scene.
87 | _currentScene = nextScene.Instantiate();
88 |
89 | // Add it to the active scene, as child of root.
90 | _tree.Root.AddChild(_currentScene);
91 |
92 | // Optionally, to make it compatible with the SceneTree.change_scene_to_file() API.
93 | _tree.CurrentScene = _currentScene;
94 |
95 | TransType transType = transTypeVariant.As();
96 |
97 | switch (transType)
98 | {
99 | case TransType.None:
100 | break;
101 | case TransType.Fade:
102 | FadeTo(TransColor.Transparent, 1);
103 | break;
104 | }
105 | }
106 |
107 | private void SetupFields(Autoloads autoloads, MenuScenes scenes)
108 | {
109 | _autoloads = autoloads;
110 | _menuScenes = scenes;
111 | _tree = autoloads.GetTree();
112 |
113 | Window root = _tree.Root;
114 |
115 | _currentScene = root.GetChild(root.GetChildCount() - 1);
116 | }
117 |
118 | private void OnPreSceneChanged(string scene) => Game.Audio.FadeOutSFX();
119 |
120 | private void ChangeScene(string scenePath, TransType transType)
121 | {
122 | // Wait for engine to be ready before switching scenes
123 | _autoloads.CallDeferred(nameof(Autoloads.DeferredSwitchSceneProxy), scenePath, Variant.From(transType));
124 | }
125 |
126 | private void FadeTo(TransColor transColor, double duration, Action finished = null)
127 | {
128 | // Add canvas layer to scene
129 | CanvasLayer canvasLayer = new()
130 | {
131 | Layer = 10 // render on top of everything else
132 | };
133 |
134 | _currentScene.AddChild(canvasLayer);
135 |
136 | // Setup color rect
137 | ColorRect colorRect = new()
138 | {
139 | Color = new Color(0, 0, 0, transColor == TransColor.Black ? 0 : 1),
140 | MouseFilter = Control.MouseFilterEnum.Ignore
141 | };
142 |
143 | // Make the color rect cover the entire screen
144 | colorRect.SetLayout(Control.LayoutPreset.FullRect);
145 | canvasLayer.AddChild(colorRect);
146 |
147 | // Animate color rect
148 | new GodotTween(colorRect)
149 | .Animate(ColorRect.PropertyName.Color, new Color(0, 0, 0, transColor == TransColor.Black ? 1 : 0), duration)
150 | .Callback(() =>
151 | {
152 | canvasLayer.QueueFree();
153 | finished?.Invoke();
154 | });
155 | }
156 |
157 | public enum TransType
158 | {
159 | None,
160 | Fade
161 | }
162 |
163 | public enum TransColor
164 | {
165 | Black,
166 | Transparent
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/project.godot:
--------------------------------------------------------------------------------
1 | ; Engine configuration file.
2 | ; It's best edited using the editor UI and not directly,
3 | ; since the parameters that go here are not all obvious.
4 | ;
5 | ; Format:
6 | ; [section] ; section goes between []
7 | ; param=value ; assign values to parameters
8 |
9 | config_version=5
10 |
11 | [application]
12 |
13 | config/name="Template"
14 | run/main_scene="uid://dnmu3cujgayk2"
15 | config/features=PackedStringArray("4.5", "C#", "Forward Plus")
16 | boot_splash/show_image=false
17 | config/icon="res://Framework/Icon.svg"
18 |
19 | [autoload]
20 |
21 | Autoloads="*res://Framework/Autoloads/Autoloads.tscn"
22 | ImGuiRoot="*res://addons/imgui-godot/data/ImGuiRoot.tscn"
23 |
24 | [display]
25 |
26 | window/size/viewport_width=1920
27 | window/size/viewport_height=1080
28 |
29 | [dotnet]
30 |
31 | project/assembly_name="Template"
32 |
33 | [editor]
34 |
35 | naming/scene_name_casing=1
36 | naming/script_name_casing=1
37 |
38 | [editor_plugins]
39 |
40 | enabled=PackedStringArray("res://addons/imgui-godot/plugin.cfg")
41 |
42 | [file_customization]
43 |
44 | folder_colors={
45 | "res://Scenes/": "blue"
46 | }
47 |
48 | [filesystem]
49 |
50 | import/blender/enabled=false
51 |
52 | [gui]
53 |
54 | theme/custom="uid://8fdrucqkrf5m"
55 |
56 | [input]
57 |
58 | move_left={
59 | "deadzone": 0.5,
60 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
61 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
62 | ]
63 | }
64 | move_right={
65 | "deadzone": 0.5,
66 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
67 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
68 | ]
69 | }
70 | move_up={
71 | "deadzone": 0.5,
72 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
73 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
74 | ]
75 | }
76 | move_down={
77 | "deadzone": 0.5,
78 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
79 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
80 | ]
81 | }
82 | fullscreen={
83 | "deadzone": 0.5,
84 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194342,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
85 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
86 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
87 | ]
88 | }
89 | remove_hotkey={
90 | "deadzone": 0.5,
91 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194312,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
92 | ]
93 | }
94 | debug_overlay={
95 | "deadzone": 0.5,
96 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
97 | ]
98 | }
99 | toggle_console={
100 | "deadzone": 0.5,
101 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194343,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
102 | ]
103 | }
104 |
105 | [internationalization]
106 |
107 | locale/translations=PackedStringArray("res://Framework/Localisation/text.en.translation", "res://Framework/Localisation/text.fr.translation", "res://Framework/Localisation/text.ja.translation")
108 |
109 | [rendering]
110 |
111 | textures/canvas_textures/default_texture_filter=0
112 | environment/defaults/default_clear_color=Color(0.29219, 0.29219, 0.29219, 1)
113 |
--------------------------------------------------------------------------------
/Framework/Autoloads/AudioManager.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using GodotUtils;
3 | using System;
4 |
5 | namespace __TEMPLATE__;
6 |
7 | public class AudioManager : IDisposable
8 | {
9 | private const float MinDefaultRandomPitch = 0.8f; // Default minimum pitch value for SFX.
10 | private const float MaxDefaultRandomPitch = 1.2f; // Default maximum pitch value for SFX.
11 | private const float RandomPitchThreshold = 0.1f; // Minimum difference in pitch between repeated sounds.
12 | private const int MutedVolume = -80; // dB value representing mute.
13 | private const int MutedVolumeNormalized = -40; // Normalized muted volume for volume mapping.
14 |
15 | private AudioStreamPlayer _musicPlayer;
16 | private ResourceOptions _options;
17 | private Autoloads _autoloads;
18 | private float _lastPitch;
19 |
20 | private GodotNodePool _sfxPool;
21 |
22 | ///
23 | /// Initializes the AudioManager by attaching a music player to the given autoload node.
24 | ///
25 | public AudioManager(Autoloads autoloads)
26 | {
27 | SetupFields(autoloads);
28 | SetupSfxPool();
29 | SetupMusicPlayer();
30 | }
31 |
32 | ///
33 | /// Frees all managed players and clears references for cleanup.
34 | ///
35 | public void Dispose()
36 | {
37 | _musicPlayer.QueueFree();
38 | _sfxPool.Clear();
39 | }
40 |
41 | ///
42 | /// Plays a music track, instantly or with optional fade between tracks. Music volume is in config scale (0-100).
43 | ///
44 | public void PlayMusic(AudioStream song, bool instant = true, double fadeOut = 1.5, double fadeIn = 0.5)
45 | {
46 | if (!instant && _musicPlayer.Playing)
47 | {
48 | // Slowly transition to the new song
49 | PlayAudioCrossfade(_musicPlayer, song, _options.MusicVolume, fadeOut, fadeIn);
50 | }
51 | else
52 | {
53 | // Instantly switch to the new song
54 | PlayAudio(_musicPlayer, song, _options.MusicVolume);
55 | }
56 | }
57 |
58 | ///
59 | /// Plays a sound effect at the specified global position with randomized pitch to reduce repetition. Volume is normalized (0-100).
60 | ///
61 | public void PlaySFX(AudioStream sound, Vector2 position, float minPitch = MinDefaultRandomPitch, float maxPitch = MaxDefaultRandomPitch)
62 | {
63 | AudioStreamPlayer2D sfxPlayer = _sfxPool.Get();
64 |
65 | sfxPlayer.GlobalPosition = position;
66 | sfxPlayer.Stream = sound;
67 | sfxPlayer.VolumeDb = NormalizeConfigVolume(_options.SFXVolume);
68 | sfxPlayer.PitchScale = GetRandomPitch(minPitch, maxPitch);
69 | sfxPlayer.Finished += OnFinished;
70 | sfxPlayer.Play();
71 |
72 | void OnFinished()
73 | {
74 | sfxPlayer.Finished -= OnFinished;
75 | _sfxPool.Release(sfxPlayer);
76 | }
77 | }
78 |
79 | ///
80 | /// Fades out all currently playing sound effects over the specified duration in seconds.
81 | ///
82 | public void FadeOutSFX(double fadeTime = 1)
83 | {
84 | foreach (AudioStreamPlayer2D sfxPlayer in _sfxPool.ActiveNodes)
85 | {
86 | new GodotTween(sfxPlayer).Animate(AudioStreamPlayer.PropertyName.VolumeDb, MutedVolume, fadeTime);
87 | }
88 | }
89 |
90 | ///
91 | /// Sets the music volume, affecting current playback. Volume is in config scale (0-100).
92 | ///
93 | public void SetMusicVolume(float volume)
94 | {
95 | _musicPlayer.VolumeDb = NormalizeConfigVolume(volume);
96 | _options.MusicVolume = volume;
97 | }
98 |
99 | ///
100 | /// Sets the SFX volume for all active sound effect players. Volume is in config scale (0-100).
101 | ///
102 | public void SetSFXVolume(float volume)
103 | {
104 | _options.SFXVolume = volume;
105 |
106 | float mappedVolume = NormalizeConfigVolume(volume);
107 |
108 | foreach (AudioStreamPlayer2D sfxPlayer in _sfxPool.ActiveNodes)
109 | {
110 | sfxPlayer.VolumeDb = mappedVolume;
111 | }
112 | }
113 |
114 | private void SetupFields(Autoloads autoloads)
115 | {
116 | _autoloads = autoloads;
117 | _options = Game.Options.GetOptions();
118 | }
119 |
120 | private void SetupSfxPool()
121 | {
122 | _sfxPool = new GodotNodePool(_autoloads,
123 | () => new AudioStreamPlayer2D());
124 | }
125 |
126 | private void SetupMusicPlayer()
127 | {
128 | _musicPlayer = new AudioStreamPlayer();
129 | _autoloads.AddChild(_musicPlayer);
130 | }
131 |
132 | ///
133 | /// Generates a random pitch between min and max, avoiding values too similar to the previous sound.
134 | ///
135 | private float GetRandomPitch(float min, float max)
136 | {
137 | RandomNumberGenerator rng = new();
138 | rng.Randomize();
139 |
140 | float pitch = rng.RandfRange(min, max);
141 |
142 | while (Mathf.Abs(pitch - _lastPitch) < RandomPitchThreshold)
143 | {
144 | rng.Randomize();
145 | pitch = rng.RandfRange(min, max);
146 | }
147 |
148 | _lastPitch = pitch;
149 | return pitch;
150 | }
151 |
152 | ///
153 | /// Instantly plays the given audio stream with the specified player and volume.
154 | ///
155 | private static void PlayAudio(AudioStreamPlayer player, AudioStream song, float volume)
156 | {
157 | player.Stream = song;
158 | player.VolumeDb = NormalizeConfigVolume(volume);
159 | player.Play();
160 | }
161 |
162 | ///
163 | /// Smoothly crossfades between songs by fading out the current and fading in the new one. Volume is in config scale (0-100).
164 | ///
165 | private static void PlayAudioCrossfade(AudioStreamPlayer player, AudioStream song, float volume, double fadeOut, double fadeIn)
166 | {
167 | new GodotTween(player)
168 | .SetAnimatingProp(AudioStreamPlayer.PropertyName.VolumeDb)
169 | .AnimateProp(MutedVolume, fadeOut).EaseIn()
170 | .Callback(() => PlayAudio(player, song, volume))
171 | .AnimateProp(NormalizeConfigVolume(volume), fadeIn).EaseIn();
172 | }
173 |
174 | ///
175 | /// Maps a config volume value (0-100) to an AudioStreamPlayer VolumeDb value, returning mute if zero.
176 | ///
177 | private static float NormalizeConfigVolume(float volume)
178 | {
179 | return volume == 0 ? MutedVolume : volume.Remap(0, 100, MutedVolumeNormalized, 0);
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/Framework/Scenes/Options/Scripts/OptionsDisplay.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using GodotUtils;
3 | using System;
4 |
5 | using static Godot.DisplayServer;
6 | using WindowMode = GodotUtils.WindowMode;
7 |
8 | namespace __TEMPLATE__.UI;
9 |
10 | public class OptionsDisplay
11 | {
12 | public event Action OnResolutionChanged;
13 |
14 | private ResourceOptions _options;
15 |
16 | // Max FPS
17 | private HSlider _sliderMaxFps;
18 | private Label _labelMaxFpsFeedback;
19 |
20 | // Window Size
21 | private LineEdit _resX, _resY;
22 | private int _prevNumX, _prevNumY;
23 | private int _minResolution = 36;
24 | private readonly Options options;
25 |
26 | public OptionsDisplay(Options options, Button displayBtn)
27 | {
28 | this.options = options;
29 |
30 | GetOptions();
31 | SetupMaxFps(displayBtn);
32 | SetupWindowSize(displayBtn);
33 | SetupWindowMode(displayBtn);
34 | SetupResolution(displayBtn);
35 | SetupVSyncMode(displayBtn);
36 | }
37 |
38 | private void GetOptions()
39 | {
40 | _options = Game.Options.GetOptions();
41 | }
42 |
43 | private void SetupMaxFps(Button displayBtn)
44 | {
45 | HSlider maxFps = options.GetNode("%MaxFPS");
46 | maxFps.ValueChanged += OnMaxFpsValueChanged;
47 | maxFps.DragEnded += OnMaxFpsDragEnded;
48 | maxFps.FocusNeighborLeft = displayBtn.GetPath();
49 |
50 | _labelMaxFpsFeedback = options.GetNode