From 4326ca850d12f1bd1caac1361e2a7668916e8f59 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Mon, 21 Jul 2025 22:02:05 +0200 Subject: [PATCH 01/16] Implement BeamComponent in C# and enhance marketplace button functionality --- Mr. Brick Adventures.csproj | 7 ++ Mr. Brick Adventures.sln | 19 ++++ Mr. Brick Adventures.sln.DotSettings.user | 4 + scripts/components/BeamComponent.cs | 109 ++++++++++++++++++++++ scripts/components/BeamComponent.cs.uid | 1 + scripts/components/beam_component.gd | 2 +- scripts/ui/marketplace.gd | 27 +++--- 7 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 Mr. Brick Adventures.csproj create mode 100644 Mr. Brick Adventures.sln create mode 100644 Mr. Brick Adventures.sln.DotSettings.user create mode 100644 scripts/components/BeamComponent.cs create mode 100644 scripts/components/BeamComponent.cs.uid diff --git a/Mr. Brick Adventures.csproj b/Mr. Brick Adventures.csproj new file mode 100644 index 0000000..8d43770 --- /dev/null +++ b/Mr. Brick Adventures.csproj @@ -0,0 +1,7 @@ + + + net8.0 + true + Mr.BrickAdventures + + \ No newline at end of file diff --git a/Mr. Brick Adventures.sln b/Mr. Brick Adventures.sln new file mode 100644 index 0000000..7ef00af --- /dev/null +++ b/Mr. Brick Adventures.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mr. Brick Adventures", "Mr. Brick Adventures.csproj", "{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + ExportDebug|Any CPU = ExportDebug|Any CPU + ExportRelease|Any CPU = ExportRelease|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU + {A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU + {A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU + {A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU + EndGlobalSection +EndGlobal diff --git a/Mr. Brick Adventures.sln.DotSettings.user b/Mr. Brick Adventures.sln.DotSettings.user new file mode 100644 index 0000000..e8e9aa0 --- /dev/null +++ b/Mr. Brick Adventures.sln.DotSettings.user @@ -0,0 +1,4 @@ + + ForceIncluded + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/scripts/components/BeamComponent.cs b/scripts/components/BeamComponent.cs new file mode 100644 index 0000000..8f667e0 --- /dev/null +++ b/scripts/components/BeamComponent.cs @@ -0,0 +1,109 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +[GlobalClass] +public partial class BeamComponent : Node2D +{ + private float _currentLength = 0.0f; + private const float PixelSize = 16.0f; // Assuming 16 pixels per unit for scaling + + [Export] + public float ExpansionSpeed { get; set; } = 100.0f; + [Export] + public float MaxLength { get; set; } = 512.0f; + [Export] + public Vector2 Direction { get; set; } = Vector2.Down; + [Export] + public Node2D Root { get; set; } + [Export] + public Sprite2D Sprite { get; set; } + [Export] + public CollisionShape2D CollisionShape { get; set; } + + public override void _Ready() + { + if (Root == null) + { + GD.PrintErr("Root node is not set for BeamComponent."); + } + if (Sprite == null) + { + GD.PrintErr("Sprite node is not set for BeamComponent."); + } + if (CollisionShape == null) + { + GD.PrintErr("CollisionShape node is not set for BeamComponent."); + } + + var shape = CollisionShape?.Shape as RectangleShape2D; + shape?.SetSize(new Vector2(_currentLength / 2.0f, _currentLength / 2.0f)); + Sprite?.SetScale(new Vector2(1f, 1f)); + CollisionShape?.SetPosition(Vector2.Zero); + } + + public override void _Process(double delta) + { + var newLength = _currentLength + ExpansionSpeed * (float)delta; + if (newLength > MaxLength) newLength = MaxLength; + + if (!CheckForObstacle(newLength)) ExpandBeam(newLength); + } + + private void ExpandBeam(float newLength) + { + _currentLength = newLength; + + if (Direction == Vector2.Up) + { + var pos = Sprite.Position; + var scale = Sprite.Scale; + var shape = CollisionShape?.Shape as RectangleShape2D; + Sprite.SetScale(new Vector2(scale.X, _currentLength / PixelSize)); + Sprite.SetPosition(new Vector2(pos.X, -_currentLength / 2.0f)); + shape?.SetSize(new Vector2(PixelSize / 2f, _currentLength / 2.0f)); + CollisionShape?.SetPosition(new Vector2(CollisionShape.Position.X, -_currentLength / 2.0f)); + } else if (Direction == Vector2.Down) + { + var pos = Sprite.Position; + var scale = Sprite.Scale; + var shape = CollisionShape?.Shape as RectangleShape2D; + Sprite.SetScale(new Vector2(scale.X, _currentLength / PixelSize)); + Sprite.SetPosition(new Vector2(pos.X, _currentLength / 2.0f)); + shape?.SetSize(new Vector2(PixelSize / 2f, _currentLength / 2.0f)); + CollisionShape?.SetPosition(new Vector2(CollisionShape.Position.X, _currentLength / 2.0f)); + } else if (Direction == Vector2.Left) + { + var pos = Sprite.Position; + var scale = Sprite.Scale; + var shape = CollisionShape?.Shape as RectangleShape2D; + Sprite.SetScale(new Vector2(_currentLength / PixelSize, scale.Y)); + Sprite.SetPosition(new Vector2(-_currentLength / 2.0f, pos.Y)); + shape?.SetSize(new Vector2(_currentLength / 2.0f, PixelSize / 2f)); + CollisionShape?.SetPosition(new Vector2(-_currentLength / 2.0f, CollisionShape.Position.Y)); + } else if (Direction == Vector2.Right) + { + var pos = Sprite.Position; + var scale = Sprite.Scale; + var shape = CollisionShape?.Shape as RectangleShape2D; + Sprite.SetScale(new Vector2(_currentLength / PixelSize, scale.Y)); + Sprite.SetPosition(new Vector2(_currentLength / 2.0f, pos.Y)); + shape?.SetSize(new Vector2(_currentLength / 2.0f, PixelSize / 2f)); + CollisionShape?.SetPosition(new Vector2(_currentLength / 2.0f, CollisionShape.Position.Y)); + } + } + + private bool CheckForObstacle(float newLength) + { + var spaceState = GetWorld2D().DirectSpaceState; + var queryStart = GlobalPosition; + var queryEnd = queryStart + Direction.Normalized() * newLength; + var query = PhysicsRayQueryParameters2D.Create(queryStart, queryEnd); + + query.CollideWithAreas = false; + query.CollideWithBodies = true; + + var result = spaceState.IntersectRay(query); + return result.Count > 0; + } +} \ No newline at end of file diff --git a/scripts/components/BeamComponent.cs.uid b/scripts/components/BeamComponent.cs.uid new file mode 100644 index 0000000..440ee32 --- /dev/null +++ b/scripts/components/BeamComponent.cs.uid @@ -0,0 +1 @@ +uid://df1llrbm80e02 diff --git a/scripts/components/beam_component.gd b/scripts/components/beam_component.gd index 844734e..69bda9f 100644 --- a/scripts/components/beam_component.gd +++ b/scripts/components/beam_component.gd @@ -1,4 +1,4 @@ -class_name BeamComponent +# class_name BeamComponent extends Node2D @export var expansion_speed: float = 16.0 diff --git a/scripts/ui/marketplace.gd b/scripts/ui/marketplace.gd index 172aadb..41a5309 100644 --- a/scripts/ui/marketplace.gd +++ b/scripts/ui/marketplace.gd @@ -13,7 +13,6 @@ extends Node @onready var game_manager: GM = $"/root/GameManager" - var unlock_buttons: Array[Button] = [] var skill_buttons: Array[SkillButton] = [] @@ -36,6 +35,14 @@ func _ready() -> void: skill_unlocker.skill_unlocked.connect(on_skill_unlocked) +func _process(_delta: float) -> void: + for btn in skill_buttons: + if not btn.skill_data: + continue + if btn.skill_data.is_active: + btn.activate() + else: + btn.deactivate() func _input(event: InputEvent) -> void: if event.is_action_pressed("show_marketplace"): @@ -53,7 +60,6 @@ func get_button_text(skill: SkillData) -> String: return tr(skill.name) + " " + str(skill.cost) - func create_upgrade_button(skill: SkillData) -> void: var button := marketplace_button.instantiate() as MarketplaceButton button.text = get_button_text(skill) @@ -102,26 +108,19 @@ func _on_button_pressed(skill: SkillData) -> void: func on_skill_unlocked(skill: SkillData) -> void: - # need to fix this method if not skill: return - if skill_buttons.size() == 0: - create_skill_button(skill) - for button in skill_buttons: - if button.skill_data.is_active: - button.activate() - else: - button.deactivate() + for btn in skill_buttons: + if btn.skill_data and btn.skill_data.name == skill.name: + return + create_skill_button(skill) + func on_skill_button_pressed(button: SkillButton) -> void: if not skill_unlocker or not button.skill_data: return skill_unlocker.skill_manager.toggle_skill_activation(button.skill_data) - button.activate() - for other_button in skill_buttons: - if other_button != button: - other_button.deactivate() -- 2.49.1 From b54d886145483f2940a2bef971fe6a96d1f50db6 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 10 Aug 2025 01:35:35 +0200 Subject: [PATCH 02/16] Add core game components including ConfigFileHandler, GameManager, SaveSystem, and UIManager --- Autoloads/ConfigFileHandler.cs | 25 +++ Autoloads/GameManager.cs | 195 ++++++++++++++++++ Autoloads/SaveSystem.cs | 46 +++++ Autoloads/UIManager.cs | 65 ++++++ Mr. Brick Adventures.csproj | 136 ++++++++++++ autoloads/ui_manager.gd | 2 +- project.godot | 2 +- scripts/Resources/CollectableResource.cs | 9 + scripts/Resources/CollectableType.cs | 8 + scripts/Resources/SkillData.cs | 19 ++ scripts/Resources/SkillType.cs | 8 + scripts/Resources/StatusEffectDataResource.cs | 8 + scripts/Resources/StatusEffectType.cs | 8 + scripts/Screenshot.cs | 15 ++ scripts/SkillManager.cs | 144 +++++++++++++ scripts/components/.idea/.gitignore | 13 ++ scripts/components/.idea/encodings.xml | 4 + scripts/components/.idea/indexLayout.xml | 8 + .../inspectionProfiles/Project_Default.xml | 6 + scripts/components/.idea/vcs.xml | 6 + scripts/components/CollectableComponent.cs | 50 +++++ scripts/components/DamageComponent.cs | 105 ++++++++++ scripts/components/FlashingComponent.cs | 83 ++++++++ scripts/components/HealthComponent.cs | 65 ++++++ .../components/InvulnerabilityComponent.cs | 31 +++ scripts/components/PlatformMovement.cs | 169 +++++++++++++++ scripts/components/PlatformMovement.cs.uid | 1 + scripts/components/PlayerController.cs | 103 +++++++++ scripts/components/PlayerController.cs.uid | 1 + scripts/interfaces/IMovement.cs | 13 ++ scripts/interfaces/IMovement.cs.uid | 1 + 31 files changed, 1347 insertions(+), 2 deletions(-) create mode 100644 Autoloads/ConfigFileHandler.cs create mode 100644 Autoloads/GameManager.cs create mode 100644 Autoloads/SaveSystem.cs create mode 100644 Autoloads/UIManager.cs create mode 100644 scripts/Resources/CollectableResource.cs create mode 100644 scripts/Resources/CollectableType.cs create mode 100644 scripts/Resources/SkillData.cs create mode 100644 scripts/Resources/SkillType.cs create mode 100644 scripts/Resources/StatusEffectDataResource.cs create mode 100644 scripts/Resources/StatusEffectType.cs create mode 100644 scripts/Screenshot.cs create mode 100644 scripts/SkillManager.cs create mode 100644 scripts/components/.idea/.gitignore create mode 100644 scripts/components/.idea/encodings.xml create mode 100644 scripts/components/.idea/indexLayout.xml create mode 100644 scripts/components/.idea/inspectionProfiles/Project_Default.xml create mode 100644 scripts/components/.idea/vcs.xml create mode 100644 scripts/components/CollectableComponent.cs create mode 100644 scripts/components/DamageComponent.cs create mode 100644 scripts/components/FlashingComponent.cs create mode 100644 scripts/components/HealthComponent.cs create mode 100644 scripts/components/InvulnerabilityComponent.cs create mode 100644 scripts/components/PlatformMovement.cs create mode 100644 scripts/components/PlatformMovement.cs.uid create mode 100644 scripts/components/PlayerController.cs create mode 100644 scripts/components/PlayerController.cs.uid create mode 100644 scripts/interfaces/IMovement.cs create mode 100644 scripts/interfaces/IMovement.cs.uid diff --git a/Autoloads/ConfigFileHandler.cs b/Autoloads/ConfigFileHandler.cs new file mode 100644 index 0000000..5799faf --- /dev/null +++ b/Autoloads/ConfigFileHandler.cs @@ -0,0 +1,25 @@ +using Godot; + +namespace Mr.BrickAdventures.Autoloads; + +public partial class ConfigFileHandler : Node +{ + private ConfigFile _settingsConfig = new(); + private const string SettingsPath = "user://settings.ini"; + + public override void _Ready() + { + if (!FileAccess.FileExists(SettingsPath)) + { + var err = _settingsConfig.Save(SettingsPath); + if (err != Error.Ok) + GD.PushError($"Failed to create settings file at {SettingsPath}: {err}"); + } + else + { + var err = _settingsConfig.Load(SettingsPath); + if (err != Error.Ok) + GD.PushError($"Failed to load settings file at {SettingsPath}: {err}"); + } + } +} \ No newline at end of file diff --git a/Autoloads/GameManager.cs b/Autoloads/GameManager.cs new file mode 100644 index 0000000..588fc65 --- /dev/null +++ b/Autoloads/GameManager.cs @@ -0,0 +1,195 @@ +using Godot; +using Godot.Collections; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.Autoloads; + +public partial class GameManager : Node +{ + [Export] public Array LevelScenes { get; set; } = new(); + + public Dictionary PlayerState { get; set; } = new() + { + { "coins", 0 }, + { "lives", 3 }, + { "current_level", 0 }, + { "completed_levels", new Array() }, + { "unlocked_levels", new Array() {0}}, + { "unlocked_skills", new Array() } + }; + + private Dictionary _currentSessionState = new() + { + { "coins_collected", 0 }, + { "skills_unlocked", new Array() } + }; + + public void AddCoins(int amount) + { + PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] + amount); + } + + public void SetCoins(int amount) => PlayerState["coins"] = Mathf.Max(0, amount); + + public int GetCoins() => (int)PlayerState["coins"] + (int)_currentSessionState["coins_collected"]; + + public void RemoveCoins(int amount) + { + var sessionCoins = (int)_currentSessionState["coins_collected"]; + if (amount <= sessionCoins) + { + _currentSessionState["coins_collected"] = sessionCoins - amount; + } + else + { + var remaining = amount - sessionCoins; + _currentSessionState["coins_collected"] = 0; + PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] - remaining); + } + PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"]); + } + + public void AddLives(int amount) => PlayerState["lives"] = (int)PlayerState["lives"] + amount; + public void RemoveLives(int amount) => PlayerState["lives"] = (int)PlayerState["lives"] - amount; + public void SetLives(int amount) => PlayerState["lives"] = amount; + public int GetLives() => (int)PlayerState["lives"]; + + public bool IsSkillUnlocked(SkillData skill) + { + return ((Array)PlayerState["unlocked_skills"]).Contains(skill) + || ((Array)_currentSessionState["skills_unlocked"]).Contains(skill); + } + + public void UnlockSkill(SkillData skill) + { + if (!IsSkillUnlocked(skill)) + ((Array)PlayerState["unlocked_skills"]).Add(skill); + } + + public void RemoveSkill(string skillName) + { + var arr = (Array)PlayerState["unlocked_skills"]; + foreach (SkillData s in arr) + { + if (s.Name != skillName) continue; + + arr.Remove(s); + break; + } + } + + public void UnlockSkills(Array skills) + { + foreach (var s in skills) + UnlockSkill(s); + } + + public void ResetPlayerState() + { + PlayerState = new Dictionary + { + { "coins", 0 }, + { "lives", 3 }, + { "current_level", 0 }, + { "completed_levels", new Array() }, + { "unlocked_levels", new Array() {0}}, + { "unlocked_skills", new Array() } + }; + } + + public void UnlockLevel(int levelIndex) + { + var unlocked = (Array)PlayerState["unlocked_levels"]; + if (!unlocked.Contains(levelIndex)) unlocked.Add(levelIndex); + } + + public void TryToGoToNextLevel() + { + var next = (int)PlayerState["current_level"] + 1; + var unlocked = (Array)PlayerState["unlocked_levels"]; + if (next < LevelScenes.Count && unlocked.Contains(next)) + { + PlayerState["current_level"] = next; + GetTree().ChangeSceneToPacked(LevelScenes[next]); + } + } + + public void MarkLevelComplete(int levelIndex) + { + UnlockLevel(levelIndex + 1); + var completed = (Array)PlayerState["completed_levels"]; + if (!completed.Contains(levelIndex)) completed.Add(levelIndex); + } + + public void ResetCurrentSessionState() + { + _currentSessionState = new Dictionary + { + { "coins_collected", 0 }, + { "skills_unlocked", new Array() } + }; + } + + public void RestartGame() + { + ResetPlayerState(); + ResetCurrentSessionState(); + GetTree().ChangeSceneToPacked(LevelScenes[0]); + GetNode("/root/SaveSystem").SaveGame(); + } + + public void QuitGame() => GetTree().Quit(); + + public void PauseGame() => Engine.TimeScale = 0; + public void ResumeGame() => Engine.TimeScale = 1; + + public void StartNewGame() + { + ResetPlayerState(); + ResetCurrentSessionState(); + GetTree().ChangeSceneToPacked(LevelScenes[0]); + GetNode("/root/SaveSystem").SaveGame(); + } + + public void ContinueGame() + { + var save = GetNode("/root/SaveSystem"); + if (!save.LoadGame()) + { + GD.PrintErr("Failed to load game. Starting a new game instead."); + StartNewGame(); + return; + } + + var idx = (int)PlayerState["current_level"]; + if (idx < LevelScenes.Count) + GetTree().ChangeSceneToPacked(LevelScenes[idx]); + else + GD.PrintErr("No levels unlocked to continue."); + } + + public void OnLevelComplete() + { + var levelIndex = (int)PlayerState["current_level"]; + MarkLevelComplete(levelIndex); + AddCoins((int)_currentSessionState["coins_collected"]); + foreach (var s in (Array)_currentSessionState["skills_unlocked"]) + UnlockSkill((SkillData)s); + + ResetCurrentSessionState(); + TryToGoToNextLevel(); + GetNode("/root/SaveSystem").SaveGame(); + } + + public Array GetUnlockedSkills() + { + var unlocked = (Array)PlayerState["unlocked_skills"]; + var session = (Array)_currentSessionState["skills_unlocked"]; + if ((((Array)session)!).Count == 0) return (Array)unlocked; + if ((((Array)unlocked)!).Count == 0) return (Array)session; + var joined = new Array(); + joined.AddRange((Array)unlocked ?? new Array()); + joined.AddRange((Array)session ?? new Array()); + return joined; + } +} \ No newline at end of file diff --git a/Autoloads/SaveSystem.cs b/Autoloads/SaveSystem.cs new file mode 100644 index 0000000..0e47221 --- /dev/null +++ b/Autoloads/SaveSystem.cs @@ -0,0 +1,46 @@ +using Godot; +using Godot.Collections; + +namespace Mr.BrickAdventures.Autoloads; + +public partial class SaveSystem : Node +{ + [Export] public string SavePath { get; set; } = "user://savegame.save"; + [Export] public int Version { get; set; } = 1; + + //private GM _gm; + + public override void _Ready() + { + //_gm = GetNode("/root/GameManager"); + } + + public void SaveGame() + { + //TODO: Implement saving logic + } + + public bool LoadGame() + { + //TODO: Implement loading logic + + if (!FileAccess.FileExists(SavePath)) + return false; + + using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read); + var saveDataObj = (Dictionary)file.GetVar(); + + if (saveDataObj.ContainsKey("version") && (int)saveDataObj["version"] != Version) + { + GD.Print($"Save file version mismatch. Expected: {Version}, Found: {saveDataObj["version"]}"); + return false; + } + + GD.Print("Game state loaded from: ", SavePath); + GD.Print("Player state: ", saveDataObj["player_state"]); + + return true; + } + + public bool CheckSaveExists() => FileAccess.FileExists(SavePath); +} \ No newline at end of file diff --git a/Autoloads/UIManager.cs b/Autoloads/UIManager.cs new file mode 100644 index 0000000..cd576be --- /dev/null +++ b/Autoloads/UIManager.cs @@ -0,0 +1,65 @@ +using Godot; +using Godot.Collections; + +namespace Mr.BrickAdventures.Autoloads; + +public partial class UIManager : Node +{ + [Export] public Array UiStack { get; set; } = new(); + + [Signal] public delegate void ScreenPushedEventHandler(Control screen); + [Signal] public delegate void ScreenPoppedEventHandler(Control screen); + + public void PushScreen(Control screen) + { + if (screen == null) + { + GD.PushError($"Cannot push a null screen."); + return; + } + + UiStack.Add(screen); + screen.Show(); + screen.SetProcessInput(true); + screen.SetFocusMode(Control.FocusModeEnum.All); + screen.GrabFocus(); + EmitSignalScreenPushed(screen); + } + + public void PopScreen() + { + if (UiStack.Count == 0) + { + GD.PushError($"Cannot pop screen from an empty stack."); + return; + } + + var top = (Control)UiStack[^1]; + UiStack.RemoveAt(UiStack.Count - 1); + top.Hide(); + top.SetProcessInput(false); + EmitSignalScreenPopped(top); + top.AcceptEvent(); + + if (UiStack.Count > 0) ((Control)UiStack[^1]).GrabFocus(); + } + + public Control TopScreen() => UiStack.Count > 0 ? (Control)UiStack[^1] : null; + + public bool IsScreenOnTop(Control screen) => UiStack.Count > 0 && (Control)UiStack[^1] == screen; + + public bool IsVisibleOnStack(Control screen) => UiStack.Contains(screen) && screen.Visible; + + public void CloseAll() + { + while (UiStack.Count > 0) + PopScreen(); + } + + public static void HideAndDisable(Control screen) + { + screen.Hide(); + screen.SetProcessInput(false); + } + +} \ No newline at end of file diff --git a/Mr. Brick Adventures.csproj b/Mr. Brick Adventures.csproj index 8d43770..b7064e9 100644 --- a/Mr. Brick Adventures.csproj +++ b/Mr. Brick Adventures.csproj @@ -4,4 +4,140 @@ true Mr.BrickAdventures + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/autoloads/ui_manager.gd b/autoloads/ui_manager.gd index 91e1171..a5c8914 100644 --- a/autoloads/ui_manager.gd +++ b/autoloads/ui_manager.gd @@ -51,4 +51,4 @@ func close_all() -> void: func hide_and_disable(screen: Control) -> void: screen.hide() - screen.set_process_input(false) \ No newline at end of file + screen.set_process_input(false) diff --git a/project.godot b/project.godot index 570baac..42f3f74 100644 --- a/project.godot +++ b/project.godot @@ -19,7 +19,7 @@ config/version="in-dev" run/main_scene="uid://cl00e2ocomk3m" config/use_custom_user_dir=true config/custom_user_dir_name="MrBrickAdventures" -config/features=PackedStringArray("4.4", "GL Compatibility") +config/features=PackedStringArray("4.4", "C#", "GL Compatibility") run/max_fps=180 boot_splash/bg_color=Color(0, 0, 0, 1) boot_splash/show_image=false diff --git a/scripts/Resources/CollectableResource.cs b/scripts/Resources/CollectableResource.cs new file mode 100644 index 0000000..88b338b --- /dev/null +++ b/scripts/Resources/CollectableResource.cs @@ -0,0 +1,9 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.Resources; + +public partial class CollectableResource : Resource +{ + [Export] public Variant Amount { get; set; } = 0.0; + [Export] public CollectableType Type { get; set; } +} \ No newline at end of file diff --git a/scripts/Resources/CollectableType.cs b/scripts/Resources/CollectableType.cs new file mode 100644 index 0000000..840b8b9 --- /dev/null +++ b/scripts/Resources/CollectableType.cs @@ -0,0 +1,8 @@ +namespace Mr.BrickAdventures.scripts.Resources; + +public enum CollectableType +{ + Coin, + Kid, + Health, +} \ No newline at end of file diff --git a/scripts/Resources/SkillData.cs b/scripts/Resources/SkillData.cs new file mode 100644 index 0000000..2204c6f --- /dev/null +++ b/scripts/Resources/SkillData.cs @@ -0,0 +1,19 @@ +using System; +using Godot; +using Godot.Collections; + +namespace Mr.BrickAdventures.scripts.Resources; + +public partial class SkillData : Resource +{ + [Export] public String Name { get; set; } = "New Skill"; + [Export] public String Description { get; set; } = "New Skill"; + [Export] public Dictionary Config { get; set; } = new(); + [Export] public int Cost { get; set; } = 0; + [Export] public Texture2D Icon { get; set; } + [Export] public bool IsActive { get; set; } = false; + [Export] public int Level { get; set; } = 1; + [Export] public int MaxLevel { get; set; } = 1; + [Export] public SkillType Type { get; set; } = SkillType.Throw; + [Export] public PackedScene Node { get; set; } +} \ No newline at end of file diff --git a/scripts/Resources/SkillType.cs b/scripts/Resources/SkillType.cs new file mode 100644 index 0000000..ceb0b4d --- /dev/null +++ b/scripts/Resources/SkillType.cs @@ -0,0 +1,8 @@ +namespace Mr.BrickAdventures.scripts.Resources; + +public enum SkillType +{ + Attack, + Throw, + Misc, +} \ No newline at end of file diff --git a/scripts/Resources/StatusEffectDataResource.cs b/scripts/Resources/StatusEffectDataResource.cs new file mode 100644 index 0000000..31ff410 --- /dev/null +++ b/scripts/Resources/StatusEffectDataResource.cs @@ -0,0 +1,8 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.Resources; + +public partial class StatusEffectDataResource : Resource +{ + [Export] public StatusEffectType Type { get; set; } +} \ No newline at end of file diff --git a/scripts/Resources/StatusEffectType.cs b/scripts/Resources/StatusEffectType.cs new file mode 100644 index 0000000..7f61f98 --- /dev/null +++ b/scripts/Resources/StatusEffectType.cs @@ -0,0 +1,8 @@ +namespace Mr.BrickAdventures.scripts.Resources; + +public enum StatusEffectType +{ + None, + Fire, + Ice +} \ No newline at end of file diff --git a/scripts/Screenshot.cs b/scripts/Screenshot.cs new file mode 100644 index 0000000..b389dd5 --- /dev/null +++ b/scripts/Screenshot.cs @@ -0,0 +1,15 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts; + +public partial class Screenshot : Node +{ + public override void _Process(double delta) + { + if (!OS.IsDebugBuild() || !Input.IsActionJustPressed("screenshot")) return; + var img = GetViewport().GetTexture().GetImage(); + var id = OS.GetUniqueId() + "_" + Time.GetDatetimeStringFromSystem(); + var path = "user://screenshots/screenshot_" + id + ".png"; + img.SavePng(path); + } +} \ No newline at end of file diff --git a/scripts/SkillManager.cs b/scripts/SkillManager.cs new file mode 100644 index 0000000..ad53727 --- /dev/null +++ b/scripts/SkillManager.cs @@ -0,0 +1,144 @@ +using Godot; +using Godot.Collections; +using Mr.BrickAdventures.Autoloads; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts; + +public partial class SkillManager : Node +{ + private GameManager _gameManager; + [Export] public Array AvailableSkills { get; set; } = []; + + public Dictionary ActiveComponents { get; private set; } = new(); + + public override void _Ready() + { + _gameManager = GetNode("/root/GameManager"); + ApplyUnlockedSkills(); + } + + public void AddSkill(SkillData skillData) + { + if (ActiveComponents.ContainsKey(skillData.Name)) + return; + + if (skillData.Type == SkillType.Throw) + { + var unlocked = _gameManager.GetUnlockedSkills(); + foreach (var skill in unlocked) + { + SkillData data = null; + foreach (var s in AvailableSkills) + { + if (s == (SkillData)skill) + { + data = s; + break; + } + } + if (data != null && data.Type == SkillType.Throw) + RemoveSkill(data.Name); + } + } + + var instance = skillData.Node.Instantiate(); + foreach (var key in skillData.Config.Keys) + { + if (instance.HasMethod("get")) // rough presence check + { + var value = skillData.Config[key]; + var parent = GetParent(); + + if (value.VariantType == Variant.Type.NodePath) + { + var np = (NodePath)value; + if (parent.HasNode(np)) + value = parent.GetNode(np); + else if (instance.HasNode(np)) + value = instance.GetNode(np); + else + continue; + } + + // Set via property if exists + instance.Set(key, value); + } + } + + Owner.AddChild(instance); + ActiveComponents[skillData.Name] = instance; + } + + public void RemoveSkill(string skillName) + { + if (!ActiveComponents.TryGetValue(skillName, out var component)) + return; + + var inst = (Node)component; + if (IsInstanceValid(inst)) + inst.QueueFree(); + + var skills = _gameManager.GetUnlockedSkills(); + foreach (SkillData s in skills) + { + if (s.Name == skillName) + { + s.IsActive = false; + break; + } + } + ActiveComponents.Remove(skillName); + } + + public void ApplyUnlockedSkills() + { + foreach (var sd in AvailableSkills) + { + if (_gameManager.IsSkillUnlocked(sd)) + { + GD.Print("Applying skill: ", sd.Name); + CallDeferred(MethodName.AddSkill, sd); + } + else + { + RemoveSkill(sd.Name); + } + } + } + + public SkillData GetSkillByName(string skillName) + { + foreach (var sd in AvailableSkills) + if (sd.Name == skillName) return sd; + return null; + } + + public void ActivateSkill(SkillData skill) + { + if (!ActiveComponents.ContainsKey(skill.Name)) + { + AddSkill(skill); + skill.IsActive = true; + } + } + + public void DeactivateSkill(SkillData skill) + { + if (ActiveComponents.ContainsKey(skill.Name)) + { + RemoveSkill(skill.Name); + skill.IsActive = false; + } + } + + public void ToggleSkillActivation(SkillData skill) + { + if (skill == null) return; + + if (ActiveComponents.ContainsKey(skill.Name)) + DeactivateSkill(skill); + else + ActivateSkill(skill); + } +} \ No newline at end of file diff --git a/scripts/components/.idea/.gitignore b/scripts/components/.idea/.gitignore new file mode 100644 index 0000000..f36cc23 --- /dev/null +++ b/scripts/components/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.components.iml +/contentModel.xml +/modules.xml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/scripts/components/.idea/encodings.xml b/scripts/components/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/scripts/components/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/scripts/components/.idea/indexLayout.xml b/scripts/components/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/scripts/components/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/scripts/components/.idea/inspectionProfiles/Project_Default.xml b/scripts/components/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/scripts/components/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/scripts/components/.idea/vcs.xml b/scripts/components/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/scripts/components/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/scripts/components/CollectableComponent.cs b/scripts/components/CollectableComponent.cs new file mode 100644 index 0000000..0233570 --- /dev/null +++ b/scripts/components/CollectableComponent.cs @@ -0,0 +1,50 @@ +using System; +using Godot; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class CollectableComponent : Node +{ + private bool _hasFadeAway = false; + + [Export] public Area2D Area2D { get; set; } + [Export] public CollisionShape2D CollisionShape { get; set; } + [Export] public CollectableResource Data { get; set; } + [Export] public AudioStreamPlayer2D Sfx {get; set; } + + [Signal] public delegate void CollectedEventHandler(Variant amount, CollectableType type, Node2D body); + + public override void _Ready() + { + if (Area2D != null) + Area2D.BodyEntered += OnArea2DBodyEntered; + else + GD.PushError("Collectable node missing Area2D node."); + + if (Owner.HasNode("FadeAwayComponent")) + _hasFadeAway = true; + } + + private async void OnArea2DBodyEntered(Node2D body) + { + try + { + if (!body.HasNode("CanPickUpComponent")) return; + + EmitSignalCollected(Data.Amount, Data.Type, body); + CollisionShape?.CallDeferred("set_disabled", true); + Sfx?.Play(); + + if (_hasFadeAway) return; + + if (Sfx != null) + await ToSignal(Sfx, AudioStreamPlayer2D.SignalName.Finished); + Owner.QueueFree(); + } + catch (Exception e) + { + GD.PushError($"Error in CollectableComponent.OnArea2DBodyEntered: {e.Message}"); + } + } +} \ No newline at end of file diff --git a/scripts/components/DamageComponent.cs b/scripts/components/DamageComponent.cs new file mode 100644 index 0000000..d343ce7 --- /dev/null +++ b/scripts/components/DamageComponent.cs @@ -0,0 +1,105 @@ +using Godot; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class DamageComponent : Node +{ + [Export] public float Damage { get; set; } = 0.25f; + [Export] public Area2D Area { get; set; } + [Export] public StatusEffectDataResource StatusEffectData { get; set; } + [Export] public Timer DamageTimer { get; set; } + + private Node _currentTarget = null; + + [Signal] public delegate void EffectInflictedEventHandler(Node2D target, StatusEffectDataResource effect); + + public override void _Ready() + { + if (Area == null) + { + GD.PushError($"DamageComponent: Area2D node is not set."); + return; + } + + Area.BodyEntered += OnAreaBodyEntered; + Area.BodyExited += OnAreaBodyExited; + Area.AreaEntered += OnAreaAreaEntered; + + if (DamageTimer != null) + { + DamageTimer.Timeout += OnDamageTimerTimeout; + } + } + + public override void _Process(double delta) + { + if (_currentTarget == null) return; + if (DamageTimer != null) return; + + ProcessEntityAndApplyDamage(_currentTarget as Node2D); + } + + public void DealDamage(HealthComponent target) => target.DecreaseHealth(Damage); + + private void OnAreaAreaEntered(Area2D area) + { + if (!CheckIfProcessingIsOn()) + return; + if (area == Area) return; + + var parent = area.GetParent(); + if (parent.HasNode("DamageComponent")) + ProcessEntityAndApplyDamage(parent as Node2D); + } + + private void OnAreaBodyExited(Node2D body) + { + if (body != _currentTarget) return; + _currentTarget = null; + DamageTimer?.Stop(); + } + + private void OnAreaBodyEntered(Node2D body) + { + _currentTarget = body; + + if (!CheckIfProcessingIsOn()) + return; + + DamageTimer?.Start(); + + ProcessEntityAndApplyDamage(body); + } + + private void OnDamageTimerTimeout() + { + if (_currentTarget == null) return; + + ProcessEntityAndApplyDamage(_currentTarget as Node2D); + } + + private void ProcessEntityAndApplyDamage(Node2D body) + { + if (body == null) return; + + if (!body.HasNode("HealthComponent")) return; + var health = body.GetNode("HealthComponent"); + var inv = body.GetNodeOrNull("InvulnerabilityComponent"); + + if (inv != null && inv.IsInvulnerable()) + return; + + if (StatusEffectData != null && StatusEffectData.Type != StatusEffectType.None) + EmitSignalEffectInflicted(body, StatusEffectData); + + DealDamage(health); + + inv?.Activate(); + } + + private bool CheckIfProcessingIsOn() + { + return ProcessMode is ProcessModeEnum.Inherit or ProcessModeEnum.Always; + } +} \ No newline at end of file diff --git a/scripts/components/FlashingComponent.cs b/scripts/components/FlashingComponent.cs new file mode 100644 index 0000000..7a7db70 --- /dev/null +++ b/scripts/components/FlashingComponent.cs @@ -0,0 +1,83 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class FlashingComponent : Node +{ + [Export] public Node2D Sprite { get; set; } + [Export] public float FlashDuration { get; set; } = 0.5f; + [Export] public float FlashTime { get; set; } = 0.1f; + [Export] public bool UseModulate { get; set; } = true; + [Export] public HealthComponent HealthComponent { get; set; } + + private Tween _tween; + + public override void _Ready() + { + if (HealthComponent != null) + { + HealthComponent.HealthChanged += OnHealthChanged; + HealthComponent.Death += OnDeath; + } + + if (Sprite == null) + { + GD.PushError("FlashingComponent: Sprite node is not set."); + return; + } + } + + public void StartFlashing() + { + if (Sprite == null) return; + + _tween?.Kill(); + + _tween = CreateTween(); + _tween.SetParallel(true); + + var flashes = (int)(FlashDuration / FlashTime); + for (var i = 0; i < flashes; i++) + { + if (UseModulate) + { + var opacity = i % 2 == 0 ? 1.0f : 0.3f; + _tween.TweenProperty(Sprite, "modulate:a", opacity, FlashTime); + } + else + { + var visible = i % 2 == 0; + _tween.TweenProperty(Sprite, "visible", visible, FlashTime); + } + } + + _tween.TweenCallback(Callable.From(StopFlashing)); + } + + public void StopFlashing() + { + if (UseModulate) + { + var modulateColor = Sprite.GetModulate(); + modulateColor.A = 1.0f; + Sprite.SetModulate(modulateColor); + } + else + { + Sprite.SetVisible(true); + } + } + + private void OnHealthChanged(float delta, float totalHealth) + { + if (delta < 0f) + { + StartFlashing(); + } + } + + private void OnDeath() + { + StopFlashing(); + } +} \ No newline at end of file diff --git a/scripts/components/HealthComponent.cs b/scripts/components/HealthComponent.cs new file mode 100644 index 0000000..2473807 --- /dev/null +++ b/scripts/components/HealthComponent.cs @@ -0,0 +1,65 @@ +using System.Threading.Tasks; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class HealthComponent : Node +{ + [Export] public float Health { get; set; } = 1.0f; + [Export] public float MaxHealth { get; set; } = 1.0f; + [Export] public AudioStreamPlayer2D HurtSfx { get; set; } + [Export] public AudioStreamPlayer2D HealSfx { get; set; } + + [Signal] public delegate void HealthChangedEventHandler(float delta, float totalHealth); + [Signal] public delegate void DeathEventHandler(); + + public void SetHealth(float newValue) + { + _ = ApplyHealthChange(newValue); + } + + public void IncreaseHealth(float delta) + { + _ = ApplyHealthChange(Health + delta); + } + + public void DecreaseHealth(float delta) + { + _ = ApplyHealthChange(Health - delta); + } + + public float GetDelta(float newValue) => newValue - Health; + + private async Task ApplyHealthChange(float newHealth, bool playSfx = true) + { + newHealth = Mathf.Clamp(newHealth, 0.0f, MaxHealth); + var delta = newHealth - Health; + + if (delta == 0.0f) + return; + + if (playSfx) + { + if (delta > 0f && HealSfx != null) + { + HealSfx.Play(); + } + else if (delta < 0f && HurtSfx != null) + { + HurtSfx.Play(); + await HurtSfx.ToSignal(HurtSfx, AudioStreamPlayer2D.SignalName.Finished); + } + } + + Health = newHealth; + + if (Health <= 0f) + { + EmitSignalDeath(); + } + else + { + EmitSignalHealthChanged(delta, Health); + } + } +} \ No newline at end of file diff --git a/scripts/components/InvulnerabilityComponent.cs b/scripts/components/InvulnerabilityComponent.cs new file mode 100644 index 0000000..d69c63d --- /dev/null +++ b/scripts/components/InvulnerabilityComponent.cs @@ -0,0 +1,31 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class InvulnerabilityComponent : Node +{ + [Export] public float Duration { get; set; } = 1f; + [Export] public FlashingComponent FlashingComponent { get; set; } + + private bool _isInvulnerable = false; + + public void Activate() + { + if (_isInvulnerable) + return; + + _isInvulnerable = true; + FlashingComponent?.StartFlashing(); + + var timer = GetTree().CreateTimer(Duration); + timer.Timeout += Deactivate; + } + + private void Deactivate() + { + _isInvulnerable = false; + FlashingComponent?.StopFlashing(); + } + + public bool IsInvulnerable() => _isInvulnerable; +} \ No newline at end of file diff --git a/scripts/components/PlatformMovement.cs b/scripts/components/PlatformMovement.cs new file mode 100644 index 0000000..6775850 --- /dev/null +++ b/scripts/components/PlatformMovement.cs @@ -0,0 +1,169 @@ +using Godot; +using Mr.BrickAdventures.scripts.interfaces; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class PlatformMovement : Node2D, IMovement +{ + [Export] + public float Speed { get; set; } = 300.0f; + + [Export] + public float JumpHeight { get; set; } = 100f; + + [Export] + public float JumpTimeToPeak { get; set; } = 0.5f; + + [Export] + public float JumpTimeToDescent { get; set; } = 0.4f; + + [Export] + public int CoyoteFrames { get; set; } = 6; + + [Export] + public AudioStreamPlayer2D JumpSfx { get; set; } + + [Export] + public Node2D RotationTarget { get; set; } + + [Export] + public CharacterBody2D Body { get; set; } + + private float _gravity; + private bool _wasLastFloor = false; + private bool _coyoteMode = false; + private Timer _coyoteTimer; + private Vector2 _lastDirection = new Vector2(1, 0); + + private float _jumpVelocity; + private float _jumpGravity; + private float _fallGravity; + + public override void _Ready() + { + base._Ready(); + + if (Body == null) + return; + + _gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity"); + _jumpVelocity = ((2.0f * JumpHeight) / JumpTimeToPeak) * -1.0f; + _jumpGravity = ((-2.0f * JumpHeight) / (JumpTimeToPeak * JumpTimeToPeak)) * -1.0f; + _fallGravity = ((-2.0f * JumpHeight) / (JumpTimeToDescent * JumpTimeToDescent)) * -1.0f; + + _coyoteTimer = new Timer + { + OneShot = true, + WaitTime = CoyoteFrames / 60.0f + }; + _coyoteTimer.Timeout += OnCoyoteTimerTimeout; + AddChild(_coyoteTimer); + } + + public string MovementType { get; } = "platform"; + public bool Enabled { get; set; } + public Vector2 PreviousVelocity { get; set; } + + public override void _Process(double delta) + { + base._Process(delta); + + if (Body == null || !Enabled) + return; + + if (Body.Velocity.X > 0.0f) + RotationTarget.Rotation = Mathf.DegToRad(-10); + else if (Body.Velocity.X < 0.0f) + RotationTarget.Rotation = Mathf.DegToRad(10); + else + RotationTarget.Rotation = 0; + + CalculateJumpVars(); + } + + public override void _PhysicsProcess(double delta) + { + base._PhysicsProcess(delta); + + if (Body == null || !Enabled) + return; + + if (Body.IsOnFloor()) + { + _wasLastFloor = true; + _coyoteMode = false; // Reset coyote mode when back on the floor + _coyoteTimer.Stop(); // Stop timer when grounded + } + else + { + if (_wasLastFloor) // Start coyote timer only once + { + _coyoteMode = true; + _coyoteTimer.Start(); + } + _wasLastFloor = false; + } + + if (!Body.IsOnFloor()) + Body.Velocity += new Vector2(0, CalculateGravity()) * (float)delta; + + if (Input.IsActionPressed("jump") && (Body.IsOnFloor() || _coyoteMode)) + Jump(); + + if (Input.IsActionJustPressed("down")) + Body.Position += new Vector2(0, 1); + + float direction = Input.GetAxis("left", "right"); + if (direction != 0) + _lastDirection = HandleDirection(direction); + + if (direction != 0) + Body.Velocity = new Vector2(direction * Speed, Body.Velocity.Y); + else + Body.Velocity = new Vector2(Mathf.MoveToward(Body.Velocity.X, 0, Speed), Body.Velocity.Y); + + Body.MoveAndSlide(); + } + + private void Jump() + { + if (Body == null) + return; + + Body.Velocity = new Vector2(Body.Velocity.X, _jumpVelocity); + _coyoteMode = false; + if (JumpSfx != null) + JumpSfx.Play(); + } + + private float CalculateGravity() + { + return Body.Velocity.Y < 0.0f ? _jumpGravity : _fallGravity; + } + + private void OnCoyoteTimerTimeout() + { + _coyoteMode = false; + } + + private Vector2 HandleDirection(float inputDir) + { + if (inputDir > 0) + return new Vector2(1, 0); + else if (inputDir < 0) + return new Vector2(-1, 0); + return _lastDirection; + } + + public void OnShipEntered() + { + RotationTarget.Rotation = 0; + } + + private void CalculateJumpVars() + { + _jumpVelocity = ((2.0f * JumpHeight) / JumpTimeToPeak) * -1.0f; + _jumpGravity = ((-2.0f * JumpHeight) / (JumpTimeToPeak * JumpTimeToPeak)) * -1.0f; + _fallGravity = ((-2.0f * JumpHeight) / (JumpTimeToDescent * JumpTimeToDescent)) * -1.0f; + } +} \ No newline at end of file diff --git a/scripts/components/PlatformMovement.cs.uid b/scripts/components/PlatformMovement.cs.uid new file mode 100644 index 0000000..6a641b5 --- /dev/null +++ b/scripts/components/PlatformMovement.cs.uid @@ -0,0 +1 @@ +uid://btlm1f3l70il diff --git a/scripts/components/PlayerController.cs b/scripts/components/PlayerController.cs new file mode 100644 index 0000000..1f2bf5b --- /dev/null +++ b/scripts/components/PlayerController.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using Godot; +using Mr.BrickAdventures.scripts.interfaces; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class PlayerController : Node2D +{ + [Export] + public string DefaultMovementType { get; set; } = "platform"; + + [Export] + public Godot.Collections.Dictionary MovementTypes { get; set; } + + [Export] + public Sprite2D ShipSprite { get; set; } + + private IMovement _currentMovement = null; + [Signal] + public delegate void MovementSwitchedEventHandler(string movementType); + + public override void _Ready() + { + base._Ready(); + + foreach (var movementType in MovementTypes.Keys) + { + var movementNode = GetNodeOrNull(movementType); + if (movementNode is IMovement playerMovement) + { + playerMovement.Enabled = false; + } + } + + SwitchMovement(DefaultMovementType); + } + + public override void _UnhandledInput(InputEvent @event) + { + base._UnhandledInput(@event); + + if (@event is InputEventKey inputEventKey && inputEventKey.IsActionPressed("switch_movement")) + { + var nextMovementType = GetNextMovementType(); + SwitchMovement(nextMovementType); + } + } + + private void SwitchMovement(string movementType) + { + if (_currentMovement != null) + { + _currentMovement.Enabled = false; + } + + if (MovementTypes.TryGetValue(movementType, out var movement)) + { + _currentMovement = GetNodeOrNull(movement); + if (_currentMovement == null) + { + GD.PushError($"Movement type '{movementType}' not found in MovementTypes."); + return; + } + _currentMovement.Enabled = true; + EmitSignalMovementSwitched(movementType); + } + else + { + GD.PushError($"Movement type '{movementType}' not found in MovementTypes."); + } + + if (_currentMovement == null) + { + GD.PushError("No current movement set after switching."); + } + } + + private string GetNextMovementType() + { + var keys = new List(MovementTypes.Keys); + var currentIndex = keys.IndexOf(_currentMovement?.MovementType); + + if (currentIndex == -1) + { + return DefaultMovementType; + } + + currentIndex = (currentIndex + 1) % keys.Count; + return keys[currentIndex]; + } + + public void OnSpaceshipEntered() + { + SwitchMovement("ship"); + ShipSprite.Visible = true; + } + + public void OnSpaceshipExited() + { + SwitchMovement(DefaultMovementType); + ShipSprite.Visible = false; + } +} \ No newline at end of file diff --git a/scripts/components/PlayerController.cs.uid b/scripts/components/PlayerController.cs.uid new file mode 100644 index 0000000..b30ccfe --- /dev/null +++ b/scripts/components/PlayerController.cs.uid @@ -0,0 +1 @@ +uid://csel4s0e4g5uf diff --git a/scripts/interfaces/IMovement.cs b/scripts/interfaces/IMovement.cs new file mode 100644 index 0000000..ece7b69 --- /dev/null +++ b/scripts/interfaces/IMovement.cs @@ -0,0 +1,13 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.interfaces; + +public interface IMovement +{ + string MovementType { get; } + bool Enabled { get; set; } + Vector2 PreviousVelocity { get; set; } + + void _Process(double delta); + void _PhysicsProcess(double delta); +} \ No newline at end of file diff --git a/scripts/interfaces/IMovement.cs.uid b/scripts/interfaces/IMovement.cs.uid new file mode 100644 index 0000000..9383ca5 --- /dev/null +++ b/scripts/interfaces/IMovement.cs.uid @@ -0,0 +1 @@ +uid://bt2g2im63o8fh -- 2.49.1 From bdd5ce04da653833ff68534900e2078884e571d5 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 10 Aug 2025 01:36:13 +0200 Subject: [PATCH 03/16] cleanup --- scripts/components/.idea/.gitignore | 13 ------------- scripts/components/.idea/encodings.xml | 4 ---- scripts/components/.idea/indexLayout.xml | 8 -------- .../.idea/inspectionProfiles/Project_Default.xml | 6 ------ scripts/components/.idea/vcs.xml | 6 ------ 5 files changed, 37 deletions(-) delete mode 100644 scripts/components/.idea/.gitignore delete mode 100644 scripts/components/.idea/encodings.xml delete mode 100644 scripts/components/.idea/indexLayout.xml delete mode 100644 scripts/components/.idea/inspectionProfiles/Project_Default.xml delete mode 100644 scripts/components/.idea/vcs.xml diff --git a/scripts/components/.idea/.gitignore b/scripts/components/.idea/.gitignore deleted file mode 100644 index f36cc23..0000000 --- a/scripts/components/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/.idea.components.iml -/contentModel.xml -/modules.xml -/projectSettingsUpdater.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/scripts/components/.idea/encodings.xml b/scripts/components/.idea/encodings.xml deleted file mode 100644 index df87cf9..0000000 --- a/scripts/components/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/scripts/components/.idea/indexLayout.xml b/scripts/components/.idea/indexLayout.xml deleted file mode 100644 index 7b08163..0000000 --- a/scripts/components/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/scripts/components/.idea/inspectionProfiles/Project_Default.xml b/scripts/components/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 03d9549..0000000 --- a/scripts/components/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/scripts/components/.idea/vcs.xml b/scripts/components/.idea/vcs.xml deleted file mode 100644 index b2bdec2..0000000 --- a/scripts/components/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file -- 2.49.1 From 3066f0c7b45313e1e22fcecb56f2302f193ce1de Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 10 Aug 2025 12:57:58 +0200 Subject: [PATCH 04/16] Add new components: CanPickUpComponent, CollapsableComponent, DestroyableComponent, EffectInflictorComponent, StatusEffectComponent, and StatusEffectDataResource --- scripts/Resources/StatusEffectDataResource.cs | 2 + scripts/components/CanPickUpComponent.cs | 8 ++ scripts/components/CollapsableComponent.cs | 81 +++++++++++++++++++ scripts/components/DestroyableComponent.cs | 35 ++++++++ .../components/EffectInflictorComponent.cs | 27 +++++++ scripts/components/HealthComponent.cs | 2 +- scripts/components/StatusEffectComponent.cs | 55 +++++++++++++ 7 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 scripts/components/CanPickUpComponent.cs create mode 100644 scripts/components/CollapsableComponent.cs create mode 100644 scripts/components/DestroyableComponent.cs create mode 100644 scripts/components/EffectInflictorComponent.cs create mode 100644 scripts/components/StatusEffectComponent.cs diff --git a/scripts/Resources/StatusEffectDataResource.cs b/scripts/Resources/StatusEffectDataResource.cs index 31ff410..3152115 100644 --- a/scripts/Resources/StatusEffectDataResource.cs +++ b/scripts/Resources/StatusEffectDataResource.cs @@ -4,5 +4,7 @@ namespace Mr.BrickAdventures.scripts.Resources; public partial class StatusEffectDataResource : Resource { + [Export] public float Duration { get; set; } = 1f; + [Export] public float DamagePerSecond { get; set; } = 0.25f; [Export] public StatusEffectType Type { get; set; } } \ No newline at end of file diff --git a/scripts/components/CanPickUpComponent.cs b/scripts/components/CanPickUpComponent.cs new file mode 100644 index 0000000..ca3fe8f --- /dev/null +++ b/scripts/components/CanPickUpComponent.cs @@ -0,0 +1,8 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class CanPickUpComponent : Node +{ + +} \ No newline at end of file diff --git a/scripts/components/CollapsableComponent.cs b/scripts/components/CollapsableComponent.cs new file mode 100644 index 0000000..9afc5a7 --- /dev/null +++ b/scripts/components/CollapsableComponent.cs @@ -0,0 +1,81 @@ +using System.Threading.Tasks; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class CollapsableComponent : Node +{ + [Export] public Timer ToCollapseTimer { get; set; } + [Export] public Timer ResetTimer { get; set; } + [Export] public Sprite2D Sprite2D { get; set; } + [Export] public CollisionShape2D CollisionShape { get; set; } + [Export] public float CollapseTime { get; set; } = 0.5f; + [Export] public float ResetTime { get; set; } = 0.5f; + [Export] public float AnimationTime { get; set; } = 0.25f; + + public override void _Ready() + { + ResetTimers(); + + ToCollapseTimer.Timeout += OnToCollapseTimerTimeout; + ResetTimer.Timeout += OnResetTimerTimeout; + } + + public void OnCollapsableDetectorBodyEntered(Node2D body) + { + ToCollapseTimer.Start(); + } + + public void OnCollapsableDetectorBodyExited(Node2D body) + { + var collapseTimeLeft = Mathf.Abs(ToCollapseTimer.TimeLeft - CollapseTime); + if (collapseTimeLeft < (0.1f * CollapseTime)) + { + ResetTimers(); + } + } + + private void OnToCollapseTimerTimeout() + { + _ = Collapse(); + } + + private void OnResetTimerTimeout() + { + _ = Reactivate(); + } + + private async Task Collapse() + { + ToCollapseTimer.Stop(); + ToCollapseTimer.SetWaitTime(CollapseTime); + + var tween = CreateTween(); + tween.TweenProperty(Sprite2D, "modulate:a", 0f, AnimationTime); + await ToSignal(tween, Tween.SignalName.Finished); + + CollisionShape?.CallDeferred("set_disabled", true); + ResetTimer.Start(); + } + + private async Task Reactivate() + { + ResetTimer.Stop(); + ResetTimer.SetWaitTime(ResetTime); + + var tween = CreateTween(); + tween.TweenProperty(Sprite2D, "modulate:a", 1f, AnimationTime); + await ToSignal(tween, Tween.SignalName.Finished); + + CollisionShape?.CallDeferred("set_disabled", false); + } + + private void ResetTimers() + { + ToCollapseTimer.Stop(); + ToCollapseTimer.SetWaitTime(CollapseTime); + + ResetTimer.Stop(); + ResetTimer.SetWaitTime(ResetTime); + } +} \ No newline at end of file diff --git a/scripts/components/DestroyableComponent.cs b/scripts/components/DestroyableComponent.cs new file mode 100644 index 0000000..abf2679 --- /dev/null +++ b/scripts/components/DestroyableComponent.cs @@ -0,0 +1,35 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class DestroyableComponent : Node2D +{ + [Export] public HealthComponent Health { get; set; } + [Export] public PackedScene DestroyEffect { get; set; } + + public override void _Ready() + { + if (Health == null) + { + GD.PushError("DestroyableComponent: HealthComponent is not set."); + return; + } + + Health.Death += OnHealthDeath; + } + + private void OnHealthDeath() + { + if (DestroyEffect == null) + { + Owner.QueueFree(); + return; + } + + var effect = DestroyEffect.Instantiate(); + Health.GetParent().AddChild(effect); + effect.SetGlobalPosition(Health.GlobalPosition); + Owner.QueueFree(); + } + +} \ No newline at end of file diff --git a/scripts/components/EffectInflictorComponent.cs b/scripts/components/EffectInflictorComponent.cs new file mode 100644 index 0000000..c741a04 --- /dev/null +++ b/scripts/components/EffectInflictorComponent.cs @@ -0,0 +1,27 @@ +using Godot; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class EffectInflictorComponent : Node +{ + [Export] public DamageComponent Damage { get; set; } + + public override void _Ready() + { + if (Damage == null) + { + GD.PushError("EffectInflictorComponent requires a DamageComponent to be set."); + return; + } + + Damage.EffectInflicted += OnEffectInflicted; + } + + private void OnEffectInflicted(Node2D target, StatusEffectDataResource effect) + { + var statusEffect = target.GetNodeOrNull("StatusEffectComponent"); + + statusEffect?.ApplyEffect(effect); + } +} \ No newline at end of file diff --git a/scripts/components/HealthComponent.cs b/scripts/components/HealthComponent.cs index 2473807..34fddb3 100644 --- a/scripts/components/HealthComponent.cs +++ b/scripts/components/HealthComponent.cs @@ -3,7 +3,7 @@ using Godot; namespace Mr.BrickAdventures.scripts.components; -public partial class HealthComponent : Node +public partial class HealthComponent : Node2D { [Export] public float Health { get; set; } = 1.0f; [Export] public float MaxHealth { get; set; } = 1.0f; diff --git a/scripts/components/StatusEffectComponent.cs b/scripts/components/StatusEffectComponent.cs new file mode 100644 index 0000000..ebada8f --- /dev/null +++ b/scripts/components/StatusEffectComponent.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Godot; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class StatusEffect : GodotObject +{ + public StatusEffectDataResource EffectData { get; set; } + public float ElapsedTime { get; set; } + public Timer Timer { get; set; } +} + +public partial class StatusEffectComponent : Node +{ + private List _activeEffects = []; + + [Signal] public delegate void EffectAppliedEventHandler(StatusEffect statusEffect); + [Signal] public delegate void EffectRemovedEventHandler(StatusEffectType type); + + public void ApplyEffect(StatusEffectDataResource effectData) + { + var data = effectData.Duplicate() as StatusEffectDataResource; + var timer = CreateTimer(effectData.Duration, data); + + var statusEffect = new StatusEffect + { + EffectData = data, + ElapsedTime = 0f, + Timer = timer + }; + _activeEffects.Add(statusEffect); + EmitSignalEffectApplied(statusEffect); + } + + public void RemoveEffect(StatusEffectType type) + { + var effectToRemove = _activeEffects.Find(effect => effect.EffectData.Type == type); + if (effectToRemove.EffectData == null) return; + _activeEffects.Remove(effectToRemove); + effectToRemove.Timer.QueueFree(); + EmitSignalEffectRemoved(type); + } + + private Timer CreateTimer(float duration, StatusEffectDataResource effectData) + { + var timer = new Timer(); + timer.SetWaitTime(duration); + timer.SetOneShot(true); + timer.SetAutostart(true); + timer.Timeout += () => RemoveEffect(effectData.Type); + AddChild(timer); + return timer; + } +} \ No newline at end of file -- 2.49.1 From aeb024cbac4892b4c1f5e7bb15038c4c68e0ceba Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 10 Aug 2025 14:50:18 +0200 Subject: [PATCH 05/16] Add new components: EnemyDeathComponent, EnemyWaveTriggerComponent, and ExitDoorComponent --- scripts/components/EnemyDeathComponent.cs | 42 +++++++++++++++ .../components/EnemyWaveTriggerComponent.cs | 51 +++++++++++++++++++ scripts/components/ExitDoorComponent.cs | 51 +++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 scripts/components/EnemyDeathComponent.cs create mode 100644 scripts/components/EnemyWaveTriggerComponent.cs create mode 100644 scripts/components/ExitDoorComponent.cs diff --git a/scripts/components/EnemyDeathComponent.cs b/scripts/components/EnemyDeathComponent.cs new file mode 100644 index 0000000..491af69 --- /dev/null +++ b/scripts/components/EnemyDeathComponent.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class EnemyDeathComponent : Node +{ + [Export] public float TweenDuration { get; set; } = 0.5f; + [Export] public CollisionShape2D CollisionShape { get; set; } + [Export] public HealthComponent Health { get; set; } + + public override void _Ready() + { + if (CollisionShape == null) + { + GD.PushError("EnemyDeathComponent: CollisionShape is not set."); + return; + } + + if (Health == null) + { + GD.PushError("EnemyDeathComponent: Health is not set."); + return; + } + + Health.Death += OnDeath; + } + + private void OnDeath() + { + CallDeferred(nameof(Die)); + } + + private async Task Die() + { + CollisionShape.SetDisabled(true); + var tween = CreateTween(); + tween.TweenProperty(Owner, "scale", Vector2.Zero, TweenDuration); + await ToSignal(tween, Tween.SignalName.Finished); + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/EnemyWaveTriggerComponent.cs b/scripts/components/EnemyWaveTriggerComponent.cs new file mode 100644 index 0000000..972454c --- /dev/null +++ b/scripts/components/EnemyWaveTriggerComponent.cs @@ -0,0 +1,51 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class EnemyWaveTriggerComponent : Node +{ + [Export] public Area2D Area2D { get; set; } + [Export] public PathFollow2D PathFollowNode { get; set; } + [Export] public float Speed { get; set; } = 100f; + [Export] public bool Loop { get; set; } = false; + [Export] public bool ActivateOnEnter { get; set; } = true; + + private bool _isActive = false; + + public override void _Ready() + { + Area2D.BodyEntered += OnBodyEntered; + + if (PathFollowNode == null) return; + + PathFollowNode.SetProgress(0f); + PathFollowNode.SetProcess(false); + } + + public override void _Process(double delta) + { + if (!_isActive || PathFollowNode == null) return; + + var progress = PathFollowNode.Progress; + progress += (float)(delta * Speed); + PathFollowNode.SetProgress(progress); + + if (!(PathFollowNode.ProgressRatio >= 1f) || Loop) return; + + _isActive = false; + PathFollowNode.SetProcess(false); + } + + private void OnBodyEntered(Node2D body) + { + if (ActivateOnEnter) StartWave(); + } + + private void StartWave() + { + if (PathFollowNode == null) return; + + PathFollowNode.SetProcess(true); + _isActive = true; + } +} \ No newline at end of file diff --git a/scripts/components/ExitDoorComponent.cs b/scripts/components/ExitDoorComponent.cs new file mode 100644 index 0000000..9eb2652 --- /dev/null +++ b/scripts/components/ExitDoorComponent.cs @@ -0,0 +1,51 @@ +using Godot; +using Mr.BrickAdventures.Autoloads; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ExitDoorComponent : Node +{ + [Export] public bool Locked { get; set; } = true; + [Export] public Area2D ExitArea { get; set; } + [Export] public Sprite2D DoorSprite { get; set; } + [Export] public AudioStreamPlayer2D OpenDoorSfx { get; set; } + [Export] public int OpenedDoorFrame { get; set; } = 0; + + [Signal] public delegate void ExitTriggeredEventHandler(); + + private GameManager _gameManager; + + public override void _Ready() + { + if (ExitArea == null) + { + GD.PushError("ExitDoorComponent: ExitArea is not set."); + return; + } + + ExitArea.BodyEntered += OnExitAreaBodyEntered; + + _gameManager = GetNode("/root/gameManager"); + } + + private void OnExitAreaBodyEntered(Node2D body) + { + throw new System.NotImplementedException(); + } + + private void Unlock() + { + Locked = false; + if (DoorSprite != null) + { + DoorSprite.Frame = OpenedDoorFrame; + } + + OpenDoorSfx?.Play(); + } + + private void GoToNextLevel() + { + _gameManager.OnLevelComplete(); + } +} \ No newline at end of file -- 2.49.1 From cd3143bbad0dd4ebd772d22e413cd289e7a723ba Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 10 Aug 2025 17:53:06 +0200 Subject: [PATCH 06/16] Add new components: ExplosiveComponent, FadeAwayComponent, FireEffectComponent, FlipComponent, GravityMotionComponent, LaunchComponent, and update PlatformMovement with LastDirection property --- scripts/components/ExplosiveComponent.cs | 81 ++++++++++++++++++++ scripts/components/FadeAwayComponent.cs | 32 ++++++++ scripts/components/FireEffectComponent.cs | 71 +++++++++++++++++ scripts/components/FlipComponent.cs | 36 +++++++++ scripts/components/GravityMotionComponent.cs | 37 +++++++++ scripts/components/LaunchComponent.cs | 24 ++++++ scripts/components/PlatformMovement.cs | 2 + 7 files changed, 283 insertions(+) create mode 100644 scripts/components/ExplosiveComponent.cs create mode 100644 scripts/components/FadeAwayComponent.cs create mode 100644 scripts/components/FireEffectComponent.cs create mode 100644 scripts/components/FlipComponent.cs create mode 100644 scripts/components/GravityMotionComponent.cs create mode 100644 scripts/components/LaunchComponent.cs diff --git a/scripts/components/ExplosiveComponent.cs b/scripts/components/ExplosiveComponent.cs new file mode 100644 index 0000000..907788a --- /dev/null +++ b/scripts/components/ExplosiveComponent.cs @@ -0,0 +1,81 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ExplosiveComponent : Node2D +{ + [Export] public DamageComponent Damage { get; set; } + [Export] public Area2D Area { get; set; } + [Export] public Area2D ExplodeArea { get; set; } + [Export] public PackedScene ExplosionEffect { get; set; } + [Export] public float TimeToExplode { get; set; } = 9f; + + [Signal] public delegate void OnExplosionEventHandler(Node2D body); + + private Timer _timer; + + public override void _Ready() + { + if (Damage != null) + { + GD.PushError("ExplosiveComponent: DamageComponent is not set."); + return; + } + + if (ExplodeArea != null) + { + GD.PushError("ExplosiveComponent: ExplodeArea is not set."); + return; + } + + Area.BodyEntered += OnAreaBodyEntered; + Area.AreaEntered += OnAreaAreaEntered; + } + + private void OnAreaAreaEntered(Area2D area) + { + Explode(); + } + + private void OnAreaBodyEntered(Node2D body) + { + Explode(); + } + + private void PrepareTimer() + { + _timer = new Timer(); + _timer.SetWaitTime(TimeToExplode); + _timer.OneShot = true; + _timer.Autostart = true; + _timer.Timeout += Explode; + AddChild(_timer); + } + + private void Explode() + { + _timer.Stop(); + + if (ExplosionEffect != null) + { + var explosionInstance = ExplosionEffect.Instantiate(); + if (Owner is Node2D root) explosionInstance.SetGlobalPosition(root.GlobalPosition); + GetTree().CurrentScene.AddChild(explosionInstance); + explosionInstance.SetEmitting(true); + } + + var bodies = ExplodeArea.GetOverlappingBodies(); + foreach (var body in bodies) + { + var health = body.GetNodeOrNull("HealthComponent"); + if (Damage != null && health != null) + { + Damage.DealDamage(health); + } + + EmitSignalOnExplosion(body); + } + + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/FadeAwayComponent.cs b/scripts/components/FadeAwayComponent.cs new file mode 100644 index 0000000..083d384 --- /dev/null +++ b/scripts/components/FadeAwayComponent.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class FadeAwayComponent : Node +{ + [Export] public Sprite2D Sprite { get; set; } + [Export] public float FadeDuration { get; set; } = 1f; + [Export] public float Speed { get; set; } = 10f; + [Export] public Vector2 Direction { get; set; } = Vector2.Up; + [Export] public Area2D Area { get; set; } + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private void OnBodyEntered(Node2D body) + { + _ = FadeAway(); + } + + private async Task FadeAway() + { + var tween = CreateTween().SetParallel(true); + tween.TweenProperty(Sprite, "modulate:a", 0f, FadeDuration); + tween.TweenProperty(Sprite, "position", Sprite.Position + (Direction * Speed), FadeDuration); + await ToSignal(tween, Tween.SignalName.Finished); + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/FireEffectComponent.cs b/scripts/components/FireEffectComponent.cs new file mode 100644 index 0000000..c89e571 --- /dev/null +++ b/scripts/components/FireEffectComponent.cs @@ -0,0 +1,71 @@ +using Godot; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class FireEffectComponent : Node +{ + [Export] public HealthComponent Health { get; set; } + [Export] public StatusEffectComponent StatusEffectComponent { get; set; } + [Export] public GpuParticles2D FireFX { get; set; } + + private StatusEffectDataResource _data = null; + private bool _shouldDealDamage = false; + private double _timeElapsed = 0f; + + public override void _Ready() + { + if (Health == null) + { + Health = GetNode("HealthComponent"); + } + if (StatusEffectComponent == null) + { + StatusEffectComponent = GetNode("StatusEffectComponent"); + } + + if (Health == null) + { + GD.PushError("FireEffectComponent: HealthComponent is not set."); + return; + } + if (StatusEffectComponent == null) + { + GD.PushError("FireEffectComponent: StatusEffectComponent is not set."); + return; + } + + StatusEffectComponent.EffectApplied += OnEffectApplied; + StatusEffectComponent.EffectRemoved += OnEffectRemoved; + } + + public override void _Process(double delta) + { + if (!_shouldDealDamage || _data == null || Health == null) return; + + _timeElapsed += delta; + if (_timeElapsed >= 1f) + { + Health.DecreaseHealth(_data.DamagePerSecond); + _timeElapsed = 0f; + } + } + + private void OnEffectApplied(StatusEffect statusEffect) + { + if (statusEffect.EffectData.Type != StatusEffectType.Fire) return; + + _data = statusEffect.EffectData; + _shouldDealDamage = true; + FireFX?.SetEmitting(true); + } + + private void OnEffectRemoved(StatusEffectType type) + { + if (type != StatusEffectType.Fire) return; + + _shouldDealDamage = false; + _data = null; + FireFX?.SetEmitting(false); + } +} \ No newline at end of file diff --git a/scripts/components/FlipComponent.cs b/scripts/components/FlipComponent.cs new file mode 100644 index 0000000..62e263d --- /dev/null +++ b/scripts/components/FlipComponent.cs @@ -0,0 +1,36 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class FlipComponent : Node2D +{ + [Export] public Sprite2D LeftEye { get; set; } + [Export] public Sprite2D RightEye { get; set; } + [Export] public PlatformMovement PlatformMovement { get; set; } + + public override void _Process(double delta) + { + if (PlatformMovement == null) return; + + var velocity = PlatformMovement.LastDirection; + switch (velocity.X) + { + case < 0f: + LeftEye.Frame = 1; + RightEye.Frame = 1; + LeftEye.FlipH = true; + RightEye.FlipH = true; + break; + case > 0f: + LeftEye.Frame = 1; + RightEye.Frame = 1; + LeftEye.FlipH = false; + RightEye.FlipH = false; + break; + default: + LeftEye.Frame = 0; + RightEye.Frame = 0; + break; + } + } +} \ No newline at end of file diff --git a/scripts/components/GravityMotionComponent.cs b/scripts/components/GravityMotionComponent.cs new file mode 100644 index 0000000..17066e6 --- /dev/null +++ b/scripts/components/GravityMotionComponent.cs @@ -0,0 +1,37 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class GravityMotionComponent : Node2D +{ + [Export] public CharacterBody2D Body { get; set; } + [Export] public LaunchComponent LaunchComponent { get; set; } + [Export] public Vector2 Gravity { get; set; } = new Vector2(0, 1000f); + [Export] public Vector2 TargetDirection { get; set; } = Vector2.Up; + + private Vector2 _velocity = Vector2.Zero; + + public override void _Ready() + { + if (LaunchComponent == null) return; + + var direction = LaunchComponent.InitialDirection.X > 0f ? TargetDirection : new Vector2(-TargetDirection.X, TargetDirection.Y); + direction = direction.Normalized(); + _velocity = direction * LaunchComponent.Speed; + } + + public override void _PhysicsProcess(double delta) + { + if (Body == null) return; + + _velocity += Gravity * (float)delta; + Body.Velocity = _velocity; + + Body.MoveAndSlide(); + + if (_velocity.LengthSquared() > 0.01f) + { + Body.Rotation = _velocity.Angle(); + } + } +} \ No newline at end of file diff --git a/scripts/components/LaunchComponent.cs b/scripts/components/LaunchComponent.cs new file mode 100644 index 0000000..af9fae4 --- /dev/null +++ b/scripts/components/LaunchComponent.cs @@ -0,0 +1,24 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class LaunchComponent : Node2D +{ + [Export] public Vector2 InitialDirection { get; set; } = Vector2.Right; + [Export] public float Speed { get; set; } = 16f; + [Export] public Vector2 SpawnPosition { get; set; } = Vector2.Zero; + [Export] public float SpawnRotation { get; set; } = 0f; + + public override void _Ready() + { + if (Owner is not Node2D root) return; + + root.GlobalPosition = SpawnPosition; + root.GlobalRotation = SpawnRotation; + } + + public Vector2 GetInitialVelocity() + { + return InitialDirection.Normalized() * Speed; + } +} \ No newline at end of file diff --git a/scripts/components/PlatformMovement.cs b/scripts/components/PlatformMovement.cs index 6775850..6cdd526 100644 --- a/scripts/components/PlatformMovement.cs +++ b/scripts/components/PlatformMovement.cs @@ -39,6 +39,8 @@ public partial class PlatformMovement : Node2D, IMovement private float _jumpGravity; private float _fallGravity; + public Vector2 LastDirection => _lastDirection; + public override void _Ready() { base._Ready(); -- 2.49.1 From 0e41c811e4ac71da7cea00d7076999d16bc19708 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 10 Aug 2025 22:38:19 +0200 Subject: [PATCH 07/16] Add new components: HealComponent, HitComponent, HomingMissileMotionComponent, LeverComponent, and TriggerLeverComponent --- scripts/components/HealComponent.cs | 48 ++++++++++++ scripts/components/HitComponent.cs | 75 +++++++++++++++++++ .../HomingMissileMotionComponent.cs | 64 ++++++++++++++++ scripts/components/LeverComponent.cs | 66 ++++++++++++++++ scripts/components/TriggerLeverComponent.cs | 8 ++ 5 files changed, 261 insertions(+) create mode 100644 scripts/components/HealComponent.cs create mode 100644 scripts/components/HitComponent.cs create mode 100644 scripts/components/HomingMissileMotionComponent.cs create mode 100644 scripts/components/LeverComponent.cs create mode 100644 scripts/components/TriggerLeverComponent.cs diff --git a/scripts/components/HealComponent.cs b/scripts/components/HealComponent.cs new file mode 100644 index 0000000..897a6a4 --- /dev/null +++ b/scripts/components/HealComponent.cs @@ -0,0 +1,48 @@ +using Godot; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class HealComponent : Node +{ + [Export] public GpuParticles2D HealFx { get; set; } + [Export] public CollectableComponent Collectable { get; set; } + + public override void _Ready() + { + if (Collectable == null) + { + GD.PushError("HealComponent: Collectable is not set."); + return; + } + + Collectable.Collected += OnCollected; + } + + private void OnCollected(Variant amount, CollectableType type, Node2D body) + { + if (type != CollectableType.Health) return; + + if (Collectable == null) return; + + var healthComponent = body.GetNodeOrNull("HealthComponent"); + if (healthComponent == null) return; + + var value = amount.AsSingle(); + healthComponent.IncreaseHealth(value); + if (HealFx != null) + { + PlayHealFx(); + } + + Owner.QueueFree(); + } + + private void PlayHealFx() + { + if (HealFx == null) return; + + HealFx.Restart(); + HealFx.Emitting = true; + } +} \ No newline at end of file diff --git a/scripts/components/HitComponent.cs b/scripts/components/HitComponent.cs new file mode 100644 index 0000000..d64f10e --- /dev/null +++ b/scripts/components/HitComponent.cs @@ -0,0 +1,75 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class HitComponent : Node +{ + [Export] public Sprite2D Sprite { get; set; } + [Export] public HealthComponent Health { get; set; } + [Export] public float HitDuration { get; set; } = 0.1f; + [Export] public GpuParticles2D HitFx { get; set; } + [Export] public bool FlashMode { get; set; } = true; + + public override void _Ready() + { + if (Health != null) + { + Health.HealthChanged += OnHealthChange; + Health.Death += OnDeath; + } + + if (Sprite == null) + { + GD.PushError("HitComponent: Sprite is null"); + return; + } + + if (Sprite.Material != null && FlashMode) + { + Sprite.Material = (Material)Sprite.Material.Duplicate(); + } + } + + private void Activate() + { + if (!FlashMode) return; + + Sprite.SetInstanceShaderParameter("enabled", true); + } + + private void Deactivate() + { + if (!FlashMode) return; + + Sprite.SetInstanceShaderParameter("enabled", false); + } + + private async void OnHealthChange(float delta, float totalHealth) + { + if (!(delta < 0f)) return; + + Activate(); + await ToSignal(GetTree().CreateTimer(HitDuration), Timer.SignalName.Timeout); + Deactivate(); + + if (totalHealth > 0f && delta < 0f) + { + HandleHitFx(); + } + } + + private async void OnDeath() + { + Activate(); + await ToSignal(GetTree().CreateTimer(HitDuration), Timer.SignalName.Timeout); + Deactivate(); + } + + private void HandleHitFx() + { + if (HitFx == null) return; + + HitFx.Restart(); + HitFx.Emitting = true; + } +} \ No newline at end of file diff --git a/scripts/components/HomingMissileMotionComponent.cs b/scripts/components/HomingMissileMotionComponent.cs new file mode 100644 index 0000000..bd41dd8 --- /dev/null +++ b/scripts/components/HomingMissileMotionComponent.cs @@ -0,0 +1,64 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class HomingMissileMotionComponent : Node +{ + [Export] public LaunchComponent Launch { get; set; } + [Export] public float MaxSpeed { get; set; } = 16f; + [Export] public float Acceleration { get; set; } = 8f; + [Export] public float MaxTurnRate { get; set; } = 180f; // degrees per second + [Export] public float WobbleStrength { get; set; } = 5f; // degrees + [Export] public float Drag { get; set; } = 0.98f; + [Export] public float SteeringLerp { get; set; } = 0.05f; // low = sluggish, high = responsive + [Export] public Area2D DetectionArea { get; set; } + + private Vector2 _steeringDirection = Vector2.Zero; + private Vector2 _velocity = Vector2.Zero; + private Node2D _target = null; + + public override void _Ready() + { + DetectionArea.BodyEntered += OnBodyEntered; + _velocity = Launch.GetInitialVelocity(); + } + + public override void _PhysicsProcess(double delta) + { + if (Launch == null) return; + if (Owner is not Node2D owner) return; + if (_target == null) + { + owner.Position += _velocity * (float)delta; + return; + } + + var toTarget = (_target.GlobalPosition - owner.GlobalPosition).Normalized(); + _steeringDirection = _steeringDirection.Lerp(toTarget, SteeringLerp); + + var angleToTarget = _velocity.AngleTo(_steeringDirection); + var maxAngle = Mathf.DegToRad(MaxTurnRate) * (float)delta; + var clampedAngle = Mathf.Clamp(angleToTarget, -maxAngle, maxAngle); + + var rng = new RandomNumberGenerator(); + var wobble = Mathf.DegToRad(rng.RandfRange(-WobbleStrength, WobbleStrength)); + clampedAngle += wobble; + + _velocity = _velocity.Rotated(clampedAngle); + _velocity *= Drag; + + var desiredSpeed = Mathf.Min(MaxSpeed, _velocity.Length() + Acceleration * (float)delta); + _velocity = _velocity.Normalized() * desiredSpeed; + + owner.Position += _velocity * (float)delta; + owner.Rotation = _velocity.Angle(); + } + + private void OnBodyEntered(Node2D body) + { + if (_target != null) return; + if (body == null) return; + + _target = body; + } +} \ No newline at end of file diff --git a/scripts/components/LeverComponent.cs b/scripts/components/LeverComponent.cs new file mode 100644 index 0000000..5c059ce --- /dev/null +++ b/scripts/components/LeverComponent.cs @@ -0,0 +1,66 @@ +using System.Threading.Tasks; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class LeverComponent : Node +{ + [Export] public Area2D Area { get; set; } + [Export] public Sprite2D Sprite { get; set; } + [Export] public int StartAnimationIndex { get; set; } = 0; + [Export] public float AnimationDuration { get; set; } = 0.5f; + [Export] public AudioStreamPlayer2D Sfx { get; set; } + + [Signal] + public delegate void ActivatedEventHandler(); + + public override void _Ready() + { + if (Area == null) + { + GD.PushError("LeverComponent: Area is not set."); + return; + } + + if (Sprite == null) + { + GD.PushError("LeverComponent: Sprite is not set."); + return; + } + + Area.BodyEntered += OnBodyEntered; + Area.AreaEntered += OnAreaEntered; + } + + private void OnAreaEntered(Area2D area) + { + HandleTriggerLogic(area); + } + + private void OnBodyEntered(Node2D body) + { + HandleTriggerLogic(body); + } + + private async Task Activate() + { + EmitSignalActivated(); + Sfx?.Play(); + Sprite.Frame = StartAnimationIndex + 1; + var timer = GetTree().CreateTimer(AnimationDuration); + await timer.ToSignal(timer, Timer.SignalName.Timeout); + Sprite.Frame = StartAnimationIndex; + } + + private void HandleTriggerLogic(Node2D obj) + { + var triggerLever = obj.GetNodeOrNull("TriggerLeverComponent"); + if (triggerLever == null) + { + GD.PushWarning("LeverComponent: TriggerLeverComponent not found in body."); + return; + } + + _ = Activate(); + } +} \ No newline at end of file diff --git a/scripts/components/TriggerLeverComponent.cs b/scripts/components/TriggerLeverComponent.cs new file mode 100644 index 0000000..50ca915 --- /dev/null +++ b/scripts/components/TriggerLeverComponent.cs @@ -0,0 +1,8 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class TriggerLeverComponent : Node +{ + +} \ No newline at end of file -- 2.49.1 From a859ff9fe78fc7401edee40965a2f06baf77d5e4 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 12 Aug 2025 03:38:23 +0200 Subject: [PATCH 08/16] Refactor GameManager session state handling and add new components: CanBeLaunchedComponent, IceEffectComponent, JumpPadComponent, KillPlayerOutOfScreenComponent, KnockbackComponent, LifetimeComponent, MagneticSkillComponent, OutOfScreenComponent, PeriodicShootingComponent, PlayerDeathComponent, ProgressiveDamageComponent, ProjectileComponent, ProjectileInitComponent, RequirementComponent, ScoreComponent, ShipMovementComponent, ShipShooterComponent, and SideToSideMovementComponent --- Autoloads/GameManager.cs | 20 +-- Mr. Brick Adventures.csproj | 4 +- Mr. Brick Adventures.sln.DotSettings.user | 2 + scripts/Resources/SkillData.cs | 6 +- scripts/components/CanBeLaunchedComponent.cs | 8 ++ scripts/components/FlipComponent.cs | 2 +- scripts/components/IceEffectComponent.cs | 70 +++++++++++ scripts/components/JumpPadComponent.cs | 39 ++++++ .../KillPlayerOutOfScreenComponent.cs | 21 ++++ scripts/components/KnockbackComponent.cs | 48 +++++++ scripts/components/LifetimeComponent.cs | 27 ++++ scripts/components/MagneticSkillComponent.cs | 78 ++++++++++++ scripts/components/OutOfScreenComponent.cs | 18 +++ .../components/PeriodicShootingComponent.cs | 71 +++++++++++ ...vement.cs => PlatformMovementComponent.cs} | 2 +- ...s.uid => PlatformMovementComponent.cs.uid} | 0 scripts/components/PlayerController.cs | 16 +-- scripts/components/PlayerDeathComponent.cs | 36 ++++++ .../components/ProgressiveDamageComponent.cs | 65 ++++++++++ scripts/components/ProjectileComponent.cs | 26 ++++ scripts/components/ProjectileInitComponent.cs | 37 ++++++ scripts/components/RequirementComponent.cs | 44 +++++++ scripts/components/ScoreComponent.cs | 42 +++++++ scripts/components/ShipMovementComponent.cs | 37 ++++++ scripts/components/ShipShooterComponent.cs | 59 +++++++++ .../components/SideToSideMovementComponent.cs | 117 +++++++++++++++++ scripts/components/platform_movement.gd | 118 ------------------ scripts/components/platform_movement.gd.uid | 1 - 28 files changed, 869 insertions(+), 145 deletions(-) create mode 100644 scripts/components/CanBeLaunchedComponent.cs create mode 100644 scripts/components/IceEffectComponent.cs create mode 100644 scripts/components/JumpPadComponent.cs create mode 100644 scripts/components/KillPlayerOutOfScreenComponent.cs create mode 100644 scripts/components/KnockbackComponent.cs create mode 100644 scripts/components/LifetimeComponent.cs create mode 100644 scripts/components/MagneticSkillComponent.cs create mode 100644 scripts/components/OutOfScreenComponent.cs create mode 100644 scripts/components/PeriodicShootingComponent.cs rename scripts/components/{PlatformMovement.cs => PlatformMovementComponent.cs} (98%) rename scripts/components/{PlatformMovement.cs.uid => PlatformMovementComponent.cs.uid} (100%) create mode 100644 scripts/components/PlayerDeathComponent.cs create mode 100644 scripts/components/ProgressiveDamageComponent.cs create mode 100644 scripts/components/ProjectileComponent.cs create mode 100644 scripts/components/ProjectileInitComponent.cs create mode 100644 scripts/components/RequirementComponent.cs create mode 100644 scripts/components/ScoreComponent.cs create mode 100644 scripts/components/ShipMovementComponent.cs create mode 100644 scripts/components/ShipShooterComponent.cs create mode 100644 scripts/components/SideToSideMovementComponent.cs delete mode 100644 scripts/components/platform_movement.gd delete mode 100644 scripts/components/platform_movement.gd.uid diff --git a/Autoloads/GameManager.cs b/Autoloads/GameManager.cs index 588fc65..40bf0a8 100644 --- a/Autoloads/GameManager.cs +++ b/Autoloads/GameManager.cs @@ -18,7 +18,7 @@ public partial class GameManager : Node { "unlocked_skills", new Array() } }; - private Dictionary _currentSessionState = new() + public Dictionary CurrentSessionState { get; private set; } = new() { { "coins_collected", 0 }, { "skills_unlocked", new Array() } @@ -31,19 +31,19 @@ public partial class GameManager : Node public void SetCoins(int amount) => PlayerState["coins"] = Mathf.Max(0, amount); - public int GetCoins() => (int)PlayerState["coins"] + (int)_currentSessionState["coins_collected"]; + public int GetCoins() => (int)PlayerState["coins"] + (int)CurrentSessionState["coins_collected"]; public void RemoveCoins(int amount) { - var sessionCoins = (int)_currentSessionState["coins_collected"]; + var sessionCoins = (int)CurrentSessionState["coins_collected"]; if (amount <= sessionCoins) { - _currentSessionState["coins_collected"] = sessionCoins - amount; + CurrentSessionState["coins_collected"] = sessionCoins - amount; } else { var remaining = amount - sessionCoins; - _currentSessionState["coins_collected"] = 0; + CurrentSessionState["coins_collected"] = 0; PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] - remaining); } PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"]); @@ -57,7 +57,7 @@ public partial class GameManager : Node public bool IsSkillUnlocked(SkillData skill) { return ((Array)PlayerState["unlocked_skills"]).Contains(skill) - || ((Array)_currentSessionState["skills_unlocked"]).Contains(skill); + || ((Array)CurrentSessionState["skills_unlocked"]).Contains(skill); } public void UnlockSkill(SkillData skill) @@ -123,7 +123,7 @@ public partial class GameManager : Node public void ResetCurrentSessionState() { - _currentSessionState = new Dictionary + CurrentSessionState = new Dictionary { { "coins_collected", 0 }, { "skills_unlocked", new Array() } @@ -172,8 +172,8 @@ public partial class GameManager : Node { var levelIndex = (int)PlayerState["current_level"]; MarkLevelComplete(levelIndex); - AddCoins((int)_currentSessionState["coins_collected"]); - foreach (var s in (Array)_currentSessionState["skills_unlocked"]) + AddCoins((int)CurrentSessionState["coins_collected"]); + foreach (var s in (Array)CurrentSessionState["skills_unlocked"]) UnlockSkill((SkillData)s); ResetCurrentSessionState(); @@ -184,7 +184,7 @@ public partial class GameManager : Node public Array GetUnlockedSkills() { var unlocked = (Array)PlayerState["unlocked_skills"]; - var session = (Array)_currentSessionState["skills_unlocked"]; + var session = (Array)CurrentSessionState["skills_unlocked"]; if ((((Array)session)!).Count == 0) return (Array)unlocked; if ((((Array)unlocked)!).Count == 0) return (Array)session; var joined = new Array(); diff --git a/Mr. Brick Adventures.csproj b/Mr. Brick Adventures.csproj index b7064e9..3fcf8b5 100644 --- a/Mr. Brick Adventures.csproj +++ b/Mr. Brick Adventures.csproj @@ -91,9 +91,7 @@ - - - + diff --git a/Mr. Brick Adventures.sln.DotSettings.user b/Mr. Brick Adventures.sln.DotSettings.user index e8e9aa0..38db013 100644 --- a/Mr. Brick Adventures.sln.DotSettings.user +++ b/Mr. Brick Adventures.sln.DotSettings.user @@ -1,4 +1,6 @@  + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded \ No newline at end of file diff --git a/scripts/Resources/SkillData.cs b/scripts/Resources/SkillData.cs index 2204c6f..a96a8b1 100644 --- a/scripts/Resources/SkillData.cs +++ b/scripts/Resources/SkillData.cs @@ -6,9 +6,9 @@ namespace Mr.BrickAdventures.scripts.Resources; public partial class SkillData : Resource { - [Export] public String Name { get; set; } = "New Skill"; - [Export] public String Description { get; set; } = "New Skill"; - [Export] public Dictionary Config { get; set; } = new(); + [Export] public string Name { get; set; } = "New Skill"; + [Export] public string Description { get; set; } = "New Skill"; + [Export] public Dictionary Config { get; set; } = new(); [Export] public int Cost { get; set; } = 0; [Export] public Texture2D Icon { get; set; } [Export] public bool IsActive { get; set; } = false; diff --git a/scripts/components/CanBeLaunchedComponent.cs b/scripts/components/CanBeLaunchedComponent.cs new file mode 100644 index 0000000..787c39a --- /dev/null +++ b/scripts/components/CanBeLaunchedComponent.cs @@ -0,0 +1,8 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class CanBeLaunchedComponent : Node +{ + +} \ No newline at end of file diff --git a/scripts/components/FlipComponent.cs b/scripts/components/FlipComponent.cs index 62e263d..69ce6d8 100644 --- a/scripts/components/FlipComponent.cs +++ b/scripts/components/FlipComponent.cs @@ -6,7 +6,7 @@ public partial class FlipComponent : Node2D { [Export] public Sprite2D LeftEye { get; set; } [Export] public Sprite2D RightEye { get; set; } - [Export] public PlatformMovement PlatformMovement { get; set; } + [Export] public PlatformMovementComponent PlatformMovement { get; set; } public override void _Process(double delta) { diff --git a/scripts/components/IceEffectComponent.cs b/scripts/components/IceEffectComponent.cs new file mode 100644 index 0000000..39d63e8 --- /dev/null +++ b/scripts/components/IceEffectComponent.cs @@ -0,0 +1,70 @@ +using Godot; +using Godot.Collections; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class IceEffectComponent : Node +{ + [Export] public Array ComponentsToDisable { get; set; } = []; + [Export] public StatusEffectComponent StatusEffectComponent { get; set; } + [Export] public Node2D IceFx { get; set; } + + private StatusEffectDataResource _data = null; + private int _iceEffectsApplied = 0; + + public override void _Ready() + { + StatusEffectComponent.EffectApplied += OnEffectApplied; + StatusEffectComponent.EffectRemoved += OnEffectRemoved; + } + + private void OnEffectApplied(StatusEffect statusEffect) + { + if (statusEffect.EffectData.Type != StatusEffectType.Ice) return; + + _data = statusEffect.EffectData; + _iceEffectsApplied++; + ApplyFreeze(); + } + + private void OnEffectRemoved(StatusEffectType type) + { + if (type != StatusEffectType.Ice) return; + _data = null; + _iceEffectsApplied--; + RemoveFreeze(); + } + + private void ApplyFreeze() + { + if (IceFx != null) + { + IceFx.Visible = true; + } + + foreach (var component in ComponentsToDisable) + { + if (component == null || _iceEffectsApplied == 0) continue; + + component.ProcessMode = ProcessModeEnum.Disabled; + } + } + + private void RemoveFreeze() + { + if (_iceEffectsApplied > 0) return; + + if (IceFx != null) + { + IceFx.Visible = false; + } + + foreach (var component in ComponentsToDisable) + { + if (component == null) continue; + + component.ProcessMode = ProcessModeEnum.Inherit; + } + } +} \ No newline at end of file diff --git a/scripts/components/JumpPadComponent.cs b/scripts/components/JumpPadComponent.cs new file mode 100644 index 0000000..e203bf0 --- /dev/null +++ b/scripts/components/JumpPadComponent.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class JumpPadComponent : Node +{ + [Export] public float JumpForce { get; set; } = 10f; + [Export] public Area2D Area { get; set; } + [Export] public Sprite2D Sprite { get; set; } + [Export] public int StartAnimationIndex { get; set; } = 0; + [Export] public float AnimationDuration { get; set; } = 0.5f; + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private void OnBodyEntered(Node2D body) + { + var canBeLaunched = body.GetNodeOrNull("CanBeLaunchedComponent"); + if (canBeLaunched == null) return; + + if (body is not PlayerController { CurrentMovement: PlatformMovementComponent movement }) return; + _ = HandleLaunchPadAnimation(); + movement.Body.Velocity = new Vector2(movement.Body.Velocity.X, -JumpForce); + movement.JumpSfx?.Play(); + } + + private async Task HandleLaunchPadAnimation() + { + if (Sprite == null) return; + + var timer = GetTree().CreateTimer(AnimationDuration); + Sprite.Frame = StartAnimationIndex + 1; + await ToSignal(timer, Timer.SignalName.Timeout); + Sprite.Frame = StartAnimationIndex; + } +} \ No newline at end of file diff --git a/scripts/components/KillPlayerOutOfScreenComponent.cs b/scripts/components/KillPlayerOutOfScreenComponent.cs new file mode 100644 index 0000000..9d039e7 --- /dev/null +++ b/scripts/components/KillPlayerOutOfScreenComponent.cs @@ -0,0 +1,21 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class KillPlayerOutOfScreenComponent : Node +{ + [Export] public VisibleOnScreenNotifier2D ScreenNotifier { get; set; } + [Export] public HealthComponent HealthComponent { get; set; } + + private const float Damage = 6000f; + + public override void _Ready() + { + ScreenNotifier.ScreenExited += HandleOutOfScreen; + } + + private void HandleOutOfScreen() + { + HealthComponent?.DecreaseHealth(Damage); + } +} \ No newline at end of file diff --git a/scripts/components/KnockbackComponent.cs b/scripts/components/KnockbackComponent.cs new file mode 100644 index 0000000..588625a --- /dev/null +++ b/scripts/components/KnockbackComponent.cs @@ -0,0 +1,48 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class KnockbackComponent : Node +{ + [Export] public CharacterBody2D Body { get; set; } + [Export] public float KnockbackForce { get; set; } = 25f; + [Export] public HealthComponent HealthComponent { get; set; } + + private bool _knockbackMode = false; + private int _knockbackFrames = 0; + + public override void _Ready() + { + HealthComponent.HealthChanged += OnHealthChanged; + } + + public override void _Process(double delta) + { + if (_knockbackMode) _knockbackFrames++; + + if (_knockbackFrames <= 1) return; + + _knockbackMode = false; + _knockbackFrames = 0; + } + + public override void _PhysicsProcess(double delta) + { + if (_knockbackMode) ApplyKnockback((float)delta); + } + + private void OnHealthChanged(float delta, float totalHealth) + { + if (totalHealth <= 0f || delta >= 0f) return; + + _knockbackMode = true; + } + + private void ApplyKnockback(float delta) + { + var velocity = Body.Velocity.Normalized(); + var knockbackDirection = new Vector2(Mathf.Sign(velocity.X), 0.4f); + var knockbackVector = -knockbackDirection * KnockbackForce * delta; + Body.Velocity += knockbackVector; + } +} \ No newline at end of file diff --git a/scripts/components/LifetimeComponent.cs b/scripts/components/LifetimeComponent.cs new file mode 100644 index 0000000..50929e0 --- /dev/null +++ b/scripts/components/LifetimeComponent.cs @@ -0,0 +1,27 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class LifetimeComponent : Node +{ + [Export] public float LifeTime { get; set; } = 5.0f; + + private Timer _lifetimeTimer; + + public override void _Ready() + { + _lifetimeTimer = new Timer(); + _lifetimeTimer.WaitTime = LifeTime; + _lifetimeTimer.OneShot = true; + _lifetimeTimer.Autostart = true; + _lifetimeTimer.Timeout += OnLifetimeTimeout; + + AddChild(_lifetimeTimer); + _lifetimeTimer.Start(); + } + + private void OnLifetimeTimeout() + { + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/MagneticSkillComponent.cs b/scripts/components/MagneticSkillComponent.cs new file mode 100644 index 0000000..3d92090 --- /dev/null +++ b/scripts/components/MagneticSkillComponent.cs @@ -0,0 +1,78 @@ +using System; +using Godot; +using Godot.Collections; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class MagneticSkillComponent : Node +{ + [Export] public Area2D MagneticArea { get; set; } + [Export] public float MagneticMoveDuration { get; set; } = 1.25f; + + private Array _collectablesToPickUp = []; + + public override void _Ready() + { + MagneticArea.AreaEntered += OnAreaEntered; + MagneticArea.BodyEntered += OnBodyEntered; + } + + public override void _Process(double delta) + { + foreach (var collectable in _collectablesToPickUp) + { + if (!IsInstanceValid(collectable)) + { + _collectablesToPickUp.Remove(collectable); + continue; + } + + MoveCollectableToOwner(collectable); + } + } + + private void OnBodyEntered(Node2D body) + { + if (!HasComponentInChildren(body, "Collectable")) return; + + if (_collectablesToPickUp.Contains(body)) return; + _collectablesToPickUp.Add(body); + } + + private void OnAreaEntered(Area2D area) + { + if (!HasComponentInChildren(area, "Collectable")) return; + + if (_collectablesToPickUp.Contains(area)) return; + _collectablesToPickUp.Add(area); + } + + private bool HasComponentInChildren(Node node, string componentName) + { + if (node == null) return false; + + if (node.HasNode(componentName)) return true; + + foreach (var child in node.GetChildren()) + { + if (child is { } childNode && HasComponentInChildren(childNode, componentName)) + { + return true; + } + } + + return false; + } + + private void MoveCollectableToOwner(Node2D collectable) + { + if (!IsInstanceValid(collectable)) return; + + if (Owner is not Node2D root) return; + + var direction = (root.GlobalPosition - collectable.GlobalPosition).Normalized(); + var speed = direction.Length() / MagneticMoveDuration; + + collectable.GlobalPosition += direction.Normalized() * speed; + } +} \ No newline at end of file diff --git a/scripts/components/OutOfScreenComponent.cs b/scripts/components/OutOfScreenComponent.cs new file mode 100644 index 0000000..be71168 --- /dev/null +++ b/scripts/components/OutOfScreenComponent.cs @@ -0,0 +1,18 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class OutOfScreenComponent : Node +{ + [Export] public VisibleOnScreenNotifier2D VisibilityNotifier { get; set; } + + public override void _Ready() + { + VisibilityNotifier.ScreenExited += OnScreenExited; + } + + private void OnScreenExited() + { + Owner?.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/PeriodicShootingComponent.cs b/scripts/components/PeriodicShootingComponent.cs new file mode 100644 index 0000000..13efd59 --- /dev/null +++ b/scripts/components/PeriodicShootingComponent.cs @@ -0,0 +1,71 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class PeriodicShootingComponent : Node +{ + [Export] public PackedScene BulletScene { get; set; } + [Export] public float ShootInterval { get; set; } = 1.0f; + [Export] public Vector2 ShootDirection { get; set; } = Vector2.Right; + [Export] public SideToSideMovementComponent SideToSideMovement { get; set; } + [Export] public Node2D BulletSpawnRight { get; set; } + [Export] public Node2D BulletSpawnLeft { get; set; } + [Export] public float ShootingIntervalVariation { get; set; } = 0.0f; + + private Timer _timer; + + public override void _Ready() + { + SetupTimer(); + } + + public override void _Process(double delta) + { + if (SideToSideMovement == null) return; + + ShootDirection = SideToSideMovement.Direction != Vector2.Zero ? SideToSideMovement.Direction : Vector2.Right; + } + + private void SetupTimer() + { + _timer = new Timer(); + _timer.WaitTime = GetShootInterval(); + _timer.OneShot = false; + _timer.Autostart = true; + _timer.Timeout += OnTimerTimeout; + AddChild(_timer); + } + + private void OnTimerTimeout() + { + Shoot(); + _timer.Start(); + } + + private double GetShootInterval() + { + if (ShootingIntervalVariation == 0f) return ShootInterval; + + var rng = new RandomNumberGenerator(); + return ShootInterval + rng.RandfRange(-ShootingIntervalVariation, ShootingIntervalVariation); + } + + private void Shoot() + { + if (ShootDirection == Vector2.Zero) return; + + var root = Owner as Node2D; + var bulletInstance = BulletScene.Instantiate(); + var launchComponent = bulletInstance.GetNodeOrNull("LaunchComponent"); + var spawnPosition = ShootDirection == Vector2.Right ? BulletSpawnRight.GlobalPosition : BulletSpawnLeft.GlobalPosition; + if (launchComponent != null) + { + launchComponent.InitialDirection = ShootDirection; + launchComponent.SpawnPosition = spawnPosition; + if (root != null) launchComponent.SpawnRotation = root.Rotation; + } + + bulletInstance.Position = spawnPosition; + GetTree().CurrentScene.AddChild(bulletInstance); + } +} \ No newline at end of file diff --git a/scripts/components/PlatformMovement.cs b/scripts/components/PlatformMovementComponent.cs similarity index 98% rename from scripts/components/PlatformMovement.cs rename to scripts/components/PlatformMovementComponent.cs index 6cdd526..8f4f7c6 100644 --- a/scripts/components/PlatformMovement.cs +++ b/scripts/components/PlatformMovementComponent.cs @@ -3,7 +3,7 @@ using Mr.BrickAdventures.scripts.interfaces; namespace Mr.BrickAdventures.scripts.components; -public partial class PlatformMovement : Node2D, IMovement +public partial class PlatformMovementComponent : Node2D, IMovement { [Export] public float Speed { get; set; } = 300.0f; diff --git a/scripts/components/PlatformMovement.cs.uid b/scripts/components/PlatformMovementComponent.cs.uid similarity index 100% rename from scripts/components/PlatformMovement.cs.uid rename to scripts/components/PlatformMovementComponent.cs.uid diff --git a/scripts/components/PlayerController.cs b/scripts/components/PlayerController.cs index 1f2bf5b..3006488 100644 --- a/scripts/components/PlayerController.cs +++ b/scripts/components/PlayerController.cs @@ -15,7 +15,7 @@ public partial class PlayerController : Node2D [Export] public Sprite2D ShipSprite { get; set; } - private IMovement _currentMovement = null; + public IMovement CurrentMovement = null; [Signal] public delegate void MovementSwitchedEventHandler(string movementType); @@ -48,20 +48,20 @@ public partial class PlayerController : Node2D private void SwitchMovement(string movementType) { - if (_currentMovement != null) + if (CurrentMovement != null) { - _currentMovement.Enabled = false; + CurrentMovement.Enabled = false; } if (MovementTypes.TryGetValue(movementType, out var movement)) { - _currentMovement = GetNodeOrNull(movement); - if (_currentMovement == null) + CurrentMovement = GetNodeOrNull(movement); + if (CurrentMovement == null) { GD.PushError($"Movement type '{movementType}' not found in MovementTypes."); return; } - _currentMovement.Enabled = true; + CurrentMovement.Enabled = true; EmitSignalMovementSwitched(movementType); } else @@ -69,7 +69,7 @@ public partial class PlayerController : Node2D GD.PushError($"Movement type '{movementType}' not found in MovementTypes."); } - if (_currentMovement == null) + if (CurrentMovement == null) { GD.PushError("No current movement set after switching."); } @@ -78,7 +78,7 @@ public partial class PlayerController : Node2D private string GetNextMovementType() { var keys = new List(MovementTypes.Keys); - var currentIndex = keys.IndexOf(_currentMovement?.MovementType); + var currentIndex = keys.IndexOf(CurrentMovement?.MovementType); if (currentIndex == -1) { diff --git a/scripts/components/PlayerDeathComponent.cs b/scripts/components/PlayerDeathComponent.cs new file mode 100644 index 0000000..2c62493 --- /dev/null +++ b/scripts/components/PlayerDeathComponent.cs @@ -0,0 +1,36 @@ +using Godot; +using Mr.BrickAdventures.Autoloads; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class PlayerDeathComponent : Node2D +{ + [Export] public AudioStreamPlayer2D DeathSfx { get; set; } + [Export] public PackedScene DeathEffect { get; set; } + [Export] public HealthComponent HealthComponent { get; set; } + [Export] public Vector2 EffectScale { get; set; } = new Vector2(1.5f, 1.5f); + + private GameManager _gameManager; + + public override void _Ready() + { + _gameManager = GetNode("/root/gameManager"); + HealthComponent.Death += OnDeath; + } + + private void OnDeath() + { + DeathSfx?.Play(); + + if (DeathEffect != null) + { + var effect = DeathEffect.Instantiate(); + GetParent().AddChild(effect); + effect.GlobalPosition = GlobalPosition; + effect.Scale = EffectScale; + } + + _gameManager.RemoveLives(1); + _gameManager.ResetCurrentSessionState(); + } +} \ No newline at end of file diff --git a/scripts/components/ProgressiveDamageComponent.cs b/scripts/components/ProgressiveDamageComponent.cs new file mode 100644 index 0000000..ebd6b63 --- /dev/null +++ b/scripts/components/ProgressiveDamageComponent.cs @@ -0,0 +1,65 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ProgressiveDamageComponent : Node +{ + [Export] public HealthComponent HealthComponent { get; set; } + [Export] public Sprite2D Sprite { get; set; } + [Export] public PlatformMovementComponent PlatformMovement { get; set; } + [Export] public float MinJumpHeight { get; set; } = 60f; + [Export] public float JumpReductionPercentage { get; set; } = 0.1f; // this is a percentage of the jump height per hit + + private float _maxHealth; + private float _ogJumpHeight; + + public override void _Ready() + { + _maxHealth = HealthComponent.MaxHealth; + HealthComponent.HealthChanged += OnHealthChanged; + + if (PlatformMovement != null) + { + _ogJumpHeight = PlatformMovement.JumpHeight; + } + } + + private void OnHealthChanged(float delta, float totalHealth) + { + var frame = GetDamageFrame(); + if (frame < 0 || frame >= Sprite.GetHframes()) return; + + Sprite.Frame = frame; + if (PlatformMovement != null) + { + PlatformMovement.JumpHeight = GetJumpHeight(); + } + } + + private int GetDamageFrame() + { + if (Sprite == null || HealthComponent == null) return 0; + + var framesCount = Sprite.GetHframes(); + if (framesCount == 0) return 0; + + var currentHealth = HealthComponent.Health; + var healthRatio = currentHealth / _maxHealth; + return (int)(framesCount * (1f - healthRatio)); + } + + private float GetJumpHeight() + { + if (PlatformMovement == null) return 0f; + + var jumpHeight = _ogJumpHeight; + if (jumpHeight <= 0f) return 0f; + + var damageFrame = GetDamageFrame(); + if (damageFrame < 0 || damageFrame >= Sprite.GetHframes()) return jumpHeight; + + var reduction = JumpReductionPercentage * jumpHeight; + var calculatedJumpHeight = jumpHeight - (damageFrame * reduction); + return Mathf.Max(calculatedJumpHeight, MinJumpHeight); + } +} \ No newline at end of file diff --git a/scripts/components/ProjectileComponent.cs b/scripts/components/ProjectileComponent.cs new file mode 100644 index 0000000..5741ca2 --- /dev/null +++ b/scripts/components/ProjectileComponent.cs @@ -0,0 +1,26 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ProjectileComponent : Node2D +{ + [Export] public float Speed { get; set; } = 16f; + [Export] public float AngleDirection { get; set; } + [Export] public Vector2 SpawnPosition { get; set; } = Vector2.Zero; + [Export] public float SpawnRotation { get; set; } = 0f; + [Export] public CharacterBody2D Body { get; set; } + + public override void _Ready() + { + GlobalPosition = SpawnPosition; + GlobalRotation = SpawnRotation; + } + + public override void _PhysicsProcess(double delta) + { + if (Body == null) return; + + Body.Velocity += new Vector2(0f, -Speed).Rotated(AngleDirection); + Body.MoveAndSlide(); + } +} \ No newline at end of file diff --git a/scripts/components/ProjectileInitComponent.cs b/scripts/components/ProjectileInitComponent.cs new file mode 100644 index 0000000..4821ad6 --- /dev/null +++ b/scripts/components/ProjectileInitComponent.cs @@ -0,0 +1,37 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ProjectileInitParams +{ + public Vector2 Position { get; set; } = Vector2.Zero; + public Vector2 Direction { get; set; } = Vector2.Right; + public float Rotation { get; set; } = 0f; + public float PowerMultiplier { get; set; } = 1f; +} + +public partial class ProjectileInitComponent : Node +{ + [Export] public LaunchComponent LaunchComponent { get; set; } + + public void Initialize(ProjectileInitParams p) + { + var position = p.Position; + var direction = p.Direction; + var rotation = p.Rotation; + var power = p.PowerMultiplier; + + if (Owner is Node2D root) + { + root.GlobalPosition = position; + root.GlobalRotation = rotation; + } + + if (LaunchComponent == null) return; + + LaunchComponent.InitialDirection = direction; + LaunchComponent.SpawnPosition = position; + LaunchComponent.SpawnRotation = rotation; + LaunchComponent.Speed *= power; + } +} \ No newline at end of file diff --git a/scripts/components/RequirementComponent.cs b/scripts/components/RequirementComponent.cs new file mode 100644 index 0000000..70c83d7 --- /dev/null +++ b/scripts/components/RequirementComponent.cs @@ -0,0 +1,44 @@ +using Godot; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class RequirementComponent : Node +{ + [Export] public CollectableType RequirementType { get; set; } + [Export] public int RequirementAmount { get; set; } = 1; + + private int _currentAmount = 0; + + private const string CollectableGroupName = "Collectables"; + + [Signal] + public delegate void RequirementMetEventHandler(CollectableType requirementType); + + public override void _Ready() + { + var collectables = GetTree().GetNodesInGroup(CollectableGroupName); + foreach (var collectable in collectables) + { + var c = collectable.GetNodeOrNull("CollectableComponent"); + if (c != null && c.Data.Type == RequirementType) + { + c.Collected += OnCollected; + } + } + } + + private void OnCollected(Variant amount, CollectableType type, Node2D body) + { + AddProgress(amount.As()); + } + + private void AddProgress(int amount = 1) + { + _currentAmount += amount; + if (_currentAmount >= RequirementAmount) + { + EmitSignalRequirementMet(RequirementType); + } + } +} \ No newline at end of file diff --git a/scripts/components/ScoreComponent.cs b/scripts/components/ScoreComponent.cs new file mode 100644 index 0000000..c612990 --- /dev/null +++ b/scripts/components/ScoreComponent.cs @@ -0,0 +1,42 @@ +using Godot; +using Mr.BrickAdventures.Autoloads; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ScoreComponent : Node +{ + private GameManager _gameManager; + private const string CoinsGroupName = "Coins"; + + public override async void _Ready() + { + await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); + + _gameManager = GetNode("/root/GameManager"); + if (_gameManager == null) + { + GD.PrintErr("GameManager not found in the scene tree."); + return; + } + + var coins = GetTree().GetNodesInGroup("Coins"); + foreach (var coin in coins) + { + var c = coin.GetNodeOrNull("CollectableComponent"); + if (c != null) + { + c.Collected += OnCollected; + } + } + } + + private void OnCollected(Variant amount, CollectableType type, Node2D body) + { + if (type != CollectableType.Coin) return; + + var coinAmount = amount.As(); + var currentCoins = (int)_gameManager.CurrentSessionState["coins_collected"]; + _gameManager.CurrentSessionState["coins_collected"] = currentCoins + coinAmount; + } +} \ No newline at end of file diff --git a/scripts/components/ShipMovementComponent.cs b/scripts/components/ShipMovementComponent.cs new file mode 100644 index 0000000..547da4c --- /dev/null +++ b/scripts/components/ShipMovementComponent.cs @@ -0,0 +1,37 @@ +using Godot; +using Mr.BrickAdventures.scripts.interfaces; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ShipMovementComponent : Node, IMovement +{ + [Export] public float MaxSpeed { get; set; } = 200f; + [Export] public float Acceleration { get; set; } = 100f; + [Export] public float Friction { get; set; } = 50f; + [Export] public CharacterBody2D Body { get; set; } + + public string MovementType { get; } = "ship"; + public bool Enabled { get; set; } + public Vector2 PreviousVelocity { get; set; } + + private Vector2 _velocity = Vector2.Zero; + + public Vector2 Velocity => _velocity; + + public override void _PhysicsProcess(double delta) + { + if (Body == null || !Enabled) return; + + var inputVector = new Vector2( + Input.GetActionStrength("right") - Input.GetActionStrength("left"), + Input.GetActionStrength("down") - Input.GetActionStrength("up") + ).Normalized(); + + _velocity = inputVector != Vector2.Zero ? _velocity.MoveToward(inputVector * MaxSpeed, Acceleration * (float)delta) : _velocity.MoveToward(Vector2.Zero, Friction * (float)delta); + + _velocity = _velocity.LimitLength(MaxSpeed); + Body.Velocity = _velocity; + PreviousVelocity = Body.Velocity; + Body.MoveAndSlide(); + } +} \ No newline at end of file diff --git a/scripts/components/ShipShooterComponent.cs b/scripts/components/ShipShooterComponent.cs new file mode 100644 index 0000000..cb6d4d9 --- /dev/null +++ b/scripts/components/ShipShooterComponent.cs @@ -0,0 +1,59 @@ +using System.Threading.Tasks; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ShipShooterComponent : Node +{ + [Export] public PackedScene BulletScene { get; set; } + [Export] public float FireRate { get; set; } = 0.2f; + [Export] public Marker2D BulletSpawn { get; set; } + [Export] public AudioStreamPlayer2D ShootSfx { get; set; } + + private bool _canShoot = false; + + public override void _Ready() + { + SetProcess(false); + } + + public override void _Process(double delta) + { + if (Input.IsActionJustPressed("attack") && _canShoot) + { + _ = Shoot(); + } + } + + private async Task Shoot() + { + if (!_canShoot) return; + + var bullet = BulletScene.Instantiate(); + var init = bullet.GetNodeOrNull("ProjectileInitComponent"); + init?.Initialize(new ProjectileInitParams() + { + Position = BulletSpawn.GlobalPosition, + }); + + GetTree().CurrentScene.AddChild(bullet); + ShootSfx?.Play(); + + _canShoot = false; + await ToSignal(GetTree().CreateTimer(FireRate), Timer.SignalName.Timeout); + _canShoot = true; + } + + private void OnShipEntered() + { + _canShoot = true; + SetProcess(true); + } + + private void OnShipExited() + { + _canShoot = false; + SetProcess(false); + ShootSfx?.Stop(); + } +} \ No newline at end of file diff --git a/scripts/components/SideToSideMovementComponent.cs b/scripts/components/SideToSideMovementComponent.cs new file mode 100644 index 0000000..bfad24f --- /dev/null +++ b/scripts/components/SideToSideMovementComponent.cs @@ -0,0 +1,117 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SideToSideMovementComponent : Node +{ + [Export] public Sprite2D Sprite { get; set; } + [Export] public float Speed { get; set; } = 10.0f; + [Export] public float WaitTime { get; set; } = 1.0f; + [Export] public RayCast2D LeftRay { get; set; } + [Export] public RayCast2D RightRay { get; set; } + [Export] public RayCast2D LeftWallRay { get; set; } + [Export] public RayCast2D RightWallRay { get; set; } + + private Vector2 _direction = Vector2.Left; + private Vector2 _newDirection = Vector2.Left; + private Timer _timer; + private bool _triggeredDirectionChange = false; + + [Signal] + public delegate void DirectionChangedEventHandler(); + + public Vector2 Direction => _direction; + + public override void _Ready() + { + SetupTimer(); + DirectionChanged += OnDirectionChanged; + } + + public override void _PhysicsProcess(double delta) + { + HandleDirection(); + HandleSpriteFlip(); + HandleMovement(delta); + } + + private void HandleDirection() + { + // Check if we are colliding with the left wall + if (LeftWallRay.IsColliding()) + { + _newDirection = Vector2.Right; + EmitSignalDirectionChanged(); + return; + } + + // Check if we are colliding with the right wall + if (RightWallRay.IsColliding()) + { + _newDirection = Vector2.Left; + EmitSignalDirectionChanged(); + return; + } + // We are not colliding with anything, which means we don't have ground to walk on. Stop moving. + if (!LeftRay.IsColliding() && !RightRay.IsColliding()) + { + _newDirection = Vector2.Zero; + return; + } + + // If the left ray is not colliding and the right ray is colliding, that means we have ground to the right and we should change direction to the right. + if (!LeftRay.IsColliding() && RightRay.IsColliding()) + { + _newDirection = Vector2.Right; + EmitSignalDirectionChanged(); + return; + } + + // If the right ray is not colliding and the left ray is colliding, that means we have ground to the left and we should change direction to the left. + if (!RightRay.IsColliding() && LeftRay.IsColliding()) + { + _newDirection = Vector2.Left; + EmitSignalDirectionChanged(); + return; + } + } + + private void HandleSpriteFlip() + { + Sprite.FlipH = _direction == Vector2.Left; + } + + private void HandleMovement(double delta) + { + var root = Owner as Node2D; + if (root == null) return; + + root.Position += _direction * Speed * (float)delta; + } + + private void OnDirectionChanged() + { + if (_direction == _newDirection || _triggeredDirectionChange) + return; + + _triggeredDirectionChange = true; + _direction = Vector2.Zero; + _timer.Start(); + } + + private void OnTimerTimeout() + { + _timer.Stop(); + _direction = _newDirection; + _triggeredDirectionChange = false; + } + + private void SetupTimer() + { + _timer = new Timer(); + AddChild(_timer); + _timer.WaitTime = WaitTime; + _timer.OneShot = true; + _timer.Timeout += OnTimerTimeout; + } +} \ No newline at end of file diff --git a/scripts/components/platform_movement.gd b/scripts/components/platform_movement.gd deleted file mode 100644 index 38773da..0000000 --- a/scripts/components/platform_movement.gd +++ /dev/null @@ -1,118 +0,0 @@ -class_name PlatformMovement -extends PlayerMovement - -@export var speed: float = 300.0 -@export var jump_height: float = 100 -@export var jump_time_to_peak: float = 0.5 -@export var jump_time_to_descent: float = 0.4 -@export var coyote_frames: int = 6 -@export var jump_sfx: AudioStreamPlayer2D -@export var rotation_target: Node2D -@export var body: CharacterBody2D - -var gravity = ProjectSettings.get_setting("physics/2d/default_gravity") -var was_last_floor := false -var coyote_mode := false -var coyote_timer: Timer -var last_direction := Vector2.RIGHT - -@onready var jump_velocity: float = ((2.0 * jump_height) / jump_time_to_peak) * -1.0 -@onready var jump_gravity: float = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0 -@onready var fall_gravity: float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0 - - -func _ready() -> void: - if not body: - return - - coyote_timer = Timer.new() - coyote_timer.one_shot = true - coyote_timer.wait_time = coyote_frames / 60.0 - coyote_timer.timeout.connect(on_coyote_timer_timeout) - add_child(coyote_timer) - - -func _process(_delta: float) -> void: - if not body or not enabled: - return - - if body.velocity.x > 0.0: - rotation_target.rotation = deg_to_rad(-10) - elif body.velocity.x < 0.0: - rotation_target.rotation = deg_to_rad(10) - else: - rotation_target.rotation = 0 - - calculate_jump_vars() - - -func _physics_process(delta) -> void: - if not body or not enabled: - return - - if body.is_on_floor(): - was_last_floor = true - coyote_mode = false # Reset coyote mode when back on the floor - coyote_timer.stop() # Stop timer when grounded - else: - if was_last_floor: # Start coyote timer only once - coyote_mode = true - coyote_timer.start() - was_last_floor = false - - if not body.is_on_floor(): - body.velocity.y += calculate_gravity() * delta - - if Input.is_action_pressed("jump") and (body.is_on_floor() or coyote_mode): - jump() - - if Input.is_action_just_pressed("down"): - body.position.y += 1 - - var direction := Input.get_axis("left", "right") - if direction != 0: - last_direction = handle_direction(direction) - - if direction: - body.velocity.x = direction * speed - else: - body.velocity.x = move_toward(body.velocity.x, 0, speed) - - previous_velocity = body.velocity - body.move_and_slide() - - -func jump() -> void: - if not body: - return - - body.velocity.y = jump_velocity - coyote_mode = false - if jump_sfx: - jump_sfx.play() - - -func calculate_gravity() -> float: - return jump_gravity if body.velocity.y < 0.0 else fall_gravity - - -func on_coyote_timer_timeout() -> void: - coyote_mode = false - - -func handle_direction(input_dir: float) -> Vector2: - if input_dir > 0: - return Vector2.RIGHT - elif input_dir < 0: - return Vector2.LEFT - return last_direction - - -func on_ship_entered() -> void: - rotation_target.rotation = 0 - - -func calculate_jump_vars() -> void: - jump_velocity = ((2.0 * jump_height) / jump_time_to_peak) * -1.0 - jump_gravity = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0 - fall_gravity = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0 \ No newline at end of file diff --git a/scripts/components/platform_movement.gd.uid b/scripts/components/platform_movement.gd.uid deleted file mode 100644 index 80f7a5f..0000000 --- a/scripts/components/platform_movement.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c1wtrgw0x77xo -- 2.49.1 From 2dc4306be8898f74e08749699623d89f1ec54c44 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 12 Aug 2025 12:19:18 +0200 Subject: [PATCH 09/16] Add new components: CannotStompComponent, SkillUnlockedComponent, SpaceshipEnterComponent, SpaceshipExitComponent, SpinComponent, StompDamageComponent, StraightMotionComponent, TerrainHitFx, TooltipComponent, TrailComponent, and UnlockOnRequirementComponent --- scripts/components/CannotStompComponent.cs | 8 +++ scripts/components/ExitDoorComponent.cs | 5 +- scripts/components/SkillUnlockedComponent.cs | 70 +++++++++++++++++++ scripts/components/SpaceshipEnterComponent.cs | 21 ++++++ scripts/components/SpaceshipExitComponent.cs | 20 ++++++ scripts/components/SpinComponent.cs | 23 ++++++ scripts/components/StompDamageComponent.cs | 43 ++++++++++++ scripts/components/StraightMotionComponent.cs | 19 +++++ scripts/components/TerrainHitFx.cs | 33 +++++++++ scripts/components/TooltipComponent.cs | 39 +++++++++++ scripts/components/TrailComponent.cs | 22 ++++++ .../UnlockOnRequirementComponent.cs | 25 +++++++ scripts/interfaces/IUnlockable.cs | 6 ++ 13 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 scripts/components/CannotStompComponent.cs create mode 100644 scripts/components/SkillUnlockedComponent.cs create mode 100644 scripts/components/SpaceshipEnterComponent.cs create mode 100644 scripts/components/SpaceshipExitComponent.cs create mode 100644 scripts/components/SpinComponent.cs create mode 100644 scripts/components/StompDamageComponent.cs create mode 100644 scripts/components/StraightMotionComponent.cs create mode 100644 scripts/components/TerrainHitFx.cs create mode 100644 scripts/components/TooltipComponent.cs create mode 100644 scripts/components/TrailComponent.cs create mode 100644 scripts/components/UnlockOnRequirementComponent.cs create mode 100644 scripts/interfaces/IUnlockable.cs diff --git a/scripts/components/CannotStompComponent.cs b/scripts/components/CannotStompComponent.cs new file mode 100644 index 0000000..fbc52b9 --- /dev/null +++ b/scripts/components/CannotStompComponent.cs @@ -0,0 +1,8 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class CannotStompComponent : Node +{ + +} \ No newline at end of file diff --git a/scripts/components/ExitDoorComponent.cs b/scripts/components/ExitDoorComponent.cs index 9eb2652..d204421 100644 --- a/scripts/components/ExitDoorComponent.cs +++ b/scripts/components/ExitDoorComponent.cs @@ -1,9 +1,10 @@ using Godot; using Mr.BrickAdventures.Autoloads; +using Mr.BrickAdventures.scripts.interfaces; namespace Mr.BrickAdventures.scripts.components; -public partial class ExitDoorComponent : Node +public partial class ExitDoorComponent : Node, IUnlockable { [Export] public bool Locked { get; set; } = true; [Export] public Area2D ExitArea { get; set; } @@ -33,7 +34,7 @@ public partial class ExitDoorComponent : Node throw new System.NotImplementedException(); } - private void Unlock() + public void Unlock() { Locked = false; if (DoorSprite != null) diff --git a/scripts/components/SkillUnlockedComponent.cs b/scripts/components/SkillUnlockedComponent.cs new file mode 100644 index 0000000..33d3aa9 --- /dev/null +++ b/scripts/components/SkillUnlockedComponent.cs @@ -0,0 +1,70 @@ +using Godot; +using Godot.Collections; +using Mr.BrickAdventures.Autoloads; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SkillUnlockedComponent : Node +{ + [Export] public SkillManager SkillManager { get; set; } + + [Signal] + public delegate void SkillUnlockedEventHandler(SkillData skill); + + private GameManager _gameManager; + + public override void _Ready() + { + _gameManager = GetNode("/root/GameManager"); + } + + private bool HasEnoughCoins(int amount) + { + return _gameManager != null && _gameManager.GetCoins() >= amount; + } + + public bool TryUnlockSkill(SkillData skill) + { + if (_gameManager == null) return false; + if (_gameManager.IsSkillUnlocked(skill)) return false; + if (!HasEnoughCoins(skill.Cost)) return false; + + skill.Level = 1; + skill.IsActive = true; + _gameManager.RemoveCoins(skill.Cost); + + var skillsUnlocked = (Array)_gameManager.CurrentSessionState["skills_unlocked"]; + skillsUnlocked.Add(skill); + SkillManager.AddSkill(skill); + EmitSignalSkillUnlocked(skill); + + return true; + } + + public void UnlockAllSkills() + { + var availableSkills = SkillManager.AvailableSkills; + + foreach (var skill in availableSkills) + { + EmitSignalSkillUnlocked(skill); + } + + _gameManager.UnlockSkills(availableSkills); + SkillManager.ApplyUnlockedSkills(); + } + + public bool TryUpgradeSkill(SkillData skill) + { + if (_gameManager == null) return false; + if (!_gameManager.IsSkillUnlocked(skill)) return false; + if (!HasEnoughCoins(skill.Cost)) return false; + if (skill.Level >= skill.MaxLevel) return false; + + _gameManager.RemoveCoins(skill.Cost); + skill.Level++; + EmitSignalSkillUnlocked(skill); + return true; + } +} \ No newline at end of file diff --git a/scripts/components/SpaceshipEnterComponent.cs b/scripts/components/SpaceshipEnterComponent.cs new file mode 100644 index 0000000..6eb0355 --- /dev/null +++ b/scripts/components/SpaceshipEnterComponent.cs @@ -0,0 +1,21 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SpaceshipEnterComponent : Node +{ + [Export] public Area2D Area { get; set; } + [Signal] public delegate void SpaceshipEnteredEventHandler(); + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private void OnBodyEntered(Node2D body) + { + if (body is not PlayerController) return; + EmitSignalSpaceshipEntered(); + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/SpaceshipExitComponent.cs b/scripts/components/SpaceshipExitComponent.cs new file mode 100644 index 0000000..4cf413f --- /dev/null +++ b/scripts/components/SpaceshipExitComponent.cs @@ -0,0 +1,20 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SpaceshipExitComponent : Node +{ + [Export] public Area2D Area { get; set; } + [Signal] public delegate void SpaceshipExitEventHandler(); + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private void OnBodyEntered(Node2D body) + { + if (body is not PlayerController) return; + EmitSignalSpaceshipExit(); + } +} \ No newline at end of file diff --git a/scripts/components/SpinComponent.cs b/scripts/components/SpinComponent.cs new file mode 100644 index 0000000..e099ca6 --- /dev/null +++ b/scripts/components/SpinComponent.cs @@ -0,0 +1,23 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class SpinComponent : Node +{ + [Export] public float SpinSpeed { get; set; } = 8f; + [Export] public Vector2 SpinDirection { get; set; } = Vector2.Right; + + public override void _Process(double delta) + { + Spin((float)delta); + } + + private void Spin(float delta) + { + var rotationSpeed = SpinSpeed * SpinDirection.X * delta; + if (Owner is Node2D root) + { + root.Rotation += rotationSpeed; + } + } +} \ No newline at end of file diff --git a/scripts/components/StompDamageComponent.cs b/scripts/components/StompDamageComponent.cs new file mode 100644 index 0000000..9f16b26 --- /dev/null +++ b/scripts/components/StompDamageComponent.cs @@ -0,0 +1,43 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class StompDamageComponent : Node +{ + [Export] public float Damage { get; set; } = 0.25f; + [Export] public Area2D Area { get; set; } + [Export] public PlayerController Root { get; set; } + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private async void OnBodyEntered(Node2D body) + { + var health = body.GetNodeOrNull("HealthComponent"); + if (health == null) return; + + var cannotStompComponent = body.GetNodeOrNull("CannotStompComponent"); + if (cannotStompComponent != null) return; + + if (!(Root.GlobalPosition.Y < body.GlobalPosition.Y)) return; + + var velocity = Root.CurrentMovement.PreviousVelocity; + if (!(velocity.Y > 0f)) return; + + DealDamage(health); + + var damageComponent = body.GetNodeOrNull("DamageComponent"); + if (damageComponent == null) return; + + damageComponent.SetProcess(false); + await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); + damageComponent.SetProcess(true); + } + + private void DealDamage(HealthComponent target) + { + target.DecreaseHealth(Damage); + } +} \ No newline at end of file diff --git a/scripts/components/StraightMotionComponent.cs b/scripts/components/StraightMotionComponent.cs new file mode 100644 index 0000000..0e462a6 --- /dev/null +++ b/scripts/components/StraightMotionComponent.cs @@ -0,0 +1,19 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class StraightMotionComponent : Node +{ + [Export] public LaunchComponent LaunchComponent { get; set; } + + public override void _PhysicsProcess(double delta) + { + var root = Owner as Node2D; + if (root == null || LaunchComponent == null) + { + return; + } + + root.Position += LaunchComponent.GetInitialVelocity() * (float)delta; + } +} \ No newline at end of file diff --git a/scripts/components/TerrainHitFx.cs b/scripts/components/TerrainHitFx.cs new file mode 100644 index 0000000..053652b --- /dev/null +++ b/scripts/components/TerrainHitFx.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class TerrainHitFx : Node +{ + private List _gpuParticles = []; + + public override void _Ready() + { + if (Owner is GpuParticles2D gpuParticle) _gpuParticles.Add(gpuParticle); + + foreach (var child in GetChildren()) + { + if (child is GpuParticles2D p) + { + _gpuParticles.Add(p); + } + } + } + + public void TriggerFx() + { + foreach (var fx in _gpuParticles.Where(fx => fx != null)) + { + fx.Restart(); + fx.Emitting = true; + } + } +} \ No newline at end of file diff --git a/scripts/components/TooltipComponent.cs b/scripts/components/TooltipComponent.cs new file mode 100644 index 0000000..f32961b --- /dev/null +++ b/scripts/components/TooltipComponent.cs @@ -0,0 +1,39 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class TooltipComponent : Node +{ + [Export] public Area2D Area { get; set; } + [Export] public Control UiRoot { get; set; } + [Export] public string Text { get; set; } = string.Empty; + [Export] public Label TooltipLabel { get; set; } + + public override void _Ready() + { + TooltipLabel.Text = Text; + UiRoot.Visible = false; + Area.BodyEntered += OnBodyEntered; + Area.BodyExited += OnBodyExited; + } + + private void OnBodyEntered(Node2D body) + { + ShowTooltip(); + } + + private void OnBodyExited(Node2D body) + { + HideTooltip(); + } + + private void ShowTooltip() + { + UiRoot.Visible = true; + } + + private void HideTooltip() + { + UiRoot.Visible = false; + } +} \ No newline at end of file diff --git a/scripts/components/TrailComponent.cs b/scripts/components/TrailComponent.cs new file mode 100644 index 0000000..16e6067 --- /dev/null +++ b/scripts/components/TrailComponent.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class TrailComponent : Line2D +{ + [Export] public int MaxPoints { get; set; } = 100; + + private readonly Queue _queue = new(); + + public override void _Process(double delta) + { + if (Owner is Node2D root) _queue.Enqueue(root.GlobalPosition); + + if (_queue.Count > MaxPoints) _queue.Dequeue(); + + ClearPoints(); + + foreach (var point in _queue) AddPoint(point); + } +} \ No newline at end of file diff --git a/scripts/components/UnlockOnRequirementComponent.cs b/scripts/components/UnlockOnRequirementComponent.cs new file mode 100644 index 0000000..fa70625 --- /dev/null +++ b/scripts/components/UnlockOnRequirementComponent.cs @@ -0,0 +1,25 @@ +using Godot; +using Mr.BrickAdventures.scripts.interfaces; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class UnlockOnRequirementComponent : Node +{ + [Export] public RequirementComponent RequirementComponent { get; set; } + [Export] public Node UnlockTarget { get; set; } + + public override void _Ready() + { + RequirementComponent.RequirementMet += OnRequirementMet; + } + + private void OnRequirementMet(CollectableType requirementType) + { + if (requirementType != RequirementComponent.RequirementType) return; + if (UnlockTarget is IUnlockable unlockable) + { + unlockable.Unlock(); + } + } +} \ No newline at end of file diff --git a/scripts/interfaces/IUnlockable.cs b/scripts/interfaces/IUnlockable.cs new file mode 100644 index 0000000..b77f20b --- /dev/null +++ b/scripts/interfaces/IUnlockable.cs @@ -0,0 +1,6 @@ +namespace Mr.BrickAdventures.scripts.interfaces; + +public interface IUnlockable +{ + void Unlock(); +} \ No newline at end of file -- 2.49.1 From 56da6f36a72e31c9cd28fd6f1428aa22d1333295 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 12 Aug 2025 13:12:16 +0200 Subject: [PATCH 10/16] Add new components: BrickThrowComponent, BulletComponent, CageComponent, ChaseLevelComponent, CleanupComponent, and ThrowInputResource classes; implement game saving and loading logic in SaveSystem --- Autoloads/SaveSystem.cs | 27 +++++-- scripts/Resources/ChargeThrowInputResource.cs | 70 ++++++++++++++++ scripts/Resources/TapThrowInputResource.cs | 20 +++++ scripts/Resources/ThrowInputResource.cs | 24 ++++++ scripts/components/BrickThrowComponent.cs | 70 ++++++++++++++++ scripts/components/BulletComponent.cs | 45 +++++++++++ scripts/components/CageComponent.cs | 47 +++++++++++ scripts/components/ChaseLevelComponent.cs | 79 +++++++++++++++++++ scripts/components/CleanupComponent.cs | 11 +++ scripts/components/ShipMovementComponent.cs | 1 + scripts/interfaces/IMovement.cs | 1 + scripts/interfaces/IThrowInput.cs | 10 +++ 12 files changed, 399 insertions(+), 6 deletions(-) create mode 100644 scripts/Resources/ChargeThrowInputResource.cs create mode 100644 scripts/Resources/TapThrowInputResource.cs create mode 100644 scripts/Resources/ThrowInputResource.cs create mode 100644 scripts/components/BrickThrowComponent.cs create mode 100644 scripts/components/BulletComponent.cs create mode 100644 scripts/components/CageComponent.cs create mode 100644 scripts/components/ChaseLevelComponent.cs create mode 100644 scripts/components/CleanupComponent.cs create mode 100644 scripts/interfaces/IThrowInput.cs diff --git a/Autoloads/SaveSystem.cs b/Autoloads/SaveSystem.cs index 0e47221..d5ce18a 100644 --- a/Autoloads/SaveSystem.cs +++ b/Autoloads/SaveSystem.cs @@ -1,5 +1,6 @@ using Godot; using Godot.Collections; +using Mr.BrickAdventures.scripts.Resources; namespace Mr.BrickAdventures.Autoloads; @@ -8,22 +9,28 @@ public partial class SaveSystem : Node [Export] public string SavePath { get; set; } = "user://savegame.save"; [Export] public int Version { get; set; } = 1; - //private GM _gm; + private GameManager _gameManager; public override void _Ready() { - //_gm = GetNode("/root/GameManager"); + _gameManager = GetNode("/root/GameManager"); } public void SaveGame() { - //TODO: Implement saving logic + var saveData = new Dictionary + { + { "player_state", _gameManager.PlayerState}, + { "version", Version} + }; + + using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write); + file.StoreVar(saveData); + GD.Print("Game state saved to: ", SavePath); } public bool LoadGame() { - //TODO: Implement loading logic - if (!FileAccess.FileExists(SavePath)) return false; @@ -38,7 +45,15 @@ public partial class SaveSystem : Node GD.Print("Game state loaded from: ", SavePath); GD.Print("Player state: ", saveDataObj["player_state"]); - + _gameManager.PlayerState = (Dictionary)saveDataObj["player_state"]; + + var skills = new Array(); + foreach (var skill in (Array)_gameManager.PlayerState["unlocked_skills"]) + { + skills.Add(skill); + } + + _gameManager.UnlockSkills(skills); return true; } diff --git a/scripts/Resources/ChargeThrowInputResource.cs b/scripts/Resources/ChargeThrowInputResource.cs new file mode 100644 index 0000000..a781748 --- /dev/null +++ b/scripts/Resources/ChargeThrowInputResource.cs @@ -0,0 +1,70 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.Resources; + +public partial class ChargeThrowInputResource : ThrowInputResource +{ + [Export] public float MinPower { get; set; } = 0.5f; + [Export] public float MaxPower { get; set; } = 2.0f; + [Export] public float MaxChargeTime { get; set; } = 2.0f; + [Export] public float MinChargeDuration { get; set; } = 0.1f; + + private bool _isCharging = false; + private float _chargeStartTime = 0f; + + [Signal] public delegate void ChargeStartedEventHandler(); + [Signal] public delegate void ChargeUpdatedEventHandler(float chargeRatio); + [Signal] public delegate void ChargeStoppedEventHandler(); + + public override void ProcessInput(InputEvent @event) + { + if (@event.IsActionPressed("attack")) + { + _isCharging = true; + _chargeStartTime = Time.GetTicksMsec() / 1000f; + EmitSignalChargeStarted(); + } + + if (@event.IsActionReleased("attack") && _isCharging) + { + var power = CalculatePower(); + _isCharging = false; + EmitSignalThrowRequested(power); + EmitSignalChargeStopped(); + } + } + + public override void Update(double delta) + { + if (!_isCharging) return; + + var t = Mathf.Clamp(GetChargeRatio(), MinPower, MaxPower); + EmitSignalChargeUpdated(t); + } + + public override bool SupportsCharging() + { + return true; + } + + private float CalculatePower() + { + var now = Time.GetTicksMsec() / 1000f; + var heldTime = now - _chargeStartTime; + if (heldTime < MinChargeDuration) + return MinPower; + + var t = Mathf.Clamp(heldTime / MaxChargeTime, 0f, 1f); + return Mathf.Lerp(MinPower, MaxPower, t); + } + + private float GetChargeRatio() + { + if (!_isCharging) return MinPower; + + var now = Time.GetTicksMsec() / 1000f; + var heldTime = now - _chargeStartTime; + var t = Mathf.Clamp(heldTime / MaxChargeTime, 0f, 1f); + return Mathf.Lerp(MinPower, MaxPower, t); + } +} \ No newline at end of file diff --git a/scripts/Resources/TapThrowInputResource.cs b/scripts/Resources/TapThrowInputResource.cs new file mode 100644 index 0000000..97079e8 --- /dev/null +++ b/scripts/Resources/TapThrowInputResource.cs @@ -0,0 +1,20 @@ +using Godot; +using Mr.BrickAdventures.scripts.interfaces; + +namespace Mr.BrickAdventures.scripts.Resources; + +public partial class TapThrowInputResource : ThrowInputResource +{ + public override void Update(double delta) + { + if (Input.IsActionPressed("attack")) + { + EmitSignalThrowRequested(1f); + } + } + + public override bool SupportsCharging() + { + return false; + } +} \ No newline at end of file diff --git a/scripts/Resources/ThrowInputResource.cs b/scripts/Resources/ThrowInputResource.cs new file mode 100644 index 0000000..e7285e7 --- /dev/null +++ b/scripts/Resources/ThrowInputResource.cs @@ -0,0 +1,24 @@ +using Godot; +using Mr.BrickAdventures.scripts.interfaces; + +namespace Mr.BrickAdventures.scripts.Resources; + +public abstract partial class ThrowInputResource : Resource, IThrowInput +{ + [Signal] public delegate void ThrowRequestedEventHandler(float powerMultiplier = 1f); + + public virtual void ProcessInput(InputEvent @event) + { + throw new System.NotImplementedException(); + } + + public virtual void Update(double delta) + { + throw new System.NotImplementedException(); + } + + public virtual bool SupportsCharging() + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/scripts/components/BrickThrowComponent.cs b/scripts/components/BrickThrowComponent.cs new file mode 100644 index 0000000..e55f3cf --- /dev/null +++ b/scripts/components/BrickThrowComponent.cs @@ -0,0 +1,70 @@ +using Godot; +using Mr.BrickAdventures.scripts.interfaces; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class BrickThrowComponent : Node +{ + [Export] public PackedScene BrickScene { get; set; } + [Export] public float FireRate { get; set; } = 1.0f; + [Export] public PlayerController PlayerController { get; set; } + [Export] public ThrowInputResource ThrowInputBehavior { get; set; } + + private bool _canThrow = true; + private Timer _timer; + + public override void _Ready() + { + SetupTimer(); + _canThrow = true; + + if (ThrowInputBehavior != null) ThrowInputBehavior.ThrowRequested += ThrowBrick; + } + + public override void _Input(InputEvent @event) + { + ThrowInputBehavior?.ProcessInput(@event); + } + + public override void _Process(double delta) + { + ThrowInputBehavior?.Update(delta); + } + + private void SetupTimer() + { + _timer.WaitTime = FireRate; + _timer.OneShot = false; + _timer.Autostart = false; + _timer.Timeout += OnTimerTimeout; + } + + private void OnTimerTimeout() + { + _canThrow = true; + } + + private void ThrowBrick(float powerMultiplier = 1f) + { + if (!_canThrow || PlayerController == null || BrickScene == null) + return; + + var instance = BrickScene.Instantiate(); + var init = instance.GetNodeOrNull("ProjectileInitComponent"); + if (init != null && PlayerController.CurrentMovement is PlatformMovementComponent) + { + init.Initialize(new ProjectileInitParams() + { + Position = PlayerController.GlobalPosition, + Rotation = PlayerController.Rotation, + Direction = PlayerController.CurrentMovement.LastDirection, + PowerMultiplier = powerMultiplier, + }); + } + + GetTree().CurrentScene.AddChild(instance); + _canThrow = false; + _timer.Start(); + } +} \ No newline at end of file diff --git a/scripts/components/BulletComponent.cs b/scripts/components/BulletComponent.cs new file mode 100644 index 0000000..cd138e8 --- /dev/null +++ b/scripts/components/BulletComponent.cs @@ -0,0 +1,45 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class BulletComponent : Node +{ + [Export] public Area2D Area { get; set; } + [Export] public TerrainHitFx TerrainHitFx { get; set; } + [Export] public Sprite2D BulletSprite { get; set; } + + public override void _Ready() + { + Area.BodyEntered += OnBodyEntered; + } + + private void OnBodyEntered(Node2D body) + { + if (body is TileMapLayer) + { + if (BulletSprite != null) + { + BulletSprite.Visible = false; + } + + PlayTerrainHitFx(); + return; + } + + Owner.QueueFree(); + } + + private void OnAreaEntered(Area2D area) + { + Owner.QueueFree(); + } + + private void PlayTerrainHitFx() + { + if (TerrainHitFx == null) return; + + TerrainHitFx.TriggerFx(); + + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/CageComponent.cs b/scripts/components/CageComponent.cs new file mode 100644 index 0000000..b509104 --- /dev/null +++ b/scripts/components/CageComponent.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class CageComponent : Node +{ + [Export] public LeverComponent Lever { get; set; } + [Export] public Vector2 MoveValue { get; set; } = new(0, -100f); + [Export] public float TweenDuration { get; set; } = 0.5f; + [Export] public bool ShouldFree { get; set; } = true; + + private const string LeverGroupName = "levers"; + + public override async void _Ready() + { + await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); + if (Lever == null) + { + var leverNodes = GetTree().GetNodesInGroup(LeverGroupName); + foreach (var leverNode in leverNodes) + { + var lever = leverNode.GetNodeOrNull("LeverComponent"); + if (lever != null) lever.Activated += OnLeverActivated; + } + } + } + + private void OnLeverActivated() + { + var tween = CreateTween(); + if (Owner is Node2D root) + { + var endPosition = root.Position + MoveValue; + tween.TweenProperty(root, "position", endPosition, TweenDuration); + } + + tween.TweenCallback(Callable.From(OnTweenCompleted)); + + } + + private void OnTweenCompleted() + { + if (!ShouldFree) return; + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/ChaseLevelComponent.cs b/scripts/components/ChaseLevelComponent.cs new file mode 100644 index 0000000..d22a499 --- /dev/null +++ b/scripts/components/ChaseLevelComponent.cs @@ -0,0 +1,79 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class ChaseLevelComponent : Node +{ + [Export] public float ChaseSpeed { get; set; } = 200.0f; + [Export] public Marker2D ChaseTarget { get; set; } + [Export] public GodotObject PhantomCamera { get; set; } + [Export] public float MinimumDistance { get; set; } = 10f; + + [Signal] + public delegate void ChaseStartedEventHandler(); + + [Signal] + public delegate void ChaseStoppedEventHandler(); + + private bool _isChasing = false; + private Node2D _previousCameraFollowTarget = null; + + public override void _Process(double delta) + { + if (!_isChasing) return; + if (ChaseTarget == null) return; + + if (CheckIfReachedTarget()) + { + StopChasing(); + return; + } + + var targetPosition = ChaseTarget.GlobalPosition; + + if (Owner is not Node2D root) return; + + var direction = (targetPosition - root.GlobalPosition).Normalized(); + root.GlobalPosition += direction * ChaseSpeed * (float)delta; + } + + public void OnShipEntered() + { + if (ChaseTarget == null || PhantomCamera == null) + return; + + if (_isChasing) return; + + _previousCameraFollowTarget = (Node2D)PhantomCamera.Call("get_follow_target"); + PhantomCamera.Call("set_follow_target", Owner as Node2D); + EmitSignalChaseStarted(); + _isChasing = true; + } + + public void OnShipExited() + { + StopChasing(); + } + + private bool CheckIfReachedTarget() + { + if (ChaseTarget == null) + return false; + + if (Owner is not Node2D root) return false; + + var targetPosition = ChaseTarget.GlobalPosition; + var currentPosition = root.GlobalPosition; + return currentPosition.DistanceTo(targetPosition) <= MinimumDistance; + + } + + private void StopChasing() + { + if (PhantomCamera == null) return; + + PhantomCamera.Call("set_follow_target", _previousCameraFollowTarget); + EmitSignalChaseStopped(); + _isChasing = false; + } +} \ No newline at end of file diff --git a/scripts/components/CleanupComponent.cs b/scripts/components/CleanupComponent.cs new file mode 100644 index 0000000..577291e --- /dev/null +++ b/scripts/components/CleanupComponent.cs @@ -0,0 +1,11 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.components; + +public partial class CleanupComponent : Node +{ + public void CleanUp() + { + Owner.QueueFree(); + } +} \ No newline at end of file diff --git a/scripts/components/ShipMovementComponent.cs b/scripts/components/ShipMovementComponent.cs index 547da4c..8ac70ac 100644 --- a/scripts/components/ShipMovementComponent.cs +++ b/scripts/components/ShipMovementComponent.cs @@ -17,6 +17,7 @@ public partial class ShipMovementComponent : Node, IMovement private Vector2 _velocity = Vector2.Zero; public Vector2 Velocity => _velocity; + public Vector2 LastDirection => _velocity.Normalized(); public override void _PhysicsProcess(double delta) { diff --git a/scripts/interfaces/IMovement.cs b/scripts/interfaces/IMovement.cs index ece7b69..f679885 100644 --- a/scripts/interfaces/IMovement.cs +++ b/scripts/interfaces/IMovement.cs @@ -7,6 +7,7 @@ public interface IMovement string MovementType { get; } bool Enabled { get; set; } Vector2 PreviousVelocity { get; set; } + Vector2 LastDirection { get; } void _Process(double delta); void _PhysicsProcess(double delta); diff --git a/scripts/interfaces/IThrowInput.cs b/scripts/interfaces/IThrowInput.cs new file mode 100644 index 0000000..d0507a2 --- /dev/null +++ b/scripts/interfaces/IThrowInput.cs @@ -0,0 +1,10 @@ +using Godot; + +namespace Mr.BrickAdventures.scripts.interfaces; + +public interface IThrowInput +{ + public void ProcessInput(InputEvent @event); + public void Update(double delta); + public bool SupportsCharging(); +} \ No newline at end of file -- 2.49.1 From 359a32aaf0e5587ceefe90c901a840a59b30f2c4 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 12 Aug 2025 13:28:28 +0200 Subject: [PATCH 11/16] Add audio settings management and platform movement component --- Autoloads/ConfigFileHandler.cs | 4 +- Mr. Brick Adventures.csproj | 1 + scripts/UI/AudioSettings.cs | 102 ++++++++++++++++++++ scripts/components/platform_movement.gd | 118 ++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 scripts/UI/AudioSettings.cs create mode 100644 scripts/components/platform_movement.gd diff --git a/Autoloads/ConfigFileHandler.cs b/Autoloads/ConfigFileHandler.cs index 5799faf..67acdae 100644 --- a/Autoloads/ConfigFileHandler.cs +++ b/Autoloads/ConfigFileHandler.cs @@ -5,7 +5,9 @@ namespace Mr.BrickAdventures.Autoloads; public partial class ConfigFileHandler : Node { private ConfigFile _settingsConfig = new(); - private const string SettingsPath = "user://settings.ini"; + public const string SettingsPath = "user://settings.ini"; + + public ConfigFile SettingsConfig => _settingsConfig; public override void _Ready() { diff --git a/Mr. Brick Adventures.csproj b/Mr. Brick Adventures.csproj index 3fcf8b5..35922f6 100644 --- a/Mr. Brick Adventures.csproj +++ b/Mr. Brick Adventures.csproj @@ -92,6 +92,7 @@ + diff --git a/scripts/UI/AudioSettings.cs b/scripts/UI/AudioSettings.cs new file mode 100644 index 0000000..0d605b3 --- /dev/null +++ b/scripts/UI/AudioSettings.cs @@ -0,0 +1,102 @@ +using Godot; +using Mr.BrickAdventures.Autoloads; + +namespace Mr.BrickAdventures.scripts.UI; + +public partial class AudioSettings : Node +{ + [Export] public Slider MasterVolumeSlider { get; set; } + [Export] public Slider MusicVolumeSlider { get; set; } + [Export] public Slider SfxVolumeSlider { get; set; } + [Export] public Control AudioSettingsControl { get; set; } + [Export] public float MuteThreshold { get; set; } = -20f; + + private UIManager _uiManager; + private ConfigFileHandler _configFileHandler; + + public override void _Ready() + { + _uiManager = GetNode("/root/UIManager"); + _configFileHandler = GetNode("/root/ConfigFileHandler"); + Initialize(); + MasterVolumeSlider.ValueChanged += OnMasterVolumeChanged; + MusicVolumeSlider.ValueChanged += OnMusicVolumeChanged; + SfxVolumeSlider.ValueChanged += OnSfxVolumeChanged; + } + + public override void _UnhandledInput(InputEvent @event) + { + if (!@event.IsActionReleased("ui_cancel")) return; + if (!_uiManager.IsScreenOnTop(AudioSettingsControl)) return; + + SaveSettings(); + _uiManager.PopScreen(); + } + + private void OnSfxVolumeChanged(double value) + { + AudioServer.SetBusVolumeDb(AudioServer.GetBusIndex("sfx"), (float)value); + HandleMute(AudioServer.GetBusIndex("sfx"), (float)value); + } + + private void OnMusicVolumeChanged(double value) + { + AudioServer.SetBusVolumeDb(AudioServer.GetBusIndex("music"), (float)value); + HandleMute(AudioServer.GetBusIndex("music"), (float)value); + } + + private void OnMasterVolumeChanged(double value) + { + AudioServer.SetBusVolumeDb(AudioServer.GetBusIndex("Master"), (float)value); + HandleMute(AudioServer.GetBusIndex("Master"), (float)value); + } + + private void Initialize() + { + var volumeDb = AudioServer.GetBusVolumeDb(AudioServer.GetBusIndex("Master")); + MasterVolumeSlider.Value = volumeDb; + MasterVolumeSlider.MinValue = MuteThreshold; + MasterVolumeSlider.MaxValue = 0f; + + var musicVolumeDb = AudioServer.GetBusVolumeDb(AudioServer.GetBusIndex("music")); + MusicVolumeSlider.Value = musicVolumeDb; + MusicVolumeSlider.MinValue = MuteThreshold; + MusicVolumeSlider.MaxValue = 0f; + + var sfxVolumeDb = AudioServer.GetBusVolumeDb(AudioServer.GetBusIndex("sfx")); + SfxVolumeSlider.Value = sfxVolumeDb; + SfxVolumeSlider.MinValue = MuteThreshold; + SfxVolumeSlider.MaxValue = 0f; + } + + private void HandleMute(int busIndex, float value) + { + AudioServer.SetBusMute(busIndex, value <= MuteThreshold); + } + + private void SaveSettings() + { + var settingsConfig = _configFileHandler.SettingsConfig; + settingsConfig.SetValue("audio_settings", "master_volume", MasterVolumeSlider.Value); + settingsConfig.SetValue("audio_settings", "music_volume", MusicVolumeSlider.Value); + settingsConfig.SetValue("audio_settings", "sfx_volume", SfxVolumeSlider.Value); + settingsConfig.SetValue("audio_settings", "mute_threshold", MuteThreshold); + settingsConfig.Save(ConfigFileHandler.SettingsPath); + } + + private void LoadSettings() + { + var settingsConfig = _configFileHandler.SettingsConfig; + if (!settingsConfig.HasSection("audio_settings")) return; + + var masterVolume = (float)settingsConfig.GetValue("audio_settings", "master_volume", MasterVolumeSlider.Value); + var musicVolume = (float)settingsConfig.GetValue("audio_settings", "music_volume", MusicVolumeSlider.Value); + var sfxVolume = (float)settingsConfig.GetValue("audio_settings", "sfx_volume", SfxVolumeSlider.Value); + var muteThreshold = (float)settingsConfig.GetValue("audio_settings", "mute_threshold", MuteThreshold); + + MasterVolumeSlider.Value = masterVolume; + MusicVolumeSlider.Value = musicVolume; + SfxVolumeSlider.Value = sfxVolume; + MuteThreshold = muteThreshold; + } +} \ No newline at end of file diff --git a/scripts/components/platform_movement.gd b/scripts/components/platform_movement.gd new file mode 100644 index 0000000..38773da --- /dev/null +++ b/scripts/components/platform_movement.gd @@ -0,0 +1,118 @@ +class_name PlatformMovement +extends PlayerMovement + +@export var speed: float = 300.0 +@export var jump_height: float = 100 +@export var jump_time_to_peak: float = 0.5 +@export var jump_time_to_descent: float = 0.4 +@export var coyote_frames: int = 6 +@export var jump_sfx: AudioStreamPlayer2D +@export var rotation_target: Node2D +@export var body: CharacterBody2D + +var gravity = ProjectSettings.get_setting("physics/2d/default_gravity") +var was_last_floor := false +var coyote_mode := false +var coyote_timer: Timer +var last_direction := Vector2.RIGHT + +@onready var jump_velocity: float = ((2.0 * jump_height) / jump_time_to_peak) * -1.0 +@onready var jump_gravity: float = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0 +@onready var fall_gravity: float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0 + + +func _ready() -> void: + if not body: + return + + coyote_timer = Timer.new() + coyote_timer.one_shot = true + coyote_timer.wait_time = coyote_frames / 60.0 + coyote_timer.timeout.connect(on_coyote_timer_timeout) + add_child(coyote_timer) + + +func _process(_delta: float) -> void: + if not body or not enabled: + return + + if body.velocity.x > 0.0: + rotation_target.rotation = deg_to_rad(-10) + elif body.velocity.x < 0.0: + rotation_target.rotation = deg_to_rad(10) + else: + rotation_target.rotation = 0 + + calculate_jump_vars() + + +func _physics_process(delta) -> void: + if not body or not enabled: + return + + if body.is_on_floor(): + was_last_floor = true + coyote_mode = false # Reset coyote mode when back on the floor + coyote_timer.stop() # Stop timer when grounded + else: + if was_last_floor: # Start coyote timer only once + coyote_mode = true + coyote_timer.start() + was_last_floor = false + + if not body.is_on_floor(): + body.velocity.y += calculate_gravity() * delta + + if Input.is_action_pressed("jump") and (body.is_on_floor() or coyote_mode): + jump() + + if Input.is_action_just_pressed("down"): + body.position.y += 1 + + var direction := Input.get_axis("left", "right") + if direction != 0: + last_direction = handle_direction(direction) + + if direction: + body.velocity.x = direction * speed + else: + body.velocity.x = move_toward(body.velocity.x, 0, speed) + + previous_velocity = body.velocity + body.move_and_slide() + + +func jump() -> void: + if not body: + return + + body.velocity.y = jump_velocity + coyote_mode = false + if jump_sfx: + jump_sfx.play() + + +func calculate_gravity() -> float: + return jump_gravity if body.velocity.y < 0.0 else fall_gravity + + +func on_coyote_timer_timeout() -> void: + coyote_mode = false + + +func handle_direction(input_dir: float) -> Vector2: + if input_dir > 0: + return Vector2.RIGHT + elif input_dir < 0: + return Vector2.LEFT + return last_direction + + +func on_ship_entered() -> void: + rotation_target.rotation = 0 + + +func calculate_jump_vars() -> void: + jump_velocity = ((2.0 * jump_height) / jump_time_to_peak) * -1.0 + jump_gravity = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0 + fall_gravity = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0 \ No newline at end of file -- 2.49.1 From 407523193991031b7b27fe766c02b8ce72bcf9dc Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 13 Aug 2025 00:30:37 +0200 Subject: [PATCH 12/16] Add ChargeProgressBar, Credits, and GameOverScreen components for UI management --- .../Resources/ChargeThrowInputResource.cs.uid | 1 + scripts/Resources/CollectableResource.cs.uid | 1 + scripts/Resources/CollectableType.cs.uid | 1 + scripts/Resources/SkillData.cs.uid | 1 + scripts/Resources/SkillType.cs.uid | 1 + .../Resources/StatusEffectDataResource.cs.uid | 1 + scripts/Resources/StatusEffectType.cs.uid | 1 + .../Resources/TapThrowInputResource.cs.uid | 1 + scripts/Resources/ThrowInputResource.cs.uid | 1 + scripts/Screenshot.cs.uid | 1 + scripts/SkillManager.cs.uid | 1 + scripts/UI/AudioSettings.cs.uid | 1 + scripts/UI/ChargeProgressBar.cs | 81 +++++++++++++++++++ scripts/UI/Credits.cs | 23 ++++++ scripts/UI/GameOverScreen.cs | 39 +++++++++ scripts/components/BrickThrowComponent.cs.uid | 1 + scripts/components/BulletComponent.cs.uid | 1 + scripts/components/CageComponent.cs.uid | 1 + .../components/CanBeLaunchedComponent.cs.uid | 1 + scripts/components/CanPickUpComponent.cs.uid | 1 + .../components/CannotStompComponent.cs.uid | 1 + scripts/components/ChaseLevelComponent.cs.uid | 1 + scripts/components/CleanupComponent.cs.uid | 1 + .../components/CollapsableComponent.cs.uid | 1 + .../components/CollectableComponent.cs.uid | 1 + scripts/components/DamageComponent.cs.uid | 1 + .../components/DestroyableComponent.cs.uid | 1 + .../EffectInflictorComponent.cs.uid | 1 + scripts/components/EnemyDeathComponent.cs.uid | 1 + .../EnemyWaveTriggerComponent.cs.uid | 1 + scripts/components/ExitDoorComponent.cs.uid | 1 + scripts/components/ExplosiveComponent.cs.uid | 1 + scripts/components/FadeAwayComponent.cs.uid | 1 + scripts/components/FireEffectComponent.cs.uid | 1 + scripts/components/FlashingComponent.cs.uid | 1 + scripts/components/FlipComponent.cs.uid | 1 + .../components/GravityMotionComponent.cs.uid | 1 + scripts/components/HealComponent.cs.uid | 1 + scripts/components/HealthComponent.cs.uid | 1 + scripts/components/HitComponent.cs.uid | 1 + .../HomingMissileMotionComponent.cs.uid | 1 + scripts/components/IceEffectComponent.cs.uid | 1 + .../InvulnerabilityComponent.cs.uid | 1 + scripts/components/JumpPadComponent.cs.uid | 1 + .../KillPlayerOutOfScreenComponent.cs.uid | 1 + scripts/components/KnockbackComponent.cs.uid | 1 + scripts/components/LaunchComponent.cs.uid | 1 + scripts/components/LeverComponent.cs.uid | 1 + scripts/components/LifetimeComponent.cs.uid | 1 + .../components/MagneticSkillComponent.cs.uid | 1 + .../components/OutOfScreenComponent.cs.uid | 1 + .../PeriodicShootingComponent.cs.uid | 1 + .../components/PlayerDeathComponent.cs.uid | 1 + .../ProgressiveDamageComponent.cs.uid | 1 + scripts/components/ProjectileComponent.cs.uid | 1 + .../components/ProjectileInitComponent.cs.uid | 1 + .../components/RequirementComponent.cs.uid | 1 + scripts/components/ScoreComponent.cs.uid | 1 + .../components/ShipMovementComponent.cs.uid | 1 + .../components/ShipShooterComponent.cs.uid | 1 + .../SideToSideMovementComponent.cs.uid | 1 + .../components/SkillUnlockedComponent.cs.uid | 1 + .../components/SpaceshipEnterComponent.cs.uid | 1 + .../components/SpaceshipExitComponent.cs.uid | 1 + scripts/components/SpinComponent.cs.uid | 1 + .../components/StatusEffectComponent.cs.uid | 1 + .../components/StompDamageComponent.cs.uid | 1 + .../components/StraightMotionComponent.cs.uid | 1 + scripts/components/TerrainHitFx.cs.uid | 1 + scripts/components/TooltipComponent.cs.uid | 1 + scripts/components/TrailComponent.cs.uid | 1 + .../components/TriggerLeverComponent.cs.uid | 1 + .../UnlockOnRequirementComponent.cs.uid | 1 + scripts/components/platform_movement.gd.uid | 1 + scripts/interfaces/IThrowInput.cs.uid | 1 + scripts/interfaces/IUnlockable.cs.uid | 1 + 76 files changed, 216 insertions(+) create mode 100644 scripts/Resources/ChargeThrowInputResource.cs.uid create mode 100644 scripts/Resources/CollectableResource.cs.uid create mode 100644 scripts/Resources/CollectableType.cs.uid create mode 100644 scripts/Resources/SkillData.cs.uid create mode 100644 scripts/Resources/SkillType.cs.uid create mode 100644 scripts/Resources/StatusEffectDataResource.cs.uid create mode 100644 scripts/Resources/StatusEffectType.cs.uid create mode 100644 scripts/Resources/TapThrowInputResource.cs.uid create mode 100644 scripts/Resources/ThrowInputResource.cs.uid create mode 100644 scripts/Screenshot.cs.uid create mode 100644 scripts/SkillManager.cs.uid create mode 100644 scripts/UI/AudioSettings.cs.uid create mode 100644 scripts/UI/ChargeProgressBar.cs create mode 100644 scripts/UI/Credits.cs create mode 100644 scripts/UI/GameOverScreen.cs create mode 100644 scripts/components/BrickThrowComponent.cs.uid create mode 100644 scripts/components/BulletComponent.cs.uid create mode 100644 scripts/components/CageComponent.cs.uid create mode 100644 scripts/components/CanBeLaunchedComponent.cs.uid create mode 100644 scripts/components/CanPickUpComponent.cs.uid create mode 100644 scripts/components/CannotStompComponent.cs.uid create mode 100644 scripts/components/ChaseLevelComponent.cs.uid create mode 100644 scripts/components/CleanupComponent.cs.uid create mode 100644 scripts/components/CollapsableComponent.cs.uid create mode 100644 scripts/components/CollectableComponent.cs.uid create mode 100644 scripts/components/DamageComponent.cs.uid create mode 100644 scripts/components/DestroyableComponent.cs.uid create mode 100644 scripts/components/EffectInflictorComponent.cs.uid create mode 100644 scripts/components/EnemyDeathComponent.cs.uid create mode 100644 scripts/components/EnemyWaveTriggerComponent.cs.uid create mode 100644 scripts/components/ExitDoorComponent.cs.uid create mode 100644 scripts/components/ExplosiveComponent.cs.uid create mode 100644 scripts/components/FadeAwayComponent.cs.uid create mode 100644 scripts/components/FireEffectComponent.cs.uid create mode 100644 scripts/components/FlashingComponent.cs.uid create mode 100644 scripts/components/FlipComponent.cs.uid create mode 100644 scripts/components/GravityMotionComponent.cs.uid create mode 100644 scripts/components/HealComponent.cs.uid create mode 100644 scripts/components/HealthComponent.cs.uid create mode 100644 scripts/components/HitComponent.cs.uid create mode 100644 scripts/components/HomingMissileMotionComponent.cs.uid create mode 100644 scripts/components/IceEffectComponent.cs.uid create mode 100644 scripts/components/InvulnerabilityComponent.cs.uid create mode 100644 scripts/components/JumpPadComponent.cs.uid create mode 100644 scripts/components/KillPlayerOutOfScreenComponent.cs.uid create mode 100644 scripts/components/KnockbackComponent.cs.uid create mode 100644 scripts/components/LaunchComponent.cs.uid create mode 100644 scripts/components/LeverComponent.cs.uid create mode 100644 scripts/components/LifetimeComponent.cs.uid create mode 100644 scripts/components/MagneticSkillComponent.cs.uid create mode 100644 scripts/components/OutOfScreenComponent.cs.uid create mode 100644 scripts/components/PeriodicShootingComponent.cs.uid create mode 100644 scripts/components/PlayerDeathComponent.cs.uid create mode 100644 scripts/components/ProgressiveDamageComponent.cs.uid create mode 100644 scripts/components/ProjectileComponent.cs.uid create mode 100644 scripts/components/ProjectileInitComponent.cs.uid create mode 100644 scripts/components/RequirementComponent.cs.uid create mode 100644 scripts/components/ScoreComponent.cs.uid create mode 100644 scripts/components/ShipMovementComponent.cs.uid create mode 100644 scripts/components/ShipShooterComponent.cs.uid create mode 100644 scripts/components/SideToSideMovementComponent.cs.uid create mode 100644 scripts/components/SkillUnlockedComponent.cs.uid create mode 100644 scripts/components/SpaceshipEnterComponent.cs.uid create mode 100644 scripts/components/SpaceshipExitComponent.cs.uid create mode 100644 scripts/components/SpinComponent.cs.uid create mode 100644 scripts/components/StatusEffectComponent.cs.uid create mode 100644 scripts/components/StompDamageComponent.cs.uid create mode 100644 scripts/components/StraightMotionComponent.cs.uid create mode 100644 scripts/components/TerrainHitFx.cs.uid create mode 100644 scripts/components/TooltipComponent.cs.uid create mode 100644 scripts/components/TrailComponent.cs.uid create mode 100644 scripts/components/TriggerLeverComponent.cs.uid create mode 100644 scripts/components/UnlockOnRequirementComponent.cs.uid create mode 100644 scripts/components/platform_movement.gd.uid create mode 100644 scripts/interfaces/IThrowInput.cs.uid create mode 100644 scripts/interfaces/IUnlockable.cs.uid diff --git a/scripts/Resources/ChargeThrowInputResource.cs.uid b/scripts/Resources/ChargeThrowInputResource.cs.uid new file mode 100644 index 0000000..34c7c14 --- /dev/null +++ b/scripts/Resources/ChargeThrowInputResource.cs.uid @@ -0,0 +1 @@ +uid://dtpdh4jp51jis diff --git a/scripts/Resources/CollectableResource.cs.uid b/scripts/Resources/CollectableResource.cs.uid new file mode 100644 index 0000000..314072f --- /dev/null +++ b/scripts/Resources/CollectableResource.cs.uid @@ -0,0 +1 @@ +uid://gptsgaw3agkf diff --git a/scripts/Resources/CollectableType.cs.uid b/scripts/Resources/CollectableType.cs.uid new file mode 100644 index 0000000..077048b --- /dev/null +++ b/scripts/Resources/CollectableType.cs.uid @@ -0,0 +1 @@ +uid://2ql8wj3vfeke diff --git a/scripts/Resources/SkillData.cs.uid b/scripts/Resources/SkillData.cs.uid new file mode 100644 index 0000000..7c3a3a1 --- /dev/null +++ b/scripts/Resources/SkillData.cs.uid @@ -0,0 +1 @@ +uid://d4crrfmbgxnqf diff --git a/scripts/Resources/SkillType.cs.uid b/scripts/Resources/SkillType.cs.uid new file mode 100644 index 0000000..d9acebb --- /dev/null +++ b/scripts/Resources/SkillType.cs.uid @@ -0,0 +1 @@ +uid://sma20qug2r0q diff --git a/scripts/Resources/StatusEffectDataResource.cs.uid b/scripts/Resources/StatusEffectDataResource.cs.uid new file mode 100644 index 0000000..f55ca07 --- /dev/null +++ b/scripts/Resources/StatusEffectDataResource.cs.uid @@ -0,0 +1 @@ +uid://pw0pu6gb21y2 diff --git a/scripts/Resources/StatusEffectType.cs.uid b/scripts/Resources/StatusEffectType.cs.uid new file mode 100644 index 0000000..711e14c --- /dev/null +++ b/scripts/Resources/StatusEffectType.cs.uid @@ -0,0 +1 @@ +uid://b0a7k7mse3l68 diff --git a/scripts/Resources/TapThrowInputResource.cs.uid b/scripts/Resources/TapThrowInputResource.cs.uid new file mode 100644 index 0000000..ecd7ca2 --- /dev/null +++ b/scripts/Resources/TapThrowInputResource.cs.uid @@ -0,0 +1 @@ +uid://cx7ryqxemgs56 diff --git a/scripts/Resources/ThrowInputResource.cs.uid b/scripts/Resources/ThrowInputResource.cs.uid new file mode 100644 index 0000000..b051cca --- /dev/null +++ b/scripts/Resources/ThrowInputResource.cs.uid @@ -0,0 +1 @@ +uid://mnn5wy5cyr4m diff --git a/scripts/Screenshot.cs.uid b/scripts/Screenshot.cs.uid new file mode 100644 index 0000000..4b7d34d --- /dev/null +++ b/scripts/Screenshot.cs.uid @@ -0,0 +1 @@ +uid://chrhjch4ymfvr diff --git a/scripts/SkillManager.cs.uid b/scripts/SkillManager.cs.uid new file mode 100644 index 0000000..dc14de7 --- /dev/null +++ b/scripts/SkillManager.cs.uid @@ -0,0 +1 @@ +uid://di572axt0c3s8 diff --git a/scripts/UI/AudioSettings.cs.uid b/scripts/UI/AudioSettings.cs.uid new file mode 100644 index 0000000..b819f88 --- /dev/null +++ b/scripts/UI/AudioSettings.cs.uid @@ -0,0 +1 @@ +uid://g61qqsymqfxd diff --git a/scripts/UI/ChargeProgressBar.cs b/scripts/UI/ChargeProgressBar.cs new file mode 100644 index 0000000..5e74caf --- /dev/null +++ b/scripts/UI/ChargeProgressBar.cs @@ -0,0 +1,81 @@ +using Godot; +using Mr.BrickAdventures.scripts.components; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.UI; + +public partial class ChargeProgressBar : Node +{ + [Export] public ProgressBar ProgressBar { get; set; } + [Export] public BrickThrowComponent ThrowComponent { get; set; } + + private ChargeThrowInputResource _throwInput; + + public override void _Ready() + { + Owner.ChildEnteredTree += OnNodeEntered; + ProgressBar.Hide(); + SetupDependencies(); + } + + private void OnNodeEntered(Node node) + { + if (node is not BrickThrowComponent throwComponent || ThrowComponent != null) return; + ThrowComponent = throwComponent; + SetupDependencies(); + } + + private void SetupDependencies() + { + if (ThrowComponent.ThrowInputBehavior is ChargeThrowInputResource throwInput) + { + _throwInput = throwInput; + } + else + { + _throwInput = null; + } + + if (_throwInput == null) + { + return; + } + + if (!_throwInput.SupportsCharging()) + { + ProgressBar.Hide(); + return; + } + + SetupProgressBar(); + + _throwInput.ChargeStarted += OnChargeStarted; + _throwInput.ChargeStopped += OnChargeStopped; + _throwInput.ChargeUpdated += OnChargeUpdated; + } + + private void SetupProgressBar() + { + ProgressBar.MinValue = _throwInput.MinPower; + ProgressBar.MaxValue = _throwInput.MaxPower; + ProgressBar.Value = _throwInput.MinPower; + ProgressBar.Step = 0.01f; + ProgressBar.Hide(); + } + + private void OnChargeStarted() + { + ProgressBar.Show(); + } + + private void OnChargeStopped() + { + ProgressBar.Hide(); + } + + private void OnChargeUpdated(float chargeRatio) + { + ProgressBar.Value = chargeRatio; + ProgressBar.Show(); + } +} \ No newline at end of file diff --git a/scripts/UI/Credits.cs b/scripts/UI/Credits.cs new file mode 100644 index 0000000..b0d2d0f --- /dev/null +++ b/scripts/UI/Credits.cs @@ -0,0 +1,23 @@ +using Godot; +using Mr.BrickAdventures.Autoloads; + +namespace Mr.BrickAdventures.scripts.UI; + +public partial class Credits : Control +{ + private UIManager _uiManager; + + public override void _Ready() + { + _uiManager = GetNode("/root/UIManager"); + } + + public override void _UnhandledInput(InputEvent @event) + { + if (!@event.IsActionPressed("ui_cancel")) return; + if (_uiManager != null && _uiManager.IsScreenOnTop(this)) + { + _uiManager.PopScreen(); + } + } +} \ No newline at end of file diff --git a/scripts/UI/GameOverScreen.cs b/scripts/UI/GameOverScreen.cs new file mode 100644 index 0000000..0a665f9 --- /dev/null +++ b/scripts/UI/GameOverScreen.cs @@ -0,0 +1,39 @@ +using Godot; +using Mr.BrickAdventures.Autoloads; + +namespace Mr.BrickAdventures.scripts.UI; + +public partial class GameOverScreen : Node +{ + [Export] public Control GameOverPanel { get; set; } + [Export] public Button RestartButton { get; set; } + [Export] public Button MainMenuButton { get; set; } + [Export] public PackedScene MainMenuScene { get; set; } + + private GameManager _gameManager; + + public override void _Ready() + { + _gameManager = GetNode("/root/GameManager"); + RestartButton.Pressed += OnRestartClicked; + MainMenuButton.Pressed += OnMainMenuClicked; + } + + private void OnMainMenuClicked() + { + _gameManager.ResetPlayerState(); + GetTree().ChangeSceneToPacked(MainMenuScene); + } + + private void OnRestartClicked() + { + _gameManager.RestartGame(); + } + + public void OnPlayerDeath() + { + if (_gameManager == null || _gameManager.GetLives() != 0) return; + + GameOverPanel.Show(); + } +} \ No newline at end of file diff --git a/scripts/components/BrickThrowComponent.cs.uid b/scripts/components/BrickThrowComponent.cs.uid new file mode 100644 index 0000000..de79b8c --- /dev/null +++ b/scripts/components/BrickThrowComponent.cs.uid @@ -0,0 +1 @@ +uid://b0bv8kw5w5037 diff --git a/scripts/components/BulletComponent.cs.uid b/scripts/components/BulletComponent.cs.uid new file mode 100644 index 0000000..ed8d786 --- /dev/null +++ b/scripts/components/BulletComponent.cs.uid @@ -0,0 +1 @@ +uid://cfw8nbrarex0i diff --git a/scripts/components/CageComponent.cs.uid b/scripts/components/CageComponent.cs.uid new file mode 100644 index 0000000..e1006e0 --- /dev/null +++ b/scripts/components/CageComponent.cs.uid @@ -0,0 +1 @@ +uid://dojn0gw8hsv02 diff --git a/scripts/components/CanBeLaunchedComponent.cs.uid b/scripts/components/CanBeLaunchedComponent.cs.uid new file mode 100644 index 0000000..d3803a7 --- /dev/null +++ b/scripts/components/CanBeLaunchedComponent.cs.uid @@ -0,0 +1 @@ +uid://cjcc7fia15wu3 diff --git a/scripts/components/CanPickUpComponent.cs.uid b/scripts/components/CanPickUpComponent.cs.uid new file mode 100644 index 0000000..f436e35 --- /dev/null +++ b/scripts/components/CanPickUpComponent.cs.uid @@ -0,0 +1 @@ +uid://mnjg3p0aw1ow diff --git a/scripts/components/CannotStompComponent.cs.uid b/scripts/components/CannotStompComponent.cs.uid new file mode 100644 index 0000000..e90cbf9 --- /dev/null +++ b/scripts/components/CannotStompComponent.cs.uid @@ -0,0 +1 @@ +uid://dh67n16bnl838 diff --git a/scripts/components/ChaseLevelComponent.cs.uid b/scripts/components/ChaseLevelComponent.cs.uid new file mode 100644 index 0000000..ef732c8 --- /dev/null +++ b/scripts/components/ChaseLevelComponent.cs.uid @@ -0,0 +1 @@ +uid://dnpj72mfi1ywl diff --git a/scripts/components/CleanupComponent.cs.uid b/scripts/components/CleanupComponent.cs.uid new file mode 100644 index 0000000..e7b516f --- /dev/null +++ b/scripts/components/CleanupComponent.cs.uid @@ -0,0 +1 @@ +uid://v7tt4w6bejux diff --git a/scripts/components/CollapsableComponent.cs.uid b/scripts/components/CollapsableComponent.cs.uid new file mode 100644 index 0000000..9b32392 --- /dev/null +++ b/scripts/components/CollapsableComponent.cs.uid @@ -0,0 +1 @@ +uid://xqhrb1c7f6y4 diff --git a/scripts/components/CollectableComponent.cs.uid b/scripts/components/CollectableComponent.cs.uid new file mode 100644 index 0000000..7c4c9fe --- /dev/null +++ b/scripts/components/CollectableComponent.cs.uid @@ -0,0 +1 @@ +uid://r4jybneigfcn diff --git a/scripts/components/DamageComponent.cs.uid b/scripts/components/DamageComponent.cs.uid new file mode 100644 index 0000000..fcf335a --- /dev/null +++ b/scripts/components/DamageComponent.cs.uid @@ -0,0 +1 @@ +uid://2i7p7v135u7c diff --git a/scripts/components/DestroyableComponent.cs.uid b/scripts/components/DestroyableComponent.cs.uid new file mode 100644 index 0000000..a29e11b --- /dev/null +++ b/scripts/components/DestroyableComponent.cs.uid @@ -0,0 +1 @@ +uid://ctfrbj52ejay4 diff --git a/scripts/components/EffectInflictorComponent.cs.uid b/scripts/components/EffectInflictorComponent.cs.uid new file mode 100644 index 0000000..20cdabe --- /dev/null +++ b/scripts/components/EffectInflictorComponent.cs.uid @@ -0,0 +1 @@ +uid://xjq33vj0rol0 diff --git a/scripts/components/EnemyDeathComponent.cs.uid b/scripts/components/EnemyDeathComponent.cs.uid new file mode 100644 index 0000000..0833b87 --- /dev/null +++ b/scripts/components/EnemyDeathComponent.cs.uid @@ -0,0 +1 @@ +uid://cfdugoeduudar diff --git a/scripts/components/EnemyWaveTriggerComponent.cs.uid b/scripts/components/EnemyWaveTriggerComponent.cs.uid new file mode 100644 index 0000000..c4b0029 --- /dev/null +++ b/scripts/components/EnemyWaveTriggerComponent.cs.uid @@ -0,0 +1 @@ +uid://d3fpwddc2j41x diff --git a/scripts/components/ExitDoorComponent.cs.uid b/scripts/components/ExitDoorComponent.cs.uid new file mode 100644 index 0000000..4dc9317 --- /dev/null +++ b/scripts/components/ExitDoorComponent.cs.uid @@ -0,0 +1 @@ +uid://c5mhwlyvfuaip diff --git a/scripts/components/ExplosiveComponent.cs.uid b/scripts/components/ExplosiveComponent.cs.uid new file mode 100644 index 0000000..1ecc22e --- /dev/null +++ b/scripts/components/ExplosiveComponent.cs.uid @@ -0,0 +1 @@ +uid://7uttgdr6cr5y diff --git a/scripts/components/FadeAwayComponent.cs.uid b/scripts/components/FadeAwayComponent.cs.uid new file mode 100644 index 0000000..e29ebee --- /dev/null +++ b/scripts/components/FadeAwayComponent.cs.uid @@ -0,0 +1 @@ +uid://bjln6jb1sigx2 diff --git a/scripts/components/FireEffectComponent.cs.uid b/scripts/components/FireEffectComponent.cs.uid new file mode 100644 index 0000000..7052c21 --- /dev/null +++ b/scripts/components/FireEffectComponent.cs.uid @@ -0,0 +1 @@ +uid://cxuig4xh8nfov diff --git a/scripts/components/FlashingComponent.cs.uid b/scripts/components/FlashingComponent.cs.uid new file mode 100644 index 0000000..c366098 --- /dev/null +++ b/scripts/components/FlashingComponent.cs.uid @@ -0,0 +1 @@ +uid://dvyd26ricriql diff --git a/scripts/components/FlipComponent.cs.uid b/scripts/components/FlipComponent.cs.uid new file mode 100644 index 0000000..df1be44 --- /dev/null +++ b/scripts/components/FlipComponent.cs.uid @@ -0,0 +1 @@ +uid://dy78ak8eykw6e diff --git a/scripts/components/GravityMotionComponent.cs.uid b/scripts/components/GravityMotionComponent.cs.uid new file mode 100644 index 0000000..a4ee0b1 --- /dev/null +++ b/scripts/components/GravityMotionComponent.cs.uid @@ -0,0 +1 @@ +uid://cwi5qashdag1g diff --git a/scripts/components/HealComponent.cs.uid b/scripts/components/HealComponent.cs.uid new file mode 100644 index 0000000..32858e7 --- /dev/null +++ b/scripts/components/HealComponent.cs.uid @@ -0,0 +1 @@ +uid://bmx6rk281yim2 diff --git a/scripts/components/HealthComponent.cs.uid b/scripts/components/HealthComponent.cs.uid new file mode 100644 index 0000000..b385e11 --- /dev/null +++ b/scripts/components/HealthComponent.cs.uid @@ -0,0 +1 @@ +uid://dgb8bqcri7nsj diff --git a/scripts/components/HitComponent.cs.uid b/scripts/components/HitComponent.cs.uid new file mode 100644 index 0000000..d6025de --- /dev/null +++ b/scripts/components/HitComponent.cs.uid @@ -0,0 +1 @@ +uid://bo506l4x0808e diff --git a/scripts/components/HomingMissileMotionComponent.cs.uid b/scripts/components/HomingMissileMotionComponent.cs.uid new file mode 100644 index 0000000..2d3664a --- /dev/null +++ b/scripts/components/HomingMissileMotionComponent.cs.uid @@ -0,0 +1 @@ +uid://c2hplha6af74q diff --git a/scripts/components/IceEffectComponent.cs.uid b/scripts/components/IceEffectComponent.cs.uid new file mode 100644 index 0000000..0bde80e --- /dev/null +++ b/scripts/components/IceEffectComponent.cs.uid @@ -0,0 +1 @@ +uid://d1388lhp2gpgr diff --git a/scripts/components/InvulnerabilityComponent.cs.uid b/scripts/components/InvulnerabilityComponent.cs.uid new file mode 100644 index 0000000..ac578fe --- /dev/null +++ b/scripts/components/InvulnerabilityComponent.cs.uid @@ -0,0 +1 @@ +uid://cecelixl41t3j diff --git a/scripts/components/JumpPadComponent.cs.uid b/scripts/components/JumpPadComponent.cs.uid new file mode 100644 index 0000000..3fc860e --- /dev/null +++ b/scripts/components/JumpPadComponent.cs.uid @@ -0,0 +1 @@ +uid://bgbnof7aeydmq diff --git a/scripts/components/KillPlayerOutOfScreenComponent.cs.uid b/scripts/components/KillPlayerOutOfScreenComponent.cs.uid new file mode 100644 index 0000000..6763710 --- /dev/null +++ b/scripts/components/KillPlayerOutOfScreenComponent.cs.uid @@ -0,0 +1 @@ +uid://diw6opv6yutgi diff --git a/scripts/components/KnockbackComponent.cs.uid b/scripts/components/KnockbackComponent.cs.uid new file mode 100644 index 0000000..935cf10 --- /dev/null +++ b/scripts/components/KnockbackComponent.cs.uid @@ -0,0 +1 @@ +uid://cgfynrn68lp12 diff --git a/scripts/components/LaunchComponent.cs.uid b/scripts/components/LaunchComponent.cs.uid new file mode 100644 index 0000000..dbd1cd6 --- /dev/null +++ b/scripts/components/LaunchComponent.cs.uid @@ -0,0 +1 @@ +uid://cbexrnnj47f87 diff --git a/scripts/components/LeverComponent.cs.uid b/scripts/components/LeverComponent.cs.uid new file mode 100644 index 0000000..8ef9c04 --- /dev/null +++ b/scripts/components/LeverComponent.cs.uid @@ -0,0 +1 @@ +uid://1oo22ieply7n diff --git a/scripts/components/LifetimeComponent.cs.uid b/scripts/components/LifetimeComponent.cs.uid new file mode 100644 index 0000000..4dbb6a6 --- /dev/null +++ b/scripts/components/LifetimeComponent.cs.uid @@ -0,0 +1 @@ +uid://oyf25mpc5etr diff --git a/scripts/components/MagneticSkillComponent.cs.uid b/scripts/components/MagneticSkillComponent.cs.uid new file mode 100644 index 0000000..513afb5 --- /dev/null +++ b/scripts/components/MagneticSkillComponent.cs.uid @@ -0,0 +1 @@ +uid://bi5nx8s1gisbd diff --git a/scripts/components/OutOfScreenComponent.cs.uid b/scripts/components/OutOfScreenComponent.cs.uid new file mode 100644 index 0000000..204950a --- /dev/null +++ b/scripts/components/OutOfScreenComponent.cs.uid @@ -0,0 +1 @@ +uid://cs6u3sh68f43j diff --git a/scripts/components/PeriodicShootingComponent.cs.uid b/scripts/components/PeriodicShootingComponent.cs.uid new file mode 100644 index 0000000..79ab68c --- /dev/null +++ b/scripts/components/PeriodicShootingComponent.cs.uid @@ -0,0 +1 @@ +uid://bnaxy8cw3wrko diff --git a/scripts/components/PlayerDeathComponent.cs.uid b/scripts/components/PlayerDeathComponent.cs.uid new file mode 100644 index 0000000..d98f1d1 --- /dev/null +++ b/scripts/components/PlayerDeathComponent.cs.uid @@ -0,0 +1 @@ +uid://byw1legrv1ep2 diff --git a/scripts/components/ProgressiveDamageComponent.cs.uid b/scripts/components/ProgressiveDamageComponent.cs.uid new file mode 100644 index 0000000..f5a12a0 --- /dev/null +++ b/scripts/components/ProgressiveDamageComponent.cs.uid @@ -0,0 +1 @@ +uid://3qy7rm28q66a diff --git a/scripts/components/ProjectileComponent.cs.uid b/scripts/components/ProjectileComponent.cs.uid new file mode 100644 index 0000000..0af8fd3 --- /dev/null +++ b/scripts/components/ProjectileComponent.cs.uid @@ -0,0 +1 @@ +uid://bh31avqjbniik diff --git a/scripts/components/ProjectileInitComponent.cs.uid b/scripts/components/ProjectileInitComponent.cs.uid new file mode 100644 index 0000000..7b0ff69 --- /dev/null +++ b/scripts/components/ProjectileInitComponent.cs.uid @@ -0,0 +1 @@ +uid://c7n6ecsobohjn diff --git a/scripts/components/RequirementComponent.cs.uid b/scripts/components/RequirementComponent.cs.uid new file mode 100644 index 0000000..e4222aa --- /dev/null +++ b/scripts/components/RequirementComponent.cs.uid @@ -0,0 +1 @@ +uid://dnh0mekg2vqxi diff --git a/scripts/components/ScoreComponent.cs.uid b/scripts/components/ScoreComponent.cs.uid new file mode 100644 index 0000000..5f7149a --- /dev/null +++ b/scripts/components/ScoreComponent.cs.uid @@ -0,0 +1 @@ +uid://ccqb8kd5m0eh7 diff --git a/scripts/components/ShipMovementComponent.cs.uid b/scripts/components/ShipMovementComponent.cs.uid new file mode 100644 index 0000000..818ec4b --- /dev/null +++ b/scripts/components/ShipMovementComponent.cs.uid @@ -0,0 +1 @@ +uid://cty54itmnudfm diff --git a/scripts/components/ShipShooterComponent.cs.uid b/scripts/components/ShipShooterComponent.cs.uid new file mode 100644 index 0000000..6ec2881 --- /dev/null +++ b/scripts/components/ShipShooterComponent.cs.uid @@ -0,0 +1 @@ +uid://dr3uv0j7n75s diff --git a/scripts/components/SideToSideMovementComponent.cs.uid b/scripts/components/SideToSideMovementComponent.cs.uid new file mode 100644 index 0000000..02f6833 --- /dev/null +++ b/scripts/components/SideToSideMovementComponent.cs.uid @@ -0,0 +1 @@ +uid://d2hrr8fruho1d diff --git a/scripts/components/SkillUnlockedComponent.cs.uid b/scripts/components/SkillUnlockedComponent.cs.uid new file mode 100644 index 0000000..0754a9b --- /dev/null +++ b/scripts/components/SkillUnlockedComponent.cs.uid @@ -0,0 +1 @@ +uid://dlh5xcv2sy82s diff --git a/scripts/components/SpaceshipEnterComponent.cs.uid b/scripts/components/SpaceshipEnterComponent.cs.uid new file mode 100644 index 0000000..941fd1c --- /dev/null +++ b/scripts/components/SpaceshipEnterComponent.cs.uid @@ -0,0 +1 @@ +uid://dtv2r7q4elgre diff --git a/scripts/components/SpaceshipExitComponent.cs.uid b/scripts/components/SpaceshipExitComponent.cs.uid new file mode 100644 index 0000000..b77c5d0 --- /dev/null +++ b/scripts/components/SpaceshipExitComponent.cs.uid @@ -0,0 +1 @@ +uid://d3gfg05ll8uw3 diff --git a/scripts/components/SpinComponent.cs.uid b/scripts/components/SpinComponent.cs.uid new file mode 100644 index 0000000..c0275c9 --- /dev/null +++ b/scripts/components/SpinComponent.cs.uid @@ -0,0 +1 @@ +uid://cqw134ewht3hc diff --git a/scripts/components/StatusEffectComponent.cs.uid b/scripts/components/StatusEffectComponent.cs.uid new file mode 100644 index 0000000..baebbf8 --- /dev/null +++ b/scripts/components/StatusEffectComponent.cs.uid @@ -0,0 +1 @@ +uid://t8rsvwdwt8ea diff --git a/scripts/components/StompDamageComponent.cs.uid b/scripts/components/StompDamageComponent.cs.uid new file mode 100644 index 0000000..33acea6 --- /dev/null +++ b/scripts/components/StompDamageComponent.cs.uid @@ -0,0 +1 @@ +uid://dtg6115je7b5s diff --git a/scripts/components/StraightMotionComponent.cs.uid b/scripts/components/StraightMotionComponent.cs.uid new file mode 100644 index 0000000..8263c39 --- /dev/null +++ b/scripts/components/StraightMotionComponent.cs.uid @@ -0,0 +1 @@ +uid://c7p06t0eax8am diff --git a/scripts/components/TerrainHitFx.cs.uid b/scripts/components/TerrainHitFx.cs.uid new file mode 100644 index 0000000..b0baf81 --- /dev/null +++ b/scripts/components/TerrainHitFx.cs.uid @@ -0,0 +1 @@ +uid://cypxrqoeiihbf diff --git a/scripts/components/TooltipComponent.cs.uid b/scripts/components/TooltipComponent.cs.uid new file mode 100644 index 0000000..5355c20 --- /dev/null +++ b/scripts/components/TooltipComponent.cs.uid @@ -0,0 +1 @@ +uid://cvaa6aqyijcp1 diff --git a/scripts/components/TrailComponent.cs.uid b/scripts/components/TrailComponent.cs.uid new file mode 100644 index 0000000..1d61261 --- /dev/null +++ b/scripts/components/TrailComponent.cs.uid @@ -0,0 +1 @@ +uid://cqkh5e36p5fj0 diff --git a/scripts/components/TriggerLeverComponent.cs.uid b/scripts/components/TriggerLeverComponent.cs.uid new file mode 100644 index 0000000..95cd71f --- /dev/null +++ b/scripts/components/TriggerLeverComponent.cs.uid @@ -0,0 +1 @@ +uid://cqau0810tjk4d diff --git a/scripts/components/UnlockOnRequirementComponent.cs.uid b/scripts/components/UnlockOnRequirementComponent.cs.uid new file mode 100644 index 0000000..5826a95 --- /dev/null +++ b/scripts/components/UnlockOnRequirementComponent.cs.uid @@ -0,0 +1 @@ +uid://gwr4eajx8j50 diff --git a/scripts/components/platform_movement.gd.uid b/scripts/components/platform_movement.gd.uid new file mode 100644 index 0000000..8a0e2a0 --- /dev/null +++ b/scripts/components/platform_movement.gd.uid @@ -0,0 +1 @@ +uid://cflncpa377l8l diff --git a/scripts/interfaces/IThrowInput.cs.uid b/scripts/interfaces/IThrowInput.cs.uid new file mode 100644 index 0000000..cc5d9eb --- /dev/null +++ b/scripts/interfaces/IThrowInput.cs.uid @@ -0,0 +1 @@ +uid://cgscn34soorcp diff --git a/scripts/interfaces/IUnlockable.cs.uid b/scripts/interfaces/IUnlockable.cs.uid new file mode 100644 index 0000000..8ad3634 --- /dev/null +++ b/scripts/interfaces/IUnlockable.cs.uid @@ -0,0 +1 @@ +uid://c3dw0fsemrd0f -- 2.49.1 From 94f0f02b3e0c9c3f17e3a8b5ceafc3d3d6b5a5cc Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 13 Aug 2025 00:30:56 +0200 Subject: [PATCH 13/16] Add UID files for ConfigFileHandler, GameManager, SaveSystem, and UIManager components --- Autoloads/ConfigFileHandler.cs.uid | 1 + Autoloads/GameManager.cs.uid | 1 + Autoloads/SaveSystem.cs.uid | 1 + Autoloads/UIManager.cs.uid | 1 + 4 files changed, 4 insertions(+) create mode 100644 Autoloads/ConfigFileHandler.cs.uid create mode 100644 Autoloads/GameManager.cs.uid create mode 100644 Autoloads/SaveSystem.cs.uid create mode 100644 Autoloads/UIManager.cs.uid diff --git a/Autoloads/ConfigFileHandler.cs.uid b/Autoloads/ConfigFileHandler.cs.uid new file mode 100644 index 0000000..b8f877c --- /dev/null +++ b/Autoloads/ConfigFileHandler.cs.uid @@ -0,0 +1 @@ +uid://8cyvbeyd13cj diff --git a/Autoloads/GameManager.cs.uid b/Autoloads/GameManager.cs.uid new file mode 100644 index 0000000..3fc829d --- /dev/null +++ b/Autoloads/GameManager.cs.uid @@ -0,0 +1 @@ +uid://c6eoi3ymefc0x diff --git a/Autoloads/SaveSystem.cs.uid b/Autoloads/SaveSystem.cs.uid new file mode 100644 index 0000000..a014aec --- /dev/null +++ b/Autoloads/SaveSystem.cs.uid @@ -0,0 +1 @@ +uid://bh20fqbyifidc diff --git a/Autoloads/UIManager.cs.uid b/Autoloads/UIManager.cs.uid new file mode 100644 index 0000000..c888e92 --- /dev/null +++ b/Autoloads/UIManager.cs.uid @@ -0,0 +1 @@ +uid://c3ldmnrwperr4 -- 2.49.1 From 9c74a1ffb53069f5490a4c9dd8e07e8494b5303e Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 13 Aug 2025 00:40:34 +0200 Subject: [PATCH 14/16] Add README.md file with game description and features; include project license and contribution guidelines --- Mr. Brick Adventures.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Mr. Brick Adventures.csproj b/Mr. Brick Adventures.csproj index 35922f6..91104ef 100644 --- a/Mr. Brick Adventures.csproj +++ b/Mr. Brick Adventures.csproj @@ -5,6 +5,7 @@ Mr.BrickAdventures + -- 2.49.1 From 32a0311e3cbc02f89453a6e7dd30c0d8e066f47e Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 13 Aug 2025 03:19:23 +0200 Subject: [PATCH 15/16] Add Hud component for UI management; display health, coins, and lives --- scripts/UI/ChargeProgressBar.cs.uid | 1 + scripts/UI/Credits.cs.uid | 1 + scripts/UI/GameOverScreen.cs.uid | 1 + scripts/UI/Hud.cs | 43 +++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 scripts/UI/ChargeProgressBar.cs.uid create mode 100644 scripts/UI/Credits.cs.uid create mode 100644 scripts/UI/GameOverScreen.cs.uid create mode 100644 scripts/UI/Hud.cs diff --git a/scripts/UI/ChargeProgressBar.cs.uid b/scripts/UI/ChargeProgressBar.cs.uid new file mode 100644 index 0000000..9445c4f --- /dev/null +++ b/scripts/UI/ChargeProgressBar.cs.uid @@ -0,0 +1 @@ +uid://dev2q1228otm2 diff --git a/scripts/UI/Credits.cs.uid b/scripts/UI/Credits.cs.uid new file mode 100644 index 0000000..de4edcf --- /dev/null +++ b/scripts/UI/Credits.cs.uid @@ -0,0 +1 @@ +uid://daevj4uootmcw diff --git a/scripts/UI/GameOverScreen.cs.uid b/scripts/UI/GameOverScreen.cs.uid new file mode 100644 index 0000000..43680fa --- /dev/null +++ b/scripts/UI/GameOverScreen.cs.uid @@ -0,0 +1 @@ +uid://u4qfsx4w72dv diff --git a/scripts/UI/Hud.cs b/scripts/UI/Hud.cs new file mode 100644 index 0000000..5939a8e --- /dev/null +++ b/scripts/UI/Hud.cs @@ -0,0 +1,43 @@ +using Godot; +using Mr.BrickAdventures.Autoloads; +using Mr.BrickAdventures.scripts.components; + +namespace Mr.BrickAdventures.scripts.UI; + +public partial class Hud : Node +{ + [Export] public HealthComponent Health { get; set; } + [Export] public Label CoinsLabel { get; set; } + [Export] public ProgressBar HealthBar { get; set; } + [Export] public Label LivesLabel { get; set; } + + private GameManager _gameManager; + + public override void _Ready() + { + _gameManager = GetNode("/root/GameManager"); + } + + public override void _Process(double delta) + { + SetHealthBar(); + SetLivesLabel(); + SetCoinsLabel(); + } + + private void SetCoinsLabel() + { + CoinsLabel.Text = Tr("COINS_LABEL") + ": " + _gameManager.GetCoins(); + } + + private void SetLivesLabel() + { + LivesLabel.Text = Tr("LIVES_LABEL") + ": " + _gameManager.GetLives(); + } + + private void SetHealthBar() + { + HealthBar.Value = Health.Health; + HealthBar.MaxValue = Health.MaxHealth; + } +} \ No newline at end of file -- 2.49.1 From 81ce5df88b235f462127f9619e901f25329c0aec Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 14 Aug 2025 16:06:34 +0200 Subject: [PATCH 16/16] Add MainMenu and Marketplace components; implement game management and skill unlocking features --- Autoloads/GameManager.cs | 46 +++++++++++ scripts/UI/GameOverScreen.cs | 2 +- scripts/UI/Hud.cs.uid | 1 + scripts/UI/MainMenu.cs | 67 +++++++++++++++++ scripts/UI/MainMenu.cs.uid | 1 + scripts/UI/Marketplace.cs | 113 ++++++++++++++++++++++++++++ scripts/UI/Marketplace.cs.uid | 1 + scripts/UI/MarketplaceButton.cs | 62 +++++++++++++++ scripts/UI/MarketplaceButton.cs.uid | 1 + 9 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 scripts/UI/Hud.cs.uid create mode 100644 scripts/UI/MainMenu.cs create mode 100644 scripts/UI/MainMenu.cs.uid create mode 100644 scripts/UI/Marketplace.cs create mode 100644 scripts/UI/Marketplace.cs.uid create mode 100644 scripts/UI/MarketplaceButton.cs create mode 100644 scripts/UI/MarketplaceButton.cs.uid diff --git a/Autoloads/GameManager.cs b/Autoloads/GameManager.cs index 40bf0a8..d23822f 100644 --- a/Autoloads/GameManager.cs +++ b/Autoloads/GameManager.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; +using System.Linq; using Godot; using Godot.Collections; +using Mr.BrickAdventures.scripts.components; using Mr.BrickAdventures.scripts.Resources; namespace Mr.BrickAdventures.Autoloads; @@ -7,6 +10,10 @@ namespace Mr.BrickAdventures.Autoloads; public partial class GameManager : Node { [Export] public Array LevelScenes { get; set; } = new(); + + public PlayerController Player { get; set; } + + private List _sceneNodes = new(); public Dictionary PlayerState { get; set; } = new() { @@ -24,6 +31,29 @@ public partial class GameManager : Node { "skills_unlocked", new Array() } }; + public override void _EnterTree() + { + GetTree().NodeAdded += OnNodeAdded; + GetTree().NodeRemoved += OnNodeRemoved; + } + + public override void _ExitTree() + { + GetTree().NodeAdded -= OnNodeAdded; + GetTree().NodeRemoved -= OnNodeRemoved; + _sceneNodes.Clear(); + } + + private void OnNodeAdded(Node node) + { + _sceneNodes.Add(node); + } + + private void OnNodeRemoved(Node node) + { + _sceneNodes.Remove(node); + } + public void AddCoins(int amount) { PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] + amount); @@ -192,4 +222,20 @@ public partial class GameManager : Node joined.AddRange((Array)session ?? new Array()); return joined; } + + public PlayerController GetPlayer() + { + if (Player != null) return Player; + + foreach (var node in _sceneNodes) + { + if (node is not PlayerController player) continue; + + Player = player; + return Player; + } + + GD.PrintErr("PlayerController not found in the scene tree."); + return null; + } } \ No newline at end of file diff --git a/scripts/UI/GameOverScreen.cs b/scripts/UI/GameOverScreen.cs index 0a665f9..80e55b7 100644 --- a/scripts/UI/GameOverScreen.cs +++ b/scripts/UI/GameOverScreen.cs @@ -16,7 +16,7 @@ public partial class GameOverScreen : Node { _gameManager = GetNode("/root/GameManager"); RestartButton.Pressed += OnRestartClicked; - MainMenuButton.Pressed += OnMainMenuClicked; + MainMenuButton.Pressed += OnMainMenuClicked; } private void OnMainMenuClicked() diff --git a/scripts/UI/Hud.cs.uid b/scripts/UI/Hud.cs.uid new file mode 100644 index 0000000..15ddc0b --- /dev/null +++ b/scripts/UI/Hud.cs.uid @@ -0,0 +1 @@ +uid://wfj674u4486f diff --git a/scripts/UI/MainMenu.cs b/scripts/UI/MainMenu.cs new file mode 100644 index 0000000..b4b5de0 --- /dev/null +++ b/scripts/UI/MainMenu.cs @@ -0,0 +1,67 @@ +using Godot; +using Mr.BrickAdventures.Autoloads; + +namespace Mr.BrickAdventures.scripts.UI; + +public partial class MainMenu : Node +{ + [Export] public Control MainMenuControl { get; set; } + [Export] public Button NewGameButton { get; set; } + [Export] public Button ContinueButton { get; set; } + [Export] public Button SettingsButton { get; set; } + [Export] public Button CreditsButton { get; set; } + [Export] public Button ExitButton { get; set; } + [Export] public Label VersionLabel { get; set; } + [Export] public Control SettingsControl { get; set; } + [Export] public Control CreditsControl { get; set; } + + private SaveSystem _saveSystem; + private GameManager _gameManager; + private UIManager _uiManager; + + public override void _Ready() + { + _saveSystem = GetNode("/root/SaveSystem"); + _gameManager = GetNode("/root/GameManager"); + _uiManager = GetNode("/root/UIManager"); + + NewGameButton.Pressed += OnNewGamePressed; + ContinueButton.Pressed += OnContinuePressed; + SettingsButton.Pressed += OnSettingsPressed; + CreditsButton.Pressed += OnCreditsPressed; + ExitButton.Pressed += OnExitPressed; + + VersionLabel.Text = $"v. {ProjectSettings.GetSetting("application/config/version")}"; + ContinueButton.Disabled = !_saveSystem.CheckSaveExists(); + + if (_saveSystem.CheckSaveExists()) + ContinueButton.GrabFocus(); + else + NewGameButton.GrabFocus(); + } + + private void OnExitPressed() + { + _gameManager.QuitGame(); + } + + private void OnCreditsPressed() + { + _uiManager.PushScreen(CreditsControl); + } + + private void OnSettingsPressed() + { + _uiManager.PushScreen(SettingsControl); + } + + private void OnContinuePressed() + { + _gameManager.ContinueGame(); + } + + private void OnNewGamePressed() + { + _gameManager.StartNewGame(); + } +} \ No newline at end of file diff --git a/scripts/UI/MainMenu.cs.uid b/scripts/UI/MainMenu.cs.uid new file mode 100644 index 0000000..73f54b3 --- /dev/null +++ b/scripts/UI/MainMenu.cs.uid @@ -0,0 +1 @@ +uid://bna3ggr6n7ycr diff --git a/scripts/UI/Marketplace.cs b/scripts/UI/Marketplace.cs new file mode 100644 index 0000000..1cda6b3 --- /dev/null +++ b/scripts/UI/Marketplace.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using Godot; +using Godot.Collections; +using Mr.BrickAdventures.Autoloads; +using Mr.BrickAdventures.scripts.components; +using Mr.BrickAdventures.scripts.Resources; + +namespace Mr.BrickAdventures.scripts.UI; + +public partial class Marketplace : Node +{ + [Export] public Array Skills { get; set; } = []; + [Export] public GridContainer ToUnlockGrid { get; set; } + [Export] public GridContainer UnlockedGrid { get; set; } + [Export] public Font Font { get; set; } + [Export] public SkillUnlockedComponent SkillUnlockedComponent { get; set; } + [Export] public Array ComponentsToDisable { get; set; } = []; + [Export] public PackedScene MarketplaceButtonScene { get; set; } + [Export] public PackedScene SkillButtonScene { get; set; } + + private GameManager _gameManager; + private List