Compare commits

...

15 Commits

Author SHA1 Message Date
9c74a1ffb5 Add README.md file with game description and features; include project license and contribution guidelines 2025-08-13 00:46:51 +02:00
94f0f02b3e Add UID files for ConfigFileHandler, GameManager, SaveSystem, and UIManager components 2025-08-13 00:46:49 +02:00
4075231939 Add ChargeProgressBar, Credits, and GameOverScreen components for UI management 2025-08-13 00:46:49 +02:00
359a32aaf0 Add audio settings management and platform movement component 2025-08-13 00:46:49 +02:00
56da6f36a7 Add new components: BrickThrowComponent, BulletComponent, CageComponent, ChaseLevelComponent, CleanupComponent, and ThrowInputResource classes; implement game saving and loading logic in SaveSystem 2025-08-13 00:46:49 +02:00
2dc4306be8 Add new components: CannotStompComponent, SkillUnlockedComponent, SpaceshipEnterComponent, SpaceshipExitComponent, SpinComponent, StompDamageComponent, StraightMotionComponent, TerrainHitFx, TooltipComponent, TrailComponent, and UnlockOnRequirementComponent 2025-08-13 00:46:49 +02:00
a859ff9fe7 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 2025-08-13 00:46:49 +02:00
0e41c811e4 Add new components: HealComponent, HitComponent, HomingMissileMotionComponent, LeverComponent, and TriggerLeverComponent 2025-08-13 00:46:49 +02:00
cd3143bbad Add new components: ExplosiveComponent, FadeAwayComponent, FireEffectComponent, FlipComponent, GravityMotionComponent, LaunchComponent, and update PlatformMovement with LastDirection property 2025-08-13 00:46:49 +02:00
aeb024cbac Add new components: EnemyDeathComponent, EnemyWaveTriggerComponent, and ExitDoorComponent 2025-08-13 00:46:49 +02:00
3066f0c7b4 Add new components: CanPickUpComponent, CollapsableComponent, DestroyableComponent, EffectInflictorComponent, StatusEffectComponent, and StatusEffectDataResource 2025-08-13 00:46:49 +02:00
bdd5ce04da cleanup 2025-08-13 00:46:49 +02:00
b54d886145 Add core game components including ConfigFileHandler, GameManager, SaveSystem, and UIManager 2025-08-13 00:46:49 +02:00
4326ca850d Implement BeamComponent in C# and enhance marketplace button functionality 2025-08-13 00:46:49 +02:00
cce93286be Update README.md with game story, mission, and features; enhance promotional content (#2) 2025-08-13 00:46:19 +02:00
172 changed files with 4308 additions and 18 deletions

View File

@@ -0,0 +1,27 @@
using Godot;
namespace Mr.BrickAdventures.Autoloads;
public partial class ConfigFileHandler : Node
{
private ConfigFile _settingsConfig = new();
public const string SettingsPath = "user://settings.ini";
public ConfigFile SettingsConfig => _settingsConfig;
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}");
}
}
}

View File

@@ -0,0 +1 @@
uid://8cyvbeyd13cj

195
Autoloads/GameManager.cs Normal file
View File

