make a settings menu and general refactoring

i added more to the main menu, as it has a settings menu now, that stores its files with json. i also changed the font to atkinson hyperlegible and made a BaseMenu class that takes care of the screen scaling for menus.

i also refactored the project folder structure to make it more organized.
This commit is contained in:
Fries 2023-05-20 21:42:38 -07:00
parent f84479e03a
commit 425bf2ad27
29 changed files with 440 additions and 98 deletions

Binary file not shown.

View file

@ -0,0 +1,38 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://dq8ojwul8884x"
path="res://.godot/imported/AtkinsonHyperlegible-Regular.ttf-8428368664fa87474099ab40902da12f.fontdata"
[deps]
source_file="res://Fonts/AtkinsonHyperlegible-Regular.ttf"
dest_files=["res://.godot/imported/AtkinsonHyperlegible-Regular.ttf-8428368664fa87474099ab40902da12f.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=true
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[{
"chars": [],
"glyphs": [],
"name": "New Configuration",
"size": Vector2i(16, 0)
}]
language_support={}
script_support={}
opentype_features={}

11
Resources/MenuTheme.tres Normal file
View file

@ -0,0 +1,11 @@
[gd_resource type="Theme" load_steps=2 format=3 uid="uid://bk0vu443pyq1g"]
[ext_resource type="FontFile" uid="uid://dq8ojwul8884x" path="res://Fonts/AtkinsonHyperlegible-Regular.ttf" id="1_aadm8"]
[resource]
Button/font_sizes/font_size = 32
Button/fonts/font = ExtResource("1_aadm8")
Label/font_sizes/font_size = 32
Label/fonts/font = ExtResource("1_aadm8")
LineEdit/font_sizes/font_size = 32
LineEdit/fonts/font = ExtResource("1_aadm8")

View file

@ -0,0 +1,7 @@
[gd_resource type="LabelSettings" load_steps=2 format=3 uid="uid://dkyqusswobqb3"]
[ext_resource type="FontFile" uid="uid://dq8ojwul8884x" path="res://Fonts/AtkinsonHyperlegible-Regular.ttf" id="1_hcuvo"]
[resource]
font = ExtResource("1_hcuvo")
font_size = 64

View file

@ -1,29 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://0vkrkvlevwkj"]
[ext_resource type="Script" path="res://Scripts/Managers/MainMenuManager.cs" id="1_tuygx"]
[sub_resource type="LabelSettings" id="LabelSettings_vv6u3"]
font_size = 64
[node name="MainMenu" type="VBoxContainer"]
offset_right = 800.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_tuygx")
[node name="Label" type="Label" parent="."]
layout_mode = 2
text = "Pong
"
label_settings = SubResource("LabelSettings_vv6u3")
horizontal_alignment = 1
vertical_alignment = 1
[node name="Button" type="Button" parent="."]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Play
"
[connection signal="pressed" from="Button" to="." method="OnButtonPressed"]

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=4 format=3 uid="uid://67qguep14iko"]
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://Scripts/Nodes/Ball.cs" id="1_474si"]
[ext_resource type="Script" path="res://Scripts/Objects/Ball.cs" id="1_474si"]
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_yq7ub"]
size = Vector2(50, 50)

View file

@ -1,7 +1,7 @@
[gd_scene load_steps=5 format=3]
[ext_resource type="PhysicsMaterial" uid="uid://e05n66x8ug77" path="res://BouncyMaterial.tres" id="1_e3kk5"]
[ext_resource type="Script" path="res://Scripts/Nodes/Enemy.cs" id="1_fbrtv"]
[ext_resource type="PhysicsMaterial" uid="uid://e05n66x8ug77" path="res://Resources/BouncyMaterial.tres" id="1_e3kk5"]
[ext_resource type="Script" path="res://Scripts/Objects/Enemy.cs" id="1_fbrtv"]
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_iw3nx"]
size = Vector2(50, 75)

View file