@@ -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<PackedScene> LevelScenes { get; set; } = new();
public Dictionary PlayerState { get; set; } = new()
{
{ "coins", 0 },
{ "lives", 3 },
{ "current_level", 0 },
{ "completed_levels", new Array<int>() },
{ "unlocked_levels", new Array<int>() {0}},
{ "unlocked_skills", new Array<SkillData>() }
};
public Dictionary CurrentSessionState { get; private set; } = new()
{
{ "coins_collected", 0 },
{ "skills_unlocked", new Array<SkillData>() }
};
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<SkillData> 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<int>() },
{ "unlocked_levels", new Array<int>() {0}},
{ "unlocked_skills", new Array<SkillData>() }
};
}
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<SkillData>() }
};
}
public void RestartGame()
{
ResetPlayerState();
ResetCurrentSessionState();
GetTree().ChangeSceneToPacked(LevelScenes[0]);
GetNode<SaveSystem>("/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<SaveSystem>("/root/SaveSystem").SaveGame();
}
public void ContinueGame()
{
var save = GetNode<SaveSystem>("/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<SaveSystem>("/root/SaveSystem").SaveGame();
}
public Array GetUnlockedSkills()
{
var unlocked = (Array<SkillData>)PlayerState["unlocked_skills"];
var session = (Array<SkillData>)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;
}
}

View File

@@ -0,0 +1 @@
uid://c6eoi3ymefc0x

61
Autoloads/SaveSystem.cs Normal file
View File

@@ -0,0 +1,61 @@
using Godot;
using Godot.Collections;
using Mr.BrickAdventures.scripts.Resources;
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 GameManager _gameManager;
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/GameManager");
}
public void SaveGame()
{
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()
{
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"]);
_gameManager.PlayerState = (Dictionary)saveDataObj["player_state"];
var skills = new Array<SkillData>();
foreach (var skill in (Array<SkillData>)_gameManager.PlayerState["unlocked_skills"])
{
skills.Add(skill);
}
_gameManager.UnlockSkills(skills);
return true;
}
public bool CheckSaveExists() => FileAccess.FileExists(SavePath);
}

View File

@@ -0,0 +1 @@
uid://bh20fqbyifidc

65
Autoloads/UIManager.cs Normal file
View File

@@ -0,0 +1,65 @@
using Godot;
using Godot.Collections;
namespace Mr.BrickAdventures.Autoloads;
public partial class UIManager : Node
{
[Export] public Array<Control> 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);
}
}

View File

@@ -0,0 +1 @@
uid://c3ldmnrwperr4

143
Mr. Brick Adventures.csproj Normal file
View File