@ -1,7 +1,7 @@
[gd_scene load_steps=5 format=3]
[ext_resource type="PhysicsMaterial" uid="uid://e05n66x8ug77" path="res://BouncyMaterial.tres" id="1_76uik"]
[ext_resource type="Script" path="res://Scripts/Nodes/Paddle.cs" id="1_uv7s3"]
[ext_resource type="PhysicsMaterial" uid="uid://e05n66x8ug77" path="res://Resources/BouncyMaterial.tres" id="1_76uik"]
[ext_resource type="Script" path="res://Scripts/Objects/Paddle.cs" id="1_uv7s3"]
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_iw3nx"]
size = Vector2(50, 75)

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=8 format=3 uid="uid://dsvv7vqonylr1"]
[gd_scene load_steps=8 format=3]
[ext_resource type="Script" path="res://Scripts/Managers/WallManager.cs" id="1_mxer2"]
[ext_resource type="Script" path="res://Scripts/Managers/Walls.cs" id="1_mxer2"]
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_f7jy5"]
size = Vector2(30, 1)

View file

@ -1,14 +1,14 @@
[gd_scene load_steps=7 format=3 uid="uid://wn48xnd0lstq"]
[gd_scene load_steps=7 format=3 uid="uid://bh3p1hnvsviu6"]
[ext_resource type="PackedScene" path="res://Scenes/Paddle.tscn" id="1_5rs0o"]
[ext_resource type="Script" path="res://Scripts/Managers/SceneManager.cs" id="1_m8437"]
[ext_resource type="PackedScene" uid="uid://co2rv0nci8sbm" path="res://Scenes/Score.tscn" id="2_f3jwj"]
[ext_resource type="PackedScene" uid="uid://67qguep14iko" path="res://Scenes/Ball.tscn" id="2_u2ksv"]
[ext_resource type="PackedScene" uid="uid://dsvv7vqonylr1" path="res://Scenes/Walls.tscn" id="3_jfis7"]
[ext_resource type="PackedScene" path="res://Scenes/Enemy.tscn" id="4_uwvof"]
[ext_resource type="PackedScene" path="res://Scenes/Objects/Paddle.tscn" id="1_5rs0o"]
[ext_resource type="Script" path="res://Scripts/Managers/PongSceneManager.cs" id="1_ee533"]
[ext_resource type="PackedScene" uid="uid://clsmrwvyrt7av" path="res://Scenes/UI/Score.tscn" id="2_f3jwj"]
[ext_resource type="PackedScene" path="res://Scenes/Objects/Ball.tscn" id="2_u2ksv"]
[ext_resource type="PackedScene" path="res://Scenes/Objects/Walls.tscn" id="3_jfis7"]
[ext_resource type="PackedScene" path="res://Scenes/Objects/Enemy.tscn" id="4_uwvof"]
[node name="Pong" type="Node2D"]
script = ExtResource("1_m8437")
script = ExtResource("1_ee533")
[node name="Score" parent="." instance=ExtResource("2_f3jwj")]

View file

@ -0,0 +1,32 @@
[gd_scene load_steps=4 format=3 uid="uid://buvptlse06qej"]
[ext_resource type="Script" path="res://Scripts/Managers/UI/Menus/MainMenu.cs" id="1_essvw"]
[ext_resource type="Theme" uid="uid://bk0vu443pyq1g" path="res://Resources/MenuTheme.tres" id="1_qt6h4"]
[ext_resource type="LabelSettings" uid="uid://dkyqusswobqb3" path="res://Resources/ScoreLabelSettings.tres" id="2_mtqkf"]
[node name="MainMenu" type="VBoxContainer"]
offset_right = 800.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme = ExtResource("1_qt6h4")
script = ExtResource("1_essvw")
[node name="Label" type="Label" parent="."]
layout_mode = 2
text = "Pong"
label_settings = ExtResource("2_mtqkf")
horizontal_alignment = 1
vertical_alignment = 1
[node name="PlayButton" type="Button" parent="."]
layout_mode = 2
text = "Play
"
[node name="SettingsButton" type="Button" parent="."]
layout_mode = 2
text = "Settings"
[connection signal="pressed" from="PlayButton" to="." method="OnPlayButtonPressed"]
[connection signal="pressed" from="SettingsButton" to="." method="OnSettingsButtonPressed"]