@@ -0,0 +1,143 @@
<Project Sdk="Godot.NET.Sdk/4.4.1">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>Mr.BrickAdventures</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Content Include="README.md" />
<Content Include="scripts\components\.idea\.gitignore" />
<Content Include="scripts\components\.idea\encodings.xml" />
<Content Include="scripts\components\.idea\indexLayout.xml" />
<Content Include="scripts\components\.idea\inspectionProfiles\Project_Default.xml" />
<Content Include="scripts\components\.idea\projectSettingsUpdater.xml" />
<Content Include="scripts\components\.idea\vcs.xml" />
<Content Include="scripts\components\.idea\workspace.xml" />
<Content Include="scripts\components\BeamComponent.cs.uid" />
<Content Include="scripts\components\beam_component.gd" />
<Content Include="scripts\components\beam_component.gd.uid" />
<Content Include="scripts\components\brick_throw.gd" />
<Content Include="scripts\components\brick_throw.gd.uid" />
<Content Include="scripts\components\bullet_component.gd" />
<Content Include="scripts\components\bullet_component.gd.uid" />
<Content Include="scripts\components\cage_component.gd" />
<Content Include="scripts\components\cage_component.gd.uid" />
<Content Include="scripts\components\cannot_stomp_component.gd" />
<Content Include="scripts\components\cannot_stomp_component.gd.uid" />
<Content Include="scripts\components\can_be_launched_component.gd" />
<Content Include="scripts\components\can_be_launched_component.gd.uid" />
<Content Include="scripts\components\can_pickup.gd" />
<Content Include="scripts\components\can_pickup.gd.uid" />
<Content Include="scripts\components\charge_throw_component.gd" />
<Content Include="scripts\components\charge_throw_component.gd.uid" />
<Content Include="scripts\components\chase_level_component.gd" />
<Content Include="scripts\components\chase_level_component.gd.uid" />
<Content Include="scripts\components\cleanup_component.gd" />
<Content Include="scripts\components\cleanup_component.gd.uid" />
<Content Include="scripts\components\collapsable.gd" />
<Content Include="scripts\components\collapsable.gd.uid" />
<Content Include="scripts\components\collectable.gd" />
<Content Include="scripts\components\collectable.gd.uid" />
<Content Include="scripts\components\damage_component.gd" />
<Content Include="scripts\components\damage_component.gd.uid" />
<Content Include="scripts\components\destroyable_component.gd" />
<Content Include="scripts\components\destroyable_component.gd.uid" />
<Content Include="scripts\components\effect_inflictor_component.gd" />
<Content Include="scripts\components\effect_inflictor_component.gd.uid" />
<Content Include="scripts\components\enemy_death.gd" />
<Content Include="scripts\components\enemy_death.gd.uid" />
<Content Include="scripts\components\enemy_wave_trigger.gd" />
<Content Include="scripts\components\enemy_wave_trigger.gd.uid" />
<Content Include="scripts\components\exit_door_component.gd" />
<Content Include="scripts\components\exit_door_component.gd.uid" />
<Content Include="scripts\components\explosive_component.gd" />
<Content Include="scripts\components\explosive_component.gd.uid" />
<Content Include="scripts\components\fade_away.gd" />
<Content Include="scripts\components\fade_away.gd.uid" />
<Content Include="scripts\components\fire_effect_component.gd" />
<Content Include="scripts\components\fire_effect_component.gd.uid" />
<Content Include="scripts\components\flashing_component.gd" />
<Content Include="scripts\components\flashing_component.gd.uid" />
<Content Include="scripts\components\flip_player.gd" />
<Content Include="scripts\components\flip_player.gd.uid" />
<Content Include="scripts\components\gravity_motion_component.gd" />
<Content Include="scripts\components\gravity_motion_component.gd.uid" />
<Content Include="scripts\components\health.gd" />
<Content Include="scripts\components\health.gd.uid" />
<Content Include="scripts\components\heal_component.gd" />
<Content Include="scripts\components\heal_component.gd.uid" />
<Content Include="scripts\components\hit_component.gd" />
<Content Include="scripts\components\hit_component.gd.uid" />
<Content Include="scripts\components\homing_missile_motion.gd" />
<Content Include="scripts\components\homing_missile_motion.gd.uid" />
<Content Include="scripts\components\ice_effect_component.gd" />
<Content Include="scripts\components\ice_effect_component.gd.uid" />
<Content Include="scripts\components\invulnerability_component.gd" />
<Content Include="scripts\components\invulnerability_component.gd.uid" />
<Content Include="scripts\components\jump_pad_component.gd" />
<Content Include="scripts\components\jump_pad_component.gd.uid" />
<Content Include="scripts\components\kill_player_out_of_screen.gd" />
<Content Include="scripts\components\kill_player_out_of_screen.gd.uid" />
<Content Include="scripts\components\knockback.gd" />
<Content Include="scripts\components\knockback.gd.uid" />
<Content Include="scripts\components\launch_component.gd" />
<Content Include="scripts\components\launch_component.gd.uid" />
<Content Include="scripts\components\lever_component.gd" />
<Content Include="scripts\components\lever_component.gd.uid" />
<Content Include="scripts\components\lifetime_component.gd" />
<Content Include="scripts\components\lifetime_component.gd.uid" />
<Content Include="scripts\components\magnetic_skill.gd" />
<Content Include="scripts\components\magnetic_skill.gd.uid" />
<Content Include="scripts\components\out_of_screen_component.gd" />
<Content Include="scripts\components\out_of_screen_component.gd.uid" />
<Content Include="scripts\components\periodic_shooting.gd" />
<Content Include="scripts\components\periodic_shooting.gd.uid" />
<Content Include="scripts\components\PlatformMovementComponent.cs.uid" />
<Content Include="scripts\components\platform_movement.gd" />
<Content Include="scripts\components\PlayerController.cs.uid" />
<Content Include="scripts\components\player_death.gd" />
<Content Include="scripts\components\player_death.gd.uid" />
<Content Include="scripts\components\player_movement.gd" />
<Content Include="scripts\components\player_movement.gd.uid" />
<Content Include="scripts\components\progressive_damage_component.gd" />
<Content Include="scripts\components\progressive_damage_component.gd.uid" />
<Content Include="scripts\components\projectile_component.gd" />
<Content Include="scripts\components\projectile_component.gd.uid" />
<Content Include="scripts\components\projectile_init_component.gd" />
<Content Include="scripts\components\projectile_init_component.gd.uid" />
<Content Include="scripts\components\requirement_component.gd" />
<Content Include="scripts\components\requirement_component.gd.uid" />
<Content Include="scripts\components\score.gd" />
<Content Include="scripts\components\score.gd.uid" />
<Content Include="scripts\components\ship_movement.gd" />
<Content Include="scripts\components\ship_movement.gd.uid" />
<Content Include="scripts\components\ship_shooter.gd" />
<Content Include="scripts\components\ship_shooter.gd.uid" />
<Content Include="scripts\components\side_to_side_movement.gd" />
<Content Include="scripts\components\side_to_side_movement.gd.uid" />
<Content Include="scripts\components\skill_unlocker_component.gd" />
<Content Include="scripts\components\skill_unlocker_component.gd.uid" />
<Content Include="scripts\components\spaceship_enter_component.gd" />
<Content Include="scripts\components\spaceship_enter_component.gd.uid" />
<Content Include="scripts\components\spaceship_exit_component.gd" />
<Content Include="scripts\components\spaceship_exit_component.gd.uid" />
<Content Include="scripts\components\spin_component.gd" />
<Content Include="scripts\components\spin_component.gd.uid" />
<Content Include="scripts\components\status_effect_component.gd" />
<Content Include="scripts\components\status_effect_component.gd.uid" />
<Content Include="scripts\components\stomp_damage_component.gd" />
<Content Include="scripts\components\stomp_damage_component.gd.uid" />
<Content Include="scripts\components\straight_motion_component.gd" />
<Content Include="scripts\components\straight_motion_component.gd.uid" />
<Content Include="scripts\components\terrain_hit_fx.gd" />
<Content Include="scripts\components\terrain_hit_fx.gd.uid" />
<Content Include="scripts\components\tooltip_component.gd" />
<Content Include="scripts\components\tooltip_component.gd.uid" />
<Content Include="scripts\components\trail_component.gd" />
<Content Include="scripts\components\trail_component.gd.uid" />
<Content Include="scripts\components\trigger_lever_component.gd" />
<Content Include="scripts\components\trigger_lever_component.gd.uid" />
<Content Include="scripts\components\unlock_on_requirement_component.gd" />
<Content Include="scripts\components\unlock_on_requirement_component.gd.uid" />
</ItemGroup>
</Project>