View file

@ -0,0 +1,63 @@
[gd_scene load_steps=5 format=3 uid="uid://ic7535p11pds"]
[ext_resource type="Theme" uid="uid://bk0vu443pyq1g" path="res://Resources/MenuTheme.tres" id="1_p8g8v"]
[ext_resource type="Script" path="res://Scripts/Managers/UI/Menus/Settings.cs" id="2_vlocu"]
[ext_resource type="LabelSettings" uid="uid://dkyqusswobqb3" path="res://Resources/ScoreLabelSettings.tres" id="2_wkoeb"]
[ext_resource type="FontFile" uid="uid://dq8ojwul8884x" path="res://Fonts/AtkinsonHyperlegible-Regular.ttf" id="3_ngmnl"]
[node name="Settings" type="VBoxContainer"]
offset_right = 800.0
offset_bottom = 600.0
theme = ExtResource("1_p8g8v")
script = ExtResource("2_vlocu")
[node name="Label" type="Label" parent="."]
layout_mode = 2
text = "Settings
"
label_settings = ExtResource("2_wkoeb")
horizontal_alignment = 1
[node name="ScrollContainer" type="ScrollContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
[node name="InternalMainContainer" type="VBoxContainer" parent="ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ScaleContainer" type="VBoxContainer" parent="ScrollContainer/InternalMainContainer"]
layout_mode = 2
size_flags_horizontal = 4
[node name="Label" type="Label" parent="ScrollContainer/InternalMainContainer/ScaleContainer"]
layout_mode = 2
size_flags_horizontal = 4
text = "Scale
"
[node name="Scale" type="SpinBox" parent="ScrollContainer/InternalMainContainer/ScaleContainer"]
layout_mode = 2
min_value = 1.0
max_value = 2.0
step = 0.01
value = 1.0
alignment = 1
[node name="SaveButton" type="Button" parent="."]
layout_mode = 2
disabled = true
text = "Save
"
[node name="BackButton" type="Button" parent="."]
layout_mode = 2
size_flags_vertical = 8
theme_override_fonts/font = ExtResource("3_ngmnl")
theme_override_font_sizes/font_size = 32
text = "Back
"
[connection signal="pressed" from="SaveButton" to="." method="OnSaveButtonPressed"]
[connection signal="pressed" from="BackButton" to="." method="OnBackButtonPressed"]

View file

@ -1,9 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://co2rv0nci8sbm"]
[gd_scene load_steps=3 format=3 uid="uid://clsmrwvyrt7av"]
[ext_resource type="Script" path="res://Scripts/Managers/ScoreManager.gd" id="1_bdtqr"]
[sub_resource type="LabelSettings" id="LabelSettings_piogj"]
font_size = 64
[ext_resource type="Script" path="res://Scripts/Managers/UI/Score.gd" id="1_ytifn"]
[ext_resource type="LabelSettings" uid="uid://dkyqusswobqb3" path="res://Resources/ScoreLabelSettings.tres" id="2_mh7dc"]
[node name="Score" type="HFlowContainer"]
z_index = 1
@ -11,20 +9,20 @@ offset_left = -378.0
offset_top = -272.0
offset_right = 378.0
offset_bottom = 268.0
script = ExtResource("1_bdtqr")
script = ExtResource("1_ytifn")
[node name="Player1" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
text = "0
"
label_settings = SubResource("LabelSettings_piogj")
label_settings = ExtResource("2_mh7dc")
vertical_alignment = 1
[node name="Player2" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
text = "0"
label_settings = SubResource("LabelSettings_piogj")
label_settings = ExtResource("2_mh7dc")
horizontal_alignment = 2
vertical_alignment = 1

20
Scripts/Data/Settings.cs Normal file
View file

@ -0,0 +1,20 @@
using System;
using System.Text.Json.Serialization;
namespace Pong.Scripts.Data;
/// <summary>
/// a structure containing the data of the settings.
/// </summary>
public struct Settings
{
/// <summary>
/// the scale of the game. this can be from 1.0 to 2.0
/// </summary>
public double Scale;
/// <summary>
/// the default values of settings.
/// </summary>
public static Settings Default => new() { Scale = 1.0 };
}

View file

@ -1,28 +0,0 @@
using Godot;
namespace Pong.Scripts.Managers;
public partial class MainMenuManager : VBoxContainer
{
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
GetTree().Root.SizeChanged += AdaptUiToScreenResolution;
AdaptUiToScreenResolution();
}
/// <summary>
/// code that executes when the start button is pressed. this loads the Pong scene to the root
/// of the scene tree.
/// </summary>
private void OnButtonPressed()
{
GetTree().Root.SizeChanged -= AdaptUiToScreenResolution;
GetTree().ChangeSceneToFile("res://Scenes/Pong.tscn");
}
private void AdaptUiToScreenResolution()
{
Size = DisplayServer.WindowGetSize();
}
}

View file

@ -2,7 +2,7 @@ using Godot;
namespace Pong.Scripts.Managers;
public partial class SceneManager : Node2D
public partial class PongSceneManager : Node2D
{
public override void _Process(double delta)
{

View file

@ -0,0 +1,78 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Godot;
using Pong.Scripts.Data;
namespace Pong.Scripts.Managers;
public partial class SettingsManager : Node
{
private const string SettingsPath = "user://settings.json";
public Settings SettingsData = Settings.Default;
private static readonly JsonSerializerOptions SerializerOptions = new() { IncludeFields = true };
public event Action SettingsChanged;
public override async void _EnterTree()
{
await LoadSettings();
}
/// <summary>
/// load the settings data from the filesystem.
/// </summary>
private async Task LoadSettings()
{
try
{
if (!FileAccess.FileExists(SettingsPath))
{
await SaveSettings(Settings.Default);
return;
}
using var file = await OpenSettingsFile(FileAccess.ModeFlags.Read);
SettingsData =
await Task.Run(() => JsonSerializer.Deserialize<Settings>(file.GetAsText(), SerializerOptions));
SettingsChanged?.Invoke();
}
catch (Exception e)
{
PrintFileError(e);
}
}
/// <summary>
/// open the settings file from the filesystem.
/// </summary>
/// <returns> a godot FileAccess object.</returns>
private static async Task<FileAccess> OpenSettingsFile(FileAccess.ModeFlags flag)
{
return await Task.Run(() => FileAccess.Open(SettingsPath, flag));
}
internal async Task<bool> SaveSettings(Settings settings)
{
try
{
using var file = await OpenSettingsFile(FileAccess.ModeFlags.WriteRead);
var json = await Task.Run(() => JsonSerializer.Serialize(settings, SerializerOptions));
await Task.Run(() => file.StoreBuffer(json.ToUtf8Buffer()));
SettingsData = settings;
SettingsChanged?.Invoke();
return true;
}
catch (Exception e)
{
PrintFileError(e);
return false;
}
}
private static void PrintFileError(Exception e)
{
GD.PrintErr($"a {e.GetType()} has occured while trying to load the settings file: {e}.");
GD.PrintErr("default settings will be used.");
}
}

View file

@ -0,0 +1,43 @@
using Godot;
namespace Pong.Scripts.Managers.UI;
public partial class BaseMenu : VBoxContainer
{
private SettingsManager _settingsManager;
public override void _EnterTree()
{
_settingsManager = GetNode<SettingsManager>("/root/SettingsManager");
_settingsManager.SettingsChanged += AdaptUiToGameResolution;
GetTree().Root.SizeChanged += AdaptUiToGameResolution;
AdaptUiToGameResolution();
EnterTree();
}
public override void _ExitTree()
{
GetTree().Root.SizeChanged -= AdaptUiToGameResolution;
_settingsManager.SettingsChanged -= AdaptUiToGameResolution;
ExitTree();
}
protected virtual void EnterTree()
{
}
protected virtual void ExitTree()
{
}
/// <summary>
/// change the size of the UI container to the current resolution of the game.
/// </summary>
protected void AdaptUiToGameResolution()
{
var scale = _settingsManager.SettingsData.Scale;
Size = (Vector2)DisplayServer.WindowGetSize() / scale;
Scale = new Vector2(scale, scale);
}
}

View file

@ -0,0 +1,20 @@
using Godot;
namespace Pong.Scripts.Managers.UI.Menus;
public partial class MainMenu : BaseMenu
{
/// <summary>
/// code that executes when the start button is pressed. this loads the Pong scene to the root
/// of the scene tree.
/// </summary>
private void OnPlayButtonPressed()
{
GetTree().ChangeSceneToFile("res://Scenes/Pong.tscn");
}
private void OnSettingsButtonPressed()
{
GetTree().ChangeSceneToFile("res://Scenes/UI/Menus/Settings.tscn");
}
}

View file

@ -0,0 +1,52 @@
using Godot;
namespace Pong.Scripts.Managers.UI.Menus;
public partial class Settings : BaseMenu
{
private SettingsManager _settings;
private SpinBox _scale;
private Button _saveButton;
private bool _scaleModified;
protected override void EnterTree()
{
GetNodes();
_scale.Value = _settings.SettingsData.Scale;
}
private void OnBackButtonPressed()
{
GetTree().ChangeSceneToFile("res://Scenes/UI/Menus/MainMenu.tscn");
}
private void GetNodes()
{
_settings = GetNode<SettingsManager>("/root/SettingsManager");
_scale = GetNode<SpinBox>("ScrollContainer/InternalMainContainer/ScaleContainer/Scale");
_saveButton = GetNode<Button>("SaveButton");
_scale.ValueChanged += ScaleChanged;
}
private void ScaleChanged(double value)
{
_scaleModified = !value.Equals(_settings.SettingsData.Scale);
CheckIfModified();
}
private bool CheckIfModified()
{
var isModified = _scaleModified;
_saveButton.Disabled = !isModified;
return isModified;
}
private async void OnSaveButtonPressed()
{
if (!await _settings.SaveSettings(_settings.SettingsData with { Scale = _scale.Value })) return;
_saveButton.Disabled = true;
}
}

View file

@ -1,9 +1,10 @@
using Godot;
using Pong.Scripts.Data;
using Pong.Scripts.Objects;
namespace Pong.Scripts.Managers;
public partial class WallManager : Area2D
public partial class Walls : Area2D
{
[Export] internal PlayerNumber PlayerNumber;
@ -21,7 +22,7 @@ public partial class WallManager : Area2D
/// <param name="body">the body of the collider.</param>
private void OnBodyEntered(Node2D body)
{
if (body is not Nodes.Ball ball) return;
if (body is not Ball ball) return;
_scoreManager.Call(_score, (long)PlayerNumber);
ResetBall(ball);
@ -30,8 +31,8 @@ public partial class WallManager : Area2D
/// <summary>
/// reset the ball to the starting position and flick it again.
/// </summary>
/// <param name="ball">a <see cref="Nodes.Ball"/> object that you want to reset.</param>
private async void ResetBall(Nodes.Ball ball)
/// <param name="ball">a <see cref="Ball"/> object that you want to reset.</param>
private async void ResetBall(Ball ball)
{
ball.Velocity = Vector2.Zero;
ball.Position = Vector2.Zero;

View file

@ -1,6 +1,6 @@
using Godot;
namespace Pong.Scripts.Nodes;
namespace Pong.Scripts.Objects;
public partial class Ball : CharacterBody2D
{
@ -26,7 +26,8 @@ public partial class Ball : CharacterBody2D
using var rng = new RandomNumberGenerator();
rng.Randomize();
// set the velocity currently to the left of the screen with a random y angle.
var randomAngle = new Vector2(0, rng.RandfRange(-_maxRandomAngle, _maxRandomAngle) * BallSpeed);
var randomAngle = new Vector2 { Y = rng.RandfRange(-_maxRandomAngle, _maxRandomAngle) * BallSpeed };
Velocity = GetRandomStartingDirection() * BallSpeed + randomAngle;
}

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using Godot;
using Godot.Collections;
namespace Pong.Scripts.Nodes;
namespace Pong.Scripts.Objects;
public partial class Enemy : RigidBody2D
{
@ -33,7 +33,7 @@ public partial class Enemy : RigidBody2D
if (!_drawDebugShapes) return;
DrawRect(_scanArea, Colors.Aqua);
}
private void Scan(double delta)
{
using var spaceState = GetWorld2D().DirectSpaceState;
@ -51,7 +51,7 @@ public partial class Enemy : RigidBody2D
LinearVelocity = Vector2.Zero;
return;
}
TrackBall(delta, result);
}
@ -65,12 +65,12 @@ public partial class Enemy : RigidBody2D
private void TrackBall(double delta, IReadOnlyList<Dictionary> result)
{
// checks if the collider is a ball, if not, return.
if (result[0]["collider"].As<Nodes.Ball>() is not { } ball) return;
if (result[0]["collider"].As<Objects.Ball>() is not { } ball) return;
// gets the sign of the distance between the ball and the paddle on the y axis
var normalisedDistance = new Vector2(0, Mathf.Sign(ball.Position.Y - Position.Y));
var normalisedDistance = new Vector2 { Y = Mathf.Sign(ball.Position.Y - Position.Y) };
var linearVelocity = normalisedDistance * _moveSpeed * Constants.Meter;
// lerp the velocity to smooth out jerky movement.
LinearVelocity = LinearVelocity.Lerp(linearVelocity, delta);
}
@ -83,7 +83,7 @@ public partial class Enemy : RigidBody2D
{
if (_collisionShape.Shape is not RectangleShape2D shape)
throw new InvalidOperationException("the collision shape needs to be a rectangle shape");
// grow the area the enemy can see by around half of the screen area.
_scanArea = shape.GetRect().GrowSide(Side.Left, 400).GrowIndividual(0, 190, 0, 190);
}

View file

@ -1,6 +1,6 @@
using Godot;
namespace Pong.Scripts.Nodes;
namespace Pong.Scripts.Objects;
public partial class Paddle : RigidBody2D
{

View file

@ -0,0 +1,20 @@
#if TOOLS
using Godot;
namespace Pong.addons.BaseMenu;
[Tool]
public partial class BaseMenu : EditorPlugin
{
public override void _EnterTree()
{
var baseMenuScript = GD.Load<Script>("res://Scripts/Managers/UI/BaseMenu.cs");
AddCustomType("BaseMenu", "VBoxContainer", baseMenuScript, GetEditorIcon("VBoxContainer"));
}
private Texture2D GetEditorIcon(string iconName)
{
return GetEditorInterface().GetBaseControl().GetThemeIcon(iconName, "EditorIcons");
}
}
#endif

View file

@ -0,0 +1,7 @@
[plugin]
name="BaseMenu"
description=""
author="Fries"
version=""
script="BaseMenu.cs"

View file

@ -11,10 +11,14 @@ config_version=5
[application]
config/name="Pong"
run/main_scene="res://Scenes/MainMenu.tscn"
run/main_scene="res://Scenes/UI/Menus/MainMenu.tscn"
config/features=PackedStringArray("4.0", "C#", "Double Precision", "Mobile")
config/icon="res://icon.svg"
[autoload]
SettingsManager="*res://Scripts/Managers/SettingsManager.cs"
[display]
window/size/viewport_width=800
@ -29,6 +33,10 @@ project/assembly_name="Pong"
version_control/plugin_name="GitPlugin"
version_control/autoload_on_startup=true
[editor_plugins]
enabled=PackedStringArray("res://addons/BaseMenu/plugin.cfg")
[input]
paddle_up={