19
Mr. Brick Adventures.sln Normal file
View File

@@ -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

View File

@@ -0,0 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACanvasItem_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fef7b819b226fab796d1dfe66d415dd7510bcac87675020ddb8f03a828e763_003FCanvasItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACollisionShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F2ca9b7334678f5c97c7c2a9fbe4837be71cae11b6a30408dd4791b18f997e4a_003FCollisionShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F86db9cd834346aad02d74c1b66dd9c64d6ef3147435dd9c9c9477b48f7_003FNode2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARectangleShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fa1cc98873548652da0c14ecefa4737431426fcbb24a7f0641e3d9c266c3_003FRectangleShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3671dbbd9b17cdf2bf9075b468b6bd7e3ab13fc3be7a116484085d3b6cc9fe_003FShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

56
README.md Normal file
View File

@@ -0,0 +1,56 @@
# Przygody Pana Cegły
*"The worlds only brick-throwing dad simulator (probably)."*
---
## The Story
Disaster has struck!
Mr. Bricks kids have gone missing, scattered across mysterious lands filled with treacherous traps, cranky critters, and more collapsing bridges than an OSHA nightmare.
Armed with nothing but his legs, his wits, and an infinite supply of throwable bricks (dont ask where he keeps them), Mr. Brick will stop at nothing to bring his children home.
---
## The Mission
Run, jump, and hurl bricks with pinpoint accuracy as you navigate dangerous worlds. Smash enemies, trigger ancient mechanisms, and uncover hidden treasures, all while dodging hazards that seem designed specifically to ruin your day.
---
## Features Thatll Knock Your Bricks Off
* **Tight, Classic Platforming**
Leap over pits, time your jumps, and try not to land in something unpleasant.
* **Brick-Fu Combat**
Toss bricks to flatten foes, flip switches, and solve puzzles in the most dad-like way possible.
* **Secrets & Collectibles**
Hunt for coins, discover hidden rooms, and find upgrades to make you just a *little* less fragile.
* **NES-Inspired Pixel Art**
All the charm of an 8-bit classic, no cartridge blowing required.
---
## Play It Here
[**Wishlist on Steam**](https://store.steampowered.com/app/3575090/Mr_Brick_Adventures/) so youre ready for launch day.
---
## License
Check the [LICENSE](./LICENSE) for the legal stuff.
---
## Contributing
Not open to code contributions right now but you *can* help by:
* Reporting bugs
* Suggesting evil new trap ideas
* Spreading the word so Mr. Brick can find his kids faster

View File

@@ -51,4 +51,4 @@ func close_all() -> void:
func hide_and_disable(screen: Control) -> void: func hide_and_disable(screen: Control) -> void:
screen.hide() screen.hide()
screen.set_process_input(false) screen.set_process_input(false)

View File

@@ -19,7 +19,7 @@ config/version="in-dev"
run/main_scene="uid://cl00e2ocomk3m" run/main_scene="uid://cl00e2ocomk3m"
config/use_custom_user_dir=true config/use_custom_user_dir=true
config/custom_user_dir_name="MrBrickAdventures" 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 run/max_fps=180
boot_splash/bg_color=Color(0, 0, 0, 1) boot_splash/bg_color=Color(0, 0, 0, 1)
boot_splash/show_image=false boot_splash/show_image=false

View File

@@ -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);
}
}

View File

@@ -0,0 +1 @@
uid://dtpdh4jp51jis

View File

@@ -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; }
}

View File

@@ -0,0 +1 @@
uid://gptsgaw3agkf

View File

@@ -0,0 +1,8 @@
namespace Mr.BrickAdventures.scripts.Resources;
public enum CollectableType
{
Coin,
Kid,
Health,
}

View File

@@ -0,0 +1 @@
uid://2ql8wj3vfeke

View File

@@ -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<string, Variant> 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; }
}

View File

@@ -0,0 +1 @@
uid://d4crrfmbgxnqf

View File

@@ -0,0 +1,8 @@
namespace Mr.BrickAdventures.scripts.Resources;
public enum SkillType
{
Attack,
Throw,
Misc,
}

View File

@@ -0,0 +1 @@
uid://sma20qug2r0q

View File

@@ -0,0 +1,10 @@
using Godot;
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; }
}

View File

@@ -0,0 +1 @@
uid://pw0pu6gb21y2

View File

@@ -0,0 +1,8 @@
namespace Mr.BrickAdventures.scripts.Resources;
public enum StatusEffectType
{
None,
Fire,
Ice
}

View File

@@ -0,0 +1 @@
uid://b0a7k7mse3l68

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
uid://cx7ryqxemgs56

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
uid://mnn5wy5cyr4m

15
scripts/Screenshot.cs Normal file
View File

@@ -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);
}
}

View File

@@ -0,0 +1 @@
uid://chrhjch4ymfvr

144
scripts/SkillManager.cs Normal file
View File

@@ -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<SkillData> AvailableSkills { get; set; } = [];
public Dictionary ActiveComponents { get; private set; } = new();
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/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);
}
}

View File

@@ -0,0 +1 @@
uid://di572axt0c3s8

102
scripts/UI/AudioSettings.cs Normal file
View File

@@ -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<UIManager>("/root/UIManager");
_configFileHandler = GetNode<ConfigFileHandler>("/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;
}
}

View File

@@ -0,0 +1 @@
uid://g61qqsymqfxd

View File

@@ -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();
}
}

23
scripts/UI/Credits.cs Normal file
View File

@@ -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<UIManager>("/root/UIManager");
}
public override void _UnhandledInput(InputEvent @event)
{
if (!@event.IsActionPressed("ui_cancel")) return;
if (_uiManager != null && _uiManager.IsScreenOnTop(this))
{
_uiManager.PopScreen();
}
}
}

View File

@@ -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<GameManager>("/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();
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
uid://df1llrbm80e02

View File

@@ -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<Node2D>();
var init = instance.GetNodeOrNull<ProjectileInitComponent>("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();
}
}

View File

@@ -0,0 +1 @@
uid://b0bv8kw5w5037

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
uid://cfw8nbrarex0i

View File

@@ -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>("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();
}
}

View File

@@ -0,0 +1 @@
uid://dojn0gw8hsv02

View File

@@ -0,0 +1,8 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CanBeLaunchedComponent : Node
{
}

View File

@@ -0,0 +1 @@
uid://cjcc7fia15wu3

View File

@@ -0,0 +1,8 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CanPickUpComponent : Node
{
}

View File

@@ -0,0 +1 @@
uid://mnjg3p0aw1ow

View File

@@ -0,0 +1,8 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CannotStompComponent : Node
{
}

View File

@@ -0,0 +1 @@
uid://dh67n16bnl838

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
uid://dnpj72mfi1ywl

View File

@@ -0,0 +1,11 @@
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class CleanupComponent : Node
{
public void CleanUp()
{
Owner.QueueFree();
}
}

View File

@@ -0,0 +1 @@
uid://v7tt4w6bejux

View File

@@ -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);
}
}

View File

@@ -0,0 +1 @@
uid://xqhrb1c7f6y4

View File

@@ -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}");
}
}
}

View File

@@ -0,0 +1 @@
uid://r4jybneigfcn

View File

@@ -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>("HealthComponent");
var inv = body.GetNodeOrNull<InvulnerabilityComponent>("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;
}
}

View File

@@ -0,0 +1 @@
uid://2i7p7v135u7c

View File

@@ -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<Node2D>();
Health.GetParent().AddChild(effect);
effect.SetGlobalPosition(Health.GlobalPosition);
Owner.QueueFree();
}
}

View File

@@ -0,0 +1 @@
uid://ctfrbj52ejay4

View File

@@ -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>("StatusEffectComponent");
statusEffect?.ApplyEffect(effect);
}
}

View File

@@ -0,0 +1 @@
uid://xjq33vj0rol0

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
uid://cfdugoeduudar

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
uid://d3fpwddc2j41x

View File

@@ -0,0 +1,52 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.interfaces;
namespace Mr.BrickAdventures.scripts.components;
public partial class ExitDoorComponent : Node, IUnlockable
{
[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<GameManager>("/root/gameManager");
}
private void OnExitAreaBodyEntered(Node2D body)
{
throw new System.NotImplementedException();
}
public void Unlock()
{
Locked = false;
if (DoorSprite != null)
{
DoorSprite.Frame = OpenedDoorFrame;
}
OpenDoorSfx?.Play();
}
private void GoToNextLevel()
{
_gameManager.OnLevelComplete();
}
}

View File

@@ -0,0 +1 @@
uid://c5mhwlyvfuaip

View File

@@ -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<GpuParticles2D>();
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>("HealthComponent");
if (Damage != null && health != null)
{
Damage.DealDamage(health);
}
EmitSignalOnExplosion(body);
}
Owner.QueueFree();
}
}

View File

@@ -0,0 +1 @@
uid://7uttgdr6cr5y

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
uid://bjln6jb1sigx2

View File

@@ -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>("HealthComponent");
}
if (StatusEffectComponent == null)
{
StatusEffectComponent = GetNode<StatusEffectComponent>("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);
}
}

View File

@@ -0,0 +1 @@
uid://cxuig4xh8nfov

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
uid://dvyd26ricriql

View File

@@ -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 PlatformMovementComponent 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;
}
}
}

View File

@@ -0,0 +1 @@
uid://dy78ak8eykw6e

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1 @@
uid://cwi5qashdag1g

View File

@@ -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>("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;
}
}

View File

@@ -0,0 +1 @@
uid://bmx6rk281yim2

View File

@@ -0,0 +1,65 @@
using System.Threading.Tasks;
using Godot;
namespace Mr.BrickAdventures.scripts.components;
public partial class HealthComponent : Node2D
{
[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);
}
}
}

View File

@@ -0,0 +1 @@
uid://dgb8bqcri7nsj

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
uid://bo506l4x0808e

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
uid://c2hplha6af74q

View File

@@ -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<Node> 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;
}
}
}

View File

@@ -0,0 +1 @@
uid://d1388lhp2gpgr

View File

@@ -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;
}

View File

@@ -0,0 +1 @@
uid://cecelixl41t3j

View File

@@ -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>("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;
}
}

Some files were not shown because too many files have changed in this diff Show